From 94d29e154ed1637118b2e5e931c20aa7ea01eea5 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 5 Mar 2019 18:57:58 -0500 Subject: [PATCH 001/151] Temporary fix to address precision discrepancies with weightfactors numpy array --- openmc/cmfd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index d1ea77fa06..18a5caa1ce 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -1150,7 +1150,7 @@ class CMFDRun(object): with np.errstate(divide='ignore', invalid='ignore'): self._weightfactors = (np.divide(self._cmfd_src * norm, sourcecounts, where=div_condition, - out=np.ones_like(self._cmfd_src))) + out=np.ones_like(self._cmfd_src), dtype='float16')) if not self._feedback: return From 4094213ddad9324506f4438c6e2a948ca5e95dbe Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Wed, 6 Mar 2019 13:16:34 -0500 Subject: [PATCH 002/151] Expose restart_run and statepoint_path through C API, get rid of uncessary lines in allocate_cmfd --- include/openmc/settings.h | 4 +- openmc/capi/settings.py | 6 +++ openmc/cmfd.py | 83 ++++++++++++++------------------------- src/state_point.cpp | 3 -- 4 files changed, 37 insertions(+), 59 deletions(-) diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 4b76c3dc59..fc6fea6e7f 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -36,7 +36,7 @@ extern bool particle_restart_run; //!< particle restart run? extern "C" bool photon_transport; //!< photon transport turned on? extern "C" bool reduce_tallies; //!< reduce tallies at end of batch? extern bool res_scat_on; //!< use resonance upscattering method? -extern bool restart_run; //!< restart run? +extern "C" bool restart_run; //!< restart run? extern "C" bool run_CE; //!< run with continuous-energy data? extern bool source_latest; //!< write latest source at each batch? extern bool source_separate; //!< write source to separate file? @@ -57,7 +57,7 @@ extern std::string path_output; //!< directory where output files are extern std::string path_particle_restart; //!< path to a particle restart file extern std::string path_source; extern std::string path_sourcepoint; //!< path to a source file -extern std::string path_statepoint; //!< path to a statepoint file +extern "C" std::string path_statepoint; //!< path to a statepoint file extern int32_t index_entropy_mesh; //!< Index of entropy mesh in global mesh array extern int32_t index_ufs_mesh; //!< Index of UFS mesh in global mesh array diff --git a/openmc/capi/settings.py b/openmc/capi/settings.py index 115962395d..3dd672354f 100644 --- a/openmc/capi/settings.py +++ b/openmc/capi/settings.py @@ -22,6 +22,7 @@ class _Settings(object): generations_per_batch = _DLLGlobal(c_int32, 'gen_per_batch') inactive = _DLLGlobal(c_int32, 'n_inactive') particles = _DLLGlobal(c_int64, 'n_particles') + restart_run = _DLLGlobal(c_bool, 'restart_run') run_CE = _DLLGlobal(c_bool, 'run_CE') verbosity = _DLLGlobal(c_int, 'verbosity') @@ -43,6 +44,11 @@ class _Settings(object): else: raise ValueError('Invalid run mode: {}'.format(mode)) + @property + def path_statepoint(self): + path = c_char_p.in_dll(_dll, 'path_statepoint').value + return path.decode() + @property def seed(self): return _dll.openmc_get_seed() diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 18a5caa1ce..a7acda23a4 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -337,11 +337,11 @@ class CMFDRun(object): self._openmc_src = None self._sourcecounts = None self._weightfactors = None - self._entropy = None - self._balance = None - self._src_cmp = None - self._dom = None - self._k_cmfd = None + self._entropy = [] + self._balance = [] + self._src_cmp = [] + self._dom = [] + self._k_cmfd = [] self._resnb = None self._time_cmfd = None self._time_cmfdbuild = None @@ -636,8 +636,8 @@ class CMFDRun(object): # Configure CMFD parameters and tallies self._configure_cmfd() - # Set up CMFD coremap - self._set_coremap() + # Initialize all arrays used for CMFD solver + self._allocate_cmfd() # Compute and store array indices used to build cross section # arrays @@ -731,16 +731,24 @@ class CMFDRun(object): def _configure_cmfd(self): """Initialize CMFD parameters and set CMFD input variables""" - # Read in cmfd input defined in Python - self._read_cmfd_input() + # Check if restarting simulation from statepoint file + if not openmc.capi.settings.restart_run: + # Read in cmfd input defined in Python + self._read_cmfd_input() - # Initialize timers - self._time_cmfd = 0.0 - self._time_cmfdbuild = 0.0 - self._time_cmfdsolve = 0.0 + # Set up CMFD coremap + self._set_coremap() - # Initialize all numpy arrays used for CMFD solver - self._allocate_cmfd() + # Initialize timers + self._time_cmfd = 0.0 + self._time_cmfdbuild = 0.0 + self._time_cmfdsolve = 0.0 + + else: + # Reset CMFD parameters from statepoint file + # TODO implement reset_cmfd + path_statepoint = openmc.capi.settings.path_statepoint + sys.exit() def _read_cmfd_input(self): """Sets values of additional instance variables based on user input""" @@ -803,38 +811,13 @@ class CMFDRun(object): nz = self._indices[2] ng = self._indices[3] - # Allocate flux, cross sections and diffusion coefficient - self._flux = np.zeros((nx, ny, nz, ng)) - self._totalxs = np.zeros((nx, ny, nz, ng)) - self._p1scattxs = np.zeros((nx, ny, nz, ng)) - self._scattxs = np.zeros((nx, ny, nz, ng, ng)) # Incoming, outgoing - self._nfissxs = np.zeros((nx, ny, nz, ng, ng)) # Incoming, outgoing - self._diffcof = np.zeros((nx, ny, nz, ng)) - # Allocate dtilde and dhat self._dtilde = np.zeros((nx, ny, nz, ng, 6)) self._dhat = np.zeros((nx, ny, nz, ng, 6)) # Allocate dimensions for each mesh cell self._hxyz = np.zeros((nx, ny, nz, 3)) - - # Allocate surface currents - self._current = np.zeros((nx, ny, nz, 12, ng)) - - # Allocate source distributions - self._cmfd_src = np.zeros((nx, ny, nz, ng)) - self._openmc_src = np.zeros((nx, ny, nz, ng)) - - # Allocate source weight modification variables - self._sourcecounts = np.zeros((nx*ny*nz, ng)) - self._weightfactors = np.ones((nx, ny, nz, ng)) - - # Allocate batchwise parameters - self._entropy = [] - self._balance = [] - self._src_cmp = [] - self._dom = [] - self._k_cmfd = [] + self._hxyz[:,:,:,:] = openmc.capi.meshes[self._mesh_id].width def _cmfd_init_batch(self): """Handles CMFD options at the beginning of each batch""" @@ -1048,9 +1031,6 @@ class CMFDRun(object): ng = self._indices[3] n = self._mat_dim - # Reset CMFD source to 0 - self._cmfd_src.fill(0.) - # Compute cmfd_src in a vecotorized manner by phi to the spatial # indices of the actual problem so that cmfd_flux can be multiplied by # nfissxs @@ -1117,7 +1097,7 @@ class CMFDRun(object): ng = self._indices[3] # Set weight factors to default 1.0 - self._weightfactors.fill(1.0) + self._weightfactors = np.ones((nx, ny, nz, ng)) # Count bank site in mesh and reverse due to egrid structured outside = self._count_bank_sites() @@ -1150,7 +1130,7 @@ class CMFDRun(object): with np.errstate(divide='ignore', invalid='ignore'): self._weightfactors = (np.divide(self._cmfd_src * norm, sourcecounts, where=div_condition, - out=np.ones_like(self._cmfd_src), dtype='float16')) + out=np.ones_like(self._cmfd_src), dtype='float32')) if not self._feedback: return @@ -1208,9 +1188,11 @@ class CMFDRun(object): bank = openmc.capi.source_bank() energy = self._egrid sites_outside = np.zeros(1, dtype=bool) + nxnynz = np.prod(self._indices[0:3]) ng = self._indices[3] outside = np.zeros(1, dtype=bool) + self._sourcecounts = np.zeros((nxnynz, ng)) count = np.zeros(self._sourcecounts.shape) # Get location and energy of each particle in source bank @@ -1589,13 +1571,6 @@ class CMFDRun(object): nz = self._indices[2] ng = self._indices[3] - # Set flux object and source distribution all to zeros - self._flux.fill(0.) - self._openmc_src.fill(0.) - - # Set mesh widths - self._hxyz[:,:,:,:] = openmc.capi.meshes[self._mesh_id].width - # Reset keff_bal to zero self._keff_bal = 0. @@ -1993,7 +1968,7 @@ class CMFDRun(object): def _precompute_matrix_indices(self): """Computes the indices and row/column data used to populate CMFD CSR matrices. These indices are used in _build_loss_matrix and - _build_prod_matrix + _build_prod_matrix. """ # Extract energy group indices diff --git a/src/state_point.cpp b/src/state_point.cpp index ab14734033..a849efe10f 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -29,9 +29,6 @@ namespace openmc { -extern "C" void statepoint_write_f(hid_t file_id); -extern "C" void load_state_point_f(hid_t file_id); - extern "C" int openmc_statepoint_write(const char* filename, bool* write_source) { From 8917da499b4afef1231eabc418856c6df017cd52 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Wed, 6 Mar 2019 18:50:02 -0500 Subject: [PATCH 003/151] Add tally window schemes, restructure run with run_in_memory and iter_batches, separate begin into tally_begin and feedback_begin --- openmc/cmfd.py | 457 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 340 insertions(+), 117 deletions(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index a7acda23a4..ee337a1be0 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -10,11 +10,13 @@ References """ +from contextlib import contextmanager from collections.abc import Iterable, Mapping from numbers import Real, Integral import sys import time from ctypes import c_int, c_double +import warnings import numpy as np from scipy import sparse @@ -191,8 +193,12 @@ class CMFDRun(object): Attributes ---------- - begin : int - Batch number at which CMFD calculations should begin + tally_begin : int + Batch number at which CMFD tallies should begin accummulating + feedback_begin: int + Batch number at which CMFD feedback should be turned on + reference_d : bool + Reference diffusion coefficients to fix CMFD parameters to dhat_reset : bool Indicate whether :math:`\widehat{D}` nonlinear CMFD parameters should be reset to zero before solving CMFD eigenproblem. @@ -251,6 +257,21 @@ class CMFDRun(object): * "math" - Create adjoint matrices mathematically as the transpose of loss and production CMFD matrices + window_type : {'rolling', 'fixed', 'none'} + Specifies type of tally window scheme to use to accumulate CMFD + tallies. Options are: + + * "expanding" - Have an expanding rolling window that doubles in size + to give more weight to more recent tallies as more generations are + simulated + * "rolling" - Have a fixed window size that aggregates tallies from + the same number of previous generations tallied + * 'none" - Don't use a windowing scheme so that all tallies from last + time they were reset are used for the CMFD algorithm. + + window_size : int + Size of window to use for tally window scheme. Only relevant when + window_type is set to "rolling" indices : numpy.ndarray Stores spatial and group dimensions as [nx, ny, nz, ng] cmfd_src : numpy.ndarray @@ -287,7 +308,10 @@ class CMFDRun(object): """ # Variables that users can modify - self._begin = 1 + self._tally_begin = 1 + self._feedback_begin = 1 + self._set_reference_params = False + self._ref_d = [] self._dhat_reset = False self._display = {'balance': False, 'dominance': False, 'entropy': False, 'source': False} @@ -305,6 +329,8 @@ class CMFDRun(object): self._spectral = 0.0 self._gauss_seidel_tolerance = [1.e-10, 1.e-5] self._adjoint_type = 'physical' + self._window_type = 'none' + self._window_size = 10 self._intracomm = None # External variables used during runtime but users cannot control @@ -323,6 +349,13 @@ class CMFDRun(object): self._adj_keff = None self._phi = None self._adj_phi = None + self._openmc_src_rate = None + self._flux_rate = None + self._total_rate = None + self._p1scatt_rate = None + self._scatt_rate = None + self._nfiss_rate = None + self._current_rate = None self._flux = None self._totalxs = None self._p1scattxs = None @@ -343,6 +376,7 @@ class CMFDRun(object): self._dom = [] self._k_cmfd = [] self._resnb = None + self._reset_every = None self._time_cmfd = None self._time_cmfdbuild = None self._time_cmfdsolve = None @@ -379,8 +413,16 @@ class CMFDRun(object): self._prod_col = None @property - def begin(self): - return self._begin + def tally_begin(self): + return self._tally_begin + + @property + def feedback_begin(self): + return self._feedback_begin + + @property + def ref_d(self): + return self._ref_d @property def dhat_reset(self): @@ -414,6 +456,14 @@ class CMFDRun(object): def adjoint_type(self): return self._adjoint_type + @property + def window_type(self): + return self._window_type + + @property + def window_size(self): + return self._window_size + @property def power_monitor(self): return self._power_monitor @@ -474,11 +524,23 @@ class CMFDRun(object): def k_cmfd(self): return self._k_cmfd - @begin.setter - def begin(self, begin): - check_type('CMFD begin batch', begin, Integral) - check_greater_than('CMFD begin batch', begin, 0) - self._begin = begin + @tally_begin.setter + def tally_begin(self, begin): + check_type('CMFD tally begin batch', begin, Integral) + check_greater_than('CMFD tally begin batch', begin, 0) + self._tally_begin = begin + + @feedback_begin.setter + def feedback_begin(self, begin): + check_type('CMFD feedback begin batch', begin, Integral) + check_greater_than('CMFD feedback begin batch', begin, 0) + self._feedback_begin = begin + + @ref_d.setter + def ref_d(self, diff_params): + check_type('Reference diffusion params', diff_params, + Iterable, Real) + self._ref_d = np.array(diff_params) @dhat_reset.setter def dhat_reset(self, dhat_reset): @@ -569,6 +631,23 @@ class CMFDRun(object): ['math', 'physical']) self._adjoint_type = adjoint_type + @window_type.setter + def window_type(self, window_type): + check_type('CMFD window type', window_type, str) + check_value('CMFD window type', window_type, + ['none', 'rolling', 'expanding']) + self._window_type = window_type + + @window_size.setter + def window_size(self, window_size): + check_type('CMFD window size', window_size, Integral) + check_greater_than('CMFD window size', window_size, 0) + if self._window_type != 'fixed': + warn_msg = 'Window size will have no effect on CMFD simulation ' \ + 'unless window type is set to "fixed".' + warnings.warn(warn_msg, RuntimeWarning) + self._window_size = window_size + @power_monitor.setter def power_monitor(self, power_monitor): check_type('CMFD power monitor', power_monitor, bool) @@ -624,6 +703,13 @@ class CMFDRun(object): :func:`openmc.capi.run_in_memory`. """ + with self.run_in_memory(**kwargs): + for _ in self.iter_batches(): + pass + + @contextmanager + def run_in_memory(self, **kwargs): + # TODO add function description # Store intracomm for part of CMFD routine where MPI reduce and # broadcast calls are made if 'intracomm' in kwargs and kwargs['intracomm'] is not None: @@ -633,47 +719,65 @@ class CMFDRun(object): # Run and pass arguments to C API run_in_memory function with openmc.capi.run_in_memory(**kwargs): - # Configure CMFD parameters and tallies - self._configure_cmfd() + self.init() + try: + yield + finally: + self.finalize() - # Initialize all arrays used for CMFD solver - self._allocate_cmfd() + def iter_batches(self): + # TODO add function description + status = 0 + while status == 0: + status = self.next_batch() + yield - # Compute and store array indices used to build cross section - # arrays - self._precompute_array_indices() + def init(self): + # TODO add function description + # Configure CMFD parameters and tallies + self._configure_cmfd() - # Compute and store row and column indices used to build CMFD - # matrices - self._precompute_matrix_indices() + # Initialize all arrays used for CMFD solver + self._allocate_cmfd() - # Initialize all variables used for linear solver in C++ - self._initialize_linsolver() + # Compute and store array indices used to build cross section + # arrays + self._precompute_array_indices() - # Initialize simulation - openmc.capi.simulation_init() + # Compute and store row and column indices used to build CMFD + # matrices + self._precompute_matrix_indices() - status = 0 - while status == 0: - # Initialize CMFD batch - self._cmfd_init_batch() + # Initialize all variables used for linear solver in C++ + self._initialize_linsolver() - # Run next batch - status = openmc.capi.next_batch() + # Initialize simulation + openmc.capi.simulation_init() - # Perform CMFD calculation if on - if self._cmfd_on: - self._execute_cmfd() + def next_batch(self): + # TODO add function description + # Initialize CMFD batch + self._cmfd_init_batch() - # Write CMFD output if CMFD on for current batch - if openmc.capi.master(): - self._write_cmfd_output() + # Run next batch + status = openmc.capi.next_batch() - # Finalize simuation - openmc.capi.simulation_finalize() + # Perform CMFD calculation if on + if self._cmfd_on: + self._execute_cmfd() - # Print out CMFD timing statistics - self._write_cmfd_timing_stats() + # Write CMFD output if CMFD on for current batch + if openmc.capi.master(): + self._write_cmfd_output() + return status + + def finalize(self): + # TODO add function description + # Finalize simuation + openmc.capi.simulation_finalize() + + # Print out CMFD timing statistics + self._write_cmfd_timing_stats() def _initialize_linsolver(self): # Determine number of rows in CMFD matrix @@ -796,6 +900,11 @@ class CMFDRun(object): self._coremap = np.ones((np.product(self._indices[0:3])), dtype=int) + # Check CMFD tallies accummulated before feedback turned on + if self._feedback and self._feedback_begin < self._tally_begin: + raise ValueError('Tally begin must be less than or equal to ' + 'feedback begin') + # Set number of batches where cmfd tallies should be reset if self._reset is not None: self._n_resets = len(self._reset) @@ -811,13 +920,56 @@ class CMFDRun(object): nz = self._indices[2] ng = self._indices[3] + # Allocate dimensions for each mesh cell + self._hxyz = np.zeros((nx, ny, nz, 3)) + self._hxyz[:,:,:,:] = openmc.capi.meshes[self._mesh_id].width + + # Allocate parameters that need to stored for rolling window + self._openmc_src_rate = np.zeros((nx, ny, nz, ng, 0)) + self._flux_rate = np.zeros((nx, ny, nz, ng, 0)) + self._total_rate = np.zeros((nx, ny, nz, ng, 0)) + self._p1scatt_rate = np.zeros((nx, ny, nz, ng, 0)) + self._scatt_rate = np.zeros((nx, ny, nz, ng, ng, 0)) + self._nfiss_rate = np.zeros((nx, ny, nz, ng, ng, 0)) + self._current_rate = np.zeros((nx, ny, nz, 12, ng, 0)) + + # Allocate flux, cross sections and diffusion coefficient + self._flux = np.zeros((nx, ny, nz, ng)) + self._totalxs = np.zeros((nx, ny, nz, ng)) + self._p1scattxs = np.zeros((nx, ny, nz, ng)) + self._scattxs = np.zeros((nx, ny, nz, ng, ng)) # Incoming, outgoing + self._nfissxs = np.zeros((nx, ny, nz, ng, ng)) # Incoming, outgoing + self._diffcof = np.zeros((nx, ny, nz, ng)) + # Allocate dtilde and dhat self._dtilde = np.zeros((nx, ny, nz, ng, 6)) self._dhat = np.zeros((nx, ny, nz, ng, 6)) - # Allocate dimensions for each mesh cell - self._hxyz = np.zeros((nx, ny, nz, 3)) - self._hxyz[:,:,:,:] = openmc.capi.meshes[self._mesh_id].width + # Initialize parameters for CMFD tally windows + self._set_tally_window() + + # Set reference diffusion parameters + if len(self._ref_d) > 0: + self._set_reference_params = True + # Check length of reference diffusion parameters equal to number of + # energy groups + if len(self._ref_d) != self._indices[3]: + raise OpenMCError('Number of reference diffusion parameters ' + 'must equal number of CMFD energy groups') + + def _set_tally_window(self): + """Sets parameters to handle different tally window options""" + # Set parameters for rolling window + if self._window_type == 'expanding': + self._reset_every = True + self._window_size = 1 + # Set parameters for fixed window + elif self.window_type == 'rolling': + self._reset_every = True + # Set parameters for default case, with no window + else: + self._window_size = 1 + self._reset_every = False def _cmfd_init_batch(self): """Handles CMFD options at the beginning of each batch""" @@ -826,11 +978,12 @@ class CMFDRun(object): current_batch = openmc.capi.current_batch() + 1 # Check to activate CMFD diffusion and possible feedback - if self._begin == current_batch: + # Check to activate CMFD tallies + if self._tally_begin == current_batch: self._cmfd_on = True # Check to reset tallies - if self._n_resets > 0 and current_batch in self._reset: + if (self._n_resets > 0 and current_batch in self._reset) or self._reset_every: self._cmfd_tally_reset() def _execute_cmfd(self): @@ -868,7 +1021,8 @@ class CMFDRun(object): def _cmfd_tally_reset(self): """Resets all CMFD tallies in memory""" # Print message - if openmc.capi.settings.verbosity >= 6 and openmc.capi.master(): + if (openmc.capi.settings.verbosity >= 6 and openmc.capi.master() and + not self._reset_every): print(' CMFD tallies reset') sys.stdout.flush() @@ -881,7 +1035,7 @@ class CMFDRun(object): """Configures CMFD object for a CMFD eigenvalue calculation """ - # Calculate all cross sections based on reaction rates from last batch + # Calculate all cross sections based on rolling window averages self._compute_xs() # Compute effective downscatter cross section @@ -1096,9 +1250,6 @@ class CMFDRun(object): nz = self._indices[2] ng = self._indices[3] - # Set weight factors to default 1.0 - self._weightfactors = np.ones((nx, ny, nz, ng)) - # Count bank site in mesh and reverse due to egrid structured outside = self._count_bank_sites() @@ -1130,9 +1281,9 @@ class CMFDRun(object): with np.errstate(divide='ignore', invalid='ignore'): self._weightfactors = (np.divide(self._cmfd_src * norm, sourcecounts, where=div_condition, - out=np.ones_like(self._cmfd_src), dtype='float32')) + out=np.ones_like(self._cmfd_src), dtype=np.float32)) - if not self._feedback: + if not self._feedback or openmc.capi.current_batch() < self._feedback_begin: return # Broadcast weight factors to all procs @@ -1562,18 +1713,34 @@ class CMFDRun(object): def _compute_xs(self): """Takes CMFD tallies from OpenMC and computes macroscopic cross - sections, flux, and diffusion coefficients for each mesh cell + sections, flux, and diffusion coefficients for each mesh cell using + a tally window scheme """ + # Update window size for rolling window if necessary + num_cmfd_batches = openmc.capi.current_batch() - self._tally_begin + 1 + if (self._window_type == 'expanding' and + num_cmfd_batches == self._window_size * 2): + self._window_size *= 2 + + # Discard tallies from oldest batch if window limit reached + tally_windows = self._flux_rate.shape[-1] + 1 + if tally_windows > self._window_size: + self._flux_rate = self._flux_rate[:,:,:,:,1:] + self._total_rate = self._total_rate[:,:,:,:,1:] + self._p1scatt_rate = self._p1scatt_rate[:,:,:,:,1:] + self._scatt_rate = self._scatt_rate[:,:,:,:,:,1:] + self._nfiss_rate = self._nfiss_rate[:,:,:,:,:,1:] + self._current_rate = self._current_rate[:,:,:,:,:,1:] + self._openmc_src_rate = self._openmc_src_rate[:,:,:,:,1:] + tally_windows -= 1 + # Extract spatial and energy indices nx = self._indices[0] ny = self._indices[1] nz = self._indices[2] ng = self._indices[3] - # Reset keff_bal to zero - self._keff_bal = 0. - # Get tallies in-memory tallies = openmc.capi.tallies @@ -1607,81 +1774,118 @@ class CMFDRun(object): # Define target tally reshape dimensions. This defines how openmc # tallies are ordered by dimension - target_tally_shape = [nz, ny, nx, ng] + target_tally_shape = [nz, ny, nx, ng, 1] # Reshape flux array to target shape. Swap x and z axes so that - # flux shape is now [nx, ny, nz, ng] + # flux shape is now [nx, ny, nz, ng, 1] reshape_flux = np.swapaxes(flux.reshape(target_tally_shape), 0, 2) # Flip energy axis as tally results are given in reverse order of # energy group - self._flux = np.flip(reshape_flux, axis=3) + reshape_flux = np.flip(reshape_flux, axis=3) - # Get total rr and convert to total xs from CMFD tally 0 - tally_results = tallies[tally_id].results[:,1,1] - with np.errstate(divide='ignore', invalid='ignore'): - totalxs = np.divide(tally_results, flux, - where=flux > 0, - out=np.zeros_like(tally_results)) + # Bank flux to flux_rate + self._flux_rate = np.append(self._flux_rate, reshape_flux, axis=4) - # Reshape totalxs array to target shape. Swap x and z axes so that - # shape is now [nx, ny, nz, ng] - reshape_totalxs = np.swapaxes(totalxs.reshape(target_tally_shape), + # Compute flux as aggregate of banked flux_rate over tally window + self._flux = np.sum(self._flux_rate, axis=4) + + # Get total rr from CMFD tally 0 + totalrr = tallies[tally_id].results[:,1,1] + + # Reshape totalrr array to target shape. Swap x and z axes so that + # shape is now [nx, ny, nz, ng, 1] + reshape_totalrr = np.swapaxes(totalrr.reshape(target_tally_shape), 0, 2) - # Total xs is flipped in energy axis as tally results are given in + # Total rr is flipped in energy axis as tally results are given in # reverse order of energy group - self._totalxs = np.flip(reshape_totalxs, axis=3) + reshape_totalrr = np.flip(reshape_totalrr, axis=3) - # Get scattering xs from CMFD tally 1 + # Bank total rr to total_rate + self._total_rate = np.append(self._total_rate, reshape_totalrr, + axis=4) + + # Compute total xs as aggregate of banked total_rate over tally window + # divided by flux + with np.errstate(divide='ignore', invalid='ignore'): + self._totalxs = np.divide(np.sum(self._total_rate, axis=4), + self._flux, where=self._flux > 0, + out=np.zeros_like(self._totalxs)) + + # Get scattering rr from CMFD tally 1 # flux is repeated to account for extra dimensionality of scattering xs tally_id = self._tally_ids[1] - tally_results = tallies[tally_id].results[:,0,1] - with np.errstate(divide='ignore', invalid='ignore'): - scattxs = np.divide(tally_results, np.repeat(flux, ng), - where=np.repeat(flux > 0, ng), - out=np.zeros_like(tally_results)) + scattrr = tallies[tally_id].results[:,0,1] # Define target tally reshape dimensions for xs with incoming # and outgoing energies - target_tally_shape = [nz, ny, nx, ng, ng] + target_tally_shape = [nz, ny, nx, ng, ng, 1] - # Reshape scattxs array to target shape. Swap x and z axes so that - # shape is now [nx, ny, nz, ng, ng] - reshape_scattxs = np.swapaxes(scattxs.reshape(target_tally_shape), + # Reshape scattrr array to target shape. Swap x and z axes so that + # shape is now [nx, ny, nz, ng, ng, 1] + reshape_scattrr = np.swapaxes(scattrr.reshape(target_tally_shape), 0, 2) - # Scattering xs is flipped in both incoming and outgoing energy axes + # Scattering rr is flipped in both incoming and outgoing energy axes # as tally results are given in reverse order of energy group - self._scattxs = np.flip(reshape_scattxs, axis=3) - self._scattxs = np.flip(self._scattxs, axis=4) + reshape_scattrr = np.flip(reshape_scattrr, axis=3) + reshape_scattrr = np.flip(reshape_scattrr, axis=4) - # Get nu-fission xs from CMFD tally 1 - # flux is repeated to account for extra dimensionality of nu-fission xs - tally_results = tallies[tally_id].results[:,1,1] - num_realizations = tallies[tally_id].num_realizations + # Bank scattering rr to scatt_rate + self._scatt_rate = np.append(self._scatt_rate, reshape_scattrr, + axis=5) + + # Compute scattering xs as aggregate of banked scatt_rate over tally window + # divided by flux. Flux dimensionality increased to account for extra + # dimensionality of scattering xs + extended_flux = self._flux[:,:,:,:,np.newaxis] with np.errstate(divide='ignore', invalid='ignore'): - nfissxs = np.divide(tally_results, np.repeat(flux, ng), - where=np.repeat(flux > 0, ng), - out=np.zeros_like(tally_results)) + self._scattxs = np.divide(np.sum(self._scatt_rate, axis=5), + extended_flux, where=extended_flux > 0, + out=np.zeros_like(self._scattxs)) - # Reshape nfissxs array to target shape. Swap x and z axes so that - # shape is now [nx, ny, nz, ng, ng] - reshape_nfissxs = np.swapaxes(nfissxs.reshape(target_tally_shape), + # Get nu-fission rr from CMFD tally 1 + nfissrr = tallies[tally_id].results[:,1,1] + num_realizations = tallies[tally_id].num_realizations + + # Reshape nfissrr array to target shape. Swap x and z axes so that + # shape is now [nx, ny, nz, ng, ng, 1] + reshape_nfissrr = np.swapaxes(nfissrr.reshape(target_tally_shape), 0, 2) - # Nu-fission xs is flipped in both incoming and outgoing energy axes + # Nu-fission rr is flipped in both incoming and outgoing energy axes # as tally results are given in reverse order of energy group - self._nfissxs = np.flip(reshape_nfissxs, axis=3) - self._nfissxs = np.flip(self._nfissxs, axis=4) + reshape_nfissrr = np.flip(reshape_nfissrr, axis=3) + reshape_nfissrr = np.flip(reshape_nfissrr, axis=4) + + # Bank nu-fission rr to nfiss_rate + self._nfiss_rate = np.append(self._nfiss_rate, reshape_nfissrr, + axis=5) + + # Compute nu-fission xs as aggregate of banked nfiss_rate over tally window + # divided by flux. Flux dimensionality increased to account for extra + # dimensionality of nu-fission xs + with np.errstate(divide='ignore', invalid='ignore'): + self._nfissxs = np.divide(np.sum(self._nfiss_rate, axis=5), + extended_flux, where=extended_flux > 0, + out=np.zeros_like(self._nfissxs)) # Openmc source distribution is sum of nu-fission rr in incoming # energies - self._openmc_src = np.sum(self._nfissxs*self._flux[:,:,:,:,np.newaxis], - axis=3) + openmc_src = np.sum(reshape_nfissrr, axis=3) + + # Bank OpenMC source distribution from current batch to + # openmc_src_rate + self._openmc_src_rate = np.append(self._openmc_src_rate, openmc_src, + axis=4) + + # Compute source distribution over entire tally window + self._openmc_src = np.sum(self._openmc_src_rate, axis=4) # Compute k_eff from source distribution - self._keff_bal = np.sum(self._openmc_src) / num_realizations + self._keff_bal = (np.sum(self._openmc_src) / num_realizations / + tally_windows) # Normalize openmc source distribution self._openmc_src /= np.sum(self._openmc_src) * self._norm @@ -1691,44 +1895,63 @@ class CMFDRun(object): tally_results = tallies[tally_id].results[:,0,1] # Filter tally results to include only accelerated regions - current = np.where(np.repeat(flux > 0, 12), tally_results, 0.) + current = np.where(np.repeat(is_cmfd_accel, 12), tally_results, 0.) # Define target tally reshape dimensions for current - target_tally_shape = [nz, ny, nx, 12, ng] + target_tally_shape = [nz, ny, nx, 12, ng, 1] # Reshape current array to target shape. Swap x and z axes so that - # shape is now [nx, ny, nz, ng, 12] + # shape is now [nx, ny, nz, 12, ng, 1] reshape_current = np.swapaxes(current.reshape(target_tally_shape), 0, 2) # Current is flipped in energy axis as tally results are given in # reverse order of energy group - self._current = np.flip(reshape_current, axis=4) + reshape_current = np.flip(reshape_current, axis=4) - # Get p1 scatter xs from CMFD tally 3 + # Bank current to current_rate + self._current_rate = np.append(self._current_rate, reshape_current, + axis=5) + + # Compute current as aggregate of banked current_rate over tally window + self._current = np.sum(self._current_rate, axis=5) + + # Get p1 scatter rr from CMFD tally 3 tally_id = self._tally_ids[3] - tally_results = tallies[tally_id].results[:,0,1] + p1scattrr = tallies[tally_id].results[:,0,1] # Define target tally reshape dimensions for p1 scatter tally - target_tally_shape = [nz, ny, nx, 2, ng] + target_tally_shape = [nz, ny, nx, 2, ng, 1] # Reshape and extract only p1 data from tally results as there is # no need for p0 data - p1scattrr = np.swapaxes(tally_results.reshape(target_tally_shape), - 0, 2)[:,:,:,1,:] + reshape_p1scattrr = np.swapaxes(p1scattrr.reshape(target_tally_shape), + 0, 2)[:,:,:,1,:,:] - # Store p1 scatter xs - # p1 scatter xs is flipped in energy axis as tally results are given in + # p1-scatter rr is flipped in energy axis as tally results are given in # reverse order of energy group - with np.errstate(divide='ignore', invalid='ignore'): - self._p1scattxs = np.divide(np.flip(p1scattrr, axis=3), self._flux, - where=self._flux > 0, - out=np.zeros_like(p1scattrr)) + reshape_p1scattrr = np.flip(reshape_p1scattrr, axis=3) - # Calculate and store diffusion coefficient + # Bank p1-scatter rr to p1scatt_rate + self._p1scatt_rate = np.append(self._p1scatt_rate, reshape_p1scattrr, + axis=4) + + # Compute p1-scatter xs as aggregate of banked p1scatt_rate over tally + # window divided by flux with np.errstate(divide='ignore', invalid='ignore'): - self._diffcof = np.where(self._flux > 0, 1.0 / (3.0 * - (self._totalxs - self._p1scattxs)), 0.) + self._p1scattxs = np.divide(np.sum(self._p1scatt_rate, axis=4), + self._flux, where=self._flux > 0, + out=np.zeros_like(self._p1scattxs)) + + if self._set_reference_params: + # Set diffusion coefficients based on reference value + self._diffcof = np.where(self._flux > 0, + self._ref_d[None, None, None, :], 0.0) + else: + # Calculate and store diffusion coefficient + with np.errstate(divide='ignore', invalid='ignore'): + self._diffcof = np.where(self._flux > 0, 1.0 / (3.0 * + (self._totalxs-self._p1scattxs)), 0.) def _compute_effective_downscatter(self): """Changes downscatter rate for zero upscatter""" From a236d4c8e62f45640ffc9d67b122a0bf3a8dec79 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Wed, 6 Mar 2019 19:26:19 -0500 Subject: [PATCH 004/151] Replace mention of fixed window with rolling window --- openmc/cmfd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index ee337a1be0..72aefee3ef 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -257,7 +257,7 @@ class CMFDRun(object): * "math" - Create adjoint matrices mathematically as the transpose of loss and production CMFD matrices - window_type : {'rolling', 'fixed', 'none'} + window_type : {'expanding', 'rolling', 'none'} Specifies type of tally window scheme to use to accumulate CMFD tallies. Options are: @@ -642,9 +642,9 @@ class CMFDRun(object): def window_size(self, window_size): check_type('CMFD window size', window_size, Integral) check_greater_than('CMFD window size', window_size, 0) - if self._window_type != 'fixed': + if self._window_type != 'rolling': warn_msg = 'Window size will have no effect on CMFD simulation ' \ - 'unless window type is set to "fixed".' + 'unless window type is set to "rolling".' warnings.warn(warn_msg, RuntimeWarning) self._window_size = window_size @@ -963,7 +963,7 @@ class CMFDRun(object): if self._window_type == 'expanding': self._reset_every = True self._window_size = 1 - # Set parameters for fixed window + # Set parameters for rolling window elif self.window_type == 'rolling': self._reset_every = True # Set parameters for default case, with no window From f5d972475faff3c6c7d6512cc5626e5ea96fce27 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Wed, 6 Mar 2019 20:22:55 -0500 Subject: [PATCH 005/151] Get rid of finally statement from run_in_memory function --- openmc/cmfd.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 72aefee3ef..0a5ba7cd16 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -720,10 +720,8 @@ class CMFDRun(object): # Run and pass arguments to C API run_in_memory function with openmc.capi.run_in_memory(**kwargs): self.init() - try: - yield - finally: - self.finalize() + yield + self.finalize() def iter_batches(self): # TODO add function description From 8424a2c850350a71a812ba1db92b2b158e2bcf21 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 12 Mar 2019 01:54:30 -0400 Subject: [PATCH 006/151] First pass at statepoint_write --- include/openmc/capi.h | 2 + openmc/capi/core.py | 13 +++ openmc/cmfd.py | 91 ++++++++++++++++--- src/message_passing.cpp | 1 + src/simulation.cpp | 10 ++ .../regression_tests/cmfd_nofeed/settings.xml | 2 + 6 files changed, 108 insertions(+), 11 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 9bfe4aa9ea..ebc5126df4 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -42,9 +42,11 @@ extern "C" { int openmc_global_tallies(double** ptr); int openmc_hard_reset(); int openmc_init(int argc, char* argv[], const void* intracomm); + bool openmc_is_statepoint_batch(); int openmc_legendre_filter_get_order(int32_t index, int* order); int openmc_legendre_filter_set_order(int32_t index, int order); int openmc_load_nuclide(const char* name); + bool openmc_master(); int openmc_material_add_nuclide(int32_t index, const char name[], double density); int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n); int openmc_material_get_id(int32_t index, int32_t* id); diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 922877c8e4..b85adb32f2 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -47,6 +47,7 @@ _init_linsolver_argtypes = [_array_1d_int, c_int, _array_1d_int, c_int, c_int, c_double, _array_1d_int, _array_1d_int] _dll.openmc_initialize_linsolver.argtypes = _init_linsolver_argtypes _dll.openmc_initialize_linsolver.restype = None +_dll.openmc_is_statepoint_batch.restype = c_bool _dll.openmc_master.restype = c_bool _dll.openmc_next_batch.argtypes = [POINTER(c_int)] _dll.openmc_next_batch.restype = c_int @@ -186,6 +187,18 @@ def init(args=None, intracomm=None): _dll.openmc_init(argc, argv, intracomm) +def is_statepoint_batch(): + """Return whether statepoint will be written in current batch or not. + + Returns + ------- + bool + Whether is statepoint batch or not + + """ + return _dll.openmc_is_statepoint_batch() + + def iter_batches(): """Iterator over batches. diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 0a5ba7cd16..60156c7f5a 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -17,9 +17,11 @@ import sys import time from ctypes import c_int, c_double import warnings +import os.path import numpy as np from scipy import sparse +import h5py import openmc.capi from openmc.checkvalue import (check_type, check_length, check_value, @@ -767,6 +769,10 @@ class CMFDRun(object): # Write CMFD output if CMFD on for current batch if openmc.capi.master(): self._write_cmfd_output() + + # Write CMFD data to statepoint + if openmc.capi.is_statepoint_batch(): + self.statepoint_write() return status def finalize(self): @@ -777,6 +783,64 @@ class CMFDRun(object): # Print out CMFD timing statistics self._write_cmfd_timing_stats() + def statepoint_write(self, filename=None): + if filename is None: + batch_str_len = len(str(openmc.capi.settings.batches)) + batch_str = str(openmc.capi.current_batch()).zfill(batch_str_len) + filename = 'statepoint.{}.h5'.format(batch_str) + + # Call C API statepoint_write if statepoint file doesn't exist + if not os.path.isfile(filename): + openmc.capi.statepoint_write(filename=filename) + + # Append CMFD data to statepoint file using h5py + self._write_cmfd_statepoint(filename) + + def _write_cmfd_statepoint(self, filename): + if openmc.capi.master(): + if openmc.capi.settings.verbosity >= 5: + print(' Writing CMFD data to {}'.format(filename)) + sys.stdout.flush() + # TODO write CMFD data to statepoint file + # TODO check if "cmfd" group already exists + with h5py.File(filename, 'a') as h5f: + cmfd_group = h5f.create_group("cmfd") + cmfd_group.attrs['cmfd_on'] = self._cmfd_on + cmfd_group.attrs['feedback_begin'] = self._feedback_begin + cmfd_group.attrs['mesh_id'] = self._mesh_id + cmfd_group.attrs['tally_begin'] = self._tally_begin + cmfd_group.attrs['time_cmfd'] = self._time_cmfd + cmfd_group.attrs['time_cmfdbuild'] = self._time_cmfdbuild + cmfd_group.attrs['time_cmfdsolve'] = self._time_cmfdsolve + cmfd_group.attrs['window_size'] = self._window_size + cmfd_group.attrs['window_type'] = self._window_type + cmfd_group.create_dataset('k_cmfd', data=self._k_cmfd) + cmfd_group.create_dataset('dom', data=self._dom) + cmfd_group.create_dataset('src_cmp', data=self._src_cmp) + cmfd_group.create_dataset('balance', data=self._balance) + cmfd_group.create_dataset('entropy', data=self._entropy) + cmfd_group.create_dataset('albedo', data=self._albedo) + cmfd_group.create_dataset('coremap', data=self._coremap) + cmfd_group.create_dataset('egrid', data=self._egrid) + cmfd_group.create_dataset('indices', data=self._indices) + cmfd_group.create_dataset('tally_ids', data=self._tally_ids) + + if self._cmfd_on: + cmfd_group.create_dataset('current_rate', + data=self._current_rate) + cmfd_group.create_dataset('flux_rate', + data=self._flux_rate) + cmfd_group.create_dataset('nfiss_rate', + data=self._nfiss_rate) + cmfd_group.create_dataset('openmc_src_rate', + data=self._openmc_src_rate) + cmfd_group.create_dataset('p1scatt_rate', + data=self._p1scatt_rate) + cmfd_group.create_dataset('scatt_rate', + data=self._scatt_rate) + cmfd_group.create_dataset('total_rate', + data=self._total_rate) + def _initialize_linsolver(self): # Determine number of rows in CMFD matrix ng = self._indices[3] @@ -841,6 +905,21 @@ class CMFDRun(object): # Set up CMFD coremap self._set_coremap() + # Extract spatial and energy indices + nx = self._indices[0] + ny = self._indices[1] + nz = self._indices[2] + ng = self._indices[3] + + # Allocate parameters that need to stored for rolling window + self._openmc_src_rate = np.zeros((nx, ny, nz, ng, 0)) + self._flux_rate = np.zeros((nx, ny, nz, ng, 0)) + self._total_rate = np.zeros((nx, ny, nz, ng, 0)) + self._p1scatt_rate = np.zeros((nx, ny, nz, ng, 0)) + self._scatt_rate = np.zeros((nx, ny, nz, ng, ng, 0)) + self._nfiss_rate = np.zeros((nx, ny, nz, ng, ng, 0)) + self._current_rate = np.zeros((nx, ny, nz, 12, ng, 0)) + # Initialize timers self._time_cmfd = 0.0 self._time_cmfdbuild = 0.0 @@ -904,8 +983,7 @@ class CMFDRun(object): 'feedback begin') # Set number of batches where cmfd tallies should be reset - if self._reset is not None: - self._n_resets = len(self._reset) + self._n_resets = len(self._reset) # Create tally objects self._create_cmfd_tally() @@ -922,15 +1000,6 @@ class CMFDRun(object): self._hxyz = np.zeros((nx, ny, nz, 3)) self._hxyz[:,:,:,:] = openmc.capi.meshes[self._mesh_id].width - # Allocate parameters that need to stored for rolling window - self._openmc_src_rate = np.zeros((nx, ny, nz, ng, 0)) - self._flux_rate = np.zeros((nx, ny, nz, ng, 0)) - self._total_rate = np.zeros((nx, ny, nz, ng, 0)) - self._p1scatt_rate = np.zeros((nx, ny, nz, ng, 0)) - self._scatt_rate = np.zeros((nx, ny, nz, ng, ng, 0)) - self._nfiss_rate = np.zeros((nx, ny, nz, ng, ng, 0)) - self._current_rate = np.zeros((nx, ny, nz, 12, ng, 0)) - # Allocate flux, cross sections and diffusion coefficient self._flux = np.zeros((nx, ny, nz, ng)) self._totalxs = np.zeros((nx, ny, nz, ng)) diff --git a/src/message_passing.cpp b/src/message_passing.cpp index 278bf924d3..e53c65825d 100644 --- a/src/message_passing.cpp +++ b/src/message_passing.cpp @@ -1,4 +1,5 @@ #include "openmc/message_passing.h" +#include "openmc/capi.h" namespace openmc { namespace mpi { diff --git a/src/simulation.cpp b/src/simulation.cpp index 7d30ccea9c..c0e534fc3d 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -233,6 +233,16 @@ int openmc_next_batch(int* status) return 0; } +bool openmc_is_statepoint_batch() { + using namespace openmc; + using openmc::simulation::current_gen; + + if (!simulation::initialized) + return false; + else + return contains(settings::statepoint_batch, simulation::current_batch); +} + namespace openmc { //============================================================================== diff --git a/tests/regression_tests/cmfd_nofeed/settings.xml b/tests/regression_tests/cmfd_nofeed/settings.xml index 24b0b6ab50..5c89af8d29 100644 --- a/tests/regression_tests/cmfd_nofeed/settings.xml +++ b/tests/regression_tests/cmfd_nofeed/settings.xml @@ -23,4 +23,6 @@ 10 + + From bf4ae24639301104719c9cd45b5c1972ddc44b77 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 12 Mar 2019 17:35:14 -0400 Subject: [PATCH 007/151] Be able to read CMFD data and load CMFD tallies from statepoint file --- include/openmc/capi.h | 2 + openmc/capi/core.py | 2 + openmc/cmfd.py | 134 +++++++++++++++++++++++++++++++----------- src/state_point.cpp | 36 ++++++++++++ 4 files changed, 141 insertions(+), 33 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index ebc5126df4..d66ce5f6a3 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -45,6 +45,8 @@ extern "C" { bool openmc_is_statepoint_batch(); int openmc_legendre_filter_get_order(int32_t index, int* order); int openmc_legendre_filter_set_order(int32_t index, int order); + void openmc_load_cmfd_tallies(const int tally_ids[4], + const int sp_tally_ids[4]); int openmc_load_nuclide(const char* name); bool openmc_master(); int openmc_material_add_nuclide(int32_t index, const char name[], double density); diff --git a/openmc/capi/core.py b/openmc/capi/core.py index b85adb32f2..c637ee28f2 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -48,6 +48,8 @@ _init_linsolver_argtypes = [_array_1d_int, c_int, _array_1d_int, c_int, c_int, _dll.openmc_initialize_linsolver.argtypes = _init_linsolver_argtypes _dll.openmc_initialize_linsolver.restype = None _dll.openmc_is_statepoint_batch.restype = c_bool +_dll.openmc_load_cmfd_tallies.restype = None +_dll.openmc_load_cmfd_tallies.argtypes = [POINTER(c_int), POINTER(c_int)] _dll.openmc_master.restype = c_bool _dll.openmc_next_batch.argtypes = [POINTER(c_int)] _dll.openmc_next_batch.restype = c_int diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 60156c7f5a..cd528197aa 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -15,7 +15,7 @@ from collections.abc import Iterable, Mapping from numbers import Real, Integral import sys import time -from ctypes import c_int, c_double +from ctypes import c_int import warnings import os.path @@ -798,34 +798,34 @@ class CMFDRun(object): def _write_cmfd_statepoint(self, filename): if openmc.capi.master(): - if openmc.capi.settings.verbosity >= 5: - print(' Writing CMFD data to {}'.format(filename)) - sys.stdout.flush() - # TODO write CMFD data to statepoint file - # TODO check if "cmfd" group already exists with h5py.File(filename, 'a') as h5f: - cmfd_group = h5f.create_group("cmfd") - cmfd_group.attrs['cmfd_on'] = self._cmfd_on - cmfd_group.attrs['feedback_begin'] = self._feedback_begin - cmfd_group.attrs['mesh_id'] = self._mesh_id - cmfd_group.attrs['tally_begin'] = self._tally_begin - cmfd_group.attrs['time_cmfd'] = self._time_cmfd - cmfd_group.attrs['time_cmfdbuild'] = self._time_cmfdbuild - cmfd_group.attrs['time_cmfdsolve'] = self._time_cmfdsolve - cmfd_group.attrs['window_size'] = self._window_size - cmfd_group.attrs['window_type'] = self._window_type - cmfd_group.create_dataset('k_cmfd', data=self._k_cmfd) - cmfd_group.create_dataset('dom', data=self._dom) - cmfd_group.create_dataset('src_cmp', data=self._src_cmp) - cmfd_group.create_dataset('balance', data=self._balance) - cmfd_group.create_dataset('entropy', data=self._entropy) - cmfd_group.create_dataset('albedo', data=self._albedo) - cmfd_group.create_dataset('coremap', data=self._coremap) - cmfd_group.create_dataset('egrid', data=self._egrid) - cmfd_group.create_dataset('indices', data=self._indices) - cmfd_group.create_dataset('tally_ids', data=self._tally_ids) - - if self._cmfd_on: + if 'cmfd' not in h5f: + if openmc.capi.settings.verbosity >= 5: + print(' Writing CMFD data to {}...'.format(filename)) + sys.stdout.flush() + cmfd_group = h5f.create_group("cmfd") + cmfd_group.attrs['cmfd_on'] = self._cmfd_on + cmfd_group.attrs['feedback'] = self._feedback + cmfd_group.attrs['feedback_begin'] = self._feedback_begin + cmfd_group.attrs['mesh_id'] = self._mesh_id + cmfd_group.attrs['tally_begin'] = self._tally_begin + cmfd_group.attrs['time_cmfd'] = self._time_cmfd + cmfd_group.attrs['time_cmfdbuild'] = self._time_cmfdbuild + cmfd_group.attrs['time_cmfdsolve'] = self._time_cmfdsolve + cmfd_group.attrs['window_size'] = self._window_size + cmfd_group.attrs['window_type'] = self._window_type + cmfd_group.create_dataset('k_cmfd', data=self._k_cmfd) + cmfd_group.create_dataset('dom', data=self._dom) + cmfd_group.create_dataset('src_cmp', data=self._src_cmp) + cmfd_group.create_dataset('balance', data=self._balance) + cmfd_group.create_dataset('entropy', data=self._entropy) + cmfd_group.create_dataset('reset', data=self._reset) + cmfd_group.create_dataset('albedo', data=self._albedo) + cmfd_group.create_dataset('coremap', data=self._coremap) + cmfd_group.create_dataset('egrid', data=self._egrid) + cmfd_group.create_dataset('indices', data=self._indices) + cmfd_group.create_dataset('tally_ids', + data=self._tally_ids) cmfd_group.create_dataset('current_rate', data=self._current_rate) cmfd_group.create_dataset('flux_rate', @@ -840,6 +840,10 @@ class CMFDRun(object): data=self._scatt_rate) cmfd_group.create_dataset('total_rate', data=self._total_rate) + elif openmc.settings.verbosity >= 5: + print(' CMFD data not written to statepoint file' + 'as it already exists in {}'.format(filename)) + sys.stdout.flush() def _initialize_linsolver(self): # Determine number of rows in CMFD matrix @@ -925,11 +929,13 @@ class CMFDRun(object): self._time_cmfdbuild = 0.0 self._time_cmfdsolve = 0.0 + # Initialize parameters for CMFD tally windows + self._set_tally_window() + else: # Reset CMFD parameters from statepoint file - # TODO implement reset_cmfd path_statepoint = openmc.capi.settings.path_statepoint - sys.exit() + self._reset_cmfd(path_statepoint) def _read_cmfd_input(self): """Sets values of additional instance variables based on user input""" @@ -988,6 +994,71 @@ class CMFDRun(object): # Create tally objects self._create_cmfd_tally() + def _reset_cmfd(self, sp_filepath): + with h5py.File(sp_filepath, 'r') as h5f: + if 'cmfd' not in h5f: + raise OpenMCError('Could not find CMFD parameters in ', + 'file {}'.format(sp_filepath)) + else: + # Overwrite CMFD values from statepoint + if openmc.capi.settings.verbosity >= 5: + print(' Loading CMFD data from {}...'.format(sp_filepath)) + sys.stdout.flush() + self._cmfd_on = h5f['cmfd'].attrs['cmfd_on'] + self._feedback = h5f['cmfd'].attrs['feedback'] + self._feedback_begin = h5f['cmfd'].attrs['feedback_begin'] + self._tally_begin = h5f['cmfd'].attrs['tally_begin'] + self._time_cmfd = h5f['cmfd'].attrs['time_cmfd'] + self._time_cmfdbuild = h5f['cmfd'].attrs['time_cmfdbuild'] + self._time_cmfdsolve = h5f['cmfd'].attrs['time_cmfdsolve'] + self._window_size = h5f['cmfd'].attrs['window_size'] + self._window_type = h5f['cmfd'].attrs['window_type'] + self._k_cmfd = list(h5f['cmfd']['k_cmfd']) + self._dom = list(h5f['cmfd']['dom']) + self._src_cmp = list(h5f['cmfd']['src_cmp']) + self._balance = list(h5f['cmfd']['balance']) + self._entropy = list(h5f['cmfd']['entropy']) + self._reset = list(h5f['cmfd']['reset']) + self._albedo = h5f['cmfd']['albedo'][:] + self._coremap = h5f['cmfd']['coremap'][:] + self._egrid = h5f['cmfd']['egrid'][:] + self._indices = h5f['cmfd']['indices'][:] + self._current_rate = h5f['cmfd']['current_rate'][:] + self._flux_rate = h5f['cmfd']['flux_rate'][:] + self._nfiss_rate = h5f['cmfd']['nfiss_rate'][:] + self._openmc_src_rate = h5f['cmfd']['openmc_src_rate'][:] + self._p1scatt_rate = h5f['cmfd']['p1scatt_rate'][:] + self._scatt_rate = h5f['cmfd']['scatt_rate'][:] + self._total_rate = h5f['cmfd']['total_rate'][:] + + # Overwrite CMFD mesh properties + cmfd_mesh_name = 'mesh ' + str(h5f['cmfd'].attrs['mesh_id']) + cmfd_mesh = h5f['tallies']['meshes'][cmfd_mesh_name] + self._mesh.dimension = cmfd_mesh['dimension'][:] + self._mesh.lower_left = cmfd_mesh['lower_left'][:] + self._mesh.upper_right = cmfd_mesh['upper_right'][:] + self._mesh.width = cmfd_mesh['width'][:] + + # Store tally ids from statepoint run + sp_tally_ids = list(h5f['cmfd']['tally_ids']) + + # Set CMFD variables not in statepoint file + default_egrid = np.array([_ENERGY_MIN_NEUTRON, _ENERGY_MAX_NEUTRON]) + self._energy_filters = np.array_equal(self._egrid, default_egrid) + self._n_resets = len(self._reset) + openmc.capi.settings.run_CE = True + self._mat_dim = np.max(self._coremap) + 1 + self._reset_every = (self._window_type == 'expanding' or + self._window_type == 'rolling') + + # Recreate CMFD tallies in memory + self._create_cmfd_tally() + + # Restore tally results and n_realizations from statepoint data + arg1 = (c_int*4)(*self._tally_ids) + arg2 = (c_int*4)(*sp_tally_ids) + openmc.capi._dll.openmc_load_cmfd_tallies(arg1, arg2) + def _allocate_cmfd(self): """Allocates all numpy arrays and lists used in CMFD algorithm""" # Extract spatial and energy indices @@ -1012,9 +1083,6 @@ class CMFDRun(object): self._dtilde = np.zeros((nx, ny, nz, ng, 6)) self._dhat = np.zeros((nx, ny, nz, ng, 6)) - # Initialize parameters for CMFD tally windows - self._set_tally_window() - # Set reference diffusion parameters if len(self._ref_d) > 0: self._set_reference_params = True diff --git a/src/state_point.cpp b/src/state_point.cpp index a849efe10f..4e428a9ca4 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -299,6 +299,42 @@ openmc_statepoint_write(const char* filename, bool* write_source) return 0; } +extern "C" void +openmc_load_cmfd_tallies(const int* tally_ids, const int* sp_tally_ids) +{ + hid_t file_id = file_open(settings::path_statepoint.c_str(), 'r', true); + +#ifdef PHDF5 + if (true) { +#else + if (mpi::master) { +#endif + + hid_t tallies_group = open_group(file_id, "tallies"); + + // TODO pass this in as argument? + int n_tallies = 4; + for (int i =0; i < n_tallies; i++) { + std::string name = "tally " + std::to_string(sp_tally_ids[i]); + hid_t tally_group = open_group(tallies_group, name.c_str()); + // Get tally from tally_ids + auto& tally = model::tallies[model::tally_map[tally_ids[i]]]; + tally->init_results(); + auto& results = tally->results_; + std::cout << tally_ids[i] << "\n"; + read_tally_results(tally_group, results.shape()[0], + results.shape()[1], results.data()); + read_dataset(tally_group, "n_realizations", tally->n_realizations_); + close_group(tally_group); + } + + close_group(tallies_group); + } + // Close file + file_close(file_id); +} + + void restart_set_keff() { if (simulation::restart_batch > settings::n_inactive) { From 6b8ad7e317784baa14079d9a2378fd5ed10b53da Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 12 Mar 2019 18:30:04 -0400 Subject: [PATCH 008/151] Make statepoint restart work with feedback on --- openmc/cmfd.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index cd528197aa..211eb1b78d 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -789,9 +789,10 @@ class CMFDRun(object): batch_str = str(openmc.capi.current_batch()).zfill(batch_str_len) filename = 'statepoint.{}.h5'.format(batch_str) - # Call C API statepoint_write if statepoint file doesn't exist - if not os.path.isfile(filename): - openmc.capi.statepoint_write(filename=filename) + # Call C API statepoint_write to save source distribution with CMFD + # feedback + # TODO don't call statepoint_write in openmc if settings::cmfd_run + openmc.capi.statepoint_write(filename=filename) # Append CMFD data to statepoint file using h5py self._write_cmfd_statepoint(filename) From 5cd7db42fa5a9192fa600891e36bf4452294b7e2 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 12 Mar 2019 19:46:33 -0400 Subject: [PATCH 009/151] Fix bug when resetting energy_filters from statepoint --- openmc/cmfd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 211eb1b78d..1fdcce31d4 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -1045,7 +1045,7 @@ class CMFDRun(object): # Set CMFD variables not in statepoint file default_egrid = np.array([_ENERGY_MIN_NEUTRON, _ENERGY_MAX_NEUTRON]) - self._energy_filters = np.array_equal(self._egrid, default_egrid) + self._energy_filters = not np.array_equal(self._egrid, default_egrid) self._n_resets = len(self._reset) openmc.capi.settings.run_CE = True self._mat_dim = np.max(self._coremap) + 1 From 58b5a265c28d44734d987463042ef4a233f0991b Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 12 Mar 2019 19:51:18 -0400 Subject: [PATCH 010/151] Remove unnecessary lines --- include/openmc/capi.h | 1 - src/message_passing.cpp | 1 - tests/regression_tests/cmfd_nofeed/settings.xml | 2 -- 3 files changed, 4 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index d66ce5f6a3..f8e53e578f 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -48,7 +48,6 @@ extern "C" { void openmc_load_cmfd_tallies(const int tally_ids[4], const int sp_tally_ids[4]); int openmc_load_nuclide(const char* name); - bool openmc_master(); int openmc_material_add_nuclide(int32_t index, const char name[], double density); int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n); int openmc_material_get_id(int32_t index, int32_t* id); diff --git a/src/message_passing.cpp b/src/message_passing.cpp index e53c65825d..278bf924d3 100644 --- a/src/message_passing.cpp +++ b/src/message_passing.cpp @@ -1,5 +1,4 @@ #include "openmc/message_passing.h" -#include "openmc/capi.h" namespace openmc { namespace mpi { diff --git a/tests/regression_tests/cmfd_nofeed/settings.xml b/tests/regression_tests/cmfd_nofeed/settings.xml index 5c89af8d29..24b0b6ab50 100644 --- a/tests/regression_tests/cmfd_nofeed/settings.xml +++ b/tests/regression_tests/cmfd_nofeed/settings.xml @@ -23,6 +23,4 @@ 10 - - From 7cb847a4d0cd651e7d4f1204fd5d0bb35aa55ed5 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 12 Mar 2019 19:56:22 -0400 Subject: [PATCH 011/151] One more print statement to get rid of --- src/state_point.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/state_point.cpp b/src/state_point.cpp index 4e428a9ca4..9fe5a960f1 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -321,7 +321,6 @@ openmc_load_cmfd_tallies(const int* tally_ids, const int* sp_tally_ids) auto& tally = model::tallies[model::tally_map[tally_ids[i]]]; tally->init_results(); auto& results = tally->results_; - std::cout << tally_ids[i] << "\n"; read_tally_results(tally_group, results.shape()[0], results.shape()[1], results.data()); read_dataset(tally_group, "n_realizations", tally->n_realizations_); From dcc4b47ea4b74f6846bfc57e82fdf75ad86ffa23 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 12 Mar 2019 20:03:32 -0400 Subject: [PATCH 012/151] set_reference_params cannot be modified by user --- openmc/cmfd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 1fdcce31d4..a034d96e5a 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -312,7 +312,6 @@ class CMFDRun(object): # Variables that users can modify self._tally_begin = 1 self._feedback_begin = 1 - self._set_reference_params = False self._ref_d = [] self._dhat_reset = False self._display = {'balance': False, 'dominance': False, @@ -336,6 +335,7 @@ class CMFDRun(object): self._intracomm = None # External variables used during runtime but users cannot control + self._set_reference_params = False self._indices = np.zeros(4, dtype=np.int32) self._egrid = None self._albedo = None From 95948d66ecf41e6d285a11924585cfeb89ac482b Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 12 Mar 2019 21:20:27 -0400 Subject: [PATCH 013/151] Print statement only on master --- openmc/cmfd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index a034d96e5a..3c69c3bdb9 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -1002,7 +1002,8 @@ class CMFDRun(object): 'file {}'.format(sp_filepath)) else: # Overwrite CMFD values from statepoint - if openmc.capi.settings.verbosity >= 5: + if (openmc.capi.master() and + openmc.capi.settings.verbosity >= 5): print(' Loading CMFD data from {}...'.format(sp_filepath)) sys.stdout.flush() self._cmfd_on = h5f['cmfd'].attrs['cmfd_on'] From 967161cee87bde5a9b73e643c888276d5abd8e53 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Fri, 15 Mar 2019 12:16:49 +0300 Subject: [PATCH 014/151] test changes --- .cproject | 108 +++++++++++++++++++++ .project | 27 ++++++ .settings/org.eclipse.cdt.codan.core.prefs | 75 ++++++++++++++ include/openmc/constants.h | 2 +- include/openmc/lattice.h | 1 + src/initialize.cpp | 4 + 6 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 .cproject create mode 100644 .project create mode 100644 .settings/org.eclipse.cdt.codan.core.prefs diff --git a/.cproject b/.cproject new file mode 100644 index 0000000000..bdc88d84b6 --- /dev/null +++ b/.cproject @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000000..5eb05adae0 --- /dev/null +++ b/.project @@ -0,0 +1,27 @@ + + + openmccc + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/.settings/org.eclipse.cdt.codan.core.prefs b/.settings/org.eclipse.cdt.codan.core.prefs new file mode 100644 index 0000000000..77496f558f --- /dev/null +++ b/.settings/org.eclipse.cdt.codan.core.prefs @@ -0,0 +1,75 @@ +eclipse.preferences.version=1 +org.eclipse.cdt.codan.checkers.errnoreturn=Warning +org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return\\")",implicit\=>false} +org.eclipse.cdt.codan.checkers.errreturnvalue=Error +org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused return value\\")"} +org.eclipse.cdt.codan.checkers.nocommentinside=-Error +org.eclipse.cdt.codan.checkers.nocommentinside.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Nesting comments\\")"} +org.eclipse.cdt.codan.checkers.nolinecomment=-Error +org.eclipse.cdt.codan.checkers.nolinecomment.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Line comments\\")"} +org.eclipse.cdt.codan.checkers.noreturn=Error +org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return value\\")",implicit\=>false} +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=Error +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Abstract class cannot be instantiated\\")"} +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=Error +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Ambiguous problem\\")"} +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment in condition\\")"} +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment to itself\\")"} +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No break at end of case\\")",no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false,enable_fallthrough_quickfix_param\=>false} +org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning +org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Catching by reference is recommended\\")",unknown\=>false,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Circular inheritance\\")"} +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class members should be properly initialized\\")",skip\=>true} +org.eclipse.cdt.codan.internal.checkers.DecltypeAutoProblem=Error +org.eclipse.cdt.codan.internal.checkers.DecltypeAutoProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid 'decltype(auto)' specifier\\")"} +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Field cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Function cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.InvalidArguments=Error +org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid arguments\\")"} +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=Error +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid template argument\\")"} +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=Error +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Label statement not found\\")"} +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=Error +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Member declaration not found\\")"} +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Method cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Name convention for function\\")",pattern\=>"^[a-z]",macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class has a virtual method and non-virtual destructor\\")"} +org.eclipse.cdt.codan.internal.checkers.OverloadProblem=Error +org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid overload\\")"} +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=Error +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redeclaration\\")"} +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=Error +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redefinition\\")"} +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Return with parenthesis\\")"} +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Format String Vulnerability\\")"} +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Statement has no effect\\")",macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suggested parenthesis around expression\\")",paramNot\=>false} +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suspicious semicolon\\")",else\=>false,afterelse\=>false} +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Type cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused function declaration\\")",macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused static function\\")",macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused variable declaration in file scope\\")",macro\=>true,exceptions\=>("@(\#)","$Id")} +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Symbol is not resolved\\")"} +org.eclipse.cdt.qt.core.qtproblem=Warning +org.eclipse.cdt.qt.core.qtproblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_ON_FILE_OPEN\=>true,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>null} diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 87388c91a5..c11f6535e9 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -25,7 +25,7 @@ constexpr int VERSION_RELEASE {0}; constexpr std::array VERSION {VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE}; // HDF5 data format -constexpr int HDF5_VERSION[] {2, 0}; +constexpr int HDF5_VERSION[] {2, 0};//DR change version HDF5 to work // Version numbers for binary files constexpr std::array VERSION_STATEPOINT {17, 0}; diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index 9c8b8af686..eb8e1c14f6 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -266,6 +266,7 @@ public: void to_hdf5_inner(hid_t group_id) const; private: + int n_hextype_; //! Date: Fri, 15 Mar 2019 12:19:33 +0300 Subject: [PATCH 015/151] new ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3832322966..f733096466 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.pyc # Python distribution +.settings/ dist/ openmc.egg-info/ From 8a1bb58d4919c5005731445d17d87a4cde74eb83 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Thu, 4 Apr 2019 15:46:11 -0400 Subject: [PATCH 016/151] Minor tabbing discrepancy --- openmc/cmfd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 3c69c3bdb9..792479c005 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -733,8 +733,8 @@ class CMFDRun(object): yield def init(self): - # TODO add function description - # Configure CMFD parameters and tallies + # TODO add function description + # Configure CMFD parameters and tallies self._configure_cmfd() # Initialize all arrays used for CMFD solver From 7eb3496ec6a467a192da245417149d9a1d13a5c4 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Mon, 8 Apr 2019 08:44:59 -0400 Subject: [PATCH 017/151] Update test suite --- .../cmfd_feed/results_true.dat | 8 ++-- tests/regression_tests/cmfd_feed/test.py | 12 ++++-- .../cmfd_feed_2g/results_true.dat | 42 +++++++++---------- tests/regression_tests/cmfd_feed_2g/test.py | 3 +- .../cmfd_feed_ng/results_true.dat | 34 +++++++-------- tests/regression_tests/cmfd_feed_ng/test.py | 3 +- tests/regression_tests/cmfd_nofeed/test.py | 2 +- 7 files changed, 55 insertions(+), 49 deletions(-) diff --git a/tests/regression_tests/cmfd_feed/results_true.dat b/tests/regression_tests/cmfd_feed/results_true.dat index c175dd7db9..03c61b4d80 100644 --- a/tests/regression_tests/cmfd_feed/results_true.dat +++ b/tests/regression_tests/cmfd_feed/results_true.dat @@ -378,7 +378,7 @@ tally 5: 5.253064E+00 1.396224E+00 2.996497E+01 -4.508840E+01 +4.508839E+01 3.818076E+00 7.509442E-01 1.574994E+01 @@ -461,11 +461,11 @@ cmfd dominance ratio cmfd openmc source comparison 6.959834E-03 5.655657E-03 -3.886185E-03 +3.886186E-03 4.035116E-03 3.043277E-03 -5.455475E-03 -4.515311E-03 +5.455474E-03 +4.515310E-03 2.439840E-03 2.114032E-03 2.673132E-03 diff --git a/tests/regression_tests/cmfd_feed/test.py b/tests/regression_tests/cmfd_feed/test.py index 04ac442e0e..d45f316c87 100644 --- a/tests/regression_tests/cmfd_feed/test.py +++ b/tests/regression_tests/cmfd_feed/test.py @@ -23,7 +23,8 @@ def test_cmfd_physical_adjoint(): # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() cmfd_run.mesh = cmfd_mesh - cmfd_run.begin = 5 + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 5 cmfd_run.feedback = True cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] cmfd_run.run_adjoint = True @@ -52,7 +53,8 @@ def test_cmfd_math_adjoint(): # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() cmfd_run.mesh = cmfd_mesh - cmfd_run.begin = 5 + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 5 cmfd_run.feedback = True cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] cmfd_run.run_adjoint = True @@ -80,7 +82,8 @@ def test_cmfd_write_matrices(): # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() cmfd_run.mesh = cmfd_mesh - cmfd_run.begin = 5 + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 5 cmfd_run.display = {'dominance': True} cmfd_run.feedback = True cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] @@ -127,7 +130,8 @@ def test_cmfd_feed(): # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() cmfd_run.mesh = cmfd_mesh - cmfd_run.begin = 5 + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 5 cmfd_run.display = {'dominance': True} cmfd_run.feedback = True cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] diff --git a/tests/regression_tests/cmfd_feed_2g/results_true.dat b/tests/regression_tests/cmfd_feed_2g/results_true.dat index 2be15c4e88..8a1c5d84b4 100644 --- a/tests/regression_tests/cmfd_feed_2g/results_true.dat +++ b/tests/regression_tests/cmfd_feed_2g/results_true.dat @@ -49,7 +49,7 @@ tally 3: 0.000000E+00 2.033842E-02 4.375294E-05 -4.296338E+00 +4.296339E+00 9.280716E-01 3.493776E+00 6.157094E-01 @@ -80,7 +80,7 @@ tally 3: 0.000000E+00 0.000000E+00 1.796382E-02 -3.190854E-05 +3.190855E-05 4.343238E+00 9.514039E-01 3.504683E+00 @@ -106,7 +106,7 @@ tally 3: 9.885292E+01 4.888720E+02 9.509199E-01 -4.670951E-02 +4.670952E-02 tally 4: 0.000000E+00 0.000000E+00 @@ -374,20 +374,20 @@ cmfd entropy 1.999984E+00 cmfd balance 3.993043E-04 -4.168558E-04 -6.384693E-04 -3.928217E-04 -3.789833E-04 +4.168559E-04 +6.384691E-04 +3.928215E-04 +3.789835E-04 2.684860E-04 -4.849910E-04 +4.849914E-04 1.084019E-03 1.091775E-03 5.459767E-04 -4.455545E-04 +4.455544E-04 4.011468E-04 -3.710248E-04 +3.710247E-04 3.577152E-04 -3.845993E-04 +3.845992E-04 3.900672E-04 cmfd dominance ratio 6.239E-03 @@ -408,21 +408,21 @@ cmfd dominance ratio 5.996E-03 cmfd openmc source comparison 1.931386E-05 -2.839162E-05 -1.407962E-05 -6.718148E-06 -9.164193E-06 -1.382539E-05 +2.839161E-05 +1.407963E-05 +6.718156E-06 +9.164199E-06 +1.382540E-05 2.871606E-05 4.192300E-05 -5.327516E-05 +5.327515E-05 6.566500E-05 -6.655541E-05 +6.655540E-05 6.034977E-05 6.004677E-05 -5.711623E-05 -6.271264E-05 -6.425363E-05 +5.711622E-05 +6.271263E-05 +6.425362E-05 cmfd source 2.510278E-01 2.480592E-01 diff --git a/tests/regression_tests/cmfd_feed_2g/test.py b/tests/regression_tests/cmfd_feed_2g/test.py index 22cc7e1602..095dbf53a8 100644 --- a/tests/regression_tests/cmfd_feed_2g/test.py +++ b/tests/regression_tests/cmfd_feed_2g/test.py @@ -16,7 +16,8 @@ def test_cmfd_feed_2g(): # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() cmfd_run.mesh = cmfd_mesh - cmfd_run.begin = 5 + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 5 cmfd_run.display = {'dominance': True} cmfd_run.feedback = True cmfd_run.downscatter = True diff --git a/tests/regression_tests/cmfd_feed_ng/results_true.dat b/tests/regression_tests/cmfd_feed_ng/results_true.dat index fea3d38b8b..ccae5d032c 100644 --- a/tests/regression_tests/cmfd_feed_ng/results_true.dat +++ b/tests/regression_tests/cmfd_feed_ng/results_true.dat @@ -573,15 +573,15 @@ cmfd entropy cmfd balance 3.179889E-04 4.166064E-04 -2.746180E-04 -3.345930E-04 -5.129605E-04 -4.008511E-04 -3.628451E-04 -3.401727E-04 -3.166255E-04 +2.746178E-04 +3.345927E-04 +5.129604E-04 +4.008510E-04 +3.628450E-04 +3.401726E-04 +3.166254E-04 2.642675E-04 -2.482640E-04 +2.482639E-04 cmfd dominance ratio 3.737E-03 3.786E-03 @@ -597,15 +597,15 @@ cmfd dominance ratio cmfd openmc source comparison 3.559244E-05 4.429506E-05 -2.739679E-05 -2.715448E-05 -5.105340E-05 -4.069206E-05 -4.033877E-05 -3.669734E-05 -3.540373E-05 -2.907247E-05 -2.734478E-05 +2.739678E-05 +2.715447E-05 +5.105338E-05 +4.069204E-05 +4.033876E-05 +3.669732E-05 +3.540372E-05 +2.907246E-05 +2.734477E-05 cmfd source 2.564687E-01 2.445733E-01 diff --git a/tests/regression_tests/cmfd_feed_ng/test.py b/tests/regression_tests/cmfd_feed_ng/test.py index db0b44f3e1..266772054f 100644 --- a/tests/regression_tests/cmfd_feed_ng/test.py +++ b/tests/regression_tests/cmfd_feed_ng/test.py @@ -17,7 +17,8 @@ def test_cmfd_feed_ng(): cmfd_run = cmfd.CMFDRun() cmfd_run.mesh = cmfd_mesh cmfd_run.reset = [5] - cmfd_run.begin = 10 + cmfd_run.tally_begin = 10 + cmfd_run.feedback_begin = 10 cmfd_run.display = {'dominance': True} cmfd_run.feedback = True cmfd_run.downscatter = True diff --git a/tests/regression_tests/cmfd_nofeed/test.py b/tests/regression_tests/cmfd_nofeed/test.py index 36f6930268..f99fcb64bc 100644 --- a/tests/regression_tests/cmfd_nofeed/test.py +++ b/tests/regression_tests/cmfd_nofeed/test.py @@ -15,7 +15,7 @@ def test_cmfd_nofeed(): # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() cmfd_run.mesh = cmfd_mesh - cmfd_run.begin = 5 + cmfd_run.tally_begin = 5 cmfd_run.display = {'dominance': True} cmfd_run.feedback = False cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] From 4b2bb55054deb234ad9eb1b6238138628f5803d9 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Mon, 8 Apr 2019 16:01:36 +0300 Subject: [PATCH 018/151] new hex first commit --- include/openmc/lattice.h | 14 +- openmc/lattice.py | 489 ++++++++++++++++++++++++++++++++++----- src/lattice.cpp | 423 +++++++++++++++++++++++++-------- src/relaxng/geometry.rnc | 1 + src/relaxng/geometry.rng | 10 + 5 files changed, 778 insertions(+), 159 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index eb8e1c14f6..752e928c48 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -249,15 +249,25 @@ public: bool are_valid_indices(const int i_xyz[3]) const; + std::pair> distance(Position r, Direction u, const std::array& i_xyz) const; + //DR OX distance calculation std::pair> - distance(Position r, Direction u, const std::array& i_xyz) const; + distance_ox(Position r, Direction u, const std::array& i_xyz) const; + //DR OY distance calculation + std::pair> + distance_oy(Position r, Direction u, const std::array& i_xyz) const; std::array get_indices(Position r, Direction u) const; + Position get_local_position(Position r, const std::array i_xyz) const; bool is_valid_index(int indx) const; + //DR fill lattice with OY by default orientation + void fill_lattice_oy(std::vector univ_words); + //DR fill lattice with OX orientation + void fill_lattice_ox(std::vector univ_words); int32_t& offset(int map, const int i_xyz[3]); @@ -266,9 +276,9 @@ public: void to_hdf5_inner(hid_t group_id) const; private: - int n_hextype_; //! pitch_; //!< Lattice tile width and height }; diff --git a/openmc/lattice.py b/openmc/lattice.py index 8bf4e47571..699a7540d9 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -141,6 +141,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): center = group['center'][...] pitch = group['pitch'][...] outer = group['outer'].value + orientation = group['orientation'].value universe_ids = group['universes'][...] @@ -152,63 +153,121 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): # If the Universe specified outer the Lattice is not void if outer >= 0: lattice.outer = universes[outer] + if (orientation.lower() is "oy"): + # Build array of Universe pointers for the Lattice. Note that + # we need to convert between the HDF5's square array of + # (x, alpha, z) to the Python API's format of a ragged nested + # list of (z, ring, theta). + uarray = [] + for z in range(n_axial): + # Add a list for this axial level. + uarray.append([]) + x = n_rings - 1 + a = 2*n_rings - 2 + for r in range(n_rings - 1, 0, -1): + # Add a list for this ring. + uarray[-1].append([]) - # Build array of Universe pointers for the Lattice. Note that - # we need to convert between the HDF5's square array of - # (x, alpha, z) to the Python API's format of a ragged nested - # list of (z, ring, theta). - uarray = [] - for z in range(n_axial): - # Add a list for this axial level. - uarray.append([]) - x = n_rings - 1 - a = 2*n_rings - 2 - for r in range(n_rings - 1, 0, -1): - # Add a list for this ring. - uarray[-1].append([]) + # Climb down the top-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + x += 1 + a -= 1 - # Climb down the top-right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - x += 1 - a -= 1 + # Climb down the right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + a -= 1 - # Climb down the right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - a -= 1 + # Climb down the bottom-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + x -= 1 - # Climb down the bottom-right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - x -= 1 + # Climb up the bottom-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + x -= 1 + a += 1 - # Climb up the bottom-left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - x -= 1 - a += 1 + # Climb up the left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + a += 1 - # Climb up the left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - a += 1 + # Climb up the top-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + x += 1 - # Climb up the top-left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - x += 1 + # Move down to the next ring. + a -= 1 - # Move down to the next ring. - a -= 1 + # Convert the ids into Universe objects. + uarray[-1][-1] = [universes[u_id] + for u_id in uarray[-1][-1]] - # Convert the ids into Universe objects. - uarray[-1][-1] = [universes[u_id] - for u_id in uarray[-1][-1]] + # Handle the degenerate center ring separately. + u_id = universe_ids[z, a, x] + uarray[-1].append([universes[u_id]]) + else: + # Build array of Universe pointers for the Lattice. Note that + # we need to convert between the HDF5's square array of + # (alpha, y, z) to the Python API's format of a ragged nested + # list of (z, ring, theta). + uarray = [] + for z in range(n_axial): + # Add a list for this axial level. + uarray.append([]) + a = 2*n_rings - 2 + y = n_rings - 1 + for r in range(n_rings - 1, 0, -1): + # Add a list for this ring. + uarray[-1].append([]) - # Handle the degenerate center ring separately. - u_id = universe_ids[z, a, x] - uarray[-1].append([universes[u_id]]) + # Climb up the top-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a -= 1 + y += 1 + + + # Climb the left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a -= 1 + + # Climb down the bottom-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + y -= 1 + + # Climb down the bottom-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a += 1 + y -= 1 + + # Climb up the right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a += 1 + + # Climb up the top-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + y += 1 + + # Move down to the next ring. + a -= 1 + + # Convert the ids into Universe objects. + uarray[-1][-1] = [universes[u_id] + for u_id in uarray[-1][-1]] + + # Handle the degenerate center ring separately. + u_id = universe_ids[z, y, a] + uarray[-1].append([universes[u_id]]) # Add the universes to the lattice. if len(pitch) == 2: @@ -815,10 +874,10 @@ class HexLattice(Lattice): Most methods for this class use a natural indexing scheme wherein elements are assigned an index corresponding to their position relative to skewed - :math:`(x,\alpha,z)` axes as described fully in - :ref:`hexagonal_indexing`. However, note that when universes are assigned to - lattice elements using the :attr:`HexLattice.universes` property, the array - indices do not correspond to natural indices. + :math:`(x,\alpha,z)` - OY orientation or :math:`(\alpha,y,z)` - OX orientation //DR + axes as described fully in :ref:`hexagonal_indexing`. However, note that when + universes are assigned to lattice elements using the :attr:`HexLattice.universes` property, + the array indices do not correspond to natural indices. Parameters ---------- @@ -855,6 +914,8 @@ class HexLattice(Lattice): possible, where z is the axial index, r is in the ring index (starting from the outermost ring), and i is the index with a ring starting from the top and proceeding clockwise. + orientation : str by default 'OY' orientation of main lattice diagonal another option + - 'OX'//DR num_rings : int Number of radial ring positions in the xy-plane num_axial : int @@ -869,11 +930,13 @@ class HexLattice(Lattice): self._num_rings = None self._num_axial = None self._center = None + self._hextype = 0 # by Default orientaion is OY def __repr__(self): string = 'HexLattice\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) + string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', "OX" if self._hextype else "OY" )#//DR string += '{0: <16}{1}{2}\n'.format('\t# Rings', '=\t', self._num_rings) string += '{0: <16}{1}{2}\n'.format('\t# Axial', '=\t', self._num_axial) string += '{0: <16}{1}{2}\n'.format('\tCenter', '=\t', @@ -901,7 +964,12 @@ class HexLattice(Lattice): @property def num_rings(self): return self._num_rings - + @property + def orientation(self):#//DR + if self._hextype: + return "OX" + else: + return "OY" @property def num_axial(self): return self._num_axial @@ -955,6 +1023,12 @@ class HexLattice(Lattice): cv.check_length('lattice center', center, 2, 3) self._center = center + @orientation.setter + def orientation(self, orientation): + cv.check_type('orientation', orientation, str) + if orientation.lower() == "ox": + self._hextype = 1 + @Lattice.pitch.setter def pitch(self, pitch): cv.check_type('lattice pitch', pitch, Iterable, Real) @@ -1045,7 +1119,7 @@ class HexLattice(Lattice): ------- 3-tuple of int Indices of corresponding lattice element in :math:`(x,\alpha,z)` - bases + or :math:`(\alpha,y,z)`basesis numpy.ndarray Carestian coordinates of the point in the corresponding lattice element coordinate system @@ -1059,15 +1133,25 @@ class HexLattice(Lattice): else: z = point[2] - self.center[2] iz = floor(z/self.pitch[1] + 0.5*self.num_axial) - alpha = y - x/sqrt(3.) - ix = floor(x/(sqrt(0.75) * self.pitch[0])) - ia = floor(alpha/self.pitch[0]) + if self._hextype is 0:#//DR + alpha = y - x/sqrt(3.) + ix = floor(x/(sqrt(0.75) * self.pitch[0])) + ia = floor(alpha/self.pitch[0]) + else: + alpha = y - x*sqrt(3.) + ia = floor(-alpha/(sqrt(3.0) * self.pitch[0])) + iy = floor(y/(sqrt(0.75) * self.pitch[0])) + # Check four lattice elements to see which one is closest based on local # coordinates + #//DR + numbersOY=[(ix, ia, iz), (ix + 1, ia, iz), (ix, ia + 1, iz), \ + (ix + 1, ia + 1, iz)]# in case OY default orientation + numbersOX=[(ia, iy, iz), (ia + 1, iy, iz), (ia, iy + 1, iz), \ + (ia + 1, iy + 1, iz)]# in case OX orientation d_min = np.inf - for idx in [(ix, ia, iz), (ix + 1, ia, iz), (ix, ia + 1, iz), - (ix + 1, ia + 1, iz)]: + for idx in (numbersOX if self._hextype else numbersOY): #//DR p = self.get_local_coordinates(point, idx) d = p[0]**2 + p[1]**2 if d < d_min: @@ -1094,16 +1178,40 @@ class HexLattice(Lattice): system """ - x = point[0] - (self.center[0] + sqrt(0.75)*self.pitch[0]*idx[0]) - y = point[1] - (self.center[1] + (0.5*idx[0] + idx[1])*self.pitch[0]) + if self._hextype:#//DR + x = point[0] - (self.center[0] + sqrt(0.75)*self.pitch[0]*idx[0]) + y = point[1] - (self.center[1] + (0.5*idx[0] + idx[1])*self.pitch[0]) + else: + x = point[0] - (self.center[0] + self.pitch[0]*idx[0] + 0.5*self.pitch[0]*idx[1]) + y = point[1] - (self.center[1] + (sqrt(0.75)*self.pitch[0]*idx[1])) + if self._num_axial is None: z = point[2] else: z = point[2] - (self.center[2] + (idx[2] + 0.5 - 0.5*self.num_axial)* self.pitch[1]) return (x, y, z) + def get_universe_index(self, idx): #//DR + r"""Return index in the universes array corresponding to a lattice element index - def get_universe_index(self, idx): + Parameters + ---------- + idx : Iterable of int + Lattice element indices in the :math:`(x,\alpha,z)` coordinate + system in 'OY' orientation case, or indices in the :math:`(\alpha,y,z)` coordinate + system in 'OX' one + + Returns + ------- + 2- or 3-tuple of int + Indices used when setting the :attr:`HexLattice.universes` property + + """ + if self._hextype: + return self.get_universe_index_ox(idx) + else: + return self.get_universe_index_oy(idx) + def get_universe_index_oy(self, idx):#//DR r"""Return index in the universes array corresponding to a lattice element index Parameters @@ -1143,13 +1251,55 @@ class HexLattice(Lattice): else: return (idx[2], i_ring, i_within) + + def get_universe_index_ox(self, idx):#//DR + r"""Return index in the universes array corresponding to a lattice element index + numeration opposite clockwise + + Parameters + ---------- + idx : Iterable of int + Lattice element indices in the :math:`(\alpha,y,z)` coordinate + system + + Returns + ------- + 2- or 3-tuple of int + Indices used when setting the :attr:`HexLattice.universes` property + + """ + + # First we determine which ring the index corresponds to. + a = idx[0] + y = idx[1] + z = -a - y + g = max(abs(y), abs(a), abs(z)) + + # Next we use a clever method to figure out where along the ring we are. + i_ring = self._num_rings - 1 - g + if y >= 0: + if a >= 0: + i_within = y + else: + i_within = 2*g + z + else: + if a <= 0: + i_within = 3*g - y + else: + i_within = 5*g - z + + if self.num_axial is None: + return (i_ring, i_within) + else: + return (idx[2], i_ring, i_within) + def is_valid_index(self, idx): r"""Determine whether lattice element index is within defined range Parameters ---------- idx : Iterable of int - Lattice element indices in the :math:`(x,\alpha,z)` coordinate + Lattice element indices in the both :math:`(x,\alpha,z)` and :math:`(\alpha,y,z)` coordinate system Returns @@ -1193,6 +1343,9 @@ class HexLattice(Lattice): self._outer.create_xml_subelement(xml_element) lattice_subelement.set("n_rings", str(self._num_rings)) + # If orientation is "OX" export it to XML + if self._hextype: + lattice_subelement.set("orient", "OX") if self._num_axial is not None: lattice_subelement.set("n_axial", str(self._num_axial)) @@ -1315,10 +1468,123 @@ class HexLattice(Lattice): j += 1 lat.universes = univs return lat - def _repr_axial_slice(self, universes): """Return string representation for the given 2D group of universes. + The 'universes' argument should be a list of lists of universes where + each sub-list represents a single ring. The first list should be the + outer ring. + """ + if self._hextype: + return self._repr_axial_slice_ox(universes) + else: + return self._repr_axial_slice_oy(universes) + + def _repr_axial_slice_ox(self, universes): + """Return string representation for the given 2D group of universes in 'OX' orientation case. + + The 'universes' argument should be a list of lists of universes where + each sub-list represents a single ring. The first list should be the + outer ring. + """ + + # Find the largest universe ID and count the number of digits so we can + # properly pad the output string later. + largest_id = max([max([univ._id for univ in ring]) + for ring in universes]) + n_digits = len(str(largest_id)) + pad = ' '*n_digits + id_form = '{: ^' + str(n_digits) + 'd}' + + # Initialize the list for each row. + rows = [[] for i in range(2*self._num_rings - 1)] + middle = self._num_rings - 1 + + # Start with the degenerate first ring. + universe = universes[-1][0] + rows[middle] = [id_form.format(universe._id)] + + # Add universes one ring at a time. + for r in range(1, self._num_rings): + # r_prime increments down while r increments up. + r_prime = self._num_rings - 1 - r + theta = 0 + y = middle + + # Climb up the top-right. + for i in range(r): + # Add the universe. + universe = universes[r_prime][theta] + rows[y].append(id_form.format(universe._id)) + + # Translate the indices. + y += 1 + theta += 1 + + # Climb left the top-left. + for i in range(r): + # Add the universe. + universe = universes[r_prime][theta] + rows[y].insert(0,id_form.format(universe._id)) + + # Translate the indices. + theta += 1 + + # Climb down the middle left. + for i in range(r): + # Add the universe. + universe = universes[r_prime][theta] + rows[y].insert(0,id_form.format(universe._id)) + + # Translate the indices. + y -= 1 + theta += 1 + + # Climb down the down left. + for i in range(r): + # Add the universe. + universe = universes[r_prime][theta] + rows[y].insert(0, id_form.format(universe._id)) + + # Translate the indices. + y -= 1 + theta += 1 + + # Climb right the down right. + for i in range(r): + # Add the universe. + universe = universes[r_prime][theta] + rows[y].append(id_form.format(universe._id)) + + # Translate the indices. + theta += 1 + + # Climb up the middle-right. + for i in range(r): + # Add the universe. + universe = universes[r_prime][theta] + rows[y].append(id_form.format(universe._id)) + + # Translate the indices. + y += 1 + theta += 1 + + # Flip the rows and join each row into a single string. + rows = [pad.join(x) for x in rows[::-1]] + + # Pad the beginning of the rows so they line up properly. + for y in range(self._num_rings - 1): + rows[y] = (self._num_rings - 1 - y)*pad + rows[y] + rows[-1 - y] = (self._num_rings - 1 - y)*pad + rows[-1 - y] + + # Join the rows together and return the string. + universe_ids = '\n'.join(rows) + return universe_ids + + + def _repr_axial_slice_oy(self, universes): + """Return string representation for the given 2D group of universes in 'OY' orientation case.. + The 'universes' argument should be a list of lists of universes where each sub-list represents a single ring. The first list should be the outer ring. @@ -1527,3 +1793,100 @@ class HexLattice(Lattice): # Join the rows together and return the string. return '\n'.join(rows) + @staticmethod + def show_indices_ox(num_rings):#//DR + """Return a diagram of the hexagonal lattice with OX orientation layout with indices. + + This method can be used to show the proper indices to be used when + setting the :attr:`HexLattice.universes` property. For example, running + this method with num_rings=3 will return the similar diagram:: + + (0, 4) (0, 3) (0, 2) + + (0, 5) (1, 2) (1, 1) (0, 1) + + (0, 6) (1, 3) (2, 0) (1, 0) (0, 0) + + (0, 7) (1, 4) (1, 5) (0,11) + + (0, 8) (0, 9) (0,10) + + Parameters + ---------- + num_rings : int + Number of rings in the hexagonal lattice + + Returns + ------- + str + Diagram of the hexagonal lattice showing indices in OX orientation + + """ + + # Find the largest string and count the number of digits so we can + # properly pad the output string later + largest_index = 6*(num_rings - 1) + n_digits_index = len(str(largest_index)) + n_digits_ring = len(str(num_rings - 1)) + str_form = '({{:{}}},{{:{}}})'.format(n_digits_ring, n_digits_index) + pad = ' '*(n_digits_index + n_digits_ring + 3) + + # Initialize the list for each row. + rows = [[] for i in range(2*num_rings - 1)] + middle = num_rings - 1 + + # Start with the degenerate first ring. + rows[middle] = [str_form.format(num_rings - 1, 0)] + + # Add universes one ring at a time. + for r in range(1, num_rings): + # r_prime increments down while r increments up. + r_prime = num_rings - 1 - r + theta = 0 + y = middle + + for i in range(r): + # Climb up the top-right. + rows[y].append(str_form.format(r_prime, theta)) + y += 1 + theta += 1 + + for i in range(r): + # Climb left the top-left. + rows[y].insert(0,str_form.format(r_prime, theta)) + theta += 1 + + for i in range(r): + # Climb down the middle left. + rows[y].insert(0,str_form.format(r_prime, theta)) + y -= 1 + theta += 1 + + for i in range(r): + # Climb down the down left. + rows[y].insert(0, str_form.format(r_prime, theta)) + y -= 1 + theta += 1 + + for i in range(r): + # Climb right the down right. + rows[y].append(str_form.format(r_prime, theta)) + theta += 1 + + for i in range(r): + # Climb up the middle-right. + rows[y].append(str_form.format(r_prime, theta)) + y += 1 + theta += 1 + + # Flip the rows and join each row into a single string. + rows = [pad.join(x) for x in rows[::-1]] + + # Pad the beginning of the rows so they line up properly. + for y in range(num_rings - 1): + rows[y] = (num_rings - 1 - y)*pad + rows[y] + rows[-1 - y] = (num_rings - 1 - y)*pad + rows[-1 - y] + + # Join the rows together and return the string. + return '\n\n'.join(rows) + diff --git a/src/lattice.cpp b/src/lattice.cpp index 6e0f2331f0..49161195df 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -429,6 +429,8 @@ HexLattice::HexLattice(pugi::xml_node lat_node) { type_ = LatticeType::hex; + hextype = 0;//DR a default OY orientation of lattice + // Read the number of lattice cells in each dimension. n_rings_ = std::stoi(get_node_value(lat_node, "n_rings")); if (check_for_node(lat_node, "n_axial")) { @@ -439,6 +441,12 @@ HexLattice::HexLattice(pugi::xml_node lat_node) is_3d_ = false; } + //DR Check if "OX" orientation desc in node + if (check_for_node(lat_node, "orient")) { + std :: string orientation = get_node_value(lat_node, "orient"); + if (orientation == "OX") {hextype=1;} + } + // Read the lattice center. std::string center_str {get_node_value(lat_node, "center")}; std::vector center_words {split(center_str)}; @@ -481,96 +489,173 @@ HexLattice::HexLattice(pugi::xml_node lat_node) // Parse the universes. // Universes in hexagonal lattices are stored in a manner that represents - // a skewed coordinate system: (x, alpha) rather than (x, y). There is + // a skewed coordinate system: (x, alpha) in case of OY orientation + // and (alpha,y) in OX one rather than (x, y). There is // no obvious, direct relationship between the order of universes in the // input and the order that they will be stored in the skewed array so // the following code walks a set of index values across the skewed array // in a manner that matches the input order. Note that i_x = 0, i_a = 0 - // corresponds to the center of the hexagonal lattice. + // or i_a=0 , i_y = 0 corresponds to the center of the hexagonal lattice. universes_.resize((2*n_rings_-1) * (2*n_rings_-1) * n_axial_, C_NONE); - int input_index = 0; - for (int m = 0; m < n_axial_; m++) { - // Initialize lattice indecies. - int i_x = 1; - int i_a = n_rings_ - 1; - // Map upper triangular region of hexagonal lattice which is found in the - // first n_rings-1 rows of the input. - for (int k = 0; k < n_rings_-1; k++) { - // Walk the index to lower-left neighbor of last row start. - i_x -= 1; - - // Iterate over the input columns. - for (int j = 0; j < k+1; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } - - // Return the lattice index to the start of the current row. - i_x -= 2 * (k+1); - i_a += (k+1); - } - - // Map the middle square region of the hexagonal lattice which is found in - // the next 2*n_rings-1 rows of the input. - for (int k = 0; k < 2*n_rings_-1; k++) { - if ((k % 2) == 0) { - // Walk the index to the lower-left neighbor of the last row start. - i_x -= 1; - } else { - // Walk the index to the lower-right neighbor of the last row start. - i_x += 1; - i_a -= 1; - } - - // Iterate over the input columns. - for (int j = 0; j < n_rings_ - (k % 2); j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } - - // Return the lattice index to the start of the current row. - i_x -= 2*(n_rings_ - (k % 2)); - i_a += n_rings_ - (k % 2); - } - - // Map the lower triangular region of the hexagonal lattice. - for (int k = 0; k < n_rings_-1; k++) { - // Walk the index to the lower-right neighbor of the last row start. - i_x += 1; - i_a -= 1; - - // Iterate over the input columns. - for (int j = 0; j < n_rings_-k-1; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } - - // Return lattice index to start of current row. - i_x -= 2*(n_rings_ - k - 1); - i_a += n_rings_ - k - 1; - } + if (hextype==0) { + fill_lattice_oy(univ_words); + } else + { + fill_lattice_ox(univ_words); } + + } +// DR fill universes in OX orientation +//============================================================================== +void +HexLattice::fill_lattice_ox(std::vector univ_words){ + + int input_index = 0; + for (int m = 0; m < n_axial_; m++) { + // Initialize lattice indecies. + int i_a = -(n_rings_ - 1); + int i_y = n_rings_ - 1; + + + //DR Map upper region of hexagonal lattice which is found in the + // first n_rings-1 rows of the input. + for (int k = 0; k < n_rings_-1; k++) { + + // Iterate over the input columns. + for (int j = 0; j < k+n_rings_; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_y+n_rings_-1) + + (i_a+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + //DR Move to the next right neighbour cell + i_a += 1; + + } + + // Return the lattice index to the start of the current row. + i_a = -(n_rings_ - 1); + i_y -= 1; + } + + // Map the lower region from the centerline of cart to down side + for (int k = 0; k < n_rings_; k++) { + // Walk the index to the lower-right neighbor of the last row start. + i_a = -(n_rings_ - 1) + k; + + // Iterate over the input columns. + for (int j = 0; j < 2*n_rings_-k-1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_y+n_rings_-1) + + (i_a+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + //DR Move to the next right neighbour cell + i_a += 1; + + } + + // Return lattice index to start of current row. + i_y -= 1; + } + } +} + +//============================================================================== + + +// DR fill universes in OY orientation +//============================================================================== +void +HexLattice::fill_lattice_oy(std::vector univ_words){ + + int input_index = 0; + for (int m = 0; m < n_axial_; m++) { + // Initialize lattice indecies. + int i_x = 1; + int i_a = n_rings_ - 1; + + // Map upper triangular region of hexagonal lattice which is found in the + // first n_rings-1 rows of the input. + for (int k = 0; k < n_rings_-1; k++) { + // Walk the index to lower-left neighbor of last row start. + i_x -= 1; + + // Iterate over the input columns. + for (int j = 0; j < k+1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } + + // Return the lattice index to the start of the current row. + i_x -= 2 * (k+1); + i_a += (k+1); + } + + // Map the middle square region of the hexagonal lattice which is found in + // the next 2*n_rings-1 rows of the input. + for (int k = 0; k < 2*n_rings_-1; k++) { + if ((k % 2) == 0) { + // Walk the index to the lower-left neighbor of the last row start. + i_x -= 1; + } else { + // Walk the index to the lower-right neighbor of the last row start. + i_x += 1; + i_a -= 1; + } + + // Iterate over the input columns. + for (int j = 0; j < n_rings_ - (k % 2); j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } + + // Return the lattice index to the start of the current row. + i_x -= 2*(n_rings_ - (k % 2)); + i_a += n_rings_ - (k % 2); + } + + // Map the lower triangular region of the hexagonal lattice. + for (int k = 0; k < n_rings_-1; k++) { + // Walk the index to the lower-right neighbor of the last row start. + i_x += 1; + i_a -= 1; + + // Iterate over the input columns. + for (int j = 0; j < n_rings_-k-1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } + + // Return lattice index to start of current row. + i_x -= 2*(n_rings_ - k - 1); + i_a += n_rings_ - k - 1; + } + } +} + +//============================================================================== //============================================================================== @@ -603,11 +688,127 @@ HexLattice::are_valid_indices(const int i_xyz[3]) const && (i_xyz[2] < n_axial_)); } -//============================================================================== - +//DR wrapper HexLattice function both for OX and OY orientation std::pair> HexLattice::distance(Position r, Direction u, const std::array& i_xyz) const +{ + if (hextype==0){ + return distance_oy(r,u,i_xyz); + } else { + return distance_ox(r,u,i_xyz); + } +} + +//DR OX distance calculation +std::pair> +HexLattice::distance_ox(Position r, Direction u, const std::array& i_xyz) +const +{ + // Compute the direction on the hexagonal basis. + // DR for OX angle=60 g + double beta_dir = u.x*0.5 + u.y * std::sqrt(3.0) / 2.0; + double gamma_dir = u.x*0.5 - u.y * std::sqrt(3.0) / 2.0; + + // Note that hexagonal lattice distance calculations are performed + // using the particle's coordinates relative to the neighbor lattice + // cells, not relative to the particle's current cell. This is done + // because there is significant disagreement between neighboring cells + // on where the lattice boundary is due to finite precision issues. + + // Upper-right and lower-left sides. + double d {INFTY}; + std::array lattice_trans; + double edge = -copysign(0.5*pitch_[0], beta_dir); // Oncoming edge + Position r_t; + if (beta_dir > 0) { + const std::array i_xyz_t {i_xyz[0], i_xyz[1]+1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0], i_xyz[1]-1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + double beta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0; + if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { + d = (edge - beta) / beta_dir; + if (beta_dir > 0) { + lattice_trans = {0, 1, 0}; + } else { + lattice_trans = {0, -1, 0}; + } + } + + // Lower-right and upper-left sides. + edge = -copysign(0.5*pitch_[0], gamma_dir); + if (gamma_dir > 0) { + const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1]-1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1]+1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + double gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0; + if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { + double this_d = (edge - gamma) / gamma_dir; + if (this_d < d) { + if (gamma_dir > 0) { + lattice_trans = {1, -1, 0}; + } else { + lattice_trans = {-1, 1, 0}; + } + d = this_d; + } + } + + // right and left sides. + edge = -copysign(0.5*pitch_[0], u.x); + if (u.x > 0) { + const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1], i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1], i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + if ((std::abs(r_t.x - edge) > FP_PRECISION) && u.x != 0) { + double this_d = (edge - r_t.x) / u.x; + if (this_d < d) { + if (u.x > 0) { + lattice_trans = {1, 0, 0}; + } else { + lattice_trans = {-1, 0, 0}; + } + d = this_d; + } + } + + // Top and bottom sides + if (is_3d_) { + double z = r.z; + double z0 {copysign(0.5 * pitch_[1], u.z)}; + if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) { + double this_d = (z0 - z) / u.z; + if (this_d < d) { + d = this_d; + if (u.z > 0) { + lattice_trans = {0, 0, 1}; + } else { + lattice_trans = {0, 0, -1}; + } + d = this_d; + } + } + } + + return {d, lattice_trans}; +} + +//============================================================================== + +//============================================================================== +//DR OY distance calculation +std::pair> +HexLattice::distance_oy(Position r, Direction u, const std::array& i_xyz) +const { // Compute the direction on the hexagonal basis. double beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0; @@ -705,6 +906,7 @@ const return {d, lattice_trans}; } +//============================================================================== //============================================================================== std::array @@ -714,7 +916,8 @@ HexLattice::get_indices(Position r, Direction u) const // information. As a result, we move the position slightly forward to // determine what lattice index the particle is most likely to be in. r += TINY_BIT * u; - + float dbg1; + float dbg2; // Offset the xyz by the lattice center. Position r_o {r.x - center_.x, r.y - center_.y, r.z}; if (is_3d_) {r_o.z -= center_.z;} @@ -727,11 +930,21 @@ HexLattice::get_indices(Position r, Direction u) const out[2] = 0; } + if (hextype==0){//DR type implement OY default behaviour // Convert coordinates into skewed bases. The (x, alpha) basis is used to // find the index of the global coordinates to within 4 cells. double alpha = r_o.y - r_o.x / std::sqrt(3.0); out[0] = std::floor(r_o.x / (0.5*std::sqrt(3.0) * pitch_[0])); - out[1] = std::floor(alpha / pitch_[0]); + out[1] = std::floor(alpha / pitch_[0]);} + else{//DR type implement OX (alpha,y) basis + // Convert coordinates into skewed bases. The (alpha,y) basis is used to + // find the index of the global coordinates to within 4 cells. + double alpha = r_o.y - r_o.x * std::sqrt(3.0); + dbg1=-alpha / (std::sqrt(3.0) * pitch_[0]); + dbg2=r_o.y / 0.5*std::sqrt(3.0) * pitch_[0]; + out[0] = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0])); + out[1] = std::floor(r_o.y / (0.5*std::sqrt(3.0) * pitch_[0])); + } // Add offset to indices (the center cell is (i_x, i_alpha) = (0, 0) but // the array is offset so that the indices never go below 0). @@ -781,16 +994,32 @@ Position HexLattice::get_local_position(Position r, const std::array i_xyz) const { - // x_l = x_g - (center + pitch_x*cos(30)*index_x) - r.x -= center_.x + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; - // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) - r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] - + (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0); - if (is_3d_) { - r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; - } + // DR for OY implementation + if (hextype == 0){ + // x_l = x_g - (center + pitch_x*cos(30)*index_x) + r.x -= center_.x + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; + // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) + r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] + + (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0); + if (is_3d_) { + r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; + } - return r; + } + else {// DR for OX implementation + + // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y) + r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] + + (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0); + // y_l = y_g - (center + pitch_y*cos(30)*index_y) + r.y -= center_.y + std::sqrt(3.0)/2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0]; + + if (is_3d_) { + r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; + } + + } + return r; } //============================================================================== @@ -848,6 +1077,12 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_string(lat_group, "type", "hexagonal", false); write_dataset(lat_group, "n_rings", n_rings_); write_dataset(lat_group, "n_axial", n_axial_); + if (hextype == 0){ + write_string(lat_group, "orientation", "oy",false); + } + else { + write_string(lat_group, "orientation", "ox",false); + } if (is_3d_) { write_dataset(lat_group, "pitch", pitch_); write_dataset(lat_group, "center", center_); diff --git a/src/relaxng/geometry.rnc b/src/relaxng/geometry.rnc index c9c53f6d64..30fdf2c8ad 100644 --- a/src/relaxng/geometry.rnc +++ b/src/relaxng/geometry.rnc @@ -36,6 +36,7 @@ element geometry { attribute dimension { list { xsd:positiveInteger+ } }) & (element lower_left { list { xsd:double+ } } | attribute lower_left { list { xsd:double+ } }) & (element pitch { list { xsd:double+ } } | attribute pitch { list { xsd:double+ } }) & + (element orient { ( "OX" ) } | attribute orient { ( "OX" ) })? & (element universes { list { xsd:int+ } } | attribute universes { list { xsd:int+ } }) & (element outer { xsd:int } | attribute outer { xsd:int })? }* diff --git a/src/relaxng/geometry.rng b/src/relaxng/geometry.rng index d2c1f635ee..e6c5f6ca16 100644 --- a/src/relaxng/geometry.rng +++ b/src/relaxng/geometry.rng @@ -294,6 +294,16 @@ + + + + OX + + + OX + + + From c47f2d1c0a203c8c3c505bcd5d9c0db0f6904a1a Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Mon, 8 Apr 2019 13:28:34 -0400 Subject: [PATCH 019/151] Remove os.path import --- openmc/cmfd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 792479c005..4fa9c9e8fd 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -17,7 +17,6 @@ import sys import time from ctypes import c_int import warnings -import os.path import numpy as np from scipy import sparse From 4ef5ad64e31631869158b369b8c4ae727e6a7139 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Wed, 10 Apr 2019 10:58:35 +0300 Subject: [PATCH 020/151] garbage remove from commit --- .cproject | 108 --------------------- .project | 27 ------ .settings/org.eclipse.cdt.codan.core.prefs | 75 -------------- 3 files changed, 210 deletions(-) delete mode 100644 .cproject delete mode 100644 .project delete mode 100644 .settings/org.eclipse.cdt.codan.core.prefs diff --git a/.cproject b/.cproject deleted file mode 100644 index bdc88d84b6..0000000000 --- a/.cproject +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.project b/.project deleted file mode 100644 index 5eb05adae0..0000000000 --- a/.project +++ /dev/null @@ -1,27 +0,0 @@ - - - openmccc - - - - - - org.eclipse.cdt.managedbuilder.core.genmakebuilder - clean,full,incremental, - - - - - org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder - full,incremental, - - - - - - org.eclipse.cdt.core.cnature - org.eclipse.cdt.core.ccnature - org.eclipse.cdt.managedbuilder.core.managedBuildNature - org.eclipse.cdt.managedbuilder.core.ScannerConfigNature - - diff --git a/.settings/org.eclipse.cdt.codan.core.prefs b/.settings/org.eclipse.cdt.codan.core.prefs deleted file mode 100644 index 77496f558f..0000000000 --- a/.settings/org.eclipse.cdt.codan.core.prefs +++ /dev/null @@ -1,75 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.cdt.codan.checkers.errnoreturn=Warning -org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return\\")",implicit\=>false} -org.eclipse.cdt.codan.checkers.errreturnvalue=Error -org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused return value\\")"} -org.eclipse.cdt.codan.checkers.nocommentinside=-Error -org.eclipse.cdt.codan.checkers.nocommentinside.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Nesting comments\\")"} -org.eclipse.cdt.codan.checkers.nolinecomment=-Error -org.eclipse.cdt.codan.checkers.nolinecomment.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Line comments\\")"} -org.eclipse.cdt.codan.checkers.noreturn=Error -org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return value\\")",implicit\=>false} -org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=Error -org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Abstract class cannot be instantiated\\")"} -org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=Error -org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Ambiguous problem\\")"} -org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning -org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment in condition\\")"} -org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error -org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment to itself\\")"} -org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning -org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No break at end of case\\")",no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false,enable_fallthrough_quickfix_param\=>false} -org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning -org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Catching by reference is recommended\\")",unknown\=>false,exceptions\=>()} -org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error -org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Circular inheritance\\")"} -org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning -org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class members should be properly initialized\\")",skip\=>true} -org.eclipse.cdt.codan.internal.checkers.DecltypeAutoProblem=Error -org.eclipse.cdt.codan.internal.checkers.DecltypeAutoProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid 'decltype(auto)' specifier\\")"} -org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error -org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Field cannot be resolved\\")"} -org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=Error -org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Function cannot be resolved\\")"} -org.eclipse.cdt.codan.internal.checkers.InvalidArguments=Error -org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid arguments\\")"} -org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=Error -org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid template argument\\")"} -org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=Error -org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Label statement not found\\")"} -org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=Error -org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Member declaration not found\\")"} -org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=Error -org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Method cannot be resolved\\")"} -org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info -org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Name convention for function\\")",pattern\=>"^[a-z]",macro\=>true,exceptions\=>()} -org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning -org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class has a virtual method and non-virtual destructor\\")"} -org.eclipse.cdt.codan.internal.checkers.OverloadProblem=Error -org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid overload\\")"} -org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=Error -org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redeclaration\\")"} -org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=Error -org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redefinition\\")"} -org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning -org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Return with parenthesis\\")"} -org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning -org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Format String Vulnerability\\")"} -org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning -org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Statement has no effect\\")",macro\=>true,exceptions\=>()} -org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning -org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suggested parenthesis around expression\\")",paramNot\=>false} -org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning -org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suspicious semicolon\\")",else\=>false,afterelse\=>false} -org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=Error -org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Type cannot be resolved\\")"} -org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning -org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused function declaration\\")",macro\=>true} -org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning -org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused static function\\")",macro\=>true} -org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning -org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused variable declaration in file scope\\")",macro\=>true,exceptions\=>("@(\#)","$Id")} -org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error -org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Symbol is not resolved\\")"} -org.eclipse.cdt.qt.core.qtproblem=Warning -org.eclipse.cdt.qt.core.qtproblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_ON_FILE_OPEN\=>true,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>null} From 7813b284ad5a58add2f2bc9a9f27cb4385f9ee1d Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Thu, 11 Apr 2019 12:23:44 +0300 Subject: [PATCH 021/151] code was corrected,tabs were replaced by spaces, results for tallies test get from current version --- include/openmc/lattice.h | 2 +- openmc/lattice.py | 27 +- src/lattice.cpp | 290 +++++++++--------- .../regression_tests/tallies/results_true.dat | 2 +- 4 files changed, 164 insertions(+), 157 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index 752e928c48..7c06117b55 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -278,7 +278,7 @@ public: private: int n_rings_; //!< Number of radial tile positions int n_axial_; //!< Number of axial tile positions - int hextype; //!DR type of hexagonal lattice orientation //0 - OY by default;1-OX + int hextype; //!DR type of hexagonal lattice orientation //0 - OY by default;1-OX Position center_; //!< Global center of lattice std::array pitch_; //!< Lattice tile width and height }; diff --git a/openmc/lattice.py b/openmc/lattice.py index 699a7540d9..1f0ec5466e 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -141,7 +141,11 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): center = group['center'][...] pitch = group['pitch'][...] outer = group['outer'].value - orientation = group['orientation'].value + #DR Temp added for compatibility with previous version + if ('orientation' in group.keys()): + orientation = group['orientation'].value + else: + orientation = "oy" universe_ids = group['universes'][...] @@ -1133,14 +1137,16 @@ class HexLattice(Lattice): else: z = point[2] - self.center[2] iz = floor(z/self.pitch[1] + 0.5*self.num_axial) - if self._hextype is 0:#//DR + if self._hextype:#//DR + alpha = y - x*sqrt(3.) + ix = 0 + ia = floor(-alpha/(sqrt(3.0) * self.pitch[0])) + iy = floor(y/(sqrt(0.75) * self.pitch[0])) + else: alpha = y - x/sqrt(3.) ix = floor(x/(sqrt(0.75) * self.pitch[0])) ia = floor(alpha/self.pitch[0]) - else: - alpha = y - x*sqrt(3.) - ia = floor(-alpha/(sqrt(3.0) * self.pitch[0])) - iy = floor(y/(sqrt(0.75) * self.pitch[0])) + iy = 0 # Check four lattice elements to see which one is closest based on local @@ -1169,7 +1175,8 @@ class HexLattice(Lattice): point : Iterable of float Cartesian coordinates of point idx : Iterable of int - Indices of lattice element in :math:`(x,\alpha,z)` bases + Indices of lattice element in :math:`(x,\alpha,z)` + or :math:`(\alpha,y,z)` basesis Returns ------- @@ -1179,11 +1186,11 @@ class HexLattice(Lattice): """ if self._hextype:#//DR - x = point[0] - (self.center[0] + sqrt(0.75)*self.pitch[0]*idx[0]) - y = point[1] - (self.center[1] + (0.5*idx[0] + idx[1])*self.pitch[0]) - else: x = point[0] - (self.center[0] + self.pitch[0]*idx[0] + 0.5*self.pitch[0]*idx[1]) y = point[1] - (self.center[1] + (sqrt(0.75)*self.pitch[0]*idx[1])) + else: + x = point[0] - (self.center[0] + sqrt(0.75)*self.pitch[0]*idx[0]) + y = point[1] - (self.center[1] + (0.5*idx[0] + idx[1])*self.pitch[0]) if self._num_axial is None: z = point[2] diff --git a/src/lattice.cpp b/src/lattice.cpp index 49161195df..0289320107 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -443,8 +443,8 @@ HexLattice::HexLattice(pugi::xml_node lat_node) //DR Check if "OX" orientation desc in node if (check_for_node(lat_node, "orient")) { - std :: string orientation = get_node_value(lat_node, "orient"); - if (orientation == "OX") {hextype=1;} + std :: string orientation = get_node_value(lat_node, "orient"); + if (orientation == "OX") {hextype=1;} } // Read the lattice center. @@ -500,10 +500,10 @@ HexLattice::HexLattice(pugi::xml_node lat_node) universes_.resize((2*n_rings_-1) * (2*n_rings_-1) * n_axial_, C_NONE); if (hextype==0) { - fill_lattice_oy(univ_words); + fill_lattice_oy(univ_words); } else { - fill_lattice_ox(univ_words); + fill_lattice_ox(univ_words); } @@ -513,55 +513,55 @@ HexLattice::HexLattice(pugi::xml_node lat_node) void HexLattice::fill_lattice_ox(std::vector univ_words){ - int input_index = 0; - for (int m = 0; m < n_axial_; m++) { - // Initialize lattice indecies. - int i_a = -(n_rings_ - 1); - int i_y = n_rings_ - 1; + int input_index = 0; + for (int m = 0; m < n_axial_; m++) { + // Initialize lattice indecies. + int i_a = -(n_rings_ - 1); + int i_y = n_rings_ - 1; - //DR Map upper region of hexagonal lattice which is found in the - // first n_rings-1 rows of the input. - for (int k = 0; k < n_rings_-1; k++) { + //DR Map upper region of hexagonal lattice which is found in the + // first n_rings-1 rows of the input. + for (int k = 0; k < n_rings_-1; k++) { - // Iterate over the input columns. - for (int j = 0; j < k+n_rings_; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_y+n_rings_-1) - + (i_a+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - //DR Move to the next right neighbour cell - i_a += 1; + // Iterate over the input columns. + for (int j = 0; j < k+n_rings_; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_y+n_rings_-1) + + (i_a+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + //DR Move to the next right neighbour cell + i_a += 1; - } + } - // Return the lattice index to the start of the current row. - i_a = -(n_rings_ - 1); - i_y -= 1; - } + // Return the lattice index to the start of the current row. + i_a = -(n_rings_ - 1); + i_y -= 1; + } - // Map the lower region from the centerline of cart to down side - for (int k = 0; k < n_rings_; k++) { - // Walk the index to the lower-right neighbor of the last row start. - i_a = -(n_rings_ - 1) + k; + // Map the lower region from the centerline of cart to down side + for (int k = 0; k < n_rings_; k++) { + // Walk the index to the lower-right neighbor of the last row start. + i_a = -(n_rings_ - 1) + k; - // Iterate over the input columns. - for (int j = 0; j < 2*n_rings_-k-1; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_y+n_rings_-1) - + (i_a+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - //DR Move to the next right neighbour cell - i_a += 1; + // Iterate over the input columns. + for (int j = 0; j < 2*n_rings_-k-1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_y+n_rings_-1) + + (i_a+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + //DR Move to the next right neighbour cell + i_a += 1; - } + } - // Return lattice index to start of current row. - i_y -= 1; - } - } + // Return lattice index to start of current row. + i_y -= 1; + } + } } //============================================================================== @@ -572,87 +572,87 @@ HexLattice::fill_lattice_ox(std::vector univ_words){ void HexLattice::fill_lattice_oy(std::vector univ_words){ - int input_index = 0; - for (int m = 0; m < n_axial_; m++) { - // Initialize lattice indecies. - int i_x = 1; - int i_a = n_rings_ - 1; + int input_index = 0; + for (int m = 0; m < n_axial_; m++) { + // Initialize lattice indecies. + int i_x = 1; + int i_a = n_rings_ - 1; - // Map upper triangular region of hexagonal lattice which is found in the - // first n_rings-1 rows of the input. - for (int k = 0; k < n_rings_-1; k++) { - // Walk the index to lower-left neighbor of last row start. - i_x -= 1; + // Map upper triangular region of hexagonal lattice which is found in the + // first n_rings-1 rows of the input. + for (int k = 0; k < n_rings_-1; k++) { + // Walk the index to lower-left neighbor of last row start. + i_x -= 1; - // Iterate over the input columns. - for (int j = 0; j < k+1; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } + // Iterate over the input columns. + for (int j = 0; j < k+1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } - // Return the lattice index to the start of the current row. - i_x -= 2 * (k+1); - i_a += (k+1); - } + // Return the lattice index to the start of the current row. + i_x -= 2 * (k+1); + i_a += (k+1); + } - // Map the middle square region of the hexagonal lattice which is found in - // the next 2*n_rings-1 rows of the input. - for (int k = 0; k < 2*n_rings_-1; k++) { - if ((k % 2) == 0) { - // Walk the index to the lower-left neighbor of the last row start. - i_x -= 1; - } else { - // Walk the index to the lower-right neighbor of the last row start. - i_x += 1; - i_a -= 1; - } + // Map the middle square region of the hexagonal lattice which is found in + // the next 2*n_rings-1 rows of the input. + for (int k = 0; k < 2*n_rings_-1; k++) { + if ((k % 2) == 0) { + // Walk the index to the lower-left neighbor of the last row start. + i_x -= 1; + } else { + // Walk the index to the lower-right neighbor of the last row start. + i_x += 1; + i_a -= 1; + } - // Iterate over the input columns. - for (int j = 0; j < n_rings_ - (k % 2); j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } + // Iterate over the input columns. + for (int j = 0; j < n_rings_ - (k % 2); j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } - // Return the lattice index to the start of the current row. - i_x -= 2*(n_rings_ - (k % 2)); - i_a += n_rings_ - (k % 2); - } + // Return the lattice index to the start of the current row. + i_x -= 2*(n_rings_ - (k % 2)); + i_a += n_rings_ - (k % 2); + } - // Map the lower triangular region of the hexagonal lattice. - for (int k = 0; k < n_rings_-1; k++) { - // Walk the index to the lower-right neighbor of the last row start. - i_x += 1; - i_a -= 1; + // Map the lower triangular region of the hexagonal lattice. + for (int k = 0; k < n_rings_-1; k++) { + // Walk the index to the lower-right neighbor of the last row start. + i_x += 1; + i_a -= 1; - // Iterate over the input columns. - for (int j = 0; j < n_rings_-k-1; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } + // Iterate over the input columns. + for (int j = 0; j < n_rings_-k-1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } - // Return lattice index to start of current row. - i_x -= 2*(n_rings_ - k - 1); - i_a += n_rings_ - k - 1; - } - } + // Return lattice index to start of current row. + i_x -= 2*(n_rings_ - k - 1); + i_a += n_rings_ - k - 1; + } + } } //============================================================================== @@ -693,11 +693,11 @@ std::pair> HexLattice::distance(Position r, Direction u, const std::array& i_xyz) const { - if (hextype==0){ - return distance_oy(r,u,i_xyz); - } else { - return distance_ox(r,u,i_xyz); - } + if (hextype==0){ + return distance_oy(r,u,i_xyz); + } else { + return distance_ox(r,u,i_xyz); + } } //DR OX distance calculation @@ -994,32 +994,32 @@ Position HexLattice::get_local_position(Position r, const std::array i_xyz) const { - // DR for OY implementation - if (hextype == 0){ - // x_l = x_g - (center + pitch_x*cos(30)*index_x) - r.x -= center_.x + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; - // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) - r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] - + (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0); - if (is_3d_) { - r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; - } + // DR for OY implementation + if (hextype == 0){ + // x_l = x_g - (center + pitch_x*cos(30)*index_x) + r.x -= center_.x + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; + // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) + r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] + + (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0); + if (is_3d_) { + r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; + } - } - else {// DR for OX implementation + } + else {// DR for OX implementation - // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y) - r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] - + (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0); - // y_l = y_g - (center + pitch_y*cos(30)*index_y) - r.y -= center_.y + std::sqrt(3.0)/2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0]; + // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y) + r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] + + (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0); + // y_l = y_g - (center + pitch_y*cos(30)*index_y) + r.y -= center_.y + std::sqrt(3.0)/2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0]; - if (is_3d_) { - r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; - } + if (is_3d_) { + r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; + } - } - return r; + } + return r; } //============================================================================== @@ -1078,10 +1078,10 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_dataset(lat_group, "n_rings", n_rings_); write_dataset(lat_group, "n_axial", n_axial_); if (hextype == 0){ - write_string(lat_group, "orientation", "oy",false); + write_string(lat_group, "orientation", "oy",false); } else { - write_string(lat_group, "orientation", "ox",false); + write_string(lat_group, "orientation", "ox",false); } if (is_3d_) { write_dataset(lat_group, "pitch", pitch_); diff --git a/tests/regression_tests/tallies/results_true.dat b/tests/regression_tests/tallies/results_true.dat index 9830ff8721..de1a18eff2 100644 --- a/tests/regression_tests/tallies/results_true.dat +++ b/tests/regression_tests/tallies/results_true.dat @@ -1 +1 @@ -de773a799f84348241ebd65bf7beae8114861d8674b652bfd443d578c146a7d37ca38f2efc5d05124fdbb1cf12829907768351e6b2d6b5f4bd2c121417757507 \ No newline at end of file +c37e0468f1684f7810429332721762884f3cdf0429d38caa99500bbde04ad84cf1e1639adc46b6db44b28b6f31028ed029f85920c8164d77463b6d89336043a8 \ No newline at end of file From 055842b5cbb2a2d9b7763311c5ce74af29f20e26 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Thu, 11 Apr 2019 12:38:36 -0400 Subject: [PATCH 022/151] Only call statepoint_write from C API if cmfd_run --- include/openmc/settings.h | 1 + openmc/capi/settings.py | 1 + openmc/cmfd.py | 26 +++++++++++++++----------- src/settings.cpp | 1 + src/simulation.cpp | 6 ++++-- src/state_point.cpp | 1 - 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/include/openmc/settings.h b/include/openmc/settings.h index fc6fea6e7f..9dff8f0eaf 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -27,6 +27,7 @@ extern bool assume_separate; //!< assume tallies are spatially separate extern bool check_overlaps; //!< check overlaps in geometry? extern bool confidence_intervals; //!< use confidence intervals for results? extern bool create_fission_neutrons; //!< create fission neutrons (fixed source)? +extern "C" bool cmfd_run; //!< is a CMFD run? extern "C" bool dagmc; //!< indicator of DAGMC geometry extern "C" bool entropy_on; //!< calculate Shannon entropy? extern bool legendre_to_tabular; //!< convert Legendre distributions to tabular? diff --git a/openmc/capi/settings.py b/openmc/capi/settings.py index 3dd672354f..78794d8563 100644 --- a/openmc/capi/settings.py +++ b/openmc/capi/settings.py @@ -18,6 +18,7 @@ _dll.openmc_get_seed.restype = c_int64 class _Settings(object): # Attributes that are accessed through a descriptor batches = _DLLGlobal(c_int32, 'n_batches') + cmfd_run = _DLLGlobal(c_bool, 'cmfd_run') entropy_on = _DLLGlobal(c_bool, 'entropy_on') generations_per_batch = _DLLGlobal(c_int32, 'gen_per_batch') inactive = _DLLGlobal(c_int32, 'n_inactive') diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 4fa9c9e8fd..bd74341c0a 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -753,6 +753,8 @@ class CMFDRun(object): # Initialize simulation openmc.capi.simulation_init() + openmc.capi.settings.cmfd_run = True + def next_batch(self): # TODO add function description # Initialize CMFD batch @@ -790,7 +792,6 @@ class CMFDRun(object): # Call C API statepoint_write to save source distribution with CMFD # feedback - # TODO don't call statepoint_write in openmc if settings::cmfd_run openmc.capi.statepoint_write(filename=filename) # Append CMFD data to statepoint file using h5py @@ -1119,7 +1120,8 @@ class CMFDRun(object): self._cmfd_on = True # Check to reset tallies - if (self._n_resets > 0 and current_batch in self._reset) or self._reset_every: + if ((self._n_resets > 0 and current_batch in self._reset) + or self._reset_every): self._cmfd_tally_reset() def _execute_cmfd(self): @@ -1417,9 +1419,11 @@ class CMFDRun(object): with np.errstate(divide='ignore', invalid='ignore'): self._weightfactors = (np.divide(self._cmfd_src * norm, sourcecounts, where=div_condition, - out=np.ones_like(self._cmfd_src), dtype=np.float32)) + out=np.ones_like(self._cmfd_src), + dtype=np.float32)) - if not self._feedback or openmc.capi.current_batch() < self._feedback_begin: + if (not self._feedback + or openmc.capi.current_batch() < self._feedback_begin): return # Broadcast weight factors to all procs @@ -1972,9 +1976,9 @@ class CMFDRun(object): self._scatt_rate = np.append(self._scatt_rate, reshape_scattrr, axis=5) - # Compute scattering xs as aggregate of banked scatt_rate over tally window - # divided by flux. Flux dimensionality increased to account for extra - # dimensionality of scattering xs + # Compute scattering xs as aggregate of banked scatt_rate over tally + # window divided by flux. Flux dimensionality increased to account for + # extra dimensionality of scattering xs extended_flux = self._flux[:,:,:,:,np.newaxis] with np.errstate(divide='ignore', invalid='ignore'): self._scattxs = np.divide(np.sum(self._scatt_rate, axis=5), @@ -1999,9 +2003,9 @@ class CMFDRun(object): self._nfiss_rate = np.append(self._nfiss_rate, reshape_nfissrr, axis=5) - # Compute nu-fission xs as aggregate of banked nfiss_rate over tally window - # divided by flux. Flux dimensionality increased to account for extra - # dimensionality of nu-fission xs + # Compute nu-fission xs as aggregate of banked nfiss_rate over tally + # window divided by flux. Flux dimensionality increased to account for + # extra dimensionality of nu-fission xs with np.errstate(divide='ignore', invalid='ignore'): self._nfissxs = np.divide(np.sum(self._nfiss_rate, axis=5), extended_flux, where=extended_flux > 0, @@ -2062,7 +2066,7 @@ class CMFDRun(object): # Reshape and extract only p1 data from tally results as there is # no need for p0 data reshape_p1scattrr = np.swapaxes(p1scattrr.reshape(target_tally_shape), - 0, 2)[:,:,:,1,:,:] + 0, 2)[:,:,:,1,:,:] # p1-scatter rr is flipped in energy axis as tally results are given in # reverse order of energy group diff --git a/src/settings.cpp b/src/settings.cpp index 4c041a080d..947fbc90ed 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -40,6 +40,7 @@ namespace settings { // Default values for boolean flags bool assume_separate {false}; bool check_overlaps {false}; +bool cmfd_run {false}; bool confidence_intervals {false}; bool create_fission_neutrons {true}; bool dagmc {false}; diff --git a/src/simulation.cpp b/src/simulation.cpp index fa565b5751..6e375fe9bd 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -364,8 +364,10 @@ void finalize_batch() settings::statepoint_batch.insert(simulation::current_batch); } - // Write out state point if it's been specified for this batch - if (contains(settings::statepoint_batch, simulation::current_batch)) { + // Write out state point if it's been specified for this batch and is not + // a CMFD run instance + if (contains(settings::statepoint_batch, simulation::current_batch) + && !settings::cmfd_run) { if (contains(settings::sourcepoint_batch, simulation::current_batch) && settings::source_write && !settings::source_separate) { bool b = true; diff --git a/src/state_point.cpp b/src/state_point.cpp index 66eaf7ffdb..9d5a385dcc 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -312,7 +312,6 @@ openmc_load_cmfd_tallies(const int* tally_ids, const int* sp_tally_ids) hid_t tallies_group = open_group(file_id, "tallies"); - // TODO pass this in as argument? int n_tallies = 4; for (int i =0; i < n_tallies; i++) { std::string name = "tally " + std::to_string(sp_tally_ids[i]); From f6bb00dc456ad398405cc0c1e95f0c90ee7ae75f Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Thu, 11 Apr 2019 16:13:19 -0400 Subject: [PATCH 023/151] Add descriptions for new functions in cmfd CMFDRun class --- openmc/cmfd.py | 53 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index bd74341c0a..26f12f8dbf 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -710,7 +710,25 @@ class CMFDRun(object): @contextmanager def run_in_memory(self, **kwargs): - # TODO add function description + """ Context manager for running CMFD functions with OpenMC shared + library functions. + + This function can be used with a 'with' statement to ensure the + CMFDRun class is properly initialized/finalized. For example:: + + from openmc import cmfd + cmfdrun = cmfd.CMFDRun() + with cmfd_run.run_in_memory(): + do_stuff_before_simulation_start() + for _ in cmfd_run.iter_batches(): + do_stuff_between_batches() + + Parameters + ---------- + **kwargs + All keyword arguments passed to :func:`openmc.capi.run_in_memory`. + + """ # Store intracomm for part of CMFD routine where MPI reduce and # broadcast calls are made if 'intracomm' in kwargs and kwargs['intracomm'] is not None: @@ -725,14 +743,25 @@ class CMFDRun(object): self.finalize() def iter_batches(self): - # TODO add function description + """ Iterator over batches. + + This function returns a generator-iterator that allows Python code to + be run between batches when running an OpenMC simulation with CMFD. + It should be used in conjunction with + :func`openmc.cmfd.CMFDRun.run_in_memory` to ensure proper + initialization/finalization of CMFDRun instance. + + """ status = 0 while status == 0: status = self.next_batch() yield def init(self): - # TODO add function description + """ Initialize CMFDRun instance by setting up CMFD parameters and + calling :func:`openmc.capi.simulation_init` + + """ # Configure CMFD parameters and tallies self._configure_cmfd() @@ -753,10 +782,19 @@ class CMFDRun(object): # Initialize simulation openmc.capi.simulation_init() + # Set cmfd_run variable to True through C API openmc.capi.settings.cmfd_run = True def next_batch(self): - # TODO add function description + """ Run next batch for CMFDRun. + + Returns + ------- + int + Status after running a batch (0=normal, 1=reached maximum number of + batches, 2=tally triggers reached) + + """ # Initialize CMFD batch self._cmfd_init_batch() @@ -777,7 +815,11 @@ class CMFDRun(object): return status def finalize(self): - # TODO add function description + """ Finalize simulation by calling + :func:`openmc.capi.simulation_finalize` and print out CMFD timing + information. + + """ # Finalize simuation openmc.capi.simulation_finalize() @@ -858,6 +900,7 @@ class CMFDRun(object): temp_loss = sparse.csr_matrix((temp_data, (loss_row, loss_col)), shape=(n, n)) + # Pass coremap as 1-d array of 32-bit integers coremap = np.swapaxes(self._coremap, 0, 2).flatten().astype(np.int32) From 5f1f37bd76ed73cbf12dbc83cea050dd28f1452e Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Wed, 17 Apr 2019 18:41:45 -0400 Subject: [PATCH 024/151] Add regression tests for cmfd restart, ref_d, rolling_window, expanding_window --- .../cmfd_feed_expanding_window/__init__.py | 0 .../cmfd_feed_expanding_window/geometry.xml | 43 ++ .../cmfd_feed_expanding_window/materials.xml | 12 + .../results_true.dat | 488 ++++++++++++++++++ .../cmfd_feed_expanding_window/settings.xml | 26 + .../cmfd_feed_expanding_window/tallies.xml | 21 + .../cmfd_feed_expanding_window/test.py | 26 + .../cmfd_feed_ref_d/__init__.py | 0 .../cmfd_feed_ref_d/geometry.xml | 43 ++ .../cmfd_feed_ref_d/materials.xml | 12 + .../cmfd_feed_ref_d/results_true.dat | 488 ++++++++++++++++++ .../cmfd_feed_ref_d/settings.xml | 26 + .../cmfd_feed_ref_d/tallies.xml | 21 + .../regression_tests/cmfd_feed_ref_d/test.py | 27 + .../cmfd_feed_rolling_window/__init__.py | 0 .../cmfd_feed_rolling_window/geometry.xml | 43 ++ .../cmfd_feed_rolling_window/materials.xml | 12 + .../cmfd_feed_rolling_window/results_true.dat | 488 ++++++++++++++++++ .../cmfd_feed_rolling_window/settings.xml | 26 + .../cmfd_feed_rolling_window/tallies.xml | 21 + .../cmfd_feed_rolling_window/test.py | 27 + .../regression_tests/cmfd_restart/__init__.py | 0 .../cmfd_restart/geometry.xml | 43 ++ .../cmfd_restart/materials.xml | 12 + .../cmfd_restart/results_true.dat | 488 ++++++++++++++++++ .../cmfd_restart/settings.xml | 28 + .../regression_tests/cmfd_restart/tallies.xml | 21 + tests/regression_tests/cmfd_restart/test.py | 72 +++ 28 files changed, 2514 insertions(+) create mode 100644 tests/regression_tests/cmfd_feed_expanding_window/__init__.py create mode 100644 tests/regression_tests/cmfd_feed_expanding_window/geometry.xml create mode 100644 tests/regression_tests/cmfd_feed_expanding_window/materials.xml create mode 100644 tests/regression_tests/cmfd_feed_expanding_window/results_true.dat create mode 100644 tests/regression_tests/cmfd_feed_expanding_window/settings.xml create mode 100644 tests/regression_tests/cmfd_feed_expanding_window/tallies.xml create mode 100644 tests/regression_tests/cmfd_feed_expanding_window/test.py create mode 100644 tests/regression_tests/cmfd_feed_ref_d/__init__.py create mode 100644 tests/regression_tests/cmfd_feed_ref_d/geometry.xml create mode 100644 tests/regression_tests/cmfd_feed_ref_d/materials.xml create mode 100644 tests/regression_tests/cmfd_feed_ref_d/results_true.dat create mode 100644 tests/regression_tests/cmfd_feed_ref_d/settings.xml create mode 100644 tests/regression_tests/cmfd_feed_ref_d/tallies.xml create mode 100644 tests/regression_tests/cmfd_feed_ref_d/test.py create mode 100644 tests/regression_tests/cmfd_feed_rolling_window/__init__.py create mode 100644 tests/regression_tests/cmfd_feed_rolling_window/geometry.xml create mode 100644 tests/regression_tests/cmfd_feed_rolling_window/materials.xml create mode 100644 tests/regression_tests/cmfd_feed_rolling_window/results_true.dat create mode 100644 tests/regression_tests/cmfd_feed_rolling_window/settings.xml create mode 100644 tests/regression_tests/cmfd_feed_rolling_window/tallies.xml create mode 100644 tests/regression_tests/cmfd_feed_rolling_window/test.py create mode 100644 tests/regression_tests/cmfd_restart/__init__.py create mode 100644 tests/regression_tests/cmfd_restart/geometry.xml create mode 100644 tests/regression_tests/cmfd_restart/materials.xml create mode 100644 tests/regression_tests/cmfd_restart/results_true.dat create mode 100644 tests/regression_tests/cmfd_restart/settings.xml create mode 100644 tests/regression_tests/cmfd_restart/tallies.xml create mode 100644 tests/regression_tests/cmfd_restart/test.py diff --git a/tests/regression_tests/cmfd_feed_expanding_window/__init__.py b/tests/regression_tests/cmfd_feed_expanding_window/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/cmfd_feed_expanding_window/geometry.xml b/tests/regression_tests/cmfd_feed_expanding_window/geometry.xml new file mode 100644 index 0000000000..73ea679c4c --- /dev/null +++ b/tests/regression_tests/cmfd_feed_expanding_window/geometry.xml @@ -0,0 +1,43 @@ + + + + + + 0 + -1 2 -3 4 -5 6 + 1 + + + + + x-plane + 10 + vacuum + + + x-plane + -10 + vacuum + + + y-plane + 1 + reflective + + + y-plane + -1 + reflective + + + z-plane + 1 + reflective + + + z-plane + -1 + reflective + + + diff --git a/tests/regression_tests/cmfd_feed_expanding_window/materials.xml b/tests/regression_tests/cmfd_feed_expanding_window/materials.xml new file mode 100644 index 0000000000..70580e3a8d --- /dev/null +++ b/tests/regression_tests/cmfd_feed_expanding_window/materials.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/regression_tests/cmfd_feed_expanding_window/results_true.dat b/tests/regression_tests/cmfd_feed_expanding_window/results_true.dat new file mode 100644 index 0000000000..b67bef0f76 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_expanding_window/results_true.dat @@ -0,0 +1,488 @@ +k-combined: +1.165403E+00 1.129918E-02 +tally 1: +1.141961E+01 +1.307497E+01 +2.075941E+01 +4.316095E+01 +2.834539E+01 +8.063510E+01 +3.513869E+01 +1.238515E+02 +3.699858E+01 +1.372516E+02 +3.612668E+01 +1.310763E+02 +3.373368E+01 +1.140458E+02 +2.836844E+01 +8.075759E+01 +2.158837E+01 +4.674848E+01 +1.141868E+01 +1.310648E+01 +tally 2: +1.126626E+00 +1.269287E+00 +7.749042E-01 +6.004765E-01 +1.991608E+00 +3.966503E+00 +1.411775E+00 +1.993108E+00 +2.978264E+00 +8.870057E+00 +2.130066E+00 +4.537180E+00 +3.708857E+00 +1.375562E+01 +2.698010E+00 +7.279259E+00 +4.233192E+00 +1.791991E+01 +3.023990E+00 +9.144518E+00 +4.062113E+00 +1.650076E+01 +2.910472E+00 +8.470848E+00 +3.506681E+00 +1.229681E+01 +2.497751E+00 +6.238760E+00 +2.974675E+00 +8.848690E+00 +2.121163E+00 +4.499332E+00 +2.329248E+00 +5.425398E+00 +1.658233E+00 +2.749736E+00 +1.158552E+00 +1.342242E+00 +8.282698E-01 +6.860309E-01 +tally 3: +7.459301E-01 +5.564117E-01 +4.877614E-02 +2.379112E-03 +1.356702E+00 +1.840641E+00 +8.361624E-02 +6.991675E-03 +2.047332E+00 +4.191570E+00 +1.521351E-01 +2.314509E-02 +2.610287E+00 +6.813596E+00 +1.718778E-01 +2.954199E-02 +2.911379E+00 +8.476127E+00 +1.823299E-01 +3.324418E-02 +2.806920E+00 +7.878801E+00 +2.090406E-01 +4.369797E-02 +2.415727E+00 +5.835736E+00 +1.486511E-01 +2.209715E-02 +2.051064E+00 +4.206862E+00 +1.196177E-01 +1.430839E-02 +1.593746E+00 +2.540026E+00 +1.126497E-01 +1.268994E-02 +7.994935E-01 +6.391898E-01 +5.806683E-02 +3.371757E-03 +tally 4: +1.341719E-01 +1.800210E-02 +0.000000E+00 +0.000000E+00 +1.420707E-01 +2.018410E-02 +2.633150E-01 +6.933479E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.633150E-01 +6.933479E-02 +1.420707E-01 +2.018410E-02 +2.628613E-01 +6.909608E-02 +3.590382E-01 +1.289084E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.590382E-01 +1.289084E-01 +2.628613E-01 +6.909608E-02 +3.851599E-01 +1.483482E-01 +4.595809E-01 +2.112146E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.595809E-01 +2.112146E-01 +3.851599E-01 +1.483482E-01 +4.679967E-01 +2.190209E-01 +4.970187E-01 +2.470276E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.970187E-01 +2.470276E-01 +4.679967E-01 +2.190209E-01 +4.909661E-01 +2.410477E-01 +4.838785E-01 +2.341384E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.838785E-01 +2.341384E-01 +4.909661E-01 +2.410477E-01 +5.152690E-01 +2.655021E-01 +4.788649E-01 +2.293116E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.788649E-01 +2.293116E-01 +5.152690E-01 +2.655021E-01 +4.266876E-01 +1.820623E-01 +3.401342E-01 +1.156913E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.401342E-01 +1.156913E-01 +4.266876E-01 +1.820623E-01 +3.724186E-01 +1.386956E-01 +2.560013E-01 +6.553669E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.560013E-01 +6.553669E-02 +3.724186E-01 +1.386956E-01 +2.892134E-01 +8.364439E-02 +1.556176E-01 +2.421685E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.556176E-01 +2.421685E-02 +2.892134E-01 +8.364439E-02 +1.497744E-01 +2.243237E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +tally 5: +7.459301E-01 +5.564117E-01 +1.149820E-01 +1.322085E-02 +1.356702E+00 +1.840641E+00 +1.801102E-01 +3.243968E-02 +2.047332E+00 +4.191570E+00 +2.627497E-01 +6.903740E-02 +2.609217E+00 +6.808012E+00 +3.360314E-01 +1.129171E-01 +2.909457E+00 +8.464941E+00 +4.095451E-01 +1.677272E-01 +2.805131E+00 +7.868760E+00 +3.957627E-01 +1.566281E-01 +2.415727E+00 +5.835736E+00 +3.196414E-01 +1.021706E-01 +2.049859E+00 +4.201923E+00 +3.162399E-01 +1.000077E-01 +1.593746E+00 +2.540026E+00 +2.169985E-01 +4.708836E-02 +7.994935E-01 +6.391898E-01 +1.002481E-01 +1.004969E-02 +cmfd indices +1.000000E+01 +1.000000E+00 +1.000000E+00 +1.000000E+00 +k cmfd +1.143597E+00 +1.163387E+00 +1.173384E+00 +1.171035E+00 +1.147196E+00 +1.122260E+00 +1.106380E+00 +1.124693E+00 +1.133192E+00 +1.134435E+00 +1.142380E+00 +1.132168E+00 +1.132560E+00 +1.153781E+00 +1.170308E+00 +1.184540E+00 +cmfd entropy +3.212002E+00 +3.206393E+00 +3.223984E+00 +3.222764E+00 +3.232123E+00 +3.242083E+00 +3.246067E+00 +3.238869E+00 +3.235585E+00 +3.234611E+00 +3.233575E+00 +3.236922E+00 +3.229545E+00 +3.225232E+00 +3.221485E+00 +3.219108E+00 +cmfd balance +8.26180E-03 +4.27338E-03 +2.22686E-03 +1.93026E-03 +1.96979E-03 +2.13756E-03 +2.01479E-03 +1.74519E-03 +2.09248E-03 +1.25545E-03 +1.48370E-03 +1.75963E-03 +1.98194E-03 +1.87306E-03 +1.24780E-03 +1.15560E-03 +cmfd dominance ratio +5.404E-01 +5.406E-01 +5.449E-01 +5.473E-01 +5.534E-01 +5.623E-01 +5.738E-01 +5.611E-01 +5.569E-01 +5.556E-01 +5.555E-01 +5.583E-01 +5.544E-01 +5.486E-01 +5.412E-01 +5.383E-01 +cmfd openmc source comparison +1.575499E-02 +1.293688E-02 +3.531746E-03 +8.281178E-03 +5.771681E-03 +7.459013E-03 +5.012869E-03 +1.770224E-03 +5.242540E-03 +3.888027E-03 +6.653433E-03 +8.839928E-03 +5.456904E-03 +5.668412E-03 +4.016377E-03 +4.179381E-03 +cmfd source +4.116210E-02 +7.797480E-02 +1.062822E-01 +1.333719E-01 +1.481091E-01 +1.370422E-01 +1.299765E-01 +9.887040E-02 +8.228753E-02 +4.492330E-02 diff --git a/tests/regression_tests/cmfd_feed_expanding_window/settings.xml b/tests/regression_tests/cmfd_feed_expanding_window/settings.xml new file mode 100644 index 0000000000..24b0b6ab50 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_expanding_window/settings.xml @@ -0,0 +1,26 @@ + + + + + eigenvalue + 20 + 10 + 1000 + + + + + box + -10 -1 -1 10 1 1 + + + + + + 10 1 1 + -10.0 -1.0 -1.0 + 10.0 1.0 1.0 + + 10 + + diff --git a/tests/regression_tests/cmfd_feed_expanding_window/tallies.xml b/tests/regression_tests/cmfd_feed_expanding_window/tallies.xml new file mode 100644 index 0000000000..c869711147 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_expanding_window/tallies.xml @@ -0,0 +1,21 @@ + + + + + regular + -10 -1 -1 + 10 1 1 + 10 1 1 + + + + mesh + 1 + + + + 1 + flux + + + diff --git a/tests/regression_tests/cmfd_feed_expanding_window/test.py b/tests/regression_tests/cmfd_feed_expanding_window/test.py new file mode 100644 index 0000000000..9a2697dff8 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_expanding_window/test.py @@ -0,0 +1,26 @@ +from tests.testing_harness import CMFDTestHarness +from openmc import cmfd + + +def test_cmfd_feed_rolling_window(): + """Test 1 group CMFD solver with CMFD feedback""" + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 10 + cmfd_run.feedback = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.window_type = 'expanding' + cmfd_run.run() + + # Initialize and run CMFD test harness + harness = CMFDTestHarness('statepoint.20.h5', cmfd_run) + harness.main() diff --git a/tests/regression_tests/cmfd_feed_ref_d/__init__.py b/tests/regression_tests/cmfd_feed_ref_d/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/cmfd_feed_ref_d/geometry.xml b/tests/regression_tests/cmfd_feed_ref_d/geometry.xml new file mode 100644 index 0000000000..73ea679c4c --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ref_d/geometry.xml @@ -0,0 +1,43 @@ + + + + + + 0 + -1 2 -3 4 -5 6 + 1 + + + + + x-plane + 10 + vacuum + + + x-plane + -10 + vacuum + + + y-plane + 1 + reflective + + + y-plane + -1 + reflective + + + z-plane + 1 + reflective + + + z-plane + -1 + reflective + + + diff --git a/tests/regression_tests/cmfd_feed_ref_d/materials.xml b/tests/regression_tests/cmfd_feed_ref_d/materials.xml new file mode 100644 index 0000000000..70580e3a8d --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ref_d/materials.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/regression_tests/cmfd_feed_ref_d/results_true.dat b/tests/regression_tests/cmfd_feed_ref_d/results_true.dat new file mode 100644 index 0000000000..06440d81f6 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ref_d/results_true.dat @@ -0,0 +1,488 @@ +k-combined: +1.165408E+00 1.127320E-02 +tally 1: +1.141009E+01 +1.305436E+01 +2.074878E+01 +4.311543E+01 +2.834824E+01 +8.065180E+01 +3.513901E+01 +1.238542E+02 +3.699846E+01 +1.372507E+02 +3.612638E+01 +1.310741E+02 +3.373329E+01 +1.140431E+02 +2.836806E+01 +8.075546E+01 +2.158796E+01 +4.674672E+01 +1.141841E+01 +1.310586E+01 +tally 2: +1.126662E+00 +1.269368E+00 +7.749275E-01 +6.005127E-01 +1.991651E+00 +3.966673E+00 +1.411802E+00 +1.993185E+00 +2.978294E+00 +8.870233E+00 +2.130084E+00 +4.537258E+00 +3.708845E+00 +1.375553E+01 +2.698001E+00 +7.279210E+00 +4.233143E+00 +1.791950E+01 +3.023958E+00 +9.144324E+00 +4.062060E+00 +1.650033E+01 +2.910435E+00 +8.470632E+00 +3.506628E+00 +1.229644E+01 +2.497713E+00 +6.238573E+00 +2.974637E+00 +8.848463E+00 +2.121135E+00 +4.499215E+00 +2.329228E+00 +5.425303E+00 +1.658218E+00 +2.749687E+00 +1.158542E+00 +1.342220E+00 +8.282626E-01 +6.860190E-01 +tally 3: +7.459525E-01 +5.564451E-01 +4.877161E-02 +2.378670E-03 +1.356729E+00 +1.840713E+00 +8.360848E-02 +6.990377E-03 +2.047350E+00 +4.191641E+00 +1.521210E-01 +2.314079E-02 +2.610278E+00 +6.813552E+00 +1.718619E-01 +2.953650E-02 +2.911348E+00 +8.475950E+00 +1.823129E-01 +3.323800E-02 +2.806884E+00 +7.878600E+00 +2.090212E-01 +4.368986E-02 +2.415690E+00 +5.835559E+00 +1.486373E-01 +2.209304E-02 +2.051037E+00 +4.206752E+00 +1.196066E-01 +1.430573E-02 +1.593732E+00 +2.539980E+00 +1.126392E-01 +1.268759E-02 +7.994865E-01 +6.391786E-01 +5.806144E-02 +3.371131E-03 +tally 4: +1.341763E-01 +1.800329E-02 +0.000000E+00 +0.000000E+00 +1.420749E-01 +2.018527E-02 +2.633206E-01 +6.933776E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.633206E-01 +6.933776E-02 +1.420749E-01 +2.018527E-02 +2.628684E-01 +6.909981E-02 +3.590428E-01 +1.289117E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.590428E-01 +1.289117E-01 +2.628684E-01 +6.909981E-02 +3.851638E-01 +1.483512E-01 +4.595776E-01 +2.112116E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.595776E-01 +2.112116E-01 +3.851638E-01 +1.483512E-01 +4.679953E-01 +2.190196E-01 +4.970127E-01 +2.470216E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.970127E-01 +2.470216E-01 +4.679953E-01 +2.190196E-01 +4.909606E-01 +2.410423E-01 +4.838733E-01 +2.341333E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.838733E-01 +2.341333E-01 +4.909606E-01 +2.410423E-01 +5.152620E-01 +2.654949E-01 +4.788580E-01 +2.293049E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.788580E-01 +2.293049E-01 +5.152620E-01 +2.654949E-01 +4.266812E-01 +1.820568E-01 +3.401298E-01 +1.156883E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.401298E-01 +1.156883E-01 +4.266812E-01 +1.820568E-01 +3.724142E-01 +1.386923E-01 +2.559996E-01 +6.553579E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.559996E-01 +6.553579E-02 +3.724142E-01 +1.386923E-01 +2.892105E-01 +8.364271E-02 +1.556166E-01 +2.421652E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.556166E-01 +2.421652E-02 +2.892105E-01 +8.364271E-02 +1.497731E-01 +2.243198E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +tally 5: +7.459525E-01 +5.564451E-01 +1.149859E-01 +1.322177E-02 +1.356729E+00 +1.840713E+00 +1.801152E-01 +3.244149E-02 +2.047350E+00 +4.191641E+00 +2.627538E-01 +6.903954E-02 +2.609208E+00 +6.807967E+00 +3.360312E-01 +1.129170E-01 +2.909427E+00 +8.464765E+00 +4.095398E-01 +1.677229E-01 +2.805095E+00 +7.868559E+00 +3.957580E-01 +1.566244E-01 +2.415690E+00 +5.835559E+00 +3.196366E-01 +1.021676E-01 +2.049833E+00 +4.201813E+00 +3.162366E-01 +1.000056E-01 +1.593732E+00 +2.539980E+00 +2.169968E-01 +4.708761E-02 +7.994865E-01 +6.391786E-01 +1.002479E-01 +1.004964E-02 +cmfd indices +1.000000E+01 +1.000000E+00 +1.000000E+00 +1.000000E+00 +k cmfd +1.143785E+00 +1.163460E+00 +1.173453E+00 +1.171056E+00 +1.147214E+00 +1.122230E+00 +1.106385E+00 +1.124706E+00 +1.133207E+00 +1.134453E+00 +1.142355E+00 +1.132121E+00 +1.132479E+00 +1.153657E+00 +1.170183E+00 +1.184408E+00 +cmfd entropy +3.211758E+00 +3.206356E+00 +3.223933E+00 +3.222754E+00 +3.232110E+00 +3.242098E+00 +3.246062E+00 +3.238858E+00 +3.235577E+00 +3.234604E+00 +3.233582E+00 +3.236922E+00 +3.229563E+00 +3.225265E+00 +3.221498E+00 +3.219126E+00 +cmfd balance +8.26180E-03 +4.27338E-03 +2.22686E-03 +1.93026E-03 +1.96979E-03 +2.13756E-03 +2.01521E-03 +1.74538E-03 +2.09240E-03 +1.25495E-03 +1.49542E-03 +1.76968E-03 +1.99174E-03 +1.87753E-03 +1.24170E-03 +1.15645E-03 +cmfd dominance ratio +5.408E-01 +5.374E-01 +5.420E-01 +5.444E-01 +5.513E-01 +5.613E-01 +5.722E-01 +5.592E-01 +5.542E-01 +5.537E-01 +5.541E-01 +5.579E-01 +5.543E-01 +5.487E-01 +5.413E-01 +5.381E-01 +cmfd openmc source comparison +1.597982E-02 +1.276493E-02 +3.495229E-03 +8.157807E-03 +5.715330E-03 +7.433111E-03 +5.006211E-03 +1.766072E-03 +5.184425E-03 +3.864321E-03 +6.617152E-03 +8.841210E-03 +5.454747E-03 +5.652690E-03 +4.006767E-03 +4.167617E-03 +cmfd source +4.116746E-02 +7.798354E-02 +1.062881E-01 +1.333681E-01 +1.481008E-01 +1.370408E-01 +1.299726E-01 +9.886814E-02 +8.228698E-02 +4.492338E-02 diff --git a/tests/regression_tests/cmfd_feed_ref_d/settings.xml b/tests/regression_tests/cmfd_feed_ref_d/settings.xml new file mode 100644 index 0000000000..24b0b6ab50 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ref_d/settings.xml @@ -0,0 +1,26 @@ + + + + + eigenvalue + 20 + 10 + 1000 + + + + + box + -10 -1 -1 10 1 1 + + + + + + 10 1 1 + -10.0 -1.0 -1.0 + 10.0 1.0 1.0 + + 10 + + diff --git a/tests/regression_tests/cmfd_feed_ref_d/tallies.xml b/tests/regression_tests/cmfd_feed_ref_d/tallies.xml new file mode 100644 index 0000000000..c869711147 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ref_d/tallies.xml @@ -0,0 +1,21 @@ + + + + + regular + -10 -1 -1 + 10 1 1 + 10 1 1 + + + + mesh + 1 + + + + 1 + flux + + + diff --git a/tests/regression_tests/cmfd_feed_ref_d/test.py b/tests/regression_tests/cmfd_feed_ref_d/test.py new file mode 100644 index 0000000000..4facabb5f8 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_ref_d/test.py @@ -0,0 +1,27 @@ +from tests.testing_harness import CMFDTestHarness +from openmc import cmfd + + +def test_cmfd_feed_rolling_window(): + """Test 1 group CMFD solver with CMFD feedback""" + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 10 + cmfd_run.feedback = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.window_type = 'expanding' + cmfd_run.ref_d = [0.542] + cmfd_run.run() + + # Initialize and run CMFD test harness + harness = CMFDTestHarness('statepoint.20.h5', cmfd_run) + harness.main() diff --git a/tests/regression_tests/cmfd_feed_rolling_window/__init__.py b/tests/regression_tests/cmfd_feed_rolling_window/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/cmfd_feed_rolling_window/geometry.xml b/tests/regression_tests/cmfd_feed_rolling_window/geometry.xml new file mode 100644 index 0000000000..73ea679c4c --- /dev/null +++ b/tests/regression_tests/cmfd_feed_rolling_window/geometry.xml @@ -0,0 +1,43 @@ + + + + + + 0 + -1 2 -3 4 -5 6 + 1 + + + + + x-plane + 10 + vacuum + + + x-plane + -10 + vacuum + + + y-plane + 1 + reflective + + + y-plane + -1 + reflective + + + z-plane + 1 + reflective + + + z-plane + -1 + reflective + + + diff --git a/tests/regression_tests/cmfd_feed_rolling_window/materials.xml b/tests/regression_tests/cmfd_feed_rolling_window/materials.xml new file mode 100644 index 0000000000..70580e3a8d --- /dev/null +++ b/tests/regression_tests/cmfd_feed_rolling_window/materials.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/regression_tests/cmfd_feed_rolling_window/results_true.dat b/tests/regression_tests/cmfd_feed_rolling_window/results_true.dat new file mode 100644 index 0000000000..5180526835 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_rolling_window/results_true.dat @@ -0,0 +1,488 @@ +k-combined: +1.172120E+00 9.761693E-03 +tally 1: +1.131066E+01 +1.287315E+01 +2.035268E+01 +4.162146E+01 +2.839961E+01 +8.098764E+01 +3.405187E+01 +1.164951E+02 +3.712390E+01 +1.380899E+02 +3.687780E+01 +1.363090E+02 +3.380903E+01 +1.147695E+02 +2.903657E+01 +8.489183E+01 +2.121421E+01 +4.519765E+01 +1.112628E+01 +1.245629E+01 +tally 2: +1.114845E+00 +1.242879E+00 +8.000235E-01 +6.400375E-01 +1.852141E+00 +3.430427E+00 +1.324803E+00 +1.755104E+00 +2.585263E+00 +6.683587E+00 +1.840969E+00 +3.389167E+00 +3.737263E+00 +1.396714E+01 +2.674345E+00 +7.152122E+00 +3.989056E+00 +1.591257E+01 +2.843382E+00 +8.084820E+00 +3.951710E+00 +1.561601E+01 +2.838216E+00 +8.055468E+00 +3.734018E+00 +1.394289E+01 +2.676071E+00 +7.161356E+00 +3.241209E+00 +1.050543E+01 +2.315684E+00 +5.362392E+00 +2.459493E+00 +6.049108E+00 +1.734344E+00 +3.007949E+00 +1.079306E+00 +1.164902E+00 +7.536940E-01 +5.680547E-01 +tally 3: +7.728275E-01 +5.972624E-01 +5.550061E-02 +3.080318E-03 +1.272034E+00 +1.618070E+00 +7.284456E-02 +5.306329E-03 +1.779821E+00 +3.167762E+00 +1.110012E-01 +1.232127E-02 +2.586020E+00 +6.687502E+00 +1.618768E-01 +2.620410E-02 +2.726552E+00 +7.434084E+00 +1.965647E-01 +3.863767E-02 +2.741747E+00 +7.517178E+00 +1.907834E-01 +3.639829E-02 +2.564084E+00 +6.574527E+00 +1.503142E-01 +2.259435E-02 +2.233122E+00 +4.986832E+00 +1.456891E-01 +2.122532E-02 +1.682929E+00 +2.832250E+00 +9.828234E-02 +9.659418E-03 +7.232788E-01 +5.231322E-01 +6.128193E-02 +3.755475E-03 +tally 4: +1.326662E-01 +1.760031E-02 +0.000000E+00 +0.000000E+00 +1.369313E-01 +1.875018E-02 +2.569758E-01 +6.603657E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.569758E-01 +6.603657E-02 +1.369313E-01 +1.875018E-02 +2.494513E-01 +6.222596E-02 +3.551002E-01 +1.260962E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.551002E-01 +1.260962E-01 +2.494513E-01 +6.222596E-02 +3.610027E-01 +1.303230E-01 +4.252566E-01 +1.808432E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.252566E-01 +1.808432E-01 +3.610027E-01 +1.303230E-01 +4.673502E-01 +2.184162E-01 +5.073957E-01 +2.574504E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.073957E-01 +2.574504E-01 +4.673502E-01 +2.184162E-01 +4.818855E-01 +2.322136E-01 +4.949753E-01 +2.450006E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.949753E-01 +2.450006E-01 +4.818855E-01 +2.322136E-01 +5.176611E-01 +2.679730E-01 +4.756654E-01 +2.262576E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.756654E-01 +2.262576E-01 +5.176611E-01 +2.679730E-01 +4.591191E-01 +2.107904E-01 +3.930251E-01 +1.544688E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.930251E-01 +1.544688E-01 +4.591191E-01 +2.107904E-01 +4.201532E-01 +1.765287E-01 +3.188250E-01 +1.016494E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.188250E-01 +1.016494E-01 +4.201532E-01 +1.765287E-01 +2.898817E-01 +8.403142E-02 +1.509185E-01 +2.277639E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.509185E-01 +2.277639E-02 +2.898817E-01 +8.403142E-02 +1.482175E-01 +2.196844E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +tally 5: +7.719619E-01 +5.959252E-01 +8.168322E-02 +6.672148E-03 +1.272034E+00 +1.618070E+00 +1.458271E-01 +2.126554E-02 +1.779821E+00 +3.167762E+00 +2.705917E-01 +7.321987E-02 +2.584069E+00 +6.677414E+00 +3.176497E-01 +1.009013E-01 +2.726552E+00 +7.434084E+00 +3.562714E-01 +1.269293E-01 +2.740788E+00 +7.511919E+00 +3.690107E-01 +1.361689E-01 +2.563145E+00 +6.569715E+00 +3.296244E-01 +1.086523E-01 +2.233122E+00 +4.986832E+00 +3.106254E-01 +9.648813E-02 +1.682929E+00 +2.832250E+00 +2.709632E-01 +7.342104E-02 +7.232788E-01 +5.231322E-01 +9.343486E-02 +8.730073E-03 +cmfd indices +1.000000E+01 +1.000000E+00 +1.000000E+00 +1.000000E+00 +k cmfd +1.143597E+00 +1.163387E+00 +1.162391E+00 +1.163351E+00 +1.145721E+00 +1.134785E+00 +1.119048E+00 +1.116124E+00 +1.109085E+00 +1.126581E+00 +1.158559E+00 +1.163719E+00 +1.162162E+00 +1.160856E+00 +1.166709E+00 +1.167223E+00 +cmfd entropy +3.212002E+00 +3.206393E+00 +3.222109E+00 +3.221996E+00 +3.229978E+00 +3.234615E+00 +3.246512E+00 +3.244634E+00 +3.244312E+00 +3.236922E+00 +3.232693E+00 +3.221849E+00 +3.215716E+00 +3.215968E+00 +3.203758E+00 +3.201798E+00 +cmfd balance +8.26180E-03 +4.27338E-03 +2.62159E-03 +2.40301E-03 +2.08484E-03 +1.58351E-03 +1.59196E-03 +1.87591E-03 +1.94451E-03 +1.91803E-03 +1.90044E-03 +1.87968E-03 +2.55363E-03 +2.39932E-03 +2.25515E-03 +1.53613E-03 +cmfd dominance ratio +5.404E-01 +5.406E-01 +5.432E-01 +5.454E-01 +5.507E-01 +5.578E-01 +5.679E-01 +5.671E-01 +5.690E-01 +5.637E-01 +5.575E-01 +5.474E-01 +5.480E-01 +5.467E-01 +5.374E-01 +5.321E-01 +cmfd openmc source comparison +1.575499E-02 +1.293688E-02 +5.920734E-03 +9.746850E-03 +7.183896E-03 +7.693485E-03 +4.158805E-03 +2.962505E-03 +4.415044E-03 +2.304495E-03 +7.921580E-03 +8.609203E-03 +8.945198E-03 +8.054204E-03 +1.164189E-02 +5.945645E-03 +cmfd source +4.077779E-02 +6.659143E-02 +1.017198E-01 +1.172269E-01 +1.458410E-01 +1.559801E-01 +1.337001E-01 +1.137262E-01 +8.397376E-02 +4.046290E-02 diff --git a/tests/regression_tests/cmfd_feed_rolling_window/settings.xml b/tests/regression_tests/cmfd_feed_rolling_window/settings.xml new file mode 100644 index 0000000000..24b0b6ab50 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_rolling_window/settings.xml @@ -0,0 +1,26 @@ + + + + + eigenvalue + 20 + 10 + 1000 + + + + + box + -10 -1 -1 10 1 1 + + + + + + 10 1 1 + -10.0 -1.0 -1.0 + 10.0 1.0 1.0 + + 10 + + diff --git a/tests/regression_tests/cmfd_feed_rolling_window/tallies.xml b/tests/regression_tests/cmfd_feed_rolling_window/tallies.xml new file mode 100644 index 0000000000..c869711147 --- /dev/null +++ b/tests/regression_tests/cmfd_feed_rolling_window/tallies.xml @@ -0,0 +1,21 @@ + + + + + regular + -10 -1 -1 + 10 1 1 + 10 1 1 + + + + mesh + 1 + + + + 1 + flux + + + diff --git a/tests/regression_tests/cmfd_feed_rolling_window/test.py b/tests/regression_tests/cmfd_feed_rolling_window/test.py new file mode 100644 index 0000000000..67861be4ac --- /dev/null +++ b/tests/regression_tests/cmfd_feed_rolling_window/test.py @@ -0,0 +1,27 @@ +from tests.testing_harness import CMFDTestHarness +from openmc import cmfd + + +def test_cmfd_feed_rolling_window(): + """Test 1 group CMFD solver with CMFD feedback""" + # Initialize and set CMFD mesh + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + + # Initialize and run CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 10 + cmfd_run.feedback = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.window_type = 'rolling' + cmfd_run.window_size = 5 + cmfd_run.run() + + # Initialize and run CMFD test harness + harness = CMFDTestHarness('statepoint.20.h5', cmfd_run) + harness.main() diff --git a/tests/regression_tests/cmfd_restart/__init__.py b/tests/regression_tests/cmfd_restart/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/cmfd_restart/geometry.xml b/tests/regression_tests/cmfd_restart/geometry.xml new file mode 100644 index 0000000000..73ea679c4c --- /dev/null +++ b/tests/regression_tests/cmfd_restart/geometry.xml @@ -0,0 +1,43 @@ + + + + + + 0 + -1 2 -3 4 -5 6 + 1 + + + + + x-plane + 10 + vacuum + + + x-plane + -10 + vacuum + + + y-plane + 1 + reflective + + + y-plane + -1 + reflective + + + z-plane + 1 + reflective + + + z-plane + -1 + reflective + + + diff --git a/tests/regression_tests/cmfd_restart/materials.xml b/tests/regression_tests/cmfd_restart/materials.xml new file mode 100644 index 0000000000..70580e3a8d --- /dev/null +++ b/tests/regression_tests/cmfd_restart/materials.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/regression_tests/cmfd_restart/results_true.dat b/tests/regression_tests/cmfd_restart/results_true.dat new file mode 100644 index 0000000000..4a2c6eea25 --- /dev/null +++ b/tests/regression_tests/cmfd_restart/results_true.dat @@ -0,0 +1,488 @@ +k-combined: +1.169891E+00 6.289489E-03 +tally 1: +1.173922E+01 +1.385461E+01 +2.164076E+01 +4.699368E+01 +2.906462E+01 +8.464937E+01 +3.382312E+01 +1.147095E+02 +3.632006E+01 +1.323878E+02 +3.655413E+01 +1.341064E+02 +3.347757E+01 +1.124264E+02 +2.931336E+01 +8.607239E+01 +2.182947E+01 +4.789565E+01 +1.147668E+01 +1.325716E+01 +tally 2: +2.298190E+01 +2.667071E+01 +1.600292E+01 +1.293670E+01 +4.268506E+01 +9.161216E+01 +3.022909E+01 +4.598915E+01 +5.680399E+01 +1.623879E+02 +4.033805E+01 +8.196263E+01 +6.814742E+01 +2.331778E+02 +4.851618E+01 +1.182330E+02 +7.392923E+01 +2.740255E+02 +5.253586E+01 +1.384152E+02 +7.332860E+01 +2.698608E+02 +5.227405E+01 +1.371810E+02 +6.830172E+01 +2.340687E+02 +4.867159E+01 +1.188724E+02 +5.885634E+01 +1.736180E+02 +4.170434E+01 +8.719622E+01 +4.371848E+01 +9.592893E+01 +3.106403E+01 +4.844308E+01 +2.338413E+01 +2.752467E+01 +1.636713E+01 +1.347770E+01 +tally 3: +1.538752E+01 +1.196478E+01 +1.079685E+00 +6.010786E-02 +2.911906E+01 +4.269070E+01 +1.822657E+00 +1.671850E-01 +3.885421E+01 +7.608218E+01 +2.541516E+00 +3.262451E-01 +4.673300E+01 +1.097036E+02 +2.885307E+00 +4.214444E-01 +5.059247E+01 +1.283984E+02 +3.222796E+00 +5.237329E-01 +5.034856E+01 +1.272538E+02 +3.230225E+00 +5.273424E-01 +4.688476E+01 +1.103152E+02 +2.941287E+00 +4.363749E-01 +4.013746E+01 +8.077506E+01 +2.634234E+00 +3.520270E-01 +2.996887E+01 +4.509953E+01 +1.946504E+00 +1.919104E-01 +1.575260E+01 +1.248707E+01 +1.020705E+00 +5.413569E-02 +tally 4: +3.049469E+00 +4.677325E-01 +0.000000E+00 +0.000000E+00 +2.770358E+00 +3.879191E-01 +5.514939E+00 +1.528899E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.514939E+00 +1.528899E+00 +2.770358E+00 +3.879191E-01 +5.032131E+00 +1.275040E+00 +7.294002E+00 +2.675589E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.294002E+00 +2.675589E+00 +5.032131E+00 +1.275040E+00 +7.036008E+00 +2.490718E+00 +8.668860E+00 +3.776102E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +8.668860E+00 +3.776102E+00 +7.036008E+00 +2.490718E+00 +8.352414E+00 +3.501945E+00 +9.345868E+00 +4.380719E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.345868E+00 +4.380719E+00 +8.352414E+00 +3.501945E+00 +9.093766E+00 +4.158282E+00 +9.223771E+00 +4.270120E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.223771E+00 +4.270120E+00 +9.093766E+00 +4.158282E+00 +9.219150E+00 +4.264346E+00 +8.530966E+00 +3.651778E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +8.530966E+00 +3.651778E+00 +9.219150E+00 +4.264346E+00 +8.690373E+00 +3.785262E+00 +7.204424E+00 +2.604203E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.204424E+00 +2.604203E+00 +8.690373E+00 +3.785262E+00 +7.513640E+00 +2.833028E+00 +5.326721E+00 +1.426975E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.326721E+00 +1.426975E+00 +7.513640E+00 +2.833028E+00 +5.662215E+00 +1.607757E+00 +2.848381E+00 +4.093396E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.848381E+00 +4.093396E-01 +5.662215E+00 +1.607757E+00 +3.025812E+00 +4.597241E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +tally 5: +1.538652E+01 +1.196332E+01 +2.252427E+00 +2.605738E-01 +2.911344E+01 +4.267319E+01 +3.873926E+00 +7.615035E-01 +3.884516E+01 +7.604619E+01 +5.280610E+00 +1.414008E+00 +4.672391E+01 +1.096625E+02 +6.261805E+00 +1.983205E+00 +5.058447E+01 +1.283588E+02 +6.733810E+00 +2.278242E+00 +5.033589E+01 +1.271898E+02 +6.714658E+00 +2.273652E+00 +4.687563E+01 +1.102719E+02 +6.215002E+00 +1.956978E+00 +4.013134E+01 +8.075062E+01 +5.253064E+00 +1.396224E+00 +2.996497E+01 +4.508839E+01 +3.818076E+00 +7.509442E-01 +1.574994E+01 +1.248291E+01 +2.219928E+00 +2.515492E-01 +cmfd indices +1.000000E+01 +1.000000E+00 +1.000000E+00 +1.000000E+00 +k cmfd +1.170416E+00 +1.172966E+00 +1.165537E+00 +1.170979E+00 +1.161922E+00 +1.157523E+00 +1.158873E+00 +1.162877E+00 +1.167101E+00 +1.168130E+00 +1.170570E+00 +1.168115E+00 +1.174081E+00 +1.169458E+00 +1.167848E+00 +1.165116E+00 +cmfd entropy +3.203643E+00 +3.207943E+00 +3.213367E+00 +3.214360E+00 +3.219634E+00 +3.222232E+00 +3.221744E+00 +3.224544E+00 +3.225990E+00 +3.227769E+00 +3.227417E+00 +3.230728E+00 +3.231662E+00 +3.233316E+00 +3.233193E+00 +3.232564E+00 +cmfd balance +4.00906E-03 +4.43177E-03 +3.15267E-03 +3.51038E-03 +2.05209E-03 +2.06865E-03 +1.50243E-03 +1.58983E-03 +1.56602E-03 +1.17001E-03 +9.50759E-04 +9.07259E-04 +1.00900E-03 +1.06470E-03 +1.16361E-03 +9.75631E-04 +cmfd dominance ratio +5.397E-01 +5.425E-01 +5.481E-01 +5.473E-01 +5.503E-01 +5.502E-01 +5.483E-01 +5.520E-01 +5.505E-01 +3.216E-01 +5.373E-01 +5.517E-01 +5.508E-01 +5.524E-01 +5.524E-01 +5.523E-01 +cmfd openmc source comparison +6.959834E-03 +5.655657E-03 +3.886186E-03 +4.035116E-03 +3.043277E-03 +5.455474E-03 +4.515310E-03 +2.439840E-03 +2.114032E-03 +2.673132E-03 +2.431749E-03 +4.330928E-03 +3.404647E-03 +3.680298E-03 +3.309620E-03 +3.705541E-03 +cmfd source +4.697085E-02 +7.920706E-02 +1.107968E-01 +1.250932E-01 +1.383930E-01 +1.380648E-01 +1.246874E-01 +1.113705E-01 +8.203754E-02 +4.337882E-02 diff --git a/tests/regression_tests/cmfd_restart/settings.xml b/tests/regression_tests/cmfd_restart/settings.xml new file mode 100644 index 0000000000..ba5495911f --- /dev/null +++ b/tests/regression_tests/cmfd_restart/settings.xml @@ -0,0 +1,28 @@ + + + + + eigenvalue + 20 + 10 + 1000 + + + + + box + -10 -1 -1 10 1 1 + + + + + + 10 1 1 + -10.0 -1.0 -1.0 + 10.0 1.0 1.0 + + 10 + + + + diff --git a/tests/regression_tests/cmfd_restart/tallies.xml b/tests/regression_tests/cmfd_restart/tallies.xml new file mode 100644 index 0000000000..c869711147 --- /dev/null +++ b/tests/regression_tests/cmfd_restart/tallies.xml @@ -0,0 +1,21 @@ + + + + + regular + -10 -1 -1 + 10 1 1 + 10 1 1 + + + + mesh + 1 + + + + 1 + flux + + + diff --git a/tests/regression_tests/cmfd_restart/test.py b/tests/regression_tests/cmfd_restart/test.py new file mode 100644 index 0000000000..6f89947f3d --- /dev/null +++ b/tests/regression_tests/cmfd_restart/test.py @@ -0,0 +1,72 @@ +import glob +import os +import copy + +from tests.testing_harness import CMFDTestHarness +from openmc import cmfd +import numpy as np + + +class CMFDRestartTestHarness(CMFDTestHarness): + def __init__(self, final_sp, restart_sp, cmfd_run1, cmfd_run2): + super().__init__(final_sp, cmfd_run1) + self._cmfd_restart_run = cmfd_run2 + self._restart_sp = restart_sp + + def execute_test(self): + try: + # Compare results from first CMFD run + self._test_output_created() + results = self._get_results() + results += self._cmfdrun_results + self._write_results(results) + self._compare_results() + + # Run CMFD from restart file + statepoint = glob.glob(os.path.join(os.getcwd(), self._restart_sp)) + assert len(statepoint) == 1 + statepoint = statepoint[0] + self._cmfd_restart_run.run(args=['-r', statepoint]) + + # Compare results from second CMFD run + self._test_output_created() + self._create_cmfd_result_str(self._cmfd_restart_run) + results = self._get_results() + results += self._cmfdrun_results + self._write_results(results) + self._compare_results() + finally: + self._cleanup() + + +def test_cmfd_restart(): + """Test 1 group CMFD solver with restart run""" + # Initialize and set CMFD mesh, create a copy for second run + cmfd_mesh = cmfd.CMFDMesh() + cmfd_mesh.lower_left = -10.0, -1.0, -1.0 + cmfd_mesh.upper_right = 10.0, 1.0, 1.0 + cmfd_mesh.dimension = 10, 1, 1 + cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh2 = copy.deepcopy(cmfd_mesh) + + # Initialize and run first CMFDRun object + cmfd_run = cmfd.CMFDRun() + cmfd_run.mesh = cmfd_mesh + cmfd_run.tally_begin = 5 + cmfd_run.feedback_begin = 5 + cmfd_run.feedback = True + cmfd_run.gauss_seidel_tolerance = [1.e-15, 1.e-20] + cmfd_run.run() + + # Initialize second CMFDRun object which will be run from restart file + cmfd_run2 = cmfd.CMFDRun() + cmfd_run2.mesh = cmfd_mesh2 + cmfd_run2.tally_begin = 5 + cmfd_run2.feedback_begin = 5 + cmfd_run2.feedback = True + cmfd_run2.gauss_seidel_tolerance = [1.e-15, 1.e-20] + + # Initialize and run CMFD restart test harness + harness = CMFDRestartTestHarness('statepoint.20.h5', 'statepoint.15.h5', + cmfd_run, cmfd_run2) + harness.main() From 8f48ecf7dec4ee6a36b431b965ce23c91429f096 Mon Sep 17 00:00:00 2001 From: amandalund Date: Thu, 18 Apr 2019 08:22:40 -0500 Subject: [PATCH 025/151] Add method for generating settings from an XML file --- openmc/mesh.py | 37 +++++ openmc/settings.py | 255 +++++++++++++++++++++++++++++++++-- openmc/source.py | 76 ++++++++++- openmc/stats/multivariate.py | 130 ++++++++++++++++++ openmc/stats/univariate.py | 155 +++++++++++++++++++++ 5 files changed, 641 insertions(+), 12 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index ba07ec178b..a30b333fb3 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -246,6 +246,43 @@ class Mesh(IDManagerMixin): return element + @classmethod + def from_xml_element(cls, elem): + """Generate mesh from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.Mesh + Mesh generated from XML element + + """ + mesh_id = int(elem.get('id')) + mesh = cls(mesh_id) + mesh.type = elem.get('type') + + dimension = elem.findtext('dimension') + if dimension is not None: + mesh.dimension = [int(x) for x in dimension.split()] + + lower_left = elem.findtext('lower_left') + if lower_left is not None: + mesh.lower_left = [float(x) for x in lower_left.split()] + + upper_right = elem.findtext('upper_right') + if upper_right is not None: + mesh.upper_right = [float(x) for x in upper_right.split()] + + width = elem.findtext('width') + if width is not None: + mesh.width = [float(x) for x in width.split()] + + return mesh + def build_cells(self, bc=['reflective'] * 6): """Generates a lattice of universes with the same dimensionality as the mesh object. The individual cells/universes produced diff --git a/openmc/settings.py b/openmc/settings.py index f348bc6cb0..a7ec086792 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -693,29 +693,29 @@ class Settings(object): elem = ET.SubElement(root, "run_mode") elem.text = self._run_mode - def _create_batches_subelement(self, run_mode_element): + def _create_batches_subelement(self, root): if self._batches is not None: - element = ET.SubElement(run_mode_element, "batches") + element = ET.SubElement(root, "batches") element.text = str(self._batches) - def _create_generations_per_batch_subelement(self, run_mode_element): + def _create_generations_per_batch_subelement(self, root): if self._generations_per_batch is not None: - element = ET.SubElement(run_mode_element, "generations_per_batch") + element = ET.SubElement(root, "generations_per_batch") element.text = str(self._generations_per_batch) - def _create_inactive_subelement(self, run_mode_element): + def _create_inactive_subelement(self, root): if self._inactive is not None: - element = ET.SubElement(run_mode_element, "inactive") + element = ET.SubElement(root, "inactive") element.text = str(self._inactive) - def _create_particles_subelement(self, run_mode_element): + def _create_particles_subelement(self, root): if self._particles is not None: - element = ET.SubElement(run_mode_element, "particles") + element = ET.SubElement(root, "particles") element.text = str(self._particles) - def _create_keff_trigger_subelement(self, run_mode_element): + def _create_keff_trigger_subelement(self, root): if self._keff_trigger is not None: - element = ET.SubElement(run_mode_element, "keff_trigger") + element = ET.SubElement(root, "keff_trigger") for key in self._keff_trigger: subelement = ET.SubElement(element, key) @@ -985,3 +985,238 @@ class Settings(object): # Write the XML Tree to the settings.xml file tree = ET.ElementTree(root_element) tree.write(str(p), xml_declaration=True, encoding='utf-8') + + @classmethod + def from_xml(cls, path='settings.xml'): + """Generate settings from XML file + + Parameters + ---------- + path : str, optional + Path to settings XML file + + Returns + ------- + openmc.Settings + Settings object + + """ + tree = ET.parse(path) + root = tree.getroot() + + settings = cls() + + # Get the run mode + elem = root.find('run_mode') + if elem is not None: + settings.run_mode = elem.text + + # Get number of particles + elem = root.find('particles') + if elem is not None: + settings.particles = int(elem.text) + + # Get number of batches + elem = root.find('batches') + if elem is not None: + settings.batches = int(elem.text) + + # Get number of inactive batches + elem = root.find('inactive') + if elem is not None: + settings.inactive = int(elem.text) + + # Get number of generations per batch + elem = root.find('generations_per_batch') + if elem is not None: + settings.generations_per_batch = int(elem.text) + + # Get keff trigger + elem = root.find('keff_trigger') + if elem is not None: + trigger = elem.findtext('type') + threshold = float(elem.findtext('threshold')) + settings.keff_trigger = {'type': trigger, 'threshold': threshold} + + # Get the source + for elem in root.findall('source'): + settings.source.append(Source.from_xml_element(elem)) + + # Get the output + elem = root.find('output') + if elem is not None: + settings.output = {} + for entry in elem: + key = entry.tag + if key in ('summary', 'tallies'): + value = entry.text == 'true' + else: + value = entry.text + settings.output[key] = value + + # Get the statepoint + elem = root.find('state_point') + if elem is not None: + batches = elem.findtext('batches') + if batches is not None: + settings.statepoint['batches'] = [int(x) for x in batches.split()] + + # Get the sourcepoint + elem = root.find('source_point') + if elem is not None: + for entry in elem: + key = entry.tag + if key in ('separate', 'write', 'overwrite'): + value = entry.text == 'true' + else: + value = [int(x) for x in entry.text.split()] + settings.sourcepoint[key] = value + + # Get confidence intervals + elem = root.find('confidence_intervals') + if elem is not None: + settings.confidence_intervals = elem.text == 'true' + + # Get electron treatment + elem = root.find('electron_treatment') + if elem is not None: + settings.electron_treatment = elem.text + + # Get energy mode + elem = root.find('energy_mode') + if elem is not None: + settings.energy_mode = elem.text + + # Get max order + elem = root.find('max_order') + if elem is not None: + settings.max_order = int(elem.text) + + # Get photon transport + elem = root.find('photon_transport') + if elem is not None: + settings.photon_transport = elem.text == 'true' + + # Get probability tables + elem = root.find('ptables') + if elem is not None: + settings.ptables = elem.text == 'true' + + # Get seed + elem = root.find('seed') + if elem is not None: + settings.seed = int(elem.text) + + # Get survival biasing + elem = root.find('survival_biasing') + if elem is not None: + settings.survival_biasing = elem.text == 'true' + + # Get cutoff + elem = root.find('cutoff') + if elem is not None: + settings.cutoff = {x.tag: float(x.text) for x in elem} + + # Get entropy mesh + elem = root.find('entropy_mesh') + if elem is not None: + settings.entropy_mesh = Mesh.from_xml_element(elem) + + # Get trigger + elem = root.find('trigger') + if elem is not None: + active = elem.find('active') + settings.trigger_active = active.text == 'true' + max_batches = elem.find('max_batches') + if max_batches is not None: + settings.trigger_max_batches = int(max_batches.text) + batch_interval = elem.find('batch_interval') + if batch_interval is not None: + settings.trigger_batch_interval = int(batch_interval.text) + + # Get no reduce + elem = root.find('no_reduce') + if elem is not None: + settings.no_reduce = elem.text == 'true' + + # Get verbosity + elem = root.find('verbosity') + if elem is not None: + settings.verbosity = int(elem.text) + + # Get tabular legendre + elem = root.find('tabular_legendre') + if elem is not None: + enable = elem.findtext('eneable') + settings.tabular_legendre['enable'] = enable == 'true' + num_points = elem.findtext('num_points') + if num_points is not None: + settings.tabular_legendre['num_points'] = int(num_points) + + # Get temperature + elem = root.findtext('temperature_default') + if elem is not None: + settings.temperature['default'] = float(elem) + elem = root.findtext('temperature_tolerance') + if elem is not None: + settings.temperature['tolerance'] = float(elem) + elem = root.findtext('temperature_method') + if elem is not None: + settings.temperature['method'] = elem + elem = root.findtext('temperature_range') + if elem is not None: + settings.temperature['range'] = [float(x) for x in elem.split()] + elem = root.findtext('temperature_multipole') + if elem is not None: + settings.temperature['multipole'] = elem == 'true' + + # Get trace + elem = root.find('trace') + if elem is not None: + settings.trace = [int(x) for x in elem.text.split()] + + # Get track + elem = root.find('track') + if elem is not None: + settings.track = [int(x) for x in elem.text.split()] + + # Get UFS mesh + elem = root.find('ufs_mesh') + if elem is not None: + settings.ufs_mesh = Mesh.from_xml_element(elem) + + # Get resonance scattering + elem = root.find('resonance_scattering') + if elem is not None: + for entry in elem: + key = entry.tag + if key == 'enable': + value = entry.text == 'true' + elif key == 'method': + value = entry.text + elif key == 'energy_min': + value = float(entry.text) + elif key == 'energy_max': + value = float(entry.text) + elif key == 'nuclides': + value = entry.text.split() + settings.resonance_scattering[key] = value + + # TODO: Get volume calculations + + # Get fission neutrons + elem = root.find('create_fission_neutrons') + if elem is not None: + settings.create_fission_neutrons = elem.text == 'true' + + # Get log grid bins + elem = root.find('log_grid_bins') + if elem is not None: + settings.log_grid_bins = int(elem.text) + + # Get dagmc + elem = root.find('dagmc') + if elem is not None: + settings.dagmc = elem.text == 'true' + + return settings diff --git a/openmc/source.py b/openmc/source.py index 6ec882ca6a..e278d80892 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -2,8 +2,11 @@ from numbers import Real import sys from xml.etree import ElementTree as ET -from openmc.stats.univariate import Univariate -from openmc.stats.multivariate import UnitSphere, Spatial +from openmc.stats.univariate import (Univariate, Discrete, Uniform, Maxwell, + Watt, Normal, Muir, Tabular) +from openmc.stats.multivariate import (UnitSphere, Spatial, PolarAzimuthal, + Isotropic, Monodirectional, Box, Point, + CartesianIndependent) import openmc.checkvalue as cv @@ -137,3 +140,72 @@ class Source(object): if self.energy is not None: element.append(self.energy.to_xml_element('energy')) return element + + @classmethod + def from_xml_element(cls, elem): + """Generate source from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.Source + Source generated from XML element + + """ + source = cls() + + strength = elem.find('strength') + if strength is not None: + source.strength = float(strength.text) + + particle = elem.find('particle') + if particle is not None: + source.particle = particle.text + + filename = elem.find('file') + if filename is not None: + source.file = filename.text + + space = elem.find('space') + if space is not None: + space_type = space.get('type') + if space_type == 'cartesian': + source.space = CartesianIndependent.from_xml_element(space) + elif space_type == 'box' or space_type == 'fission': + source.space = Box.from_xml_element(space) + elif space_type == 'point': + source.space = Point.from_xml_element(space) + + angle = elem.find('angle') + if angle is not None: + angle_type = angle.get('type') + if angle_type == 'mu-phi': + source.angle = PolarAzimuthal.from_xml_element(angle) + elif angle_type == 'isotropic': + source.angle = Isotropic.from_xml_element(angle) + elif angle_type == 'monodirectional': + source.angle = Monodirectional.from_xml_element(angle) + + energy = elem.find('energy') + if energy is not None: + energy_type = energy.get('type') + if energy_type == 'discrete': + source.energy = Discrete.from_xml_element(energy) + elif energy_type == 'uniform': + source.energy = Uniform.from_xml_element(energy) + elif energy_type == 'maxwell': + source.energy = Maxwell.from_xml_element(energy) + elif energy_type == 'watt': + source.energy = Watt.from_xml_element(energy) + elif energy_type == 'normal': + source.energy = Normal.from_xml_element(energy) + elif energy_type == 'muir': + source.energy = Muir.from_xml_element(energy) + elif energy_type == 'tabular': + source.energy = Tabular.from_xml_element(energy) + + return source diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index ac788c3443..61122ada21 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -47,6 +47,11 @@ class UnitSphere(metaclass=ABCMeta): def to_xml_element(self): return '' + @classmethod + @abstractmethod + def from_xml_element(cls, elem): + pass + class PolarAzimuthal(UnitSphere): """Angular distribution represented by polar and azimuthal angles @@ -121,6 +126,29 @@ class PolarAzimuthal(UnitSphere): element.append(self.phi.to_xml_element('phi')) return element + @classmethod + def from_xml_element(cls, elem): + """Generate angular distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.PolarAzimuthal + Angular distribution generated from XML element + + """ + mu_phi = cls() + params = elem.findtext('parameters') + if params is not None: + mu_phi.reference_uvw = [float(x) for x in params.split()] + mu_phi.mu = openmc.stats.Univariate.from_xml_element(elem.find('mu')) + mu_phi.phi = openmc.stats.Univariate.from_xml_element(elem.find('phi')) + return mu_phi + class Isotropic(UnitSphere): """Isotropic angular distribution. @@ -143,6 +171,23 @@ class Isotropic(UnitSphere): element.set("type", "isotropic") return element + @classmethod + def from_xml_element(cls, elem): + """Generate isotropic distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Isotropic + Isotropic distribution generated from XML element + + """ + return cls() + class Monodirectional(UnitSphere): """Monodirectional angular distribution. @@ -178,6 +223,27 @@ class Monodirectional(UnitSphere): element.set("reference_uvw", ' '.join(map(str, self.reference_uvw))) return element + @classmethod + def from_xml_element(cls, elem): + """Generate monodirectional distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Monodirectional + Monodirectional distribution generated from XML element + + """ + monodirectional = cls() + params = elem.findtext('parameters') + if params is not None: + monodirectional.reference_uvw = [float(x) for x in params.split()] + return monodirectional + class Spatial(metaclass=ABCMeta): """Distribution of locations in three-dimensional Euclidean space. @@ -193,6 +259,11 @@ class Spatial(metaclass=ABCMeta): def to_xml_element(self): return '' + @classmethod + @abstractmethod + def from_xml_element(cls, elem): + pass + class CartesianIndependent(Spatial): """Spatial distribution with independent x, y, and z distributions. @@ -270,6 +341,26 @@ class CartesianIndependent(Spatial): element.append(self.z.to_xml_element('z')) return element + @classmethod + def from_xml_element(cls, elem): + """Generate spatial distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.CartesianIndependent + Spatial distribution generated from XML element + + """ + x = openmc.stats.Univariate.from_xml_element(elem.find('x')) + y = openmc.stats.Univariate.from_xml_element(elem.find('y')) + z = openmc.stats.Univariate.from_xml_element(elem.find('z')) + return cls(x, y, z) + class Box(Spatial): """Uniform distribution of coordinates in a rectangular cuboid. @@ -351,6 +442,27 @@ class Box(Spatial): ' '.join(map(str, self.upper_right)) return element + @classmethod + def from_xml_element(cls, elem): + """Generate box distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Box + Box distribution generated from XML element + + """ + only_fissionable = elem.get('type') == 'fission' + params = [float(x) for x in elem.findtext('parameters').split()] + lower_left = params[:len(params)//2] + upper_right = paramx[len(params)//2:] + return cls(lower_left, upper_right, only_fissionable) + class Point(Spatial): """Delta function in three dimensions. @@ -398,3 +510,21 @@ class Point(Spatial): params = ET.SubElement(element, "parameters") params.text = ' '.join(map(str, self.xyz)) return element + + @classmethod + def from_xml_element(cls, elem): + """Generate point distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Point + Point distribution generated from XML element + + """ + xyz = [float(x) for x in elem.findtext('parameters').split()] + return cls(xyz) diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index e61f216ef0..6a83cc780e 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -32,6 +32,11 @@ class Univariate(EqualityMixin, metaclass=ABCMeta): def __len__(self): return 0 + @classmethod + @abstractmethod + def from_xml_element(cls, elem): + pass + class Discrete(Univariate): """Distribution characterized by a probability mass function. @@ -110,6 +115,26 @@ class Discrete(Univariate): return element + @classmethod + def from_xml_element(cls, elem): + """Generate discrete distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Discrete + Discrete distribution generated from XML element + + """ + params = [float(x) for x in elem.findtext('parameters').split()] + x = params[:len(params)//2] + p = params[len(params)//2:] + return cls(x, p) + class Uniform(Univariate): """Distribution with constant probability over a finite interval [a,b] @@ -181,6 +206,26 @@ class Uniform(Univariate): element.set("parameters", '{} {}'.format(self.a, self.b)) return element + @classmethod + def from_xml_element(cls, elem): + """Generate uniform distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Uniform + Uniform distribution generated from XML element + + """ + params = elem.findtext('parameters').split() + a = float(params[0]) + b = float(params[1]) + return cls(a, b) + class Maxwell(Univariate): """Maxwellian distribution in energy. @@ -237,6 +282,24 @@ class Maxwell(Univariate): element.set("parameters", str(self.theta)) return element + @classmethod + def from_xml_element(cls, elem): + """Generate Maxwellian distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Maxwell + Maxwellian distribution generated from XML element + + """ + theta = float(elem.findtext('parameters')) + return cls(theta) + class Watt(Univariate): r"""Watt fission energy spectrum. @@ -308,6 +371,27 @@ class Watt(Univariate): element.set("parameters", '{} {}'.format(self.a, self.b)) return element + @classmethod + def from_xml_element(cls, elem): + """Generate Watt distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Watt + Watt distribution generated from XML element + + """ + params = elem.findtext('parameters').split() + a = float(params[0]) + b = float(params[1]) + return watt(a, b) + + class Normal(Univariate): r"""Normally distributed sampling. @@ -377,6 +461,27 @@ class Normal(Univariate): element.set("parameters", '{} {}'.format(self.mean_value, self.std_dev)) return element + @classmethod + def from_xml_element(cls, elem): + """Generate Normal distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Normal + Normal distribution generated from XML element + + """ + params = elem.findtext('parameters').split() + mean_value = float(params[0]) + std_dev = float(params[1]) + return cls(mean_value, std_dev) + + class Muir(Univariate): """Muir energy spectrum. @@ -465,6 +570,27 @@ class Muir(Univariate): element.set("parameters", '{} {} {}'.format(self._e0, self._m_rat, self._kt)) return element + @classmethod + def from_xml_element(cls, elem): + """Generate Muir distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Muir + Muir distribution generated from XML element + + """ + params = elem.findtext('parameters').split() + e0 = float(params[0]) + m_rat = float(params[1]) + kt = float(params[2]) + return muir(e0, m_rat, kt) + class Tabular(Univariate): """Piecewise continuous probability distribution. @@ -561,6 +687,27 @@ class Tabular(Univariate): return element + @classmethod + def from_xml_element(cls, elem): + """Generate tabular distribution from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.stats.Tabular + Tabular distribution generated from XML element + + """ + interpolation = elem.get('interpolation') + params = [float(x) for x in elem.findtext('parameters').split()] + x = params[:len(params)//2] + p = paramx[len(params)//2:] + return cls(x, p, interpolation) + class Legendre(Univariate): r"""Probability density given by a Legendre polynomial expansion @@ -607,6 +754,10 @@ class Legendre(Univariate): def to_xml_element(self, element_name): raise NotImplementedError + @classmethod + def from_xml_element(cls, elem): + raise NotImplementedError + class Mixture(Univariate): """Probability distribution characterized by a mixture of random variables. @@ -660,3 +811,7 @@ class Mixture(Univariate): def to_xml_element(self, element_name): raise NotImplementedError + + @classmethod + def from_xml_element(cls, elem): + raise NotImplementedError From c253a0e6f8166229dbd375d730d4903ea91b025d Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Thu, 18 Apr 2019 23:01:22 -0400 Subject: [PATCH 026/151] Missing underscore --- openmc/cmfd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 26f12f8dbf..700d1c0bd1 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -717,7 +717,7 @@ class CMFDRun(object): CMFDRun class is properly initialized/finalized. For example:: from openmc import cmfd - cmfdrun = cmfd.CMFDRun() + cmfd_run = cmfd.CMFDRun() with cmfd_run.run_in_memory(): do_stuff_before_simulation_start() for _ in cmfd_run.iter_batches(): From 8c354b0e604dd79d3b9aaace72675ce489c4b3ba Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Fri, 19 Apr 2019 00:56:09 -0400 Subject: [PATCH 027/151] Missing space --- src/state_point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state_point.cpp b/src/state_point.cpp index 9d5a385dcc..485b95ca46 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -313,7 +313,7 @@ openmc_load_cmfd_tallies(const int* tally_ids, const int* sp_tally_ids) hid_t tallies_group = open_group(file_id, "tallies"); int n_tallies = 4; - for (int i =0; i < n_tallies; i++) { + for (int i = 0; i < n_tallies; i++) { std::string name = "tally " + std::to_string(sp_tally_ids[i]); hid_t tally_group = open_group(tallies_group, name.c_str()); // Get tally from tally_ids From 489a03c648bb7914b69c2813f996ac37c1f65b77 Mon Sep 17 00:00:00 2001 From: amandalund Date: Thu, 25 Apr 2019 20:07:53 -0500 Subject: [PATCH 028/151] Get value from either attribute or element when reading settings from XML --- openmc/mesh.py | 13 +- openmc/settings.py | 468 +++++++++++++++++++---------------- openmc/source.py | 19 +- openmc/stats/multivariate.py | 13 +- openmc/stats/univariate.py | 17 +- 5 files changed, 289 insertions(+), 241 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index a30b333fb3..fb59053aea 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -7,6 +7,7 @@ import numpy as np import openmc.checkvalue as cv import openmc +from openmc._xml import get_text from openmc.mixin import EqualityMixin, IDManagerMixin @@ -261,23 +262,23 @@ class Mesh(IDManagerMixin): Mesh generated from XML element """ - mesh_id = int(elem.get('id')) + mesh_id = int(get_text(elem, 'id')) mesh = cls(mesh_id) - mesh.type = elem.get('type') + mesh.type = get_text(elem, 'type') - dimension = elem.findtext('dimension') + dimension = get_text(elem, 'dimension') if dimension is not None: mesh.dimension = [int(x) for x in dimension.split()] - lower_left = elem.findtext('lower_left') + lower_left = get_text(elem, 'lower_left') if lower_left is not None: mesh.lower_left = [float(x) for x in lower_left.split()] - upper_right = elem.findtext('upper_right') + upper_right = get_text(elem, 'upper_right') if upper_right is not None: mesh.upper_right = [float(x) for x in upper_right.split()] - width = elem.findtext('width') + width = get_text(elem, 'width') if width is not None: mesh.width = [float(x) for x in width.split()] diff --git a/openmc/settings.py b/openmc/settings.py index a7ec086792..39312899b1 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -7,7 +7,7 @@ import sys import numpy as np -from openmc._xml import clean_indentation +from openmc._xml import clean_indentation, get_text import openmc.checkvalue as cv from openmc import VolumeCalculation, Source, Mesh @@ -927,6 +927,228 @@ class Settings(object): elem = ET.SubElement(root, "dagmc") elem.text = str(self._dagmc).lower() + def _eigenvalue_from_xml_element(self, root): + elem = root.find('eigenvalue') + if elem is not None: + self._run_mode_from_xml_element(elem) + self._particles_from_xml_element(elem) + self._batches_from_xml_element(elem) + self._inactive_from_xml_element(elem) + self._generations_per_batch_from_xml_element(elem) + + def _run_mode_from_xml_element(self, root): + text = get_text(root, 'run_mode') + if text is not None: + self.run_mode = text + + def _particles_from_xml_element(self, root): + text = get_text(root, 'particles') + if text is not None: + self.particles = int(text) + + def _batches_from_xml_element(self, root): + text = get_text(root, 'batches') + if text is not None: + self.batches = int(text) + + def _inactive_from_xml_element(self, root): + text = get_text(root, 'inactive') + if text is not None: + self.inactive = int(text) + + def _generations_per_batch_from_xml_element(self, root): + text = get_text(root, 'generations_per_batch') + if text is not None: + self.generations_per_batch = int(text) + + def _keff_trigger_from_xml_element(self, root): + elem = root.find('keff_trigger') + if elem is not None: + trigger = get_text(elem, 'type') + threshold = float(get_text(elem, 'threshold')) + self.keff_trigger = {'type': trigger, 'threshold': threshold} + + def _source_from_xml_element(self, root): + for elem in root.findall('source'): + self.source.append(Source.from_xml_element(elem)) + + def _output_from_xml_element(self, root): + elem = root.find('output') + if elem is not None: + self.output = {} + for key in ('summary', 'tallies', 'path'): + value = get_text(elem, key) + if value is not None: + if key in ('summary', 'tallies'): + value = value == 'true' + self.output[key] = value + + def _statepoint_from_xml_element(self, root): + elem = root.find('state_point') + if elem is not None: + text = get_text(elem, 'batches') + if text is not None: + self.statepoint['batches'] = [int(x) for x in text.split()] + + def _sourcepoint_from_xml_element(self, root): + elem = root.find('source_point') + if elem is not None: + for key in ('separate', 'write', 'overwrite', 'batches'): + value = get_text(elem, key) + if value is not None: + if key in ('separate', 'write', 'overwrite'): + value = value == 'true' + else: + value = [int(x) for x in value.split()] + self.sourcepoint[key] = value + + def _confidence_intervals_from_xml_element(self, root): + text = get_text(root, 'confidence_intervals') + if text is not None: + self.confidence_intervals = text == 'true' + + def _electron_treatment_from_xml_element(self, root): + text = get_text(root, 'electron_treatment') + if text is not None: + self.electron_treatment = text + + def _energy_mode_from_xml_element(self, root): + text = get_text(root, 'energy_mode') + if text is not None: + self.energy_mode = text + + def _max_order_from_xml_element(self, root): + text = get_text(root, 'max_order') + if text is not None: + self.max_order = int(text) + + def _photon_transport_from_xml_element(self, root): + text = get_text(root, 'photon_transport') + if text is not None: + self.photon_transport = text == 'true' + + def _ptables_from_xml_element(self, root): + text = get_text(root, 'ptables') + if text is not None: + self.ptables = text == 'true' + + def _seed_from_xml_element(self, root): + text = get_text(root, 'seed') + if text is not None: + self.seed = int(text) + + def _survival_biasing_from_xml_element(self, root): + text = get_text(root, 'survival_biasing') + if text is not None: + self.survival_biasing = text == 'true' + + def _cutoff_from_xml_element(self, root): + elem = root.find('cutoff') + if elem is not None: + self.cutoff = {} + for key in ('energy_neutron', 'energy_photon', 'energy_electron', + 'energy_positron', 'weight', 'weight_avg'): + value = get_text(elem, key) + if value is not None: + self.cutoff[key] = float(value) + + def _entropy_mesh_from_xml_element(self, root): + elem = root.find('entropy_mesh') + if elem is not None: + self.entropy_mesh = Mesh.from_xml_element(elem) + + def _trigger_from_xml_element(self, root): + elem = root.find('trigger') + if elem is not None: + self.trigger_active = get_text(elem, 'active') == 'true' + text = get_text(elem, 'max_batches') + if text is not None: + self.trigger_max_batches = int(text) + text = get_text(elem, 'batch_interval') + if text is not None: + self.trigger_batch_interval = int(text) + + def _no_reduce_from_xml_element(self, root): + text = get_text(root, 'no_reduce') + if text is not None: + self.no_reduce = text == 'true' + + def _verbosity_from_xml_element(self, root): + text = get_text(root, 'verbosity') + if text is not None: + self.verbosity = int(text) + + def _tabular_legendre_from_xml_element(self, root): + elem = root.find('tabular_legendre') + if elem is not None: + text = get_text(elem, 'enable') + self.tabular_legendre['enable'] = text == 'true' + text = get_text(elem, 'num_points') + if text is not None: + self.tabular_legendre['num_points'] = int(text) + + def _temperature_from_xml_element(self, root): + text = get_text(root, 'temperature_default') + if text is not None: + self.temperature['default'] = float(text) + text = get_text(root, 'temperature_tolerance') + if text is not None: + self.temperature['tolerance'] = float(text) + text = get_text(root, 'temperature_method') + if text is not None: + self.temperature['method'] = text + text = get_text(root, 'temperature_range') + if text is not None: + self.temperature['range'] = [float(x) for x in text.split()] + text = get_text(root, 'temperature_multipole') + if text is not None: + self.temperature['multipole'] = text == 'true' + + def _trace_from_xml_element(self, root): + text = get_text(root, 'trace') + if text is not None: + self.trace = [int(x) for x in text.split()] + + def _track_from_xml_element(self, root): + text = get_text(root, 'track') + if text is not None: + self.track = [int(x) for x in text.split()] + + def _ufs_mesh_from_xml_element(self, root): + elem = root.find('ufs_mesh') + if elem is not None: + self.ufs_mesh = Mesh.from_xml_element(elem) + + def _resonance_scattering_from_xml_element(self, root): + elem = root.find('resonance_scattering') + if elem is not None: + keys = ('enable', 'method', 'energy_min', 'energy_max', 'nuclides') + for key in keys: + value = get_text(elem, key) + if value is not None: + if key == 'enable': + value = value == 'true' + elif key in ('energy_min', 'energy_max'): + value = float(value) + elif key == 'nuclides': + value = value.split() + self.resonance_scattering[key] = value + + def _create_fission_neutrons_from_xml_element(self, root): + text = get_text(root, 'create_fission_neutrons') + if text is not None: + self.create_fission_neutrons = text == 'true' + + def _log_grid_bins_from_xml_element(self, root): + text = get_text(root, 'log_grid_bins') + if text is not None: + self.log_grid_bins = int(text) + + def _dagmc_from_xml_element(self, root): + text = get_text(root, 'dagmc') + if text is not None: + self.dagmc = text == 'true' + def export_to_xml(self, path='settings.xml'): """Export simulation settings to an XML file. @@ -1005,218 +1227,40 @@ class Settings(object): root = tree.getroot() settings = cls() - - # Get the run mode - elem = root.find('run_mode') - if elem is not None: - settings.run_mode = elem.text - - # Get number of particles - elem = root.find('particles') - if elem is not None: - settings.particles = int(elem.text) - - # Get number of batches - elem = root.find('batches') - if elem is not None: - settings.batches = int(elem.text) - - # Get number of inactive batches - elem = root.find('inactive') - if elem is not None: - settings.inactive = int(elem.text) - - # Get number of generations per batch - elem = root.find('generations_per_batch') - if elem is not None: - settings.generations_per_batch = int(elem.text) - - # Get keff trigger - elem = root.find('keff_trigger') - if elem is not None: - trigger = elem.findtext('type') - threshold = float(elem.findtext('threshold')) - settings.keff_trigger = {'type': trigger, 'threshold': threshold} - - # Get the source - for elem in root.findall('source'): - settings.source.append(Source.from_xml_element(elem)) - - # Get the output - elem = root.find('output') - if elem is not None: - settings.output = {} - for entry in elem: - key = entry.tag - if key in ('summary', 'tallies'): - value = entry.text == 'true' - else: - value = entry.text - settings.output[key] = value - - # Get the statepoint - elem = root.find('state_point') - if elem is not None: - batches = elem.findtext('batches') - if batches is not None: - settings.statepoint['batches'] = [int(x) for x in batches.split()] - - # Get the sourcepoint - elem = root.find('source_point') - if elem is not None: - for entry in elem: - key = entry.tag - if key in ('separate', 'write', 'overwrite'): - value = entry.text == 'true' - else: - value = [int(x) for x in entry.text.split()] - settings.sourcepoint[key] = value - - # Get confidence intervals - elem = root.find('confidence_intervals') - if elem is not None: - settings.confidence_intervals = elem.text == 'true' - - # Get electron treatment - elem = root.find('electron_treatment') - if elem is not None: - settings.electron_treatment = elem.text - - # Get energy mode - elem = root.find('energy_mode') - if elem is not None: - settings.energy_mode = elem.text - - # Get max order - elem = root.find('max_order') - if elem is not None: - settings.max_order = int(elem.text) - - # Get photon transport - elem = root.find('photon_transport') - if elem is not None: - settings.photon_transport = elem.text == 'true' - - # Get probability tables - elem = root.find('ptables') - if elem is not None: - settings.ptables = elem.text == 'true' - - # Get seed - elem = root.find('seed') - if elem is not None: - settings.seed = int(elem.text) - - # Get survival biasing - elem = root.find('survival_biasing') - if elem is not None: - settings.survival_biasing = elem.text == 'true' - - # Get cutoff - elem = root.find('cutoff') - if elem is not None: - settings.cutoff = {x.tag: float(x.text) for x in elem} - - # Get entropy mesh - elem = root.find('entropy_mesh') - if elem is not None: - settings.entropy_mesh = Mesh.from_xml_element(elem) - - # Get trigger - elem = root.find('trigger') - if elem is not None: - active = elem.find('active') - settings.trigger_active = active.text == 'true' - max_batches = elem.find('max_batches') - if max_batches is not None: - settings.trigger_max_batches = int(max_batches.text) - batch_interval = elem.find('batch_interval') - if batch_interval is not None: - settings.trigger_batch_interval = int(batch_interval.text) - - # Get no reduce - elem = root.find('no_reduce') - if elem is not None: - settings.no_reduce = elem.text == 'true' - - # Get verbosity - elem = root.find('verbosity') - if elem is not None: - settings.verbosity = int(elem.text) - - # Get tabular legendre - elem = root.find('tabular_legendre') - if elem is not None: - enable = elem.findtext('eneable') - settings.tabular_legendre['enable'] = enable == 'true' - num_points = elem.findtext('num_points') - if num_points is not None: - settings.tabular_legendre['num_points'] = int(num_points) - - # Get temperature - elem = root.findtext('temperature_default') - if elem is not None: - settings.temperature['default'] = float(elem) - elem = root.findtext('temperature_tolerance') - if elem is not None: - settings.temperature['tolerance'] = float(elem) - elem = root.findtext('temperature_method') - if elem is not None: - settings.temperature['method'] = elem - elem = root.findtext('temperature_range') - if elem is not None: - settings.temperature['range'] = [float(x) for x in elem.split()] - elem = root.findtext('temperature_multipole') - if elem is not None: - settings.temperature['multipole'] = elem == 'true' - - # Get trace - elem = root.find('trace') - if elem is not None: - settings.trace = [int(x) for x in elem.text.split()] - - # Get track - elem = root.find('track') - if elem is not None: - settings.track = [int(x) for x in elem.text.split()] - - # Get UFS mesh - elem = root.find('ufs_mesh') - if elem is not None: - settings.ufs_mesh = Mesh.from_xml_element(elem) - - # Get resonance scattering - elem = root.find('resonance_scattering') - if elem is not None: - for entry in elem: - key = entry.tag - if key == 'enable': - value = entry.text == 'true' - elif key == 'method': - value = entry.text - elif key == 'energy_min': - value = float(entry.text) - elif key == 'energy_max': - value = float(entry.text) - elif key == 'nuclides': - value = entry.text.split() - settings.resonance_scattering[key] = value + settings._eigenvalue_from_xml_element(root) + settings._run_mode_from_xml_element(root) + settings._particles_from_xml_element(root) + settings._batches_from_xml_element(root) + settings._inactive_from_xml_element(root) + settings._generations_per_batch_from_xml_element(root) + settings._keff_trigger_from_xml_element(root) + settings._source_from_xml_element(root) + settings._output_from_xml_element(root) + settings._statepoint_from_xml_element(root) + settings._sourcepoint_from_xml_element(root) + settings._confidence_intervals_from_xml_element(root) + settings._electron_treatment_from_xml_element(root) + settings._energy_mode_from_xml_element(root) + settings._max_order_from_xml_element(root) + settings._photon_transport_from_xml_element(root) + settings._ptables_from_xml_element(root) + settings._seed_from_xml_element(root) + settings._survival_biasing_from_xml_element(root) + settings._cutoff_from_xml_element(root) + settings._entropy_mesh_from_xml_element(root) + settings._trigger_from_xml_element(root) + settings._no_reduce_from_xml_element(root) + settings._verbosity_from_xml_element(root) + settings._tabular_legendre_from_xml_element(root) + settings._temperature_from_xml_element(root) + settings._trace_from_xml_element(root) + settings._track_from_xml_element(root) + settings._ufs_mesh_from_xml_element(root) + settings._resonance_scattering_from_xml_element(root) + settings._create_fission_neutrons_from_xml_element(root) + settings._log_grid_bins_from_xml_element(root) + settings._dagmc_from_xml_element(root) # TODO: Get volume calculations - # Get fission neutrons - elem = root.find('create_fission_neutrons') - if elem is not None: - settings.create_fission_neutrons = elem.text == 'true' - - # Get log grid bins - elem = root.find('log_grid_bins') - if elem is not None: - settings.log_grid_bins = int(elem.text) - - # Get dagmc - elem = root.find('dagmc') - if elem is not None: - settings.dagmc = elem.text == 'true' - return settings diff --git a/openmc/source.py b/openmc/source.py index e278d80892..5bafaf6984 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -2,6 +2,7 @@ from numbers import Real import sys from xml.etree import ElementTree as ET +from openmc._xml import get_text from openmc.stats.univariate import (Univariate, Discrete, Uniform, Maxwell, Watt, Normal, Muir, Tabular) from openmc.stats.multivariate import (UnitSphere, Spatial, PolarAzimuthal, @@ -158,21 +159,21 @@ class Source(object): """ source = cls() - strength = elem.find('strength') + strength = get_text(elem, 'strength') if strength is not None: - source.strength = float(strength.text) + source.strength = float(strength) - particle = elem.find('particle') + particle = get_text(elem, 'particle') if particle is not None: - source.particle = particle.text + source.particle = particle - filename = elem.find('file') + filename = get_text(elem, 'file') if filename is not None: - source.file = filename.text + source.file = filename space = elem.find('space') if space is not None: - space_type = space.get('type') + space_type = get_text(space, 'type') if space_type == 'cartesian': source.space = CartesianIndependent.from_xml_element(space) elif space_type == 'box' or space_type == 'fission': @@ -182,7 +183,7 @@ class Source(object): angle = elem.find('angle') if angle is not None: - angle_type = angle.get('type') + angle_type = get_text(angle, 'type') if angle_type == 'mu-phi': source.angle = PolarAzimuthal.from_xml_element(angle) elif angle_type == 'isotropic': @@ -192,7 +193,7 @@ class Source(object): energy = elem.find('energy') if energy is not None: - energy_type = energy.get('type') + energy_type = get_text(energy, 'type') if energy_type == 'discrete': source.energy = Discrete.from_xml_element(energy) elif energy_type == 'uniform': diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index 61122ada21..5a836ab63b 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -8,6 +8,7 @@ from xml.etree import ElementTree as ET import numpy as np import openmc.checkvalue as cv +from openmc._xml import get_text from openmc.stats.univariate import Univariate, Uniform @@ -142,7 +143,7 @@ class PolarAzimuthal(UnitSphere): """ mu_phi = cls() - params = elem.findtext('parameters') + params = get_text(elem, 'parameters') if params is not None: mu_phi.reference_uvw = [float(x) for x in params.split()] mu_phi.mu = openmc.stats.Univariate.from_xml_element(elem.find('mu')) @@ -239,7 +240,7 @@ class Monodirectional(UnitSphere): """ monodirectional = cls() - params = elem.findtext('parameters') + params = get_text(elem, 'parameters') if params is not None: monodirectional.reference_uvw = [float(x) for x in params.split()] return monodirectional @@ -457,10 +458,10 @@ class Box(Spatial): Box distribution generated from XML element """ - only_fissionable = elem.get('type') == 'fission' - params = [float(x) for x in elem.findtext('parameters').split()] + only_fissionable = get_text(elem, 'type') == 'fission' + params = [float(x) for x in get_text(elem, 'parameters').split()] lower_left = params[:len(params)//2] - upper_right = paramx[len(params)//2:] + upper_right = params[len(params)//2:] return cls(lower_left, upper_right, only_fissionable) @@ -526,5 +527,5 @@ class Point(Spatial): Point distribution generated from XML element """ - xyz = [float(x) for x in elem.findtext('parameters').split()] + xyz = [float(x) for x in get_text(elem, 'parameters').split()] return cls(xyz) diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index 6a83cc780e..3088f8c779 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -7,6 +7,7 @@ from xml.etree import ElementTree as ET import numpy as np import openmc.checkvalue as cv +from openmc._xml import get_text from openmc.mixin import EqualityMixin @@ -130,7 +131,7 @@ class Discrete(Univariate): Discrete distribution generated from XML element """ - params = [float(x) for x in elem.findtext('parameters').split()] + params = [float(x) for x in get_text(elem, 'parameters').split()] x = params[:len(params)//2] p = params[len(params)//2:] return cls(x, p) @@ -221,7 +222,7 @@ class Uniform(Univariate): Uniform distribution generated from XML element """ - params = elem.findtext('parameters').split() + params = get_text(elem, 'parameters').split() a = float(params[0]) b = float(params[1]) return cls(a, b) @@ -297,7 +298,7 @@ class Maxwell(Univariate): Maxwellian distribution generated from XML element """ - theta = float(elem.findtext('parameters')) + theta = float(get_text(elem, 'parameters')) return cls(theta) @@ -386,7 +387,7 @@ class Watt(Univariate): Watt distribution generated from XML element """ - params = elem.findtext('parameters').split() + params = get_text(elem, 'parameters').split() a = float(params[0]) b = float(params[1]) return watt(a, b) @@ -476,7 +477,7 @@ class Normal(Univariate): Normal distribution generated from XML element """ - params = elem.findtext('parameters').split() + params = get_text(elem, 'parameters').split() mean_value = float(params[0]) std_dev = float(params[1]) return cls(mean_value, std_dev) @@ -585,7 +586,7 @@ class Muir(Univariate): Muir distribution generated from XML element """ - params = elem.findtext('parameters').split() + params = get_text(elem, 'parameters').split() e0 = float(params[0]) m_rat = float(params[1]) kt = float(params[2]) @@ -702,8 +703,8 @@ class Tabular(Univariate): Tabular distribution generated from XML element """ - interpolation = elem.get('interpolation') - params = [float(x) for x in elem.findtext('parameters').split()] + interpolation = get_text(elem, 'interpolation') + params = [float(x) for x in get_text(elem, 'parameters').split()] x = params[:len(params)//2] p = paramx[len(params)//2:] return cls(x, p, interpolation) From c0ede1ec8dbd930d6c16bd8c0b72a9b722566f6d Mon Sep 17 00:00:00 2001 From: amandalund Date: Tue, 30 Apr 2019 11:39:17 -0400 Subject: [PATCH 029/151] Add unit tests for settings.from_xml() and a few fixes --- openmc/settings.py | 26 +++++++++----- tests/unit_tests/test_settings.py | 58 +++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/openmc/settings.py b/openmc/settings.py index 39312899b1..802046c4e2 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -174,7 +174,6 @@ class Settings(object): self._source = cv.CheckedList(Source, 'source distributions') self._confidence_intervals = None - self._cross_sections = None self._electron_treatment = None self._photon_transport = None self._ptables = None @@ -993,11 +992,14 @@ class Settings(object): def _sourcepoint_from_xml_element(self, root): elem = root.find('source_point') if elem is not None: - for key in ('separate', 'write', 'overwrite', 'batches'): + for key in ('separate', 'write', 'overwrite_latest', 'batches'): value = get_text(elem, key) if value is not None: - if key in ('separate', 'write', 'overwrite'): + if key in ('separate', 'write'): value = value == 'true' + elif key == 'overwrite_latest': + value = value == 'true' + key = 'overwrite' else: value = [int(x) for x in value.split()] self.sourcepoint[key] = value @@ -1053,9 +1055,12 @@ class Settings(object): self.cutoff[key] = float(value) def _entropy_mesh_from_xml_element(self, root): - elem = root.find('entropy_mesh') - if elem is not None: - self.entropy_mesh = Mesh.from_xml_element(elem) + text = get_text(root, 'entropy_mesh') + if text is not None: + path = "./mesh[@id='{}']".format(int(text)) + elem = root.find(path) + if elem is not None: + self.entropy_mesh = Mesh.from_xml_element(elem) def _trigger_from_xml_element(self, root): elem = root.find('trigger') @@ -1115,9 +1120,12 @@ class Settings(object): self.track = [int(x) for x in text.split()] def _ufs_mesh_from_xml_element(self, root): - elem = root.find('ufs_mesh') - if elem is not None: - self.ufs_mesh = Mesh.from_xml_element(elem) + text = get_text(root, 'ufs_mesh') + if text is not None: + path = "./mesh[@id='{}']".format(int(text)) + elem = root.find(path) + if elem is not None: + self.ufs_mesh = Mesh.from_xml_element(elem) def _resonance_scattering_from_xml_element(self, root): elem = root.find('resonance_scattering') diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index e42f6240f5..f99a32a52c 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -19,11 +19,12 @@ def test_export_to_xml(run_in_tmpdir): 'write': True, 'overwrite': True} s.statepoint = {'batches': [50, 150, 500, 1000]} s.confidence_intervals = True - s.cross_sections = '/path/to/cross_sections.xml' s.ptables = True s.seed = 17 s.survival_biasing = True - s.cutoff = {'weight': 0.25, 'weight_avg': 0.5, 'energy': 1.0e-5} + s.cutoff = {'weight': 0.25, 'weight_avg': 0.5, 'energy_neutron': 1.0e-5, + 'energy_photon': 1000.0, 'energy_electron': 1.0e-5, + 'energy_positron': 1.0e-5} mesh = openmc.Mesh() mesh.lower_left = (-10., -10., -10.) mesh.upper_right = (10., 10., 10.) @@ -47,6 +48,59 @@ def test_export_to_xml(run_in_tmpdir): upper_right = (10., 10., 10.)) s.create_fission_neutrons = True s.log_grid_bins = 2000 + s.photon_transport = False + s.electron_treatment = 'led' + s.dagmc = False # Make sure exporting XML works s.export_to_xml() + + # Generate settings from XML + s = openmc.Settings.from_xml() + assert s.run_mode == 'fixed source' + assert s.batches == 1000 + assert s.generations_per_batch == 10 + assert s.inactive == 100 + assert s.particles == 1000000 + assert s.keff_trigger == {'type': 'std_dev', 'threshold': 0.001} + assert s.energy_mode == 'continuous-energy' + assert s.max_order == 5 + assert isinstance(s.source[0], openmc.Source) + assert isinstance(s.source[0].space, openmc.stats.Point) + assert s.output == {'summary': True, 'tallies': False, 'path': 'here'} + assert s.verbosity == 7 + assert s.sourcepoint == {'batches': [50, 150, 500, 1000], 'separate': True, + 'write': True, 'overwrite': True} + assert s.statepoint == {'batches': [50, 150, 500, 1000]} + assert s.confidence_intervals + assert s.ptables + assert s.seed == 17 + assert s.survival_biasing + assert s.cutoff == {'weight': 0.25, 'weight_avg': 0.5, + 'energy_neutron': 1.0e-5, 'energy_photon': 1000.0, + 'energy_electron': 1.0e-5, 'energy_positron': 1.0e-5} + assert isinstance(s.entropy_mesh, openmc.Mesh) + assert s.entropy_mesh.lower_left == [-10., -10., -10.] + assert s.entropy_mesh.upper_right == [10., 10., 10.] + assert s.entropy_mesh.dimension == [5, 5, 5] + assert s.trigger_active + assert s.trigger_max_batches == 10000 + assert s.trigger_batch_interval == 50 + assert not s.no_reduce + assert s.tabular_legendre == {'enable': True, 'num_points': 50} + assert s.temperature == {'default': 293.6, 'method': 'interpolation', + 'multipole': True, 'range': [200., 1000.]} + assert s.trace == [10, 1, 20] + assert s.track == [1, 1, 1, 2, 1, 1] + assert isinstance(s.ufs_mesh, openmc.Mesh) + assert s.ufs_mesh.lower_left == [-10., -10., -10.] + assert s.ufs_mesh.upper_right == [10., 10., 10.] + assert s.ufs_mesh.dimension == [5, 5, 5] + assert s.resonance_scattering == {'enable': True, 'method': 'rvs', + 'energy_min': 1.0, 'energy_max': 1000.0, + 'nuclides': ['U235', 'U238', 'Pu239']} + assert s.create_fission_neutrons + assert s.log_grid_bins == 2000 + assert not s.photon_transport + assert s.electron_treatment == 'led' + assert not s.dagmc From 81674a1c2d5ee2e1c97d02dd1c84e5b936b4ca1a Mon Sep 17 00:00:00 2001 From: amandalund Date: Wed, 1 May 2019 12:20:46 -0400 Subject: [PATCH 030/151] A couple more fixes for mesh to/from XML --- openmc/mesh.py | 10 +++++++--- openmc/settings.py | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index fb59053aea..2489d5faa7 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -231,8 +231,9 @@ class Mesh(IDManagerMixin): element.set("id", str(self._id)) element.set("type", self._type) - subelement = ET.SubElement(element, "dimension") - subelement.text = ' '.join(map(str, self._dimension)) + if self._dimension is not None: + subelement = ET.SubElement(element, "dimension") + subelement.text = ' '.join(map(str, self._dimension)) subelement = ET.SubElement(element, "lower_left") subelement.text = ' '.join(map(str, self._lower_left)) @@ -264,7 +265,10 @@ class Mesh(IDManagerMixin): """ mesh_id = int(get_text(elem, 'id')) mesh = cls(mesh_id) - mesh.type = get_text(elem, 'type') + + mesh_type = get_text(elem, 'type') + if mesh_type is not None: + mesh.type = mesh_type dimension = get_text(elem, 'dimension') if dimension is not None: diff --git a/openmc/settings.py b/openmc/settings.py index 802046c4e2..0636775755 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -551,7 +551,8 @@ class Settings(object): @entropy_mesh.setter def entropy_mesh(self, entropy): cv.check_type('entropy mesh', entropy, Mesh) - cv.check_length('entropy mesh dimension', entropy.dimension, 3) + if entropy.dimension: + cv.check_length('entropy mesh dimension', entropy.dimension, 3) cv.check_length('entropy mesh lower-left corner', entropy.lower_left, 3) cv.check_length('entropy mesh upper-right corner', entropy.upper_right, 3) self._entropy_mesh = entropy From 5fbae7cab47f68fd8a1686a8181e336ef22e4033 Mon Sep 17 00:00:00 2001 From: lavistam <20384517+matiaslavista@users.noreply.github.com> Date: Sun, 19 May 2019 21:44:49 -0500 Subject: [PATCH 031/151] Moving special case of keff calculation (n<=3) to cpp side --- openmc/capi/core.py | 15 +++------------ src/eigenvalue.cpp | 12 +++++++++--- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/openmc/capi/core.py b/openmc/capi/core.py index ad4f77dfff..baba0b833f 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -230,18 +230,9 @@ def keff(): Mean k-eigenvalue and standard deviation of the mean """ - n = openmc.capi.num_realizations() - if n > 3: - # Use the combined estimator if there are enough realizations - k = (c_double*2)() - _dll.openmc_get_keff(k) - return tuple(k) - else: - # Otherwise, return the tracklength estimator - mean = c_double.in_dll(_dll, 'keff').value - std_dev = c_double.in_dll(_dll, 'keff_std').value \ - if n > 1 else np.inf - return (mean, std_dev) + k = (c_double*2)() + _dll.openmc_get_keff(k) + return tuple(k) def master(): diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index 08a4d3f91c..0d6aa19b38 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -29,6 +29,7 @@ #include // for sqrt, abs, pow #include // for back_inserter #include +#include //for infinity namespace openmc { @@ -388,10 +389,15 @@ int openmc_get_keff(double* k_combined) k_combined[0] = 0.0; k_combined[1] = 0.0; - // Make sure we have at least four realizations. Notice that at the end, - // there is a N-3 term in a denominator. + //Special case for n <=3. Notice that at the end, + //there is a N-3 term in a denominator. if (simulation::n_realizations <= 3) { - return -1; + k_combined[0] = simulation::keff; + k_combined[1] = simulation::keff_std; + if (simulation::n_realizations <=1){ + k_combined[1] = std::numeric_limits::infinity(); + } + return 0; } // Initialize variables From e15dfb27ede96775ab384a423cc801154ff4f361 Mon Sep 17 00:00:00 2001 From: guillaume Date: Mon, 20 May 2019 14:26:15 -0400 Subject: [PATCH 032/151] Add coverage of cpp source files using cpp-coveralls --- .travis.yml | 3 ++- CMakeLists.txt | 4 ++++ tools/ci/travis-install.py | 3 +++ tools/ci/travis-install.sh | 5 ++++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ade182788d..9e51a70eb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,4 +53,5 @@ before_script: script: - ./tools/ci/travis-script.sh after_success: - - coveralls + - cpp-coveralls --exclude-pattern "/usr/*" --dump cpp_cov.json + - coveralls --merge=cpp_cov.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 4033d2466d..fea014f12d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,10 @@ if(optimize) list(REMOVE_ITEM cxxflags -O2) list(APPEND cxxflags -O3) endif() +if(coverage) + list(APPEND cxxflags --coverage) + list(APPEND ldflags --coverage) +endif() # Show flags being used message(STATUS "OpenMC C++ flags: ${cxxflags}") diff --git a/tools/ci/travis-install.py b/tools/ci/travis-install.py index 9c12387fed..9a9b06dae1 100644 --- a/tools/ci/travis-install.py +++ b/tools/ci/travis-install.py @@ -48,6 +48,9 @@ def install(omp=False, mpi=False, phdf5=False, dagmc=False): if dagmc: cmake_cmd.append('-Ddagmc=ON') + # Build in coverage mode for coverage testing + cmake_cmd.append('-Dcoverage=on') + # Build and install cmake_cmd.append('..') print(' '.join(cmake_cmd)) diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 2bca50e4b4..4e32f31cc0 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -25,5 +25,8 @@ python tools/ci/travis-install.py # Install Python API in editable mode pip install -e .[test,vtk] -# For uploading to coveralls +# For coverage testing of the C++ source files +pip install cpp-coveralls + +# For coverage testing of the Python source files pip install coveralls From 14a95b63731a15ed3ce47a8ad7c63c0fdb576fa4 Mon Sep 17 00:00:00 2001 From: lavistam <20384517+matiaslavista@users.noreply.github.com> Date: Tue, 21 May 2019 13:14:50 -0500 Subject: [PATCH 033/151] Change in Calculating K-Eff from cpp side for cases of batches (n) <= 3 has changed. Therefore filter_distribcell, which ran batches of 1,1,3 and 1, has been updated to reflect the more accurate calcultion. sourcepoint_batches explanation: This test needed to be updated because statepoint files are written for 3 or fewer batches, which will change the k-eff result written in write_eigenvalue_hdf5 for those statepoint files. All other k-effective values are unaffected and the final result of the simulation is the same. --- .../regression_tests/filter_distribcell/case-1/results_true.dat | 2 +- .../regression_tests/filter_distribcell/case-2/results_true.dat | 2 +- .../regression_tests/filter_distribcell/case-3/results_true.dat | 2 +- .../regression_tests/filter_distribcell/case-4/results_true.dat | 2 +- tests/regression_tests/sourcepoint_batch/results_true.dat | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/regression_tests/filter_distribcell/case-1/results_true.dat b/tests/regression_tests/filter_distribcell/case-1/results_true.dat index 2d9835b1ab..80036a7c47 100644 --- a/tests/regression_tests/filter_distribcell/case-1/results_true.dat +++ b/tests/regression_tests/filter_distribcell/case-1/results_true.dat @@ -1,5 +1,5 @@ k-combined: -0.000000E+00 0.000000E+00 +5.497140E-02 INF tally 1: 1.548980E-02 2.399339E-04 diff --git a/tests/regression_tests/filter_distribcell/case-2/results_true.dat b/tests/regression_tests/filter_distribcell/case-2/results_true.dat index 57e40f4962..bbf38b3d1c 100644 --- a/tests/regression_tests/filter_distribcell/case-2/results_true.dat +++ b/tests/regression_tests/filter_distribcell/case-2/results_true.dat @@ -1,5 +1,5 @@ k-combined: -0.000000E+00 0.000000E+00 +2.149726E-02 INF tally 1: 7.588170E-03 5.758032E-05 diff --git a/tests/regression_tests/filter_distribcell/case-3/results_true.dat b/tests/regression_tests/filter_distribcell/case-3/results_true.dat index 0a4dd9dc76..be197b5e9f 100644 --- a/tests/regression_tests/filter_distribcell/case-3/results_true.dat +++ b/tests/regression_tests/filter_distribcell/case-3/results_true.dat @@ -1 +1 @@ -11755ecac8355b5e79384f5d72974e618b6f95500c0a5718c01bb340c5d1c8ceafc33de65b9e94b7e9d78f16ec209f8a0fdf6a531eab5430657688d0125db7ef \ No newline at end of file +2ee0162762999f71ad2178936509fd9e054928023a9e0e90c078cede3ecf6583267069486adf15f1b02188333d1bfe1fc41b341bc00aab53feddd1395441f1f8 \ No newline at end of file diff --git a/tests/regression_tests/filter_distribcell/case-4/results_true.dat b/tests/regression_tests/filter_distribcell/case-4/results_true.dat index 078e74bed1..43a837e983 100644 --- a/tests/regression_tests/filter_distribcell/case-4/results_true.dat +++ b/tests/regression_tests/filter_distribcell/case-4/results_true.dat @@ -1,5 +1,5 @@ k-combined: -0.000000E+00 0.000000E+00 +1.121246E-01 INF tally 1: 2.265319E-02 5.131668E-04 diff --git a/tests/regression_tests/sourcepoint_batch/results_true.dat b/tests/regression_tests/sourcepoint_batch/results_true.dat index 8b8dd98f2b..a9843df530 100644 --- a/tests/regression_tests/sourcepoint_batch/results_true.dat +++ b/tests/regression_tests/sourcepoint_batch/results_true.dat @@ -1,3 +1,3 @@ k-combined: -0.000000E+00 0.000000E+00 +2.976389E-01 3.770725E-03 1.892327E+00 -3.385257E+00 6.702632E-01 From 8ce7a63a045a69908920a33d91df981ccc03c3c8 Mon Sep 17 00:00:00 2001 From: Giud Date: Tue, 21 May 2019 23:05:23 -0400 Subject: [PATCH 034/151] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e51a70eb1..d0b3de9640 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,5 +53,5 @@ before_script: script: - ./tools/ci/travis-script.sh after_success: - - cpp-coveralls --exclude-pattern "/usr/*" --dump cpp_cov.json + - cpp-coveralls --exclude-pattern "/usr/*" -e "build" --dump cpp_cov.json - coveralls --merge=cpp_cov.json From 55b9db9c446b7082bb11ad8041b10700fc7bc11d Mon Sep 17 00:00:00 2001 From: Giud Date: Wed, 22 May 2019 12:27:46 -0400 Subject: [PATCH 035/151] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d0b3de9640..096459b00a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,5 +53,5 @@ before_script: script: - ./tools/ci/travis-script.sh after_success: - - cpp-coveralls --exclude-pattern "/usr/*" -e "build" --dump cpp_cov.json + - cpp-coveralls -i src -i include --exclude-pattern "/usr/*" --dump cpp_cov.json - coveralls --merge=cpp_cov.json From 8fd5d76df38bcb232c9789af1a225f6af76162ee Mon Sep 17 00:00:00 2001 From: lavistam <20384517+matiaslavista@users.noreply.github.com> Date: Wed, 22 May 2019 15:17:46 -0500 Subject: [PATCH 036/151] Style --- src/eigenvalue.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index 0d6aa19b38..04fdabab1b 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -389,12 +389,12 @@ int openmc_get_keff(double* k_combined) k_combined[0] = 0.0; k_combined[1] = 0.0; - //Special case for n <=3. Notice that at the end, - //there is a N-3 term in a denominator. + // Special case for n <=3. Notice that at the end, + // there is a N-3 term in a denominator. if (simulation::n_realizations <= 3) { k_combined[0] = simulation::keff; k_combined[1] = simulation::keff_std; - if (simulation::n_realizations <=1){ + if (simulation::n_realizations <=1) { k_combined[1] = std::numeric_limits::infinity(); } return 0; From 2f09b7ca854ba9a4370687f2bd0521a174c2407f Mon Sep 17 00:00:00 2001 From: amandalund Date: Wed, 22 May 2019 19:26:15 -0600 Subject: [PATCH 037/151] Fixed some univariate/multivariate from_xml_element issues and updated unit tests --- openmc/settings.py | 28 ++++++------ openmc/source.py | 39 +++------------- openmc/stats/multivariate.py | 26 ++++++++--- openmc/stats/univariate.py | 39 +++++++++------- tests/unit_tests/test_source.py | 8 +++- tests/unit_tests/test_stats.py | 80 +++++++++++++++++++++++++-------- 6 files changed, 130 insertions(+), 90 deletions(-) diff --git a/openmc/settings.py b/openmc/settings.py index 0636775755..4fe87d908c 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -980,7 +980,7 @@ class Settings(object): value = get_text(elem, key) if value is not None: if key in ('summary', 'tallies'): - value = value == 'true' + value = value in ('true', '1') self.output[key] = value def _statepoint_from_xml_element(self, root): @@ -997,9 +997,9 @@ class Settings(object): value = get_text(elem, key) if value is not None: if key in ('separate', 'write'): - value = value == 'true' + value = value in ('true', '1') elif key == 'overwrite_latest': - value = value == 'true' + value = value in ('true', '1') key = 'overwrite' else: value = [int(x) for x in value.split()] @@ -1008,7 +1008,7 @@ class Settings(object): def _confidence_intervals_from_xml_element(self, root): text = get_text(root, 'confidence_intervals') if text is not None: - self.confidence_intervals = text == 'true' + self.confidence_intervals = text in ('true', '1') def _electron_treatment_from_xml_element(self, root): text = get_text(root, 'electron_treatment') @@ -1028,12 +1028,12 @@ class Settings(object): def _photon_transport_from_xml_element(self, root): text = get_text(root, 'photon_transport') if text is not None: - self.photon_transport = text == 'true' + self.photon_transport = text in ('true', '1') def _ptables_from_xml_element(self, root): text = get_text(root, 'ptables') if text is not None: - self.ptables = text == 'true' + self.ptables = text in ('true', '1') def _seed_from_xml_element(self, root): text = get_text(root, 'seed') @@ -1043,7 +1043,7 @@ class Settings(object): def _survival_biasing_from_xml_element(self, root): text = get_text(root, 'survival_biasing') if text is not None: - self.survival_biasing = text == 'true' + self.survival_biasing = text in ('true', '1') def _cutoff_from_xml_element(self, root): elem = root.find('cutoff') @@ -1066,7 +1066,7 @@ class Settings(object): def _trigger_from_xml_element(self, root): elem = root.find('trigger') if elem is not None: - self.trigger_active = get_text(elem, 'active') == 'true' + self.trigger_active = get_text(elem, 'active') in ('true', '1') text = get_text(elem, 'max_batches') if text is not None: self.trigger_max_batches = int(text) @@ -1077,7 +1077,7 @@ class Settings(object): def _no_reduce_from_xml_element(self, root): text = get_text(root, 'no_reduce') if text is not None: - self.no_reduce = text == 'true' + self.no_reduce = text in ('true', '1') def _verbosity_from_xml_element(self, root): text = get_text(root, 'verbosity') @@ -1088,7 +1088,7 @@ class Settings(object): elem = root.find('tabular_legendre') if elem is not None: text = get_text(elem, 'enable') - self.tabular_legendre['enable'] = text == 'true' + self.tabular_legendre['enable'] = text in ('true', '1') text = get_text(elem, 'num_points') if text is not None: self.tabular_legendre['num_points'] = int(text) @@ -1108,7 +1108,7 @@ class Settings(object): self.temperature['range'] = [float(x) for x in text.split()] text = get_text(root, 'temperature_multipole') if text is not None: - self.temperature['multipole'] = text == 'true' + self.temperature['multipole'] = text in ('true', '1') def _trace_from_xml_element(self, root): text = get_text(root, 'trace') @@ -1136,7 +1136,7 @@ class Settings(object): value = get_text(elem, key) if value is not None: if key == 'enable': - value = value == 'true' + value = value in ('true', '1') elif key in ('energy_min', 'energy_max'): value = float(value) elif key == 'nuclides': @@ -1146,7 +1146,7 @@ class Settings(object): def _create_fission_neutrons_from_xml_element(self, root): text = get_text(root, 'create_fission_neutrons') if text is not None: - self.create_fission_neutrons = text == 'true' + self.create_fission_neutrons = text in ('true', '1') def _log_grid_bins_from_xml_element(self, root): text = get_text(root, 'log_grid_bins') @@ -1156,7 +1156,7 @@ class Settings(object): def _dagmc_from_xml_element(self, root): text = get_text(root, 'dagmc') if text is not None: - self.dagmc = text == 'true' + self.dagmc = text in ('true', '1') def export_to_xml(self, path='settings.xml'): """Export simulation settings to an XML file. diff --git a/openmc/source.py b/openmc/source.py index 5bafaf6984..88c2f86119 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -3,11 +3,8 @@ import sys from xml.etree import ElementTree as ET from openmc._xml import get_text -from openmc.stats.univariate import (Univariate, Discrete, Uniform, Maxwell, - Watt, Normal, Muir, Tabular) -from openmc.stats.multivariate import (UnitSphere, Spatial, PolarAzimuthal, - Isotropic, Monodirectional, Box, Point, - CartesianIndependent) +from openmc.stats.univariate import Univariate +from openmc.stats.multivariate import UnitSphere, Spatial import openmc.checkvalue as cv @@ -173,40 +170,14 @@ class Source(object): space = elem.find('space') if space is not None: - space_type = get_text(space, 'type') - if space_type == 'cartesian': - source.space = CartesianIndependent.from_xml_element(space) - elif space_type == 'box' or space_type == 'fission': - source.space = Box.from_xml_element(space) - elif space_type == 'point': - source.space = Point.from_xml_element(space) + source.space = Spatial.from_xml_element(space) angle = elem.find('angle') if angle is not None: - angle_type = get_text(angle, 'type') - if angle_type == 'mu-phi': - source.angle = PolarAzimuthal.from_xml_element(angle) - elif angle_type == 'isotropic': - source.angle = Isotropic.from_xml_element(angle) - elif angle_type == 'monodirectional': - source.angle = Monodirectional.from_xml_element(angle) + source.angle = UnitSphere.from_xml_element(angle) energy = elem.find('energy') if energy is not None: - energy_type = get_text(energy, 'type') - if energy_type == 'discrete': - source.energy = Discrete.from_xml_element(energy) - elif energy_type == 'uniform': - source.energy = Uniform.from_xml_element(energy) - elif energy_type == 'maxwell': - source.energy = Maxwell.from_xml_element(energy) - elif energy_type == 'watt': - source.energy = Watt.from_xml_element(energy) - elif energy_type == 'normal': - source.energy = Normal.from_xml_element(energy) - elif energy_type == 'muir': - source.energy = Muir.from_xml_element(energy) - elif energy_type == 'tabular': - source.energy = Tabular.from_xml_element(energy) + source.energy = Univariate.from_xml_element(energy) return source diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index 5a836ab63b..35afc21dd1 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -51,7 +51,13 @@ class UnitSphere(metaclass=ABCMeta): @classmethod @abstractmethod def from_xml_element(cls, elem): - pass + distribution = get_text(elem, 'type') + if distribution == 'mu-phi': + return PolarAzimuthal.from_xml_element(elem) + elif distribution == 'isotropic': + return Isotropic.from_xml_element(elem) + elif distribution == 'monodirectional': + return Monodirectional.from_xml_element(elem) class PolarAzimuthal(UnitSphere): @@ -146,8 +152,8 @@ class PolarAzimuthal(UnitSphere): params = get_text(elem, 'parameters') if params is not None: mu_phi.reference_uvw = [float(x) for x in params.split()] - mu_phi.mu = openmc.stats.Univariate.from_xml_element(elem.find('mu')) - mu_phi.phi = openmc.stats.Univariate.from_xml_element(elem.find('phi')) + mu_phi.mu = Univariate.from_xml_element(elem.find('mu')) + mu_phi.phi = Univariate.from_xml_element(elem.find('phi')) return mu_phi @@ -263,7 +269,13 @@ class Spatial(metaclass=ABCMeta): @classmethod @abstractmethod def from_xml_element(cls, elem): - pass + distribution = get_text(elem, 'type') + if distribution == 'cartesian': + return CartesianIndependent.from_xml_element(elem) + elif distribution == 'box' or distribution == 'fission': + return Box.from_xml_element(elem) + elif distribution == 'point': + return Point.from_xml_element(elem) class CartesianIndependent(Spatial): @@ -357,9 +369,9 @@ class CartesianIndependent(Spatial): Spatial distribution generated from XML element """ - x = openmc.stats.Univariate.from_xml_element(elem.find('x')) - y = openmc.stats.Univariate.from_xml_element(elem.find('y')) - z = openmc.stats.Univariate.from_xml_element(elem.find('z')) + x = Univariate.from_xml_element(elem.find('x')) + y = Univariate.from_xml_element(elem.find('y')) + z = Univariate.from_xml_element(elem.find('z')) return cls(x, y, z) diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index 3088f8c779..363dd2ee35 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -36,7 +36,25 @@ class Univariate(EqualityMixin, metaclass=ABCMeta): @classmethod @abstractmethod def from_xml_element(cls, elem): - pass + distribution = get_text(elem, 'type') + if distribution == 'discrete': + return Discrete.from_xml_element(elem) + elif distribution == 'uniform': + return Uniform.from_xml_element(elem) + elif distribution == 'maxwell': + return Maxwell.from_xml_element(elem) + elif distribution == 'watt': + return Watt.from_xml_element(elem) + elif distribution == 'normal': + return Normal.from_xml_element(elem) + elif distribution == 'muir': + return Muir.from_xml_element(elem) + elif distribution == 'tabular': + return Tabular.from_xml_element(elem) + elif distribution == 'legendre': + return Legendre.from_xml_element(elem) + elif distribution == 'mixture': + return Mixture.from_xml_element(elem) class Discrete(Univariate): @@ -223,9 +241,7 @@ class Uniform(Univariate): """ params = get_text(elem, 'parameters').split() - a = float(params[0]) - b = float(params[1]) - return cls(a, b) + return cls(*map(float, params)) class Maxwell(Univariate): @@ -388,9 +404,7 @@ class Watt(Univariate): """ params = get_text(elem, 'parameters').split() - a = float(params[0]) - b = float(params[1]) - return watt(a, b) + return cls(*map(float, params)) class Normal(Univariate): @@ -478,9 +492,7 @@ class Normal(Univariate): """ params = get_text(elem, 'parameters').split() - mean_value = float(params[0]) - std_dev = float(params[1]) - return cls(mean_value, std_dev) + return cls(*map(float, params)) class Muir(Univariate): @@ -587,10 +599,7 @@ class Muir(Univariate): """ params = get_text(elem, 'parameters').split() - e0 = float(params[0]) - m_rat = float(params[1]) - kt = float(params[2]) - return muir(e0, m_rat, kt) + return cls(*map(float, params)) class Tabular(Univariate): @@ -706,7 +715,7 @@ class Tabular(Univariate): interpolation = get_text(elem, 'interpolation') params = [float(x) for x in get_text(elem, 'parameters').split()] x = params[:len(params)//2] - p = paramx[len(params)//2:] + p = params[len(params)//2:] return cls(x, p, interpolation) diff --git a/tests/unit_tests/test_source.py b/tests/unit_tests/test_source.py index 3c963d0527..1c70e159d2 100644 --- a/tests/unit_tests/test_source.py +++ b/tests/unit_tests/test_source.py @@ -11,7 +11,6 @@ def test_source(): assert src.space == space assert src.angle == angle assert src.energy == energy - assert src.strength == 1.0 elem = src.to_xml_element() assert 'strength' in elem.attrib @@ -19,6 +18,13 @@ def test_source(): assert elem.find('angle') is not None assert elem.find('energy') is not None + src = openmc.Source.from_xml_element(elem) + assert isinstance(src.angle, openmc.stats.Isotropic) + assert src.space.xyz == [0.0, 0.0, 0.0] + assert src.energy.x == [1.0e6] + assert src.energy.p == [1.0] + assert src.strength == 1.0 + def test_source_file(): filename = 'source.h5' diff --git a/tests/unit_tests/test_stats.py b/tests/unit_tests/test_stats.py index 553f2410e4..e159584782 100644 --- a/tests/unit_tests/test_stats.py +++ b/tests/unit_tests/test_stats.py @@ -10,10 +10,15 @@ def test_discrete(): x = [0.0, 1.0, 10.0] p = [0.3, 0.2, 0.5] d = openmc.stats.Discrete(x, p) + elem = d.to_xml_element('distribution') + + d = openmc.stats.Discrete.from_xml_element(elem) assert d.x == x assert d.p == p assert len(d) == len(x) - d.to_xml_element('distribution') + + d = openmc.stats.Univariate.from_xml_element(elem) + assert isinstance(d, openmc.stats.Discrete) # Single point d2 = openmc.stats.Discrete(1e6, 1.0) @@ -25,6 +30,9 @@ def test_discrete(): def test_uniform(): a, b = 10.0, 20.0 d = openmc.stats.Uniform(a, b) + elem = d.to_xml_element('distribution') + + d = openmc.stats.Uniform.from_xml_element(elem) assert d.a == a assert d.b == b assert len(d) == 2 @@ -34,35 +42,39 @@ def test_uniform(): assert t.p == [1/(b-a), 1/(b-a)] assert t.interpolation == 'histogram' - d.to_xml_element('distribution') - def test_maxwell(): theta = 1.2895e6 d = openmc.stats.Maxwell(theta) + elem = d.to_xml_element('distribution') + + d = openmc.stats.Maxwell.from_xml_element(elem) assert d.theta == theta assert len(d) == 1 - d.to_xml_element('distribution') def test_watt(): a, b = 0.965e6, 2.29e-6 d = openmc.stats.Watt(a, b) + elem = d.to_xml_element('distribution') + + d = openmc.stats.Watt.from_xml_element(elem) assert d.a == a assert d.b == b assert len(d) == 2 - d.to_xml_element('distribution') def test_tabular(): x = [0.0, 5.0, 7.0] p = [0.1, 0.2, 0.05] d = openmc.stats.Tabular(x, p, 'linear-linear') + elem = d.to_xml_element('distribution') + + d = openmc.stats.Tabular.from_xml_element(elem) assert d.x == x assert d.p == p assert d.interpolation == 'linear-linear' assert len(d) == len(x) - d.to_xml_element('distribution') def test_legendre(): @@ -115,6 +127,15 @@ def test_polar_azimuthal(): assert elem.find('mu') is not None assert elem.find('phi') is not None + d = openmc.stats.PolarAzimuthal.from_xml_element(elem) + assert d.mu.x == [1.] + assert d.mu.p == [1.] + assert d.phi.x == [0.] + assert d.phi.p == [1.] + + d = openmc.stats.UnitSphere.from_xml_element(elem) + assert isinstance(d, openmc.stats.PolarAzimuthal) + def test_isotropic(): d = openmc.stats.Isotropic() @@ -122,24 +143,25 @@ def test_isotropic(): assert elem.tag == 'angle' assert elem.attrib['type'] == 'isotropic' + d = openmc.stats.Isotropic.from_xml_element(elem) + assert isinstance(d, openmc.stats.Isotropic) + def test_monodirectional(): d = openmc.stats.Monodirectional((1., 0., 0.)) - assert d.reference_uvw == pytest.approx((1., 0., 0.)) - elem = d.to_xml_element() assert elem.tag == 'angle' assert elem.attrib['type'] == 'monodirectional' + d = openmc.stats.Monodirectional.from_xml_element(elem) + assert d.reference_uvw == pytest.approx((1., 0., 0.)) + def test_cartesian(): x = openmc.stats.Uniform(-10., 10.) y = openmc.stats.Uniform(-10., 10.) z = openmc.stats.Uniform(0., 20.) d = openmc.stats.CartesianIndependent(x, y, z) - assert d.x == x - assert d.y == y - assert d.z == z elem = d.to_xml_element() assert elem.tag == 'space' @@ -147,55 +169,75 @@ def test_cartesian(): assert elem.find('x') is not None assert elem.find('y') is not None + d = openmc.stats.CartesianIndependent.from_xml_element(elem) + assert d.x == x + assert d.y == y + assert d.z == z + + d = openmc.stats.Spatial.from_xml_element(elem) + assert isinstance(d, openmc.stats.CartesianIndependent) + def test_box(): lower_left = (-10., -10., -10.) upper_right = (10., 10., 10.) d = openmc.stats.Box(lower_left, upper_right) - assert d.lower_left == pytest.approx(lower_left) - assert d.upper_right == pytest.approx(upper_right) - assert not d.only_fissionable elem = d.to_xml_element() assert elem.tag == 'space' assert elem.attrib['type'] == 'box' assert elem.find('parameters') is not None + d = openmc.stats.Box.from_xml_element(elem) + assert d.lower_left == pytest.approx(lower_left) + assert d.upper_right == pytest.approx(upper_right) + assert not d.only_fissionable + # only fissionable parameter d2 = openmc.stats.Box(lower_left, upper_right, True) assert d2.only_fissionable elem = d2.to_xml_element() assert elem.attrib['type'] == 'fission' + d = openmc.stats.Spatial.from_xml_element(elem) + assert isinstance(d, openmc.stats.Box) def test_point(): p = (-4., 2., 10.) d = openmc.stats.Point(p) - assert d.xyz == pytest.approx(p) elem = d.to_xml_element() assert elem.tag == 'space' assert elem.attrib['type'] == 'point' assert elem.find('parameters') is not None + d = openmc.stats.Point.from_xml_element(elem) + assert d.xyz == pytest.approx(p) + def test_normal(): mean = 10.0 std_dev = 2.0 d = openmc.stats.Normal(mean,std_dev) + + elem = d.to_xml_element('distribution') + assert elem.attrib['type'] == 'normal' + + d = openmc.stats.Normal.from_xml_element(elem) assert d.mean_value == pytest.approx(mean) assert d.std_dev == pytest.approx(std_dev) assert len(d) == 2 - elem = d.to_xml_element('distribution') - assert elem.attrib['type'] == 'normal' def test_muir(): mean = 10.0 mass = 5.0 temp = 20000. d = openmc.stats.Muir(mean,mass,temp) + + elem = d.to_xml_element('energy') + assert elem.attrib['type'] == 'muir' + + d = openmc.stats.Muir.from_xml_element(elem) assert d.e0 == pytest.approx(mean) assert d.m_rat == pytest.approx(mass) assert d.kt == pytest.approx(temp) assert len(d) == 3 - elem = d.to_xml_element('energy') - assert elem.attrib['type'] == 'muir' From 7059b56e79095d43d707cc7ce2c7fe04d3175608 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Thu, 23 May 2019 12:29:19 +0300 Subject: [PATCH 038/151] one distance function --- include/openmc/lattice.h | 8 +- src/lattice.cpp | 319 +++++++++++++++------------------------ 2 files changed, 119 insertions(+), 208 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index e2547afd06..afec39b22e 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -249,12 +249,6 @@ public: bool are_valid_indices(const int i_xyz[3]) const; std::pair> distance(Position r, Direction u, const std::array& i_xyz) const; - //DR OX distance calculation - std::pair> - distance_ox(Position r, Direction u, const std::array& i_xyz) const; - //DR OY distance calculation - std::pair> - distance_oy(Position r, Direction u, const std::array& i_xyz) const; std::array get_indices(Position r, Direction u) const; @@ -271,7 +265,7 @@ public: int32_t& offset(int map, const int i_xyz[3]); std::string index_to_string(int indx) const; - + //TODO: find a desicion for compatible old hdf5 file without orientation void to_hdf5_inner(hid_t group_id) const; private: diff --git a/src/lattice.cpp b/src/lattice.cpp index 820d4594eb..af025033ac 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -694,217 +694,134 @@ std::pair> HexLattice::distance(Position r, Direction u, const std::array& i_xyz) const { + double cosa,sina,u_xy; + // Index + 1 of translation direction + int i_trans {1}; + xt :: xarray translation_matrix; if (hextype==0){ - return distance_oy(r,u,i_xyz); + cosa = std::sqrt(3.0) / 2.0; + sina = 0.5; + u_xy = u.y; + //Translation matrix 4x3 {{beta},{gamma},{oy},{oz}} for OY case + translation_matrix = {{1, 0, 0}, + {1, -1, 0}, + {0, 1, 0}, + {0, 0, 1}}; } else { - return distance_ox(r,u,i_xyz); + cosa = 0.5; + sina = std::sqrt(3.0) / 2.0; + u_xy = u.x; + //Translation matrix 4x3 {{beta},{gamma},{ox},{oz}} for OX case + translation_matrix = {{0, 1, 0}, + {1, -1, 0}, + {1, 0, 0}, + {0, 0, 1}}; } -} + double beta_dir = u.x * cosa + u.y * sina; + double gamma_dir = u.x * cosa - u.y * sina; -//DR OX distance calculation -std::pair> -HexLattice::distance_ox(Position r, Direction u, const std::array& i_xyz) -const -{ - // Compute the direction on the hexagonal basis. - // DR for OX angle=60 g - double beta_dir = u.x*0.5 + u.y * std::sqrt(3.0) / 2.0; - double gamma_dir = u.x*0.5 - u.y * std::sqrt(3.0) / 2.0; + // Note that hexagonal lattice distance calculations are performed + // using the particle's coordinates relative to the neighbor lattice + // cells, not relative to the particle's current cell. This is done + // because there is significant disagreement between neighboring cells + // on where the lattice boundary is due to finite precision issues. - // Note that hexagonal lattice distance calculations are performed - // using the particle's coordinates relative to the neighbor lattice - // cells, not relative to the particle's current cell. This is done - // because there is significant disagreement between neighboring cells - // on where the lattice boundary is due to finite precision issues. - - // Upper-right and lower-left sides. - double d {INFTY}; - std::array lattice_trans; - double edge = -copysign(0.5*pitch_[0], beta_dir); // Oncoming edge - Position r_t; - if (beta_dir > 0) { - const std::array i_xyz_t {i_xyz[0], i_xyz[1]+1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0], i_xyz[1]-1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double beta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0; - if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { - d = (edge - beta) / beta_dir; - if (beta_dir > 0) { - lattice_trans = {0, 1, 0}; - } else { - lattice_trans = {0, -1, 0}; - } - } - - // Lower-right and upper-left sides. - edge = -copysign(0.5*pitch_[0], gamma_dir); - if (gamma_dir > 0) { - const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1]-1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1]+1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0; - if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { - double this_d = (edge - gamma) / gamma_dir; - if (this_d < d) { - if (gamma_dir > 0) { - lattice_trans = {1, -1, 0}; - } else { - lattice_trans = {-1, 1, 0}; - } - d = this_d; - } - } - - // right and left sides. - edge = -copysign(0.5*pitch_[0], u.x); - if (u.x > 0) { - const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1], i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1], i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - if ((std::abs(r_t.x - edge) > FP_PRECISION) && u.x != 0) { - double this_d = (edge - r_t.x) / u.x; - if (this_d < d) { - if (u.x > 0) { - lattice_trans = {1, 0, 0}; - } else { - lattice_trans = {-1, 0, 0}; - } - d = this_d; - } - } - - // Top and bottom sides - if (is_3d_) { - double z = r.z; - double z0 {copysign(0.5 * pitch_[1], u.z)}; - if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) { - double this_d = (z0 - z) / u.z; - if (this_d < d) { - d = this_d; - if (u.z > 0) { - lattice_trans = {0, 0, 1}; + //beta direction + double d {INFTY}; + std::array lattice_trans; + double edge = -copysign(0.5*pitch_[0], beta_dir); // Oncoming edge + Position r_t; + if (beta_dir > 0) { + const std::array i_xyz_t {i_xyz[0]+translation_matrix(0,0), i_xyz[1]+translation_matrix(0,1), i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); } else { - lattice_trans = {0, 0, -1}; + const std::array i_xyz_t {i_xyz[0]-translation_matrix(0,0), i_xyz[1]-translation_matrix(0,1), i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); } - d = this_d; - } - } - } - - return {d, lattice_trans}; -} - -//============================================================================== - -//============================================================================== -//DR OY distance calculation -std::pair> -HexLattice::distance_oy(Position r, Direction u, const std::array& i_xyz) -const -{ - // Compute the direction on the hexagonal basis. - double beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0; - double gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0; - - // Note that hexagonal lattice distance calculations are performed - // using the particle's coordinates relative to the neighbor lattice - // cells, not relative to the particle's current cell. This is done - // because there is significant disagreement between neighboring cells - // on where the lattice boundary is due to finite precision issues. - - // Upper-right and lower-left sides. - double d {INFTY}; - std::array lattice_trans; - double edge = -copysign(0.5*pitch_[0], beta_dir); // Oncoming edge - Position r_t; - if (beta_dir > 0) { - const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1], i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1], i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0; - if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { - d = (edge - beta) / beta_dir; - if (beta_dir > 0) { - lattice_trans = {1, 0, 0}; - } else { - lattice_trans = {-1, 0, 0}; - } - } - - // Lower-right and upper-left sides. - edge = -copysign(0.5*pitch_[0], gamma_dir); - if (gamma_dir > 0) { - const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1]-1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1]+1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0; - if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { - double this_d = (edge - gamma) / gamma_dir; - if (this_d < d) { - if (gamma_dir > 0) { - lattice_trans = {1, -1, 0}; - } else { - lattice_trans = {-1, 1, 0}; - } - d = this_d; - } - } - - // Upper and lower sides. - edge = -copysign(0.5*pitch_[0], u.y); - if (u.y > 0) { - const std::array i_xyz_t {i_xyz[0], i_xyz[1]+1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0], i_xyz[1]-1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - if ((std::abs(r_t.y - edge) > FP_PRECISION) && u.y != 0) { - double this_d = (edge - r_t.y) / u.y; - if (this_d < d) { - if (u.y > 0) { - lattice_trans = {0, 1, 0}; - } else { - lattice_trans = {0, -1, 0}; - } - d = this_d; - } - } - - // Top and bottom sides - if (is_3d_) { - double z = r.z; - double z0 {copysign(0.5 * pitch_[1], u.z)}; - if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) { - double this_d = (z0 - z) / u.z; - if (this_d < d) { - d = this_d; - if (u.z > 0) { - lattice_trans = {0, 0, 1}; + double beta = r_t.x * cosa + r_t.y * sina; + if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { + d = (edge - beta) / beta_dir; + if (beta_dir > 0) { + i_trans = 1; + } else { + i_trans = -1; + } + } + //gamma direction + edge = -copysign(0.5*pitch_[0], gamma_dir); + if (gamma_dir > 0) { + const std::array i_xyz_t {i_xyz[0]+translation_matrix(1,0), i_xyz[1]+translation_matrix(1,1), i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); } else { - lattice_trans = {0, 0, -1}; + const std::array i_xyz_t {i_xyz[0]-translation_matrix(1,0), i_xyz[1]-translation_matrix(1,1), i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); } - d = this_d; - } - } - } + double gamma = r_t.x * cosa - r_t.y * sina; + if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { + double this_d = (edge - gamma) / gamma_dir; + if (this_d < d) { + if (gamma_dir > 0) { + i_trans = 2; + } else { + i_trans = -2; + } + d = this_d; + } + } + //y or x direction + edge = -copysign(0.5*pitch_[0], u_xy); + if (u_xy > 0) { + const std::array i_xyz_t {i_xyz[0]+translation_matrix(2,0), i_xyz[1]+translation_matrix(2,1), i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0]-translation_matrix(2,0), i_xyz[1]-translation_matrix(2,1), i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + double r_txy; + if (hextype==0){ + r_txy = r_t.y; + } + else { + r_txy = r_t.x; + } + if ((std::abs(r_txy - edge) > FP_PRECISION) && u_xy != 0) { + double this_d = (edge - r_txy) / u_xy; + if (this_d < d) { + if (u_xy > 0) { + i_trans = 3; + } else { + i_trans = -3; + } + d = this_d; + } + } + // Top and bottom sides + if (is_3d_) { + double z = r.z; + double z0 {copysign(0.5 * pitch_[1], u.z)}; + if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) { + double this_d = (z0 - z) / u.z; + if (this_d < d) { + d = this_d; + if (u.z > 0) { + i_trans = 4; + } else { + i_trans = -4; + } + d = this_d; + } + } + } - return {d, lattice_trans}; + for (int i=0;i<3;i++){ + if (i_trans < 0){ + lattice_trans[i] = -translation_matrix(-(i_trans+1),i); + }else{ + lattice_trans[i] = translation_matrix((i_trans-1),i); + } + } + + return {d, lattice_trans}; } //============================================================================== From 827064da85ae549cf2500ab680a2cfa9fbf09aa1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 21 May 2019 21:47:20 -0500 Subject: [PATCH 039/151] Fix bug whereby coherent elastic scattering cross section was zero --- src/thermal.cpp | 2 +- tests/regression_tests/lattice_hex_coincident/results_true.dat | 2 +- tests/regression_tests/salphabeta/results_true.dat | 2 +- tests/regression_tests/triso/results_true.dat | 2 +- tests/regression_tests/void/results_true.dat | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/thermal.cpp b/src/thermal.cpp index 3c2d9ba432..5db578f9dd 100644 --- a/src/thermal.cpp +++ b/src/thermal.cpp @@ -202,7 +202,7 @@ ThermalScattering::calculate_xs(double E, double sqrtkT, int* i_temp, *inelastic = (1.0 - f) * xs[i_grid] + f * xs[i_grid + 1]; // Check for elastic data - if (E < sab.threshold_elastic_) { + if (!sab.elastic_e_in_.empty()) { // Determine whether elastic scattering is given in the coherent or // incoherent approximation. For coherent, the cross section is // represented as P/E whereas for incoherent, it is simply P diff --git a/tests/regression_tests/lattice_hex_coincident/results_true.dat b/tests/regression_tests/lattice_hex_coincident/results_true.dat index ba047f6740..8bf1c539ed 100644 --- a/tests/regression_tests/lattice_hex_coincident/results_true.dat +++ b/tests/regression_tests/lattice_hex_coincident/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.741370E+00 1.384609E-03 +1.744283E+00 1.734930E-03 diff --git a/tests/regression_tests/salphabeta/results_true.dat b/tests/regression_tests/salphabeta/results_true.dat index e1121bce50..fb9d2d77b7 100644 --- a/tests/regression_tests/salphabeta/results_true.dat +++ b/tests/regression_tests/salphabeta/results_true.dat @@ -1,2 +1,2 @@ k-combined: -8.403447E-01 2.461538E-02 +8.757212E-01 5.686265E-02 diff --git a/tests/regression_tests/triso/results_true.dat b/tests/regression_tests/triso/results_true.dat index eb06a771c9..0c5186e45b 100644 --- a/tests/regression_tests/triso/results_true.dat +++ b/tests/regression_tests/triso/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.707485E+00 9.795497E-02 +1.701412E+00 3.180877E-02 diff --git a/tests/regression_tests/void/results_true.dat b/tests/regression_tests/void/results_true.dat index 48be2778a4..c0e8184b6f 100644 --- a/tests/regression_tests/void/results_true.dat +++ b/tests/regression_tests/void/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.062505E+00 2.674375E-02 +9.612556E-01 1.990135E-02 From e4d7417f981ef25b6caaff4c713f307c375f82e5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 21 May 2019 22:03:23 -0500 Subject: [PATCH 040/151] Fix default constant on Watt spectrum when no source is specified --- src/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings.cpp b/src/settings.cpp index 4c041a080d..32032ed2a7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -400,7 +400,7 @@ void read_settings_xml() SourceDistribution source { UPtrSpace{new SpatialPoint({0.0, 0.0, 0.0})}, UPtrAngle{new Isotropic()}, - UPtrDist{new Watt(0.988, 2.249e-6)} + UPtrDist{new Watt(0.988e6, 2.249e-6)} }; model::external_sources.push_back(std::move(source)); } From 7b4d9d268d08df1a07873d794f4675aecfc75a73 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 22 May 2019 06:19:59 -0500 Subject: [PATCH 041/151] Change calculation of thermal elastic/inelastic xs --- src/thermal.cpp | 4 ++-- .../asymmetric_lattice/results_true.dat | 2 +- tests/regression_tests/cmfd_feed_2g/results_true.dat | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/thermal.cpp b/src/thermal.cpp index 5db578f9dd..40bb19e894 100644 --- a/src/thermal.cpp +++ b/src/thermal.cpp @@ -199,7 +199,7 @@ ThermalScattering::calculate_xs(double E, double sqrtkT, int* i_temp, // Calculate S(a,b) inelastic scattering cross section auto& xs = sab.inelastic_sigma_; - *inelastic = (1.0 - f) * xs[i_grid] + f * xs[i_grid + 1]; + *inelastic = xs[i_grid] + f * (xs[i_grid + 1] - xs[i_grid]); // Check for elastic data if (!sab.elastic_e_in_.empty()) { @@ -231,7 +231,7 @@ ThermalScattering::calculate_xs(double E, double sqrtkT, int* i_temp, // Calculate S(a,b) elastic scattering cross section auto& xs = sab.elastic_P_; - *elastic = (1.0 - f) * xs[i_grid] + f * xs[i_grid + 1]; + *elastic = xs[i_grid] + f*(xs[i_grid + 1] - xs[i_grid]); } } else { // No elastic data diff --git a/tests/regression_tests/asymmetric_lattice/results_true.dat b/tests/regression_tests/asymmetric_lattice/results_true.dat index ecc30a056f..43f59aa5d2 100644 --- a/tests/regression_tests/asymmetric_lattice/results_true.dat +++ b/tests/regression_tests/asymmetric_lattice/results_true.dat @@ -1 +1 @@ -4b75e203d06d0fc1b4c4dfcb8c180d6f3df8fa2bc44e9775b59bbfd8f7a3785f956f9a9f301526f69c0f8a963ce2e553e0c62c7f6c696656ce1d47b415af6076 \ No newline at end of file +4401f503237c94e9d9cfc9f60e0269d5ae5bb67be3225e18c5510ed08616482964e2962a06268751f66a455fac3ddd5faf91555638dfb56fcd09eee60219edff \ No newline at end of file diff --git a/tests/regression_tests/cmfd_feed_2g/results_true.dat b/tests/regression_tests/cmfd_feed_2g/results_true.dat index 8161b26457..4b3dc3c960 100644 --- a/tests/regression_tests/cmfd_feed_2g/results_true.dat +++ b/tests/regression_tests/cmfd_feed_2g/results_true.dat @@ -1,5 +1,5 @@ k-combined: -1.038883E+00 1.017026E-02 +1.038883E+00 1.017030E-02 tally 1: 1.167304E+02 1.362680E+03 @@ -49,7 +49,7 @@ tally 3: 0.000000E+00 2.033842E-02 4.375294E-05 -4.296338E+00 +4.296339E+00 9.280716E-01 3.493776E+00 6.157094E-01 @@ -74,7 +74,7 @@ tally 3: 9.717439E+01 4.724254E+02 8.435691E-01 -3.666151E-02 +3.666152E-02 6.192881E+01 1.924016E+02 0.000000E+00 @@ -82,7 +82,7 @@ tally 3: 1.796382E-02 3.190854E-05 4.343238E+00 -9.514039E-01 +9.514040E-01 3.504683E+00 6.157615E-01 0.000000E+00 @@ -106,7 +106,7 @@ tally 3: 9.885292E+01 4.888720E+02 9.509199E-01 -4.670951E-02 +4.670952E-02 tally 4: 0.000000E+00 0.000000E+00 @@ -383,7 +383,7 @@ cmfd balance 1.08402E-03 1.09178E-03 5.45977E-04 -4.45554E-04 +4.45555E-04 4.01147E-04 3.71025E-04 3.57715E-04 From 8231751b888a7d427282ba501cd884d4e4ead80f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 23 May 2019 08:24:39 -0500 Subject: [PATCH 042/151] Some updates to setup.py and MANIFEST.in --- MANIFEST.in | 10 ++++++++++ setup.py | 14 ++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index be82928a15..5715bfb444 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,10 @@ include CMakeLists.txt include LICENSE +include CODE_OF_CONDUCT.md +include CONTRIBUTING.md include schemas.xml include pyproject.toml +include pytest.ini include openmc/data/reconstruct.pyx include docs/source/_templates/layout.html include docs/sphinxext/LICENSE @@ -33,4 +36,11 @@ recursive-include tests *.dat recursive-include tests *.h5 recursive-include tests *.py recursive-include tests *.xml +recursive-include vendor CMakeLists.txt +recursive-include vendor *.cmake.in +recursive-include vendor *.cc +recursive-include vendor *.cpp +recursive-include vendor *.hh +recursive-include vendor *.hpp prune docs/build +prune docs/source/pythonapi/generated/ diff --git a/setup.py b/setup.py index 38ee57f716..712244efb4 100755 --- a/setup.py +++ b/setup.py @@ -39,7 +39,13 @@ kwargs = { 'author': 'The OpenMC Development Team', 'author_email': 'openmc-dev@googlegroups.com', 'description': 'OpenMC', - 'url': 'https://github.com/openmc-dev/openmc', + 'url': 'https://openmc.org', + 'download_url': 'https://github.com/openmc-dev/openmc/releases', + 'project_urls': { + 'Issue Tracker': 'https://github.com/openmc-dev/openmc/issues', + 'Documentation': 'https://openmc.readthedocs.io', + 'Source Code': 'https://github.com/openmc-dev/openmc', + }, 'classifiers': [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', @@ -48,6 +54,7 @@ kwargs = { 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Topic :: Scientific/Engineering' + 'Programming Language :: C++', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', @@ -55,13 +62,12 @@ kwargs = { 'Programming Language :: Python :: 3.7', ], - # Required dependencies + # Dependencies + 'python_requires': '>=3.4', 'install_requires': [ 'numpy>=1.9', 'h5py', 'scipy', 'ipython', 'matplotlib', 'pandas', 'lxml', 'uncertainties' ], - - # Optional dependencies 'extras_require': { 'test': ['pytest', 'pytest-cov'], 'vtk': ['vtk'], From ce2bf83254554f61ee66cde71af64e659c80853e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 24 May 2019 10:50:57 -0400 Subject: [PATCH 043/151] Start implementing RectilinearMesh --- include/openmc/mesh.h | 82 ++++- src/eigenvalue.cpp | 23 +- src/mesh.cpp | 500 +++++++++++++++++++++++++++-- src/plot.cpp | 16 +- src/settings.cpp | 2 +- src/tallies/filter_mesh.cpp | 40 ++- src/tallies/filter_meshsurface.cpp | 7 +- 7 files changed, 588 insertions(+), 82 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index f53bba224d..f7e41cf704 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -21,20 +21,48 @@ namespace openmc { // Global variables //============================================================================== -class RegularMesh; +//class RegularMesh; +class Mesh; namespace model { -extern std::vector> meshes; +//extern std::vector> meshes; +extern std::vector> meshes; extern std::unordered_map mesh_map; } // namespace model +class Mesh +{ +public: + // Constructors + Mesh() = default; + Mesh(pugi::xml_node node); + + virtual void bins_crossed(const Particle* p, std::vector& bins, + std::vector& lengths) const = 0; + + virtual void surface_bins_crossed(const Particle* p, std::vector& bins) const = 0; + + virtual int get_bin(Position r) const = 0; + + virtual int get_bin_from_indices(const int* ijk) const = 0; + + virtual void get_indices(Position r, int* ijk, bool* in_mesh) const = 0; + + virtual void get_indices_from_bin(int bin, int* ijk) const = 0; + + virtual void to_hdf5(hid_t group) const = 0; + + int id_ {-1}; //!< User-specified ID +}; + //============================================================================== //! Tessellation of n-dimensional Euclidean space by congruent squares or cubes //============================================================================== -class RegularMesh { +class RegularMesh : public Mesh +{ public: // Constructors RegularMesh() = default; @@ -47,39 +75,39 @@ public: //! \param[in] p Particle to check //! \param[out] bins Bins that were crossed //! \param[out] lengths Fraction of tracklength in each bin - void bins_crossed(const Particle* p, std::vector& bins, + virtual void bins_crossed(const Particle* p, std::vector& bins, std::vector& lengths) const; //! Determine which surface bins were crossed by a particle // //! \param[in] p Particle to check //! \param[out] bins Surface bins that were crossed - void surface_bins_crossed(const Particle* p, std::vector& bins) const; + virtual void surface_bins_crossed(const Particle* p, std::vector& bins) const; //! Get bin at a given position in space // //! \param[in] r Position to get bin for //! \return Mesh bin - int get_bin(Position r) const; + virtual int get_bin(Position r) const; //! Get bin given mesh indices // //! \param[in] Array of mesh indices //! \return Mesh bin - int get_bin_from_indices(const int* ijk) const; + virtual int get_bin_from_indices(const int* ijk) const; //! Get mesh indices given a position // //! \param[in] r Position to get indices for //! \param[out] ijk Array of mesh indices //! \param[out] in_mesh Whether position is in mesh - void get_indices(Position r, int* ijk, bool* in_mesh) const; + virtual void get_indices(Position r, int* ijk, bool* in_mesh) const; //! Get mesh indices corresponding to a mesh bin // //! \param[in] bin Mesh bin //! \param[out] ijk Mesh indices - void get_indices_from_bin(int bin, int* ijk) const; + virtual void get_indices_from_bin(int bin, int* ijk) const; //! Check where a line segment intersects the mesh and if it intersects at all // @@ -92,19 +120,18 @@ public: //! Write mesh data to an HDF5 group // //! \param[in] group HDF5 group - void to_hdf5(hid_t group) const; + virtual void to_hdf5(hid_t group) const; //! Count number of bank sites in each mesh bin / energy bin // //! \param[in] bank Array of bank sites //! \param[out] Whether any bank sites are outside the mesh //! \return Array indicating number of sites in each mesh/energy bin - xt::xarray count_sites(const std::vector& bank, - bool* outside) const; + virtual xt::xarray + count_sites(const std::vector& bank, bool* outside) const; - int id_ {-1}; //!< User-specified ID - int n_dimension_; //!< Number of dimensions double volume_frac_; //!< Volume fraction of each mesh element + int n_dimension_; //!< Number of dimensions xt::xarray shape_; //!< Number of mesh elements in each dimension xt::xarray lower_left_; //!< Lower-left coordinates of mesh xt::xarray upper_right_; //!< Upper-right coordinates of mesh @@ -116,6 +143,33 @@ private: bool intersects_3d(Position& r0, Position r1, int* ijk) const; }; +class RectilinearMesh : public Mesh +{ +public: + // Constructors + RectilinearMesh(pugi::xml_node node); + + virtual void bins_crossed(const Particle* p, std::vector& bins, + std::vector& lengths) const; + + virtual void surface_bins_crossed(const Particle* p, std::vector& bins) const; + + virtual int get_bin(Position r) const; + + virtual int get_bin_from_indices(const int* ijk) const; + + virtual void get_indices(Position r, int* ijk, bool* in_mesh) const; + + virtual void get_indices_from_bin(int bin, int* ijk) const; + + virtual void to_hdf5(hid_t group) const; + + bool intersects(Position& r0, Position r1, int* ijk) const; + + xt::xarray shape_; //!< Number of mesh elements in each dimension + std::vector> grid_; +}; + //============================================================================== // Non-member functions //============================================================================== diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index 08a4d3f91c..0f4b49f461 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -532,12 +532,13 @@ int openmc_get_keff(double* k_combined) void shannon_entropy() { - // Get pointer to entropy mesh - auto& m = model::meshes[settings::index_entropy_mesh]; + // Get reference to entropy mesh + auto& m = *dynamic_cast( + model::meshes[settings::index_entropy_mesh].get()); // Get source weight in each mesh bin bool sites_outside; - xt::xtensor p = m->count_sites(simulation::fission_bank, + xt::xtensor p = m.count_sites(simulation::fission_bank, &sites_outside); // display warning message if there were sites outside entropy box @@ -564,7 +565,8 @@ void shannon_entropy() void ufs_count_sites() { - auto &m = model::meshes[settings::index_ufs_mesh]; + auto& m = *dynamic_cast( + model::meshes[settings::index_entropy_mesh].get()); if (simulation::current_batch == 1 && simulation::current_gen == 1) { // On the first generation, just assume that the source is already evenly @@ -572,12 +574,12 @@ void ufs_count_sites() // biased auto s = xt::view(simulation::source_frac, xt::all()); - s = m->volume_frac_; + s = m.volume_frac_; } else { // count number of source sites in each ufs mesh cell bool sites_outside; - simulation::source_frac = m->count_sites(simulation::source_bank, + simulation::source_frac = m.count_sites(simulation::source_bank, &sites_outside); // Check for sites outside of the mesh @@ -587,7 +589,7 @@ void ufs_count_sites() #ifdef OPENMC_MPI // Send source fraction to all processors - int n_bins = xt::prod(m->shape_)(); + int n_bins = xt::prod(m.shape_)(); MPI_Bcast(simulation::source_frac.data(), n_bins, MPI_DOUBLE, 0, mpi::intracomm); #endif @@ -605,17 +607,18 @@ void ufs_count_sites() double ufs_get_weight(const Particle* p) { - auto& m = model::meshes[settings::index_ufs_mesh]; + auto& m = *dynamic_cast( + model::meshes[settings::index_entropy_mesh].get()); // Determine indices on ufs mesh for current location - int mesh_bin = m->get_bin(p->r()); + int mesh_bin = m.get_bin(p->r()); if (mesh_bin < 0) { p->write_restart(); fatal_error("Source site outside UFS mesh!"); } if (simulation::source_frac(mesh_bin) != 0.0) { - return m->volume_frac_ / simulation::source_frac(mesh_bin); + return m.volume_frac_ / simulation::source_frac(mesh_bin); } else { return 1.0; } diff --git a/src/mesh.cpp b/src/mesh.cpp index ddfa272a6d..676c5888fd 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -31,7 +31,7 @@ namespace openmc { namespace model { -std::vector> meshes; +std::vector> meshes; std::unordered_map mesh_map; } // namespace model @@ -63,10 +63,10 @@ inline bool check_intersection_point(double x1, double x0, double y1, } //============================================================================== -// RegularMesh implementation +// Mesh implementation //============================================================================== -RegularMesh::RegularMesh(pugi::xml_node node) +Mesh::Mesh(pugi::xml_node node) { // Copy mesh id if (check_for_node(node, "id")) { @@ -78,17 +78,15 @@ RegularMesh::RegularMesh(pugi::xml_node node) std::to_string(id_)); } } +} - // Read mesh type - if (check_for_node(node, "type")) { - auto temp = get_node_value(node, "type", true, true); - if (temp == "regular") { - // TODO: move elsewhere - } else { - fatal_error("Invalid mesh type: " + temp); - } - } +//============================================================================== +// RegularMesh implementation +//============================================================================== +RegularMesh::RegularMesh(pugi::xml_node node) + : Mesh {node} +{ // Determine number of dimensions for mesh if (check_for_node(node, "dimension")) { shape_ = get_node_xarray(node, "dimension"); @@ -783,6 +781,425 @@ RegularMesh::count_sites(const std::vector& bank, return counts; } +//============================================================================== +// RectilinearMesh implementation +//============================================================================== + +RectilinearMesh::RectilinearMesh(pugi::xml_node node) + : Mesh {node} +{ + grid_.resize(3); + grid_[0] = get_node_array(node, "x_grid"); + grid_[1] = get_node_array(node, "y_grid"); + grid_[2] = get_node_array(node, "z_grid"); + + shape_ = {static_cast(grid_[0].size()) - 1, + static_cast(grid_[1].size()) - 1, + static_cast(grid_[2].size()) - 1}; +} + +void RectilinearMesh::bins_crossed(const Particle* p, std::vector& bins, + std::vector& lengths) const +{ + // ======================================================================== + // Determine where the track intersects the mesh and if it intersects at all. + + // Copy the starting and ending coordinates of the particle. + Position last_r {p->r_last_}; + Position r {p->r()}; + Direction u {p->u()}; + + // Compute the length of the entire track. + double total_distance = (r - last_r).norm(); + + // While determining if this track intersects the mesh, offset the starting + // and ending coords by a bit. This avoid finite-precision errors that can + // occur when the mesh surfaces coincide with lattice or geometric surfaces. + Position r0 = last_r + TINY_BIT*u; + Position r1 = r - TINY_BIT*u; + + // Determine the mesh indices for the starting and ending coords. + int ijk0[3], ijk1[3]; + bool start_in_mesh; + get_indices(r0, ijk0, &start_in_mesh); + bool end_in_mesh; + get_indices(r1, ijk1, &end_in_mesh); + + // Reset coordinates and check for a mesh intersection if necessary. + if (start_in_mesh) { + // The initial coords lie in the mesh, use those coords for tallying. + r0 = last_r; + } else { + // The initial coords do not lie in the mesh. Check to see if the particle + // eventually intersects the mesh and compute the relevant coords and + // indices. + if (!intersects(r0, r1, ijk0)) return; + } + r1 = r; + + // ======================================================================== + // Find which mesh cells are traversed and the length of each traversal. + + while (true) { + if (std::equal(ijk0, ijk0+3, ijk1)) { + // The track ends in this cell. Use the particle end location rather + // than the mesh surface and stop iterating. + double distance = (r1 - r0).norm(); + bins.push_back(get_bin_from_indices(ijk0)); + lengths.push_back(distance / total_distance); + break; + } + + // The track exits this cell. Determine the distance to each mesh surface. + double d[3]; + for (int k = 0; k < 3; ++k) { + if (std::fabs(u[k]) < FP_PRECISION) { + d[k] = INFTY; + } else if (u[k] > 0) { + double xyz_cross = grid_[k][ijk0[k]]; + d[k] = (xyz_cross - r0[k]) / u[k]; + } else { + double xyz_cross = grid_[k][ijk0[k] - 1]; + d[k] = (xyz_cross - r0[k]) / u[k]; + } + } + + // Pick the closest mesh surface and append this traversal to the output. + auto j = std::min_element(d, d+3) - d; + double distance = d[j]; + bins.push_back(get_bin_from_indices(ijk0)); + lengths.push_back(distance / total_distance); + + // Translate to the oncoming mesh surface. + r0 += distance * u; + + // Increment the indices into the next mesh cell. + if (u[j] > 0.0) { + ++ijk0[j]; + } else { + --ijk0[j]; + } + + // If the next indices are invalid, then the track has left the mesh and + // we are done. + bool in_mesh = true; + for (int i = 0; i < 3; ++i) { + if (ijk0[i] < 1 || ijk0[i] > shape_[i]) { + in_mesh = false; + break; + } + } + if (!in_mesh) break; + } +} + +void RectilinearMesh::surface_bins_crossed(const Particle* p, + std::vector& bins) const +{ + // ======================================================================== + // Determine if the track intersects the tally mesh. + + // Copy the starting and ending coordinates of the particle. + Position r0 {p->r_last_current_}; + Position r1 {p->r()}; + Direction u {p->u()}; + + // Determine indices for starting and ending location. + int ijk0[3], ijk1[3]; + bool start_in_mesh; + get_indices(r0, ijk0, &start_in_mesh); + bool end_in_mesh; + get_indices(r1, ijk1, &end_in_mesh); + + // Check if the track intersects any part of the mesh. + if (!start_in_mesh) { + Position r0_copy = r0; + int ijk0_copy[3]; + for (int i = 0; i < 3; ++i) ijk0_copy[i] = ijk0[i]; + if (!intersects(r0_copy, r1, ijk0_copy)) return; + } + + // ======================================================================== + // Find which mesh surfaces are crossed. + + // Calculate number of surface crossings + int n_cross = 0; + for (int i = 0; i < 3; ++i) n_cross += std::abs(ijk1[i] - ijk0[i]); + if (n_cross == 0) return; + + // Bounding coordinates + Position xyz_cross; + for (int i = 0; i < 3; ++i) { + if (u[i] > 0.0) { + xyz_cross[i] = grid_[i][ijk0[i]]; + } else { + xyz_cross[i] = grid_[i][ijk0[i] - 1]; + } + } + + for (int j = 0; j < n_cross; ++j) { + // Set the distances to infinity + Position d {INFTY, INFTY, INFTY}; + + // Determine closest bounding surface. We need to treat + // special case where the cosine of the angle is zero since this would + // result in a divide-by-zero. + double distance = INFTY; + for (int i = 0; i < 3; ++i) { + if (u[i] == 0) { + d[i] = INFTY; + } else { + d[i] = (xyz_cross[i] - r0[i])/u[i]; + } + distance = std::min(distance, d[i]); + } + + // Loop over the dimensions + for (int i = 0; i < 3; ++i) { + // Check whether distance is the shortest distance + if (distance == d[i]) { + + // Check whether the current indices are within the mesh bounds + bool in_mesh = true; + for (int j = 0; j < 3; ++j) { + if (ijk0[j] < 1 || ijk0[j] > shape_[j]) { + in_mesh = false; + break; + } + } + + // Check whether particle is moving in positive i direction + if (u[i] > 0) { + + // Outward current on i max surface + if (in_mesh) { + int i_surf = 4*i + 3; + int i_mesh = get_bin_from_indices(ijk0); + int i_bin = 4*3*i_mesh + i_surf - 1; + + bins.push_back(i_bin); + } + + // Advance position + ++ijk0[i]; + xyz_cross[i] = grid_[i][ijk0[i]]; + in_mesh = true; + for (int j = 0; j < 3; ++j) { + if (ijk0[j] < 1 || ijk0[j] > shape_[j]) { + in_mesh = false; + break; + } + } + + // If the particle crossed the surface, tally the inward current on + // i min surface + if (in_mesh) { + int i_surf = 4*i + 2; + int i_mesh = get_bin_from_indices(ijk0); + int i_bin = 4*3*i_mesh + i_surf - 1; + + bins.push_back(i_bin); + } + + } else { + // The particle is moving in the negative i direction + + // Outward current on i min surface + if (in_mesh) { + int i_surf = 4*i + 1; + int i_mesh = get_bin_from_indices(ijk0); + int i_bin = 4*3*i_mesh + i_surf - 1; + + bins.push_back(i_bin); + } + + // Advance position + --ijk0[i]; + xyz_cross[i] = grid_[i][ijk0[i] - 1]; + in_mesh = true; + for (int j = 0; j < 3; ++j) { + if (ijk0[j] < 1 || ijk0[j] > shape_[j]) { + in_mesh = false; + break; + } + } + + // If the particle crossed the surface, tally the inward current on + // i max surface + if (in_mesh) { + int i_surf = 4*i + 4; + int i_mesh = get_bin_from_indices(ijk0); + int i_bin = 4*3*i_mesh + i_surf - 1; + + bins.push_back(i_bin); + } + } + } + } + + // Calculate new coordinates + r0 += distance * u; + } +} + +int RectilinearMesh::get_bin(Position r) const +{ + // Determine indices + int ijk[3]; + bool in_mesh; + get_indices(r, ijk, &in_mesh); + if (!in_mesh) return -1; + + // Convert indices to bin + return get_bin_from_indices(ijk); +} + +int RectilinearMesh::get_bin_from_indices(const int* ijk) const +{ + return ((ijk[2] - 1)*shape_[1] + (ijk[1] - 1))*shape_[0] + ijk[0] - 1; +} + +void RectilinearMesh::get_indices(Position r, int* ijk, bool* in_mesh) const +{ + *in_mesh = true; + + for (int i = 0; i < 3; ++i) { + if (r[i] < grid_[i].front() || r[i] > grid_[i].back()) { + ijk[i] = -1; + *in_mesh = false; + } else { + ijk[i] = lower_bound_index(grid_[i].begin(), grid_[i].end(), r[i]) + 1; + } + } +} + +void RectilinearMesh::get_indices_from_bin(int bin, int* ijk) const +{ + ijk[0] = bin % shape_[0] + 1; + ijk[1] = (bin % (shape_[0] * shape_[1])) / shape_[0] + 1; + ijk[2] = bin / (shape_[0] * shape_[1]) + 1; +} + +void RectilinearMesh::to_hdf5(hid_t group) const +{ + hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); + + write_dataset(mesh_group, "type", "rectilinear"); + + close_group(mesh_group); +} + +bool RectilinearMesh::intersects(Position& r0, Position r1, int* ijk) const +{ + // Copy coordinates of starting point + double x0 = r0.x; + double y0 = r0.y; + double z0 = r0.z; + + // Copy coordinates of ending point + double x1 = r1.x; + double y1 = r1.y; + double z1 = r1.z; + + // Copy coordinates of mesh lower_left + double xm0 = grid_[0].front(); + double ym0 = grid_[1].front(); + double zm0 = grid_[2].front(); + + // Copy coordinates of mesh upper_right + double xm1 = grid_[0].back(); + double ym1 = grid_[1].back(); + double zm1 = grid_[2].back(); + + double min_dist = INFTY; + + // Check if line intersects left surface -- calculate the intersection point + // (y,z) + if ((x0 < xm0 && x1 > xm0) || (x0 > xm0 && x1 < xm0)) { + double yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0); + double zi = z0 + (xm0 - x0) * (z1 - z0) / (x1 - x0); + if (yi >= ym0 && yi < ym1 && zi >= zm0 && zi < zm1) { + if (check_intersection_point(xm0, x0, yi, y0, zi, z0, r0, min_dist)) { + ijk[0] = 1; + ijk[1] = lower_bound_index(grid_[1].begin(), grid_[1].end(), yi) + 1; + ijk[2] = lower_bound_index(grid_[2].begin(), grid_[2].end(), zi) + 1; + } + } + } + + // Check if line intersects back surface -- calculate the intersection point + // (x,z) + if ((y0 < ym0 && y1 > ym0) || (y0 > ym0 && y1 < ym0)) { + double xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0); + double zi = z0 + (ym0 - y0) * (z1 - z0) / (y1 - y0); + if (xi >= xm0 && xi < xm1 && zi >= zm0 && zi < zm1) { + if (check_intersection_point(xi, x0, ym0, y0, zi, z0, r0, min_dist)) { + ijk[0] = lower_bound_index(grid_[0].begin(), grid_[0].end(), xi) + 1; + ijk[1] = 1; + ijk[2] = lower_bound_index(grid_[2].begin(), grid_[2].end(), zi) + 1; + } + } + } + + // Check if line intersects bottom surface -- calculate the intersection + // point (x,y) + if ((z0 < zm0 && z1 > zm0) || (z0 > zm0 && z1 < zm0)) { + double xi = x0 + (zm0 - z0) * (x1 - x0) / (z1 - z0); + double yi = y0 + (zm0 - z0) * (y1 - y0) / (z1 - z0); + if (xi >= xm0 && xi < xm1 && yi >= ym0 && yi < ym1) { + if (check_intersection_point(xi, x0, yi, y0, zm0, z0, r0, min_dist)) { + ijk[0] = lower_bound_index(grid_[0].begin(), grid_[0].end(), xi) + 1; + ijk[1] = lower_bound_index(grid_[1].begin(), grid_[1].end(), yi) + 1; + ijk[2] = 1; + } + } + } + + // Check if line intersects right surface -- calculate the intersection point + // (y,z) + if ((x0 < xm1 && x1 > xm1) || (x0 > xm1 && x1 < xm1)) { + double yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0); + double zi = z0 + (xm1 - x0) * (z1 - z0) / (x1 - x0); + if (yi >= ym0 && yi < ym1 && zi >= zm0 && zi < zm1) { + if (check_intersection_point(xm1, x0, yi, y0, zi, z0, r0, min_dist)) { + ijk[0] = shape_[0]; + ijk[1] = lower_bound_index(grid_[1].begin(), grid_[1].end(), yi) + 1; + ijk[2] = lower_bound_index(grid_[2].begin(), grid_[2].end(), zi) + 1; + } + } + } + + // Check if line intersects front surface -- calculate the intersection point + // (x,z) + if ((y0 < ym1 && y1 > ym1) || (y0 > ym1 && y1 < ym1)) { + double xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0); + double zi = z0 + (ym1 - y0) * (z1 - z0) / (y1 - y0); + if (xi >= xm0 && xi < xm1 && zi >= zm0 && zi < zm1) { + if (check_intersection_point(xi, x0, ym1, y0, zi, z0, r0, min_dist)) { + ijk[0] = lower_bound_index(grid_[0].begin(), grid_[0].end(), xi) + 1; + ijk[1] = shape_[1]; + ijk[2] = lower_bound_index(grid_[2].begin(), grid_[2].end(), zi) + 1; + } + } + } + + // Check if line intersects top surface -- calculate the intersection point + // (x,y) + if ((z0 < zm1 && z1 > zm1) || (z0 > zm1 && z1 < zm1)) { + double xi = x0 + (zm1 - z0) * (x1 - x0) / (z1 - z0); + double yi = y0 + (zm1 - z0) * (y1 - y0) / (z1 - z0); + if (xi >= xm0 && xi < xm1 && yi >= ym0 && yi < ym1) { + if (check_intersection_point(xi, x0, yi, y0, zm1, z0, r0, min_dist)) { + ijk[0] = lower_bound_index(grid_[0].begin(), grid_[0].end(), xi) + 1; + ijk[1] = lower_bound_index(grid_[1].begin(), grid_[1].end(), yi) + 1; + ijk[2] = shape_[2]; + } + } + } + + return min_dist < INFTY; +} + //============================================================================== // C API functions //============================================================================== @@ -846,8 +1263,8 @@ openmc_mesh_get_dimension(int32_t index, int** dims, int* n) set_errmsg("Index in meshes array is out of bounds."); return OPENMC_E_OUT_OF_BOUNDS; } - *dims = model::meshes[index]->shape_.data(); - *n = model::meshes[index]->n_dimension_; + *dims = dynamic_cast(model::meshes[index].get())->shape_.data(); + *n = dynamic_cast(model::meshes[index].get())->n_dimension_; return 0; } @@ -862,9 +1279,9 @@ openmc_mesh_set_dimension(int32_t index, int n, const int* dims) // Copy dimension std::vector shape = {static_cast(n)}; - auto& m = model::meshes[index]; - m->shape_ = xt::adapt(dims, n, xt::no_ownership(), shape); - m->n_dimension_ = m->shape_.size(); + auto& m = *dynamic_cast(model::meshes[index].get()); + m.shape_ = xt::adapt(dims, n, xt::no_ownership(), shape); + m.n_dimension_ = m.shape_.size(); return 0; } @@ -878,16 +1295,17 @@ openmc_mesh_get_params(int32_t index, double** ll, double** ur, double** width, return OPENMC_E_OUT_OF_BOUNDS; } - auto& m = model::meshes[index]; - if (m->lower_left_.dimension() == 0) { + //auto& m = model::meshes[index]; + auto& m = *dynamic_cast(model::meshes[index].get()); + if (m.lower_left_.dimension() == 0) { set_errmsg("Mesh parameters have not been set."); return OPENMC_E_ALLOCATE; } - *ll = m->lower_left_.data(); - *ur = m->upper_right_.data(); - *width = m->width_.data(); - *n = m->n_dimension_; + *ll = m.lower_left_.data(); + *ur = m.upper_right_.data(); + *width = m.width_.data(); + *n = m.n_dimension_; return 0; } @@ -901,20 +1319,21 @@ openmc_mesh_set_params(int32_t index, int n, const double* ll, const double* ur, return OPENMC_E_OUT_OF_BOUNDS; } - auto& m = model::meshes[index]; + //auto& m = model::meshes[index]; + auto& m = *dynamic_cast(model::meshes[index].get()); std::vector shape = {static_cast(n)}; if (ll && ur) { - m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape); - m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape); - m->width_ = (m->upper_right_ - m->lower_left_) / m->shape_; + m.lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape); + m.upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape); + m.width_ = (m.upper_right_ - m.lower_left_) / m.shape_; } else if (ll && width) { - m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape); - m->width_ = xt::adapt(width, n, xt::no_ownership(), shape); - m->upper_right_ = m->lower_left_ + m->shape_ * m->width_; + m.lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape); + m.width_ = xt::adapt(width, n, xt::no_ownership(), shape); + m.upper_right_ = m.lower_left_ + m.shape_ * m.width_; } else if (ur && width) { - m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape); - m->width_ = xt::adapt(width, n, xt::no_ownership(), shape); - m->lower_left_ = m->upper_right_ - m->shape_ * m->width_; + m.upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape); + m.width_ = xt::adapt(width, n, xt::no_ownership(), shape); + m.lower_left_ = m.upper_right_ - m.shape_ * m.width_; } else { set_errmsg("At least two parameters must be specified."); return OPENMC_E_INVALID_ARGUMENT; @@ -930,8 +1349,21 @@ openmc_mesh_set_params(int32_t index, int n, const double* ll, const double* ur, void read_meshes(pugi::xml_node root) { for (auto node : root.children("mesh")) { + std::string mesh_type; + if (check_for_node(node, "type")) { + mesh_type = get_node_value(node, "type", true, true); + } else { + mesh_type = "regular"; + } + // Read mesh and add to vector - model::meshes.push_back(std::make_unique(node)); + if (mesh_type == "regular") { + model::meshes.push_back(std::make_unique(node)); + } else if (mesh_type == "rectilinear") { + model::meshes.push_back(std::make_unique(node)); + } else { + fatal_error("Invalid mesh type: " + mesh_type); + } // Map ID to position in vector model::mesh_map[model::meshes.back()->id_] = model::meshes.size() - 1; diff --git a/src/plot.cpp b/src/plot.cpp index 57466eca1b..070726bcbb 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -707,12 +707,12 @@ void draw_mesh_lines(Plot pl, ImageData& data) Position width = ur_plot - ll_plot; - auto& m = model::meshes[pl.index_meshlines_mesh_]; + auto& m = *dynamic_cast(model::meshes[pl.index_meshlines_mesh_].get()); int ijk_ll[3], ijk_ur[3]; bool in_mesh; - m->get_indices(ll_plot, &(ijk_ll[0]), &in_mesh); - m->get_indices(ur_plot, &(ijk_ur[0]), &in_mesh); + m.get_indices(ll_plot, &(ijk_ll[0]), &in_mesh); + m.get_indices(ur_plot, &(ijk_ur[0]), &in_mesh); // Fortran/C++ index correction ijk_ur[0]++; ijk_ur[1]++; ijk_ur[2]++; @@ -722,13 +722,13 @@ void draw_mesh_lines(Plot pl, ImageData& data) for (int i = ijk_ll[outer]; i <= ijk_ur[outer]; i++) { for (int j = ijk_ll[inner]; j <= ijk_ur[inner]; j++) { // check if we're in the mesh for this ijk - if (i > 0 && i <= m->shape_[outer] && j >0 && j <= m->shape_[inner] ) { + if (i > 0 && i <= m.shape_[outer] && j >0 && j <= m.shape_[inner] ) { int outrange[3], inrange[3]; // get xyz's of lower left and upper right of this mesh cell - r_ll[outer] = m->lower_left_[outer] + m->width_[outer] * (i - 1); - r_ll[inner] = m->lower_left_[inner] + m->width_[inner] * (j - 1); - r_ur[outer] = m->lower_left_[outer] + m->width_[outer] * i; - r_ur[inner] = m->lower_left_[inner] + m->width_[inner] * j; + r_ll[outer] = m.lower_left_[outer] + m.width_[outer] * (i - 1); + r_ll[inner] = m.lower_left_[inner] + m.width_[inner] * (j - 1); + r_ur[outer] = m.lower_left_[outer] + m.width_[outer] * i; + r_ur[inner] = m.lower_left_[inner] + m.width_[inner] * j; // map the xyz ranges to pixel ranges double frac = (r_ll[outer] - ll_plot[outer]) / width[outer]; diff --git a/src/settings.cpp b/src/settings.cpp index 4c041a080d..27f8c31d28 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -508,7 +508,7 @@ void read_settings_xml() } if (index_entropy_mesh >= 0) { - auto& m = *model::meshes[index_entropy_mesh]; + auto& m = *dynamic_cast(model::meshes[index_entropy_mesh].get()); if (m.shape_.dimension() == 0) { // If the user did not specify how many mesh cells are to be used in // each direction, we automatically determine an appropriate number of diff --git a/src/tallies/filter_mesh.cpp b/src/tallies/filter_mesh.cpp index f69a966523..9658e552ec 100644 --- a/src/tallies/filter_mesh.cpp +++ b/src/tallies/filter_mesh.cpp @@ -55,27 +55,43 @@ MeshFilter::to_statepoint(hid_t filter_group) const std::string MeshFilter::text_label(int bin) const { - auto& mesh = *model::meshes[mesh_]; - int n_dim = mesh.n_dimension_; + if (auto* mesh = dynamic_cast(model::meshes[mesh_].get())) { + int n_dim = mesh->n_dimension_; - int ijk[n_dim]; - mesh.get_indices_from_bin(bin, ijk); + int ijk[n_dim]; + mesh->get_indices_from_bin(bin, ijk); - std::stringstream out; - out << "Mesh Index (" << ijk[0]; - if (n_dim > 1) out << ", " << ijk[1]; - if (n_dim > 2) out << ", " << ijk[2]; - out << ")"; + std::stringstream out; + out << "Mesh Index (" << ijk[0]; + if (n_dim > 1) out << ", " << ijk[1]; + if (n_dim > 2) out << ", " << ijk[2]; + out << ")"; - return out.str(); + return out.str(); + + } else if (auto* mesh = dynamic_cast(model::meshes[mesh_].get())) { + int ijk[3]; + mesh->get_indices_from_bin(bin, ijk); + + std::stringstream out; + out << "Mesh Index (" << ijk[0] << ", " << ijk[1] << ", " << ijk[2] << ")"; + + return out.str(); + } } void MeshFilter::set_mesh(int32_t mesh) { mesh_ = mesh; - n_bins_ = 1; - for (auto dim : model::meshes[mesh_]->shape_) n_bins_ *= dim; + auto* m_ptr = model::meshes[mesh_].get(); + if (auto* m = dynamic_cast(m_ptr)) { + n_bins_ = 1; + for (auto dim : m->shape_) n_bins_ *= dim; + } else if (auto* m = dynamic_cast(m_ptr)) { + n_bins_ = 1; + for (auto dim : m->shape_) n_bins_ *= dim; + } } //============================================================================== diff --git a/src/tallies/filter_meshsurface.cpp b/src/tallies/filter_meshsurface.cpp index 57f10b09c3..8f4ae4e356 100644 --- a/src/tallies/filter_meshsurface.cpp +++ b/src/tallies/filter_meshsurface.cpp @@ -18,7 +18,7 @@ MeshSurfaceFilter::get_all_bins(const Particle* p, int estimator, std::string MeshSurfaceFilter::text_label(int bin) const { - auto& mesh = *model::meshes[mesh_]; + auto& mesh = *dynamic_cast(model::meshes[mesh_].get()); int n_dim = mesh.n_dimension_; // Get flattend mesh index and surface index. @@ -75,8 +75,9 @@ void MeshSurfaceFilter::set_mesh(int32_t mesh) { mesh_ = mesh; - n_bins_ = 4 * model::meshes[mesh_]->n_dimension_; - for (auto dim : model::meshes[mesh_]->shape_) n_bins_ *= dim; + auto &m = *dynamic_cast(model::meshes[mesh_].get()); + n_bins_ = 4 * m.n_dimension_; + for (auto dim : m.shape_) n_bins_ *= dim; } //============================================================================== From c22d5cbc156362d9b66e0f5407453de45fca1569 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 26 May 2019 02:01:17 -0500 Subject: [PATCH 044/151] Threaded ray hist in 10 lines. --- src/cell.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/cell.cpp b/src/cell.cpp index 59057e0840..196225db60 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -608,18 +608,30 @@ CSGCell::contains_complex(Position r, Direction u, int32_t on_surface) const // DAGMC Cell implementation //============================================================================== #ifdef DAGMC + +extern moab::DagMC::RayHistory history; +extern Direction last_dir; +#pragma omp threadprivate(history, last_dir) +moab::DagMC::RayHistory history; +Direction last_dir; + DAGCell::DAGCell() : Cell{} {}; std::pair DAGCell::distance(Position r, Direction u, int32_t on_surface) const { + if (u != last_dir) { + history.reset(); + last_dir = u; + } + moab::ErrorCode rval; moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_); moab::EntityHandle hit_surf; double dist; double pnt[3] = {r.x, r.y, r.z}; double dir[3] = {u.x, u.y, u.z}; - rval = dagmc_ptr_->ray_fire(vol, pnt, dir, hit_surf, dist); + rval = dagmc_ptr_->ray_fire(vol, pnt, dir, hit_surf, dist, &history); MB_CHK_ERR_CONT(rval); int surf_idx; if (hit_surf != 0) { From 90abce144c3983bbfc563434ccf6ccf2798778f1 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 26 May 2019 14:40:33 -0500 Subject: [PATCH 045/151] Properly setting distance for lost particle. Updating history reset condition. --- src/cell.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cell.cpp b/src/cell.cpp index 196225db60..e5d0f2f007 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -620,11 +620,18 @@ DAGCell::DAGCell() : Cell{} {}; std::pair DAGCell::distance(Position r, Direction u, int32_t on_surface) const { - if (u != last_dir) { + + // if we've changed direction, reset the history and update last direction + if (u != last_dir || on_surface == 0) { history.reset(); last_dir = u; } + // always reset the history if we're on the surface + // if (on_surface == 0) { + // history.reset(); + // } + moab::ErrorCode rval; moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_); moab::EntityHandle hit_surf; @@ -638,6 +645,7 @@ DAGCell::distance(Position r, Direction u, int32_t on_surface) const surf_idx = dagmc_ptr_->index_by_handle(hit_surf); } else { // indicate that particle is lost surf_idx = -1; + dist = INFINITY; } return {dist, surf_idx}; From 4ef3e307559d8404396b837ccf3ec61caa1f6931 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Mon, 27 May 2019 10:54:44 +0300 Subject: [PATCH 046/151] lattice translation with S.Harper basis --- src/lattice.cpp | 251 ++++++++++++++++++++++++------------------------ 1 file changed, 127 insertions(+), 124 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index af025033ac..5a3fea58a2 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -694,134 +694,137 @@ std::pair> HexLattice::distance(Position r, Direction u, const std::array& i_xyz) const { - double cosa,sina,u_xy; - // Index + 1 of translation direction - int i_trans {1}; - xt :: xarray translation_matrix; - if (hextype==0){ - cosa = std::sqrt(3.0) / 2.0; - sina = 0.5; - u_xy = u.y; - //Translation matrix 4x3 {{beta},{gamma},{oy},{oz}} for OY case - translation_matrix = {{1, 0, 0}, - {1, -1, 0}, - {0, 1, 0}, - {0, 0, 1}}; - } else { - cosa = 0.5; - sina = std::sqrt(3.0) / 2.0; - u_xy = u.x; - //Translation matrix 4x3 {{beta},{gamma},{ox},{oz}} for OX case - translation_matrix = {{0, 1, 0}, - {1, -1, 0}, - {1, 0, 0}, - {0, 0, 1}}; - } - double beta_dir = u.x * cosa + u.y * sina; - double gamma_dir = u.x * cosa - u.y * sina; + //Short description + //OY - orientation: + // basis0 = (1, 0) + // basis1 = (-1/sqrt(3), 1) = +120 degrees from basis0 + // beta = (sqrt(3)/2, 1/2) = +30 degrees from basis0 + // gamma = (sqrt(3)/2, -1/2) = -60 degrees from beta + // delta = (0, 1) = +60 degrees from beta + //OX - orientation: + // basis0 = (1/sqrt(3), -1) + // basis1 = (0, 1) = +120 degrees from basis0 + // beta = (1, 0) = +30 degrees from basis0 + // gamma = (1/2, -sqrt(3)/2) = -60 degrees from beta + // delta = (1/2, sqrt(3)/2) = +60 degrees from beta + //OZ be considered separetly + double beta_dir; + double gamma_dir; + double delta_dir; + if (hextype==0){ + beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0; + gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0; + delta_dir = u.y; + } + else{ + beta_dir = u.x; + gamma_dir = u.x / 2.0 - u.y * std::sqrt(3.0) / 2.0; + delta_dir = u.x / 2.0 + u.y * std::sqrt(3.0) / 2.0; + } - // Note that hexagonal lattice distance calculations are performed - // using the particle's coordinates relative to the neighbor lattice - // cells, not relative to the particle's current cell. This is done - // because there is significant disagreement between neighboring cells - // on where the lattice boundary is due to finite precision issues. + // Note that hexagonal lattice distance calculations are performed + // using the particle's coordinates relative to the neighbor lattice + // cells, not relative to the particle's current cell. This is done + // because there is significant disagreement between neighboring cells + // on where the lattice boundary is due to finite precision issues. - //beta direction - double d {INFTY}; - std::array lattice_trans; - double edge = -copysign(0.5*pitch_[0], beta_dir); // Oncoming edge - Position r_t; - if (beta_dir > 0) { - const std::array i_xyz_t {i_xyz[0]+translation_matrix(0,0), i_xyz[1]+translation_matrix(0,1), i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-translation_matrix(0,0), i_xyz[1]-translation_matrix(0,1), i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double beta = r_t.x * cosa + r_t.y * sina; - if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { - d = (edge - beta) / beta_dir; - if (beta_dir > 0) { - i_trans = 1; - } else { - i_trans = -1; - } - } - //gamma direction - edge = -copysign(0.5*pitch_[0], gamma_dir); - if (gamma_dir > 0) { - const std::array i_xyz_t {i_xyz[0]+translation_matrix(1,0), i_xyz[1]+translation_matrix(1,1), i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-translation_matrix(1,0), i_xyz[1]-translation_matrix(1,1), i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double gamma = r_t.x * cosa - r_t.y * sina; - if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { - double this_d = (edge - gamma) / gamma_dir; - if (this_d < d) { - if (gamma_dir > 0) { - i_trans = 2; - } else { - i_trans = -2; - } - d = this_d; - } - } - //y or x direction - edge = -copysign(0.5*pitch_[0], u_xy); - if (u_xy > 0) { - const std::array i_xyz_t {i_xyz[0]+translation_matrix(2,0), i_xyz[1]+translation_matrix(2,1), i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-translation_matrix(2,0), i_xyz[1]-translation_matrix(2,1), i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double r_txy; - if (hextype==0){ - r_txy = r_t.y; - } - else { - r_txy = r_t.x; - } - if ((std::abs(r_txy - edge) > FP_PRECISION) && u_xy != 0) { - double this_d = (edge - r_txy) / u_xy; - if (this_d < d) { - if (u_xy > 0) { - i_trans = 3; - } else { - i_trans = -3; - } - d = this_d; - } - } - // Top and bottom sides - if (is_3d_) { - double z = r.z; - double z0 {copysign(0.5 * pitch_[1], u.z)}; - if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) { - double this_d = (z0 - z) / u.z; - if (this_d < d) { - d = this_d; - if (u.z > 0) { - i_trans = 4; - } else { - i_trans = -4; - } - d = this_d; - } - } - } + //beta direction + double d {INFTY}; + std::array lattice_trans; + double edge = -copysign(0.5*pitch_[0], beta_dir); // Oncoming edge + double beta; + Position r_t; + if (beta_dir > 0) { + const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1], i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1], i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + if (hextype==0){ + beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0;} + else{ + beta = r_t.x ;} + if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { + d = (edge - beta) / beta_dir; + if (beta_dir > 0) { + lattice_trans = {1, 0, 0}; + } else { + lattice_trans = {-1, 0, 0}; + } + } - for (int i=0;i<3;i++){ - if (i_trans < 0){ - lattice_trans[i] = -translation_matrix(-(i_trans+1),i); - }else{ - lattice_trans[i] = translation_matrix((i_trans-1),i); - } - } + // gamma direction. + edge = -copysign(0.5*pitch_[0], gamma_dir); + if (gamma_dir > 0) { + const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1]-1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1]+1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + double gamma; + if (hextype==0){ + gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0;} + else{ + gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0;} + if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { + double this_d = (edge - gamma) / gamma_dir; + if (this_d < d) { + if (gamma_dir > 0) { + lattice_trans = {1, -1, 0}; + } else { + lattice_trans = {-1, 1, 0}; + } + d = this_d; + } + } - return {d, lattice_trans}; + // delta directions. + edge = -copysign(0.5*pitch_[0], delta_dir); + if (delta_dir > 0) { + const std::array i_xyz_t {i_xyz[0], i_xyz[1]+1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0], i_xyz[1]-1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + double delta; + if (hextype==0){ + delta = r_t.y ;} + else{ + delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0;} + if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) { + double this_d = (edge - delta) / delta_dir; + if (this_d < d) { + if (delta_dir > 0) { + lattice_trans = {0, 1, 0}; + } else { + lattice_trans = {0, -1, 0}; + } + d = this_d; + } + } + + // Top and bottom sides + if (is_3d_) { + double z = r.z; + double z0 {copysign(0.5 * pitch_[1], u.z)}; + if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) { + double this_d = (z0 - z) / u.z; + if (this_d < d) { + d = this_d; + if (u.z > 0) { + lattice_trans = {0, 0, 1}; + } else { + lattice_trans = {0, 0, -1}; + } + d = this_d; + } + } + } + + return {d, lattice_trans}; } //============================================================================== From f8690b87a0f901b212689b2c817f2d3f9ad61837 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 27 May 2019 14:59:05 -0400 Subject: [PATCH 047/151] Modify code to comply with the style guide --- include/openmc/lattice.h | 18 +- src/lattice.cpp | 627 +++++++++++++++++++-------------------- 2 files changed, 322 insertions(+), 323 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index afec39b22e..449b92164f 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -248,30 +248,32 @@ public: bool are_valid_indices(const int i_xyz[3]) const; - std::pair> distance(Position r, Direction u, const std::array& i_xyz) const; + std::pair> + distance(Position r, Direction u, const std::array& i_xyz) const; std::array get_indices(Position r, Direction u) const; - Position get_local_position(Position r, const std::array i_xyz) const; bool is_valid_index(int indx) const; - //DR fill lattice with OY by default orientation - void fill_lattice_oy(std::vector univ_words); - //DR fill lattice with OX orientation - void fill_lattice_ox(std::vector univ_words); int32_t& offset(int map, const int i_xyz[3]); std::string index_to_string(int indx) const; - //TODO: find a desicion for compatible old hdf5 file without orientation + void to_hdf5_inner(hid_t group_id) const; private: + //! Fill universes_ vector for OY orientation + void fill_lattice_oy(std::vector univ_words); + + //! Fill universes_ vector for OX orientation + void fill_lattice_ox(std::vector univ_words); + int n_rings_; //!< Number of radial tile positions int n_axial_; //!< Number of axial tile positions - int hextype; //!DR type of hexagonal lattice orientation //0 - OY by default;1-OX + int hextype; //!< Type of hexagonal lattice orientation //0 - OY by default;1-OX Position center_; //!< Global center of lattice std::array pitch_; //!< Lattice tile width and height }; diff --git a/src/lattice.cpp b/src/lattice.cpp index 5a3fea58a2..314670939d 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -430,8 +430,6 @@ HexLattice::HexLattice(pugi::xml_node lat_node) { type_ = LatticeType::hex; - hextype = 0;//DR a default OY orientation of lattice - // Read the number of lattice cells in each dimension. n_rings_ = std::stoi(get_node_value(lat_node, "n_rings")); if (check_for_node(lat_node, "n_axial")) { @@ -442,10 +440,19 @@ HexLattice::HexLattice(pugi::xml_node lat_node) is_3d_ = false; } - //DR Check if "OX" orientation desc in node + // Read the lattice orientation. Default to OY. if (check_for_node(lat_node, "orient")) { - std :: string orientation = get_node_value(lat_node, "orient"); - if (orientation == "OX") {hextype=1;} + std::string orientation = get_node_value(lat_node, "orient"); + if (orientation == "OY") { + hextype = 0; + } else if (orientation == "OX") { + hextype = 1; + } else { + fatal_error("Unrecognized orientation '" + orientation + + "' for lattice " + std::to_string(id_)); + } + } else { + hextype = 0; } // Read the lattice center. @@ -496,167 +503,159 @@ HexLattice::HexLattice(pugi::xml_node lat_node) // input and the order that they will be stored in the skewed array so // the following code walks a set of index values across the skewed array // in a manner that matches the input order. Note that i_x = 0, i_a = 0 - // or i_a=0 , i_y = 0 corresponds to the center of the hexagonal lattice. + // or i_a = 0, i_y = 0 corresponds to the center of the hexagonal lattice. universes_.resize((2*n_rings_-1) * (2*n_rings_-1) * n_axial_, C_NONE); if (hextype==0) { fill_lattice_oy(univ_words); - } else - { + } else { fill_lattice_ox(univ_words); } } -// DR fill universes in OX orientation + //============================================================================== + void -HexLattice::fill_lattice_ox(std::vector univ_words){ +HexLattice::fill_lattice_ox(std::vector univ_words) +{ + int input_index = 0; + for (int m = 0; m < n_axial_; m++) { + // Initialize lattice indecies. + int i_a = -(n_rings_ - 1); + int i_y = n_rings_ - 1; - int input_index = 0; - for (int m = 0; m < n_axial_; m++) { - // Initialize lattice indecies. - int i_a = -(n_rings_ - 1); - int i_y = n_rings_ - 1; + // Map upper region of hexagonal lattice which is found in the + // first n_rings-1 rows of the input. + for (int k = 0; k < n_rings_-1; k++) { - - //DR Map upper region of hexagonal lattice which is found in the - // first n_rings-1 rows of the input. - for (int k = 0; k < n_rings_-1; k++) { - - // Iterate over the input columns. - for (int j = 0; j < k+n_rings_; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_y+n_rings_-1) - + (i_a+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - //DR Move to the next right neighbour cell - i_a += 1; - - } - - // Return the lattice index to the start of the current row. - i_a = -(n_rings_ - 1); - i_y -= 1; - } - - // Map the lower region from the centerline of cart to down side - for (int k = 0; k < n_rings_; k++) { - // Walk the index to the lower-right neighbor of the last row start. - i_a = -(n_rings_ - 1) + k; - - // Iterate over the input columns. - for (int j = 0; j < 2*n_rings_-k-1; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_y+n_rings_-1) - + (i_a+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - //DR Move to the next right neighbour cell - i_a += 1; - - } - - // Return lattice index to start of current row. - i_y -= 1; - } + // Iterate over the input columns. + for (int j = 0; j < k+n_rings_; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_y+n_rings_-1) + + (i_a+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Move to the next right neighbour cell + i_a += 1; } + + // Return the lattice index to the start of the current row. + i_a = -(n_rings_ - 1); + i_y -= 1; + } + + // Map the lower region from the centerline of cart to down side + for (int k = 0; k < n_rings_; k++) { + // Walk the index to the lower-right neighbor of the last row start. + i_a = -(n_rings_ - 1) + k; + + // Iterate over the input columns. + for (int j = 0; j < 2*n_rings_-k-1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_y+n_rings_-1) + + (i_a+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Move to the next right neighbour cell + i_a += 1; + } + + // Return lattice index to start of current row. + ki_y -= 1; + } + } } //============================================================================== - -// DR fill universes in OY orientation -//============================================================================== void -HexLattice::fill_lattice_oy(std::vector univ_words){ +HexLattice::fill_lattice_oy(std::vector univ_words) +{ + int input_index = 0; + for (int m = 0; m < n_axial_; m++) { + // Initialize lattice indecies. + int i_x = 1; + int i_a = n_rings_ - 1; - int input_index = 0; - for (int m = 0; m < n_axial_; m++) { - // Initialize lattice indecies. - int i_x = 1; - int i_a = n_rings_ - 1; + // Map upper triangular region of hexagonal lattice which is found in the + // first n_rings-1 rows of the input. + for (int k = 0; k < n_rings_-1; k++) { + // Walk the index to lower-left neighbor of last row start. + i_x -= 1; - // Map upper triangular region of hexagonal lattice which is found in the - // first n_rings-1 rows of the input. - for (int k = 0; k < n_rings_-1; k++) { - // Walk the index to lower-left neighbor of last row start. - i_x -= 1; - - // Iterate over the input columns. - for (int j = 0; j < k+1; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } - - // Return the lattice index to the start of the current row. - i_x -= 2 * (k+1); - i_a += (k+1); - } - - // Map the middle square region of the hexagonal lattice which is found in - // the next 2*n_rings-1 rows of the input. - for (int k = 0; k < 2*n_rings_-1; k++) { - if ((k % 2) == 0) { - // Walk the index to the lower-left neighbor of the last row start. - i_x -= 1; - } else { - // Walk the index to the lower-right neighbor of the last row start. - i_x += 1; - i_a -= 1; - } - - // Iterate over the input columns. - for (int j = 0; j < n_rings_ - (k % 2); j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } - - // Return the lattice index to the start of the current row. - i_x -= 2*(n_rings_ - (k % 2)); - i_a += n_rings_ - (k % 2); - } - - // Map the lower triangular region of the hexagonal lattice. - for (int k = 0; k < n_rings_-1; k++) { - // Walk the index to the lower-right neighbor of the last row start. - i_x += 1; - i_a -= 1; - - // Iterate over the input columns. - for (int j = 0; j < n_rings_-k-1; j++) { - int indx = (2*n_rings_-1)*(2*n_rings_-1) * m - + (2*n_rings_-1) * (i_a+n_rings_-1) - + (i_x+n_rings_-1); - universes_[indx] = std::stoi(univ_words[input_index]); - input_index++; - // Walk the index to the right neighbor (which is not adjacent). - i_x += 2; - i_a -= 1; - } - - // Return lattice index to start of current row. - i_x -= 2*(n_rings_ - k - 1); - i_a += n_rings_ - k - 1; - } + // Iterate over the input columns. + for (int j = 0; j < k+1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; } -} -//============================================================================== + // Return the lattice index to the start of the current row. + i_x -= 2 * (k+1); + i_a += (k+1); + } + + // Map the middle square region of the hexagonal lattice which is found in + // the next 2*n_rings-1 rows of the input. + for (int k = 0; k < 2*n_rings_-1; k++) { + if ((k % 2) == 0) { + // Walk the index to the lower-left neighbor of the last row start. + i_x -= 1; + } else { + // Walk the index to the lower-right neighbor of the last row start. + i_x += 1; + i_a -= 1; + } + + // Iterate over the input columns. + for (int j = 0; j < n_rings_ - (k % 2); j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } + + // Return the lattice index to the start of the current row. + i_x -= 2*(n_rings_ - (k % 2)); + i_a += n_rings_ - (k % 2); + } + + // Map the lower triangular region of the hexagonal lattice. + for (int k = 0; k < n_rings_-1; k++) { + // Walk the index to the lower-right neighbor of the last row start. + i_x += 1; + i_a -= 1; + + // Iterate over the input columns. + for (int j = 0; j < n_rings_-k-1; j++) { + int indx = (2*n_rings_-1)*(2*n_rings_-1) * m + + (2*n_rings_-1) * (i_a+n_rings_-1) + + (i_x+n_rings_-1); + universes_[indx] = std::stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } + + // Return lattice index to start of current row. + i_x -= 2*(n_rings_ - k - 1); + i_a += n_rings_ - k - 1; + } + } +} //============================================================================== @@ -689,145 +688,148 @@ HexLattice::are_valid_indices(const int i_xyz[3]) const && (i_xyz[2] < n_axial_)); } -//DR wrapper HexLattice function both for OX and OY orientation +//============================================================================== + std::pair> HexLattice::distance(Position r, Direction u, const std::array& i_xyz) const { - //Short description - //OY - orientation: - // basis0 = (1, 0) - // basis1 = (-1/sqrt(3), 1) = +120 degrees from basis0 - // beta = (sqrt(3)/2, 1/2) = +30 degrees from basis0 - // gamma = (sqrt(3)/2, -1/2) = -60 degrees from beta - // delta = (0, 1) = +60 degrees from beta - //OX - orientation: - // basis0 = (1/sqrt(3), -1) - // basis1 = (0, 1) = +120 degrees from basis0 - // beta = (1, 0) = +30 degrees from basis0 - // gamma = (1/2, -sqrt(3)/2) = -60 degrees from beta - // delta = (1/2, sqrt(3)/2) = +60 degrees from beta - //OZ be considered separetly - double beta_dir; - double gamma_dir; - double delta_dir; - if (hextype==0){ - beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0; - gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0; - delta_dir = u.y; - } - else{ - beta_dir = u.x; - gamma_dir = u.x / 2.0 - u.y * std::sqrt(3.0) / 2.0; - delta_dir = u.x / 2.0 + u.y * std::sqrt(3.0) / 2.0; - } + // Short description of the direction vectors used here. The beta, gamma, and + // delta vectors point towards the flat sides of each hexagonal tile. + // OY - orientation: + // basis0 = (1, 0) + // basis1 = (-1/sqrt(3), 1) = +120 degrees from basis0 + // beta = (sqrt(3)/2, 1/2) = +30 degrees from basis0 + // gamma = (sqrt(3)/2, -1/2) = -60 degrees from beta + // delta = (0, 1) = +60 degrees from beta + // OX - orientation: + // basis0 = (1/sqrt(3), -1) + // basis1 = (0, 1) = +120 degrees from basis0 + // beta = (1, 0) = +30 degrees from basis0 + // gamma = (1/2, -sqrt(3)/2) = -60 degrees from beta + // delta = (1/2, sqrt(3)/2) = +60 degrees from beta + // OZ is considered separately + double beta_dir; + double gamma_dir; + double delta_dir; + if (hextype == 0){ + beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0; + gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0; + delta_dir = u.y; + } else { + beta_dir = u.x; + gamma_dir = u.x / 2.0 - u.y * std::sqrt(3.0) / 2.0; + delta_dir = u.x / 2.0 + u.y * std::sqrt(3.0) / 2.0; + } - // Note that hexagonal lattice distance calculations are performed - // using the particle's coordinates relative to the neighbor lattice - // cells, not relative to the particle's current cell. This is done - // because there is significant disagreement between neighboring cells - // on where the lattice boundary is due to finite precision issues. + // Note that hexagonal lattice distance calculations are performed + // using the particle's coordinates relative to the neighbor lattice + // cells, not relative to the particle's current cell. This is done + // because there is significant disagreement between neighboring cells + // on where the lattice boundary is due to finite precision issues. - //beta direction - double d {INFTY}; - std::array lattice_trans; - double edge = -copysign(0.5*pitch_[0], beta_dir); // Oncoming edge - double beta; - Position r_t; - if (beta_dir > 0) { - const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1], i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1], i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - if (hextype==0){ - beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0;} - else{ - beta = r_t.x ;} - if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { - d = (edge - beta) / beta_dir; - if (beta_dir > 0) { - lattice_trans = {1, 0, 0}; - } else { - lattice_trans = {-1, 0, 0}; - } - } + // beta direction + double d {INFTY}; + std::array lattice_trans; + double edge = -copysign(0.5*pitch_[0], beta_dir); // Oncoming edge + Position r_t; + if (beta_dir > 0) { + const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1], i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1], i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + double beta; + if (hextype == 0) { + beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0; + } else { + beta = r_t.x; + } + if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { + d = (edge - beta) / beta_dir; + if (beta_dir > 0) { + lattice_trans = {1, 0, 0}; + } else { + lattice_trans = {-1, 0, 0}; + } + } - // gamma direction. - edge = -copysign(0.5*pitch_[0], gamma_dir); - if (gamma_dir > 0) { - const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1]-1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1]+1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double gamma; - if (hextype==0){ - gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0;} - else{ - gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0;} - if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { - double this_d = (edge - gamma) / gamma_dir; - if (this_d < d) { - if (gamma_dir > 0) { - lattice_trans = {1, -1, 0}; - } else { - lattice_trans = {-1, 1, 0}; - } - d = this_d; - } - } + // gamma direction + edge = -copysign(0.5*pitch_[0], gamma_dir); + if (gamma_dir > 0) { + const std::array i_xyz_t {i_xyz[0]+1, i_xyz[1]-1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0]-1, i_xyz[1]+1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + double gamma; + if (hextype == 0) { + gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0; + } else { + gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0; + } + if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { + double this_d = (edge - gamma) / gamma_dir; + if (this_d < d) { + if (gamma_dir > 0) { + lattice_trans = {1, -1, 0}; + } else { + lattice_trans = {-1, 1, 0}; + } + d = this_d; + } + } - // delta directions. - edge = -copysign(0.5*pitch_[0], delta_dir); - if (delta_dir > 0) { - const std::array i_xyz_t {i_xyz[0], i_xyz[1]+1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } else { - const std::array i_xyz_t {i_xyz[0], i_xyz[1]-1, i_xyz[2]}; - r_t = get_local_position(r, i_xyz_t); - } - double delta; - if (hextype==0){ - delta = r_t.y ;} - else{ - delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0;} - if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) { - double this_d = (edge - delta) / delta_dir; - if (this_d < d) { - if (delta_dir > 0) { - lattice_trans = {0, 1, 0}; - } else { - lattice_trans = {0, -1, 0}; - } - d = this_d; - } - } + // delta direction + edge = -copysign(0.5*pitch_[0], delta_dir); + if (delta_dir > 0) { + const std::array i_xyz_t {i_xyz[0], i_xyz[1]+1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } else { + const std::array i_xyz_t {i_xyz[0], i_xyz[1]-1, i_xyz[2]}; + r_t = get_local_position(r, i_xyz_t); + } + double delta; + if (hextype == 0){ + delta = r_t.y; + } else { + delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0; + } + if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) { + double this_d = (edge - delta) / delta_dir; + if (this_d < d) { + if (delta_dir > 0) { + lattice_trans = {0, 1, 0}; + } else { + lattice_trans = {0, -1, 0}; + } + d = this_d; + } + } - // Top and bottom sides - if (is_3d_) { - double z = r.z; - double z0 {copysign(0.5 * pitch_[1], u.z)}; - if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) { - double this_d = (z0 - z) / u.z; - if (this_d < d) { - d = this_d; - if (u.z > 0) { - lattice_trans = {0, 0, 1}; - } else { - lattice_trans = {0, 0, -1}; - } - d = this_d; - } - } - } + // Top and bottom sides + if (is_3d_) { + double z = r.z; + double z0 {copysign(0.5 * pitch_[1], u.z)}; + if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) { + double this_d = (z0 - z) / u.z; + if (this_d < d) { + d = this_d; + if (u.z > 0) { + lattice_trans = {0, 0, 1}; + } else { + lattice_trans = {0, 0, -1}; + } + d = this_d; + } + } + } - return {d, lattice_trans}; + return {d, lattice_trans}; } -//============================================================================== //============================================================================== std::array @@ -855,25 +857,23 @@ HexLattice::get_indices(Position r, Direction u) const } } - int i1{};// in case of OY orientation such index means x - coordinate in OY - alpha - int i2{};// in case of OX orientation such index means alpha - coordinate in OY - y - if (hextype==0){//DR type implement OY default behaviour - // Convert coordinates into skewed bases. The (x, alpha) basis is used to - // find the index of the global coordinates to within 4 cells. - double alpha = r_o.y - r_o.x / std::sqrt(3.0); - i1 = std::floor(r_o.x / (0.5*std::sqrt(3.0) * pitch_[0])); - i2 = std::floor(alpha / pitch_[0]);} - else{//DR type implement OX (alpha,y) basis - // Convert coordinates into skewed bases. The (alpha,y) basis is used to - // find the index of the global coordinates to within 4 cells. - double alpha = r_o.y - r_o.x * std::sqrt(3.0); - - i1 = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0])); - i2 = std::floor(r_o.y / (0.5*std::sqrt(3.0) * pitch_[0])); + int i1, i2; + if (hextype == 0) { + // Convert coordinates into skewed bases. The (x, alpha) basis is used to + // find the index of the global coordinates to within 4 cells. + double alpha = r_o.y - r_o.x / std::sqrt(3.0); + i1 = std::floor(r_o.x / (0.5*std::sqrt(3.0) * pitch_[0])); + i2 = std::floor(alpha / pitch_[0]); + } else { + // Convert coordinates into skewed bases. The (alpha, y) basis is used to + // find the index of the global coordinates to within 4 cells. + double alpha = r_o.y - r_o.x * std::sqrt(3.0); + i1 = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0])); + i2 = std::floor(r_o.y / (0.5*std::sqrt(3.0) * pitch_[0])); } - // Add offset to indices (the center cell is (i_x/i_alpha, i_alpha/i_y) = (0, 0) for OY/OX - // orientation but the array is offset so that the indices never go below 0). + // Add offset to indices (the center cell is (i1, i2) = (0, 0) but + // the array is offset so that the indices never go below 0). i1 += n_rings_-1; i2 += n_rings_-1; @@ -936,32 +936,30 @@ Position HexLattice::get_local_position(Position r, const std::array i_xyz) const { - // DR for OY implementation - if (hextype == 0){ - // x_l = x_g - (center + pitch_x*cos(30)*index_x) - r.x -= center_.x + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; - // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) - r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] - + (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0); - if (is_3d_) { - r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; - } + if (hextype == 0) { + // x_l = x_g - (center + pitch_x*cos(30)*index_x) + r.x -= center_.x + + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; + // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) + r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] + + (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0); + if (is_3d_) { + r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; + } - } - else {// DR for OX implementation + } else { + // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y) + r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] + + (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0); + // y_l = y_g - (center + pitch_y*cos(30)*index_y) + r.y -= center_.y + + std::sqrt(3.0)/2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0]; + if (is_3d_) { + r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; + } + } - // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y) - r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] - + (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0); - // y_l = y_g - (center + pitch_y*cos(30)*index_y) - r.y -= center_.y + std::sqrt(3.0)/2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0]; - - if (is_3d_) { - r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; - } - - } - return r; + return r; } //============================================================================== @@ -1018,11 +1016,10 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_string(lat_group, "type", "hexagonal", false); write_dataset(lat_group, "n_rings", n_rings_); write_dataset(lat_group, "n_axial", n_axial_); - if (hextype == 0){ - write_string(lat_group, "orientation", "oy",false); - } - else { - write_string(lat_group, "orientation", "ox",false); + if (hextype == 0) { + write_string(lat_group, "orientation", "oy", false); + } else { + write_string(lat_group, "orientation", "ox", false); } if (is_3d_) { write_dataset(lat_group, "pitch", pitch_); From 2394920f7dc11a12294167f3638c3ed0749a88e3 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 27 May 2019 15:03:00 -0400 Subject: [PATCH 048/151] Remove TINY_BIT to reestablish #1211 changes --- src/lattice.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index 314670939d..40f30af2c7 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -835,12 +835,6 @@ const std::array HexLattice::get_indices(Position r, Direction u) const { - - // The implementation for HexLattice currently doesn't use direction - // information. As a result, we move the position slightly forward to - // determine what lattice index the particle is most likely to be in. - r += TINY_BIT * u; - // Offset the xyz by the lattice center. Position r_o {r.x - center_.x, r.y - center_.y, r.z}; if (is_3d_) {r_o.z -= center_.z;} From e442f521029df4b63c494b2b453a669832426bab Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 27 May 2019 15:16:37 -0400 Subject: [PATCH 049/151] Use enum for hex lattice orientation --- include/openmc/lattice.h | 6 +++++- src/lattice.cpp | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index 449b92164f..6b30486b88 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -27,6 +27,10 @@ enum class LatticeType { rect, hex }; +enum class HexOrientation { + oy, ox +}; + //============================================================================== // Global variables //============================================================================== @@ -273,7 +277,7 @@ private: int n_rings_; //!< Number of radial tile positions int n_axial_; //!< Number of axial tile positions - int hextype; //!< Type of hexagonal lattice orientation //0 - OY by default;1-OX + HexOrientation orientation_; //!< Flat side up (oy) vs. sharp side up (ox) Position center_; //!< Global center of lattice std::array pitch_; //!< Lattice tile width and height }; diff --git a/src/lattice.cpp b/src/lattice.cpp index 40f30af2c7..b98ba6366f 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -444,15 +444,15 @@ HexLattice::HexLattice(pugi::xml_node lat_node) if (check_for_node(lat_node, "orient")) { std::string orientation = get_node_value(lat_node, "orient"); if (orientation == "OY") { - hextype = 0; + orientation_ = HexOrientation::oy; } else if (orientation == "OX") { - hextype = 1; + orientation_ = HexOrientation::ox; } else { fatal_error("Unrecognized orientation '" + orientation + "' for lattice " + std::to_string(id_)); } } else { - hextype = 0; + orientation_ = HexOrientation::oy; } // Read the lattice center. @@ -507,7 +507,7 @@ HexLattice::HexLattice(pugi::xml_node lat_node) universes_.resize((2*n_rings_-1) * (2*n_rings_-1) * n_axial_, C_NONE); - if (hextype==0) { + if (orientation_ == HexOrientation::oy) { fill_lattice_oy(univ_words); } else { fill_lattice_ox(univ_words); @@ -564,7 +564,7 @@ HexLattice::fill_lattice_ox(std::vector univ_words) } // Return lattice index to start of current row. - ki_y -= 1; + i_y -= 1; } } } @@ -708,11 +708,11 @@ const // beta = (1, 0) = +30 degrees from basis0 // gamma = (1/2, -sqrt(3)/2) = -60 degrees from beta // delta = (1/2, sqrt(3)/2) = +60 degrees from beta - // OZ is considered separately + // The z-axis is considered separately. double beta_dir; double gamma_dir; double delta_dir; - if (hextype == 0){ + if (orientation_ == HexOrientation::oy) { beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0; gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0; delta_dir = u.y; @@ -741,7 +741,7 @@ const r_t = get_local_position(r, i_xyz_t); } double beta; - if (hextype == 0) { + if (orientation_ == HexOrientation::oy) { beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0; } else { beta = r_t.x; @@ -765,7 +765,7 @@ const r_t = get_local_position(r, i_xyz_t); } double gamma; - if (hextype == 0) { + if (orientation_ == HexOrientation::oy) { gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0; } else { gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0; @@ -792,7 +792,7 @@ const r_t = get_local_position(r, i_xyz_t); } double delta; - if (hextype == 0){ + if (orientation_ == HexOrientation::oy) { delta = r_t.y; } else { delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0; @@ -852,7 +852,7 @@ HexLattice::get_indices(Position r, Direction u) const } int i1, i2; - if (hextype == 0) { + if (orientation_ == HexOrientation::oy) { // Convert coordinates into skewed bases. The (x, alpha) basis is used to // find the index of the global coordinates to within 4 cells. double alpha = r_o.y - r_o.x / std::sqrt(3.0); @@ -930,7 +930,7 @@ Position HexLattice::get_local_position(Position r, const std::array i_xyz) const { - if (hextype == 0) { + if (orientation_ == HexOrientation::oy) { // x_l = x_g - (center + pitch_x*cos(30)*index_x) r.x -= center_.x + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; @@ -1010,7 +1010,7 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_string(lat_group, "type", "hexagonal", false); write_dataset(lat_group, "n_rings", n_rings_); write_dataset(lat_group, "n_axial", n_axial_); - if (hextype == 0) { + if (orientation_ == HexOrientation::oy) { write_string(lat_group, "orientation", "oy", false); } else { write_string(lat_group, "orientation", "ox", false); From c196405a3406b35eb701c4007773717dcb0fe624 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 28 May 2019 13:23:59 -0400 Subject: [PATCH 050/151] Add RectilinearMesh Python class --- include/openmc/mesh.h | 125 ++++++++++++++++++++-------------- openmc/filter.py | 5 +- openmc/mesh.py | 153 +++++++++++++++++++++++++++++++++--------- openmc/statepoint.py | 2 +- src/mesh.cpp | 7 +- 5 files changed, 205 insertions(+), 87 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index f7e41cf704..ecb7a59ca1 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -21,12 +21,10 @@ namespace openmc { // Global variables //============================================================================== -//class RegularMesh; class Mesh; namespace model { -//extern std::vector> meshes; extern std::vector> meshes; extern std::unordered_map mesh_map; @@ -39,21 +37,55 @@ public: Mesh() = default; Mesh(pugi::xml_node node); + // Methods + + //! Determine which bins were crossed by a particle + // + //! \param[in] p Particle to check + //! \param[out] bins Bins that were crossed + //! \param[out] lengths Fraction of tracklength in each bin virtual void bins_crossed(const Particle* p, std::vector& bins, - std::vector& lengths) const = 0; + std::vector& lengths) const = 0; - virtual void surface_bins_crossed(const Particle* p, std::vector& bins) const = 0; + //! Determine which surface bins were crossed by a particle + // + //! \param[in] p Particle to check + //! \param[out] bins Surface bins that were crossed + virtual void + surface_bins_crossed(const Particle* p, std::vector& bins) const = 0; + //! Get bin at a given position in space + // + //! \param[in] r Position to get bin for + //! \return Mesh bin virtual int get_bin(Position r) const = 0; + //! Get bin given mesh indices + // + //! \param[in] Array of mesh indices + //! \return Mesh bin virtual int get_bin_from_indices(const int* ijk) const = 0; + //! Get mesh indices given a position + // + //! \param[in] r Position to get indices for + //! \param[out] ijk Array of mesh indices + //! \param[out] in_mesh Whether position is in mesh virtual void get_indices(Position r, int* ijk, bool* in_mesh) const = 0; + //! Get mesh indices corresponding to a mesh bin + // + //! \param[in] bin Mesh bin + //! \param[out] ijk Mesh indices virtual void get_indices_from_bin(int bin, int* ijk) const = 0; + //! Write mesh data to an HDF5 group + // + //! \param[in] group HDF5 group virtual void to_hdf5(hid_t group) const = 0; + // Data members + int id_ {-1}; //!< User-specified ID }; @@ -68,46 +100,25 @@ public: RegularMesh() = default; RegularMesh(pugi::xml_node node); - // Methods + // Overriden methods - //! Determine which bins were crossed by a particle - // - //! \param[in] p Particle to check - //! \param[out] bins Bins that were crossed - //! \param[out] lengths Fraction of tracklength in each bin - virtual void bins_crossed(const Particle* p, std::vector& bins, - std::vector& lengths) const; + void bins_crossed(const Particle* p, std::vector& bins, + std::vector& lengths) const override; - //! Determine which surface bins were crossed by a particle - // - //! \param[in] p Particle to check - //! \param[out] bins Surface bins that were crossed - virtual void surface_bins_crossed(const Particle* p, std::vector& bins) const; + void surface_bins_crossed(const Particle* p, std::vector& bins) + const override; - //! Get bin at a given position in space - // - //! \param[in] r Position to get bin for - //! \return Mesh bin - virtual int get_bin(Position r) const; + int get_bin(Position r) const override; - //! Get bin given mesh indices - // - //! \param[in] Array of mesh indices - //! \return Mesh bin - virtual int get_bin_from_indices(const int* ijk) const; + int get_bin_from_indices(const int* ijk) const override; - //! Get mesh indices given a position - // - //! \param[in] r Position to get indices for - //! \param[out] ijk Array of mesh indices - //! \param[out] in_mesh Whether position is in mesh - virtual void get_indices(Position r, int* ijk, bool* in_mesh) const; + void get_indices(Position r, int* ijk, bool* in_mesh) const override; - //! Get mesh indices corresponding to a mesh bin - // - //! \param[in] bin Mesh bin - //! \param[out] ijk Mesh indices - virtual void get_indices_from_bin(int bin, int* ijk) const; + void get_indices_from_bin(int bin, int* ijk) const override; + + void to_hdf5(hid_t group) const override; + + // New methods //! Check where a line segment intersects the mesh and if it intersects at all // @@ -117,11 +128,6 @@ public: //! \return Whether the line segment connecting r0 and r1 intersects mesh bool intersects(Position& r0, Position r1, int* ijk) const; - //! Write mesh data to an HDF5 group - // - //! \param[in] group HDF5 group - virtual void to_hdf5(hid_t group) const; - //! Count number of bank sites in each mesh bin / energy bin // //! \param[in] bank Array of bank sites @@ -130,6 +136,8 @@ public: virtual xt::xarray count_sites(const std::vector& bank, bool* outside) const; + // Data members + double volume_frac_; //!< Volume fraction of each mesh element int n_dimension_; //!< Number of dimensions xt::xarray shape_; //!< Number of mesh elements in each dimension @@ -149,24 +157,39 @@ public: // Constructors RectilinearMesh(pugi::xml_node node); - virtual void bins_crossed(const Particle* p, std::vector& bins, - std::vector& lengths) const; + // Overriden methods - virtual void surface_bins_crossed(const Particle* p, std::vector& bins) const; + void bins_crossed(const Particle* p, std::vector& bins, + std::vector& lengths) const override; - virtual int get_bin(Position r) const; + void surface_bins_crossed(const Particle* p, std::vector& bins) + const override; - virtual int get_bin_from_indices(const int* ijk) const; + int get_bin(Position r) const override; - virtual void get_indices(Position r, int* ijk, bool* in_mesh) const; + int get_bin_from_indices(const int* ijk) const override; - virtual void get_indices_from_bin(int bin, int* ijk) const; + void get_indices(Position r, int* ijk, bool* in_mesh) const override; - virtual void to_hdf5(hid_t group) const; + void get_indices_from_bin(int bin, int* ijk) const override; + void to_hdf5(hid_t group) const override; + + // New methods + + //! Check where a line segment intersects the mesh and if it intersects at all + // + //! \param[in,out] r0 In: starting position, out: intersection point + //! \param[in] r1 Ending position + //! \param[out] ijk Indices of the mesh bin containing the intersection point + //! \return Whether the line segment connecting r0 and r1 intersects mesh bool intersects(Position& r0, Position r1, int* ijk) const; + // Data members + xt::xarray shape_; //!< Number of mesh elements in each dimension + +private: std::vector> grid_; }; diff --git a/openmc/filter.py b/openmc/filter.py index 49d3ff5de1..77fe84c3ae 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -610,6 +610,9 @@ class MeshFilter(Filter): self.mesh = mesh self.id = filter_id + def __hash__(self): + return hash(repr(self)) + def __repr__(self): string = type(self).__name__ + '\n' string += '{: <16}=\t{}\n'.format('\tMesh ID', self.mesh.id) @@ -641,7 +644,7 @@ class MeshFilter(Filter): @mesh.setter def mesh(self, mesh): - cv.check_type('filter mesh', mesh, openmc.Mesh) + cv.check_type('filter mesh', mesh, openmc.MeshBase) self._mesh = mesh self.bins = list(mesh.indices) diff --git a/openmc/mesh.py b/openmc/mesh.py index ba07ec178b..9370e305d0 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -1,3 +1,4 @@ +from abc import ABCMeta from collections.abc import Iterable from numbers import Real, Integral from xml.etree import ElementTree as ET @@ -10,7 +11,72 @@ import openmc from openmc.mixin import EqualityMixin, IDManagerMixin -class Mesh(IDManagerMixin): +class MeshBase(IDManagerMixin, metaclass=ABCMeta): + """A mesh that partitions geometry for tallying purposes. + + Parameters + ---------- + mesh_id : int + Unique identifier for the mesh + name : str + Name of the mesh + + Attributes + ---------- + id : int + Unique identifier for the mesh + name : str + Name of the mesh + + """ + + next_id = 1 + used_ids = set() + + def __init__(self, mesh_id=None, name=''): + # Initialize Mesh class attributes + self.id = mesh_id + self.name = name + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + if name is not None: + cv.check_type('name for mesh ID="{0}"'.format(self._id), + name, str) + self._name = name + else: + self._name = '' + + @classmethod + def from_hdf5(cls, group): + """Create mesh from HDF5 group + + Parameters + ---------- + group : h5py.Group + Group in HDF5 file + + Returns + ------- + openmc.MeshBase + Instance of a MeshBase subclass + + """ + + mesh_type = group['type'][()].decode() + if mesh_type == 'regular': + return Mesh.from_hdf5(group) + elif mesh_type == 'rectilinear': + return RectilinearMesh.from_hdf5(group) + else: + raise ValueError('Unrecognized mesh type: "' + mesh_type + '"') + + +class Mesh(MeshBase): """A structured Cartesian mesh in one, two, or three dimensions Parameters @@ -44,23 +110,16 @@ class Mesh(IDManagerMixin): """ - next_id = 1 - used_ids = set() - def __init__(self, mesh_id=None, name=''): + super().__init__(mesh_id, name) + # Initialize Mesh class attributes - self.id = mesh_id - self.name = name self._type = 'regular' self._dimension = None self._lower_left = None self._upper_right = None self._width = None - @property - def name(self): - return self._name - @property def type(self): return self._type @@ -103,15 +162,6 @@ class Mesh(IDManagerMixin): nx, = self.dimension return ((x,) for x in range(1, nx + 1)) - @name.setter - def name(self, name): - if name is not None: - cv.check_type('name for mesh ID="{0}"'.format(self._id), - name, str) - self._name = name - else: - self._name = '' - @type.setter def type(self, meshtype): cv.check_type('type for mesh ID="{0}"'.format(self._id), @@ -157,19 +207,6 @@ class Mesh(IDManagerMixin): @classmethod def from_hdf5(cls, group): - """Create mesh from HDF5 group - - Parameters - ---------- - group : h5py.Group - Group in HDF5 file - - Returns - ------- - openmc.Mesh - Mesh instance - - """ mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) # Read and assign mesh properties @@ -374,3 +411,55 @@ class Mesh(IDManagerMixin): root_cell.fill = lattice return root_cell, cells + + +class RectilinearMesh(MeshBase): + def __init__(self, mesh_id=None, name=''): + super().__init__(mesh_id, name) + + @property + def indices(self): + nx = len(self.x_grid) - 1 + ny = len(self.y_grid) - 1 + nz = len(self.z_grid) - 1 + return ((x, y, z) + for z in range(1, nz + 1) + for y in range(1, ny + 1) + for x in range(1, nx + 1)) + + @classmethod + def from_hdf5(cls, group): + mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) + + # Read and assign mesh properties + mesh = cls(mesh_id) + mesh.x_grid = group['x_grid'][()] + mesh.y_grid = group['y_grid'][()] + mesh.z_grid = group['z_grid'][()] + + return mesh + + def to_xml_element(self): + """Return XML representation of the mesh + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing mesh data + + """ + + element = ET.Element("mesh") + element.set("id", str(self._id)) + element.set("type", "rectilinear") + + subelement = ET.SubElement(element, "x_grid") + subelement.text = ' '.join(map(str, self.x_grid)) + + subelement = ET.SubElement(element, "y_grid") + subelement.text = ' '.join(map(str, self.y_grid)) + + subelement = ET.SubElement(element, "z_grid") + subelement.text = ' '.join(map(str, self.z_grid)) + + return element diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 3dc6535841..1991c5be68 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -294,7 +294,7 @@ class StatePoint(object): # Iterate over all Meshes for group in mesh_group.values(): - mesh = openmc.Mesh.from_hdf5(group) + mesh = openmc.MeshBase.from_hdf5(group) self._meshes[mesh.id] = mesh self._meshes_read = True diff --git a/src/mesh.cpp b/src/mesh.cpp index 676c5888fd..ea2b353949 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -799,7 +799,7 @@ RectilinearMesh::RectilinearMesh(pugi::xml_node node) } void RectilinearMesh::bins_crossed(const Particle* p, std::vector& bins, - std::vector& lengths) const + std::vector& lengths) const { // ======================================================================== // Determine where the track intersects the mesh and if it intersects at all. @@ -894,7 +894,7 @@ void RectilinearMesh::bins_crossed(const Particle* p, std::vector& bins, } void RectilinearMesh::surface_bins_crossed(const Particle* p, - std::vector& bins) const + std::vector& bins) const { // ======================================================================== // Determine if the track intersects the tally mesh. @@ -1085,6 +1085,9 @@ void RectilinearMesh::to_hdf5(hid_t group) const hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); write_dataset(mesh_group, "type", "rectilinear"); + write_dataset(mesh_group, "x_grid", grid_[0]); + write_dataset(mesh_group, "y_grid", grid_[1]); + write_dataset(mesh_group, "z_grid", grid_[2]); close_group(mesh_group); } From d15090099a781c77bd5679d82fb9e75fdb596390 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 28 May 2019 15:51:51 -0400 Subject: [PATCH 051/151] Implement RectilinearMesh::surface_bins_crossed --- openmc/filter.py | 5 +- src/mesh.cpp | 139 ++++++++++++++++------------- src/tallies/filter_meshsurface.cpp | 19 ++-- 3 files changed, 95 insertions(+), 68 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 77fe84c3ae..7ad901b59d 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -773,13 +773,12 @@ class MeshSurfaceFilter(MeshFilter): @MeshFilter.mesh.setter def mesh(self, mesh): - cv.check_type('filter mesh', mesh, openmc.Mesh) + cv.check_type('filter mesh', mesh, openmc.MeshBase) self._mesh = mesh # Take the product of mesh indices and current names - n_dim = len(mesh.dimension) self.bins = [mesh_tuple + (surf,) for mesh_tuple, surf in - product(mesh.indices, _CURRENT_NAMES[:4*n_dim])] + product(mesh.indices, _CURRENT_NAMES[:4*3])] def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. diff --git a/src/mesh.cpp b/src/mesh.cpp index ea2b353949..0e3198c63f 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -911,12 +911,68 @@ void RectilinearMesh::surface_bins_crossed(const Particle* p, bool end_in_mesh; get_indices(r1, ijk1, &end_in_mesh); - // Check if the track intersects any part of the mesh. + // If the starting coordinates do not lie in the mesh, compute the coords and + // mesh indices of the first intersection, and add the bin for this first + // intersection. Return if the particle does not intersect the mesh at all. if (!start_in_mesh) { - Position r0_copy = r0; - int ijk0_copy[3]; - for (int i = 0; i < 3; ++i) ijk0_copy[i] = ijk0[i]; - if (!intersects(r0_copy, r1, ijk0_copy)) return; + // Compute the incoming intersection coordinates and indices. + if (!intersects(r0, r1, ijk0)) return; + + // Determine which surface the particle entered. + double min_dist = INFTY; + int i_surf; + for (int i = 0; i < 3; ++i) { + if (u[i] > 0.0 && ijk0[i] == 1) { + double d = std::abs(r0[i] - grid_[i][0]); + if (d < min_dist) { + min_dist = d; + i_surf = 4*i + 2; + } + } else if (u[i] < 0.0 && ijk0[i] == shape_[i]) { + double d = std::abs(r0[i] - grid_[i][shape_[i]]); + if (d < min_dist) { + min_dist = d; + i_surf = 4*i + 4; + } + } // u[i] == 0 intentionally skipped + } + + // Add the incoming current bin. + int i_mesh = get_bin_from_indices(ijk0); + int i_bin = 4*3*i_mesh + i_surf - 1; + bins.push_back(i_bin); + } + + // If the ending coordinates do not lie in the mesh, compute the coords and + // mesh indices of the last intersection, and add the bin for this last + // intersection. + if (!end_in_mesh) { + // Compute the outgoing intersection coordinates and indices. + intersects(r1, r0, ijk1); + + // Determine which surface the particle exited. + double min_dist = INFTY; + int i_surf; + for (int i = 0; i < 3; ++i) { + if (u[i] > 0.0 && ijk1[i] == shape_[i]) { + double d = std::abs(r1[i] - grid_[i][shape_[i]]); + if (d < min_dist) { + min_dist = d; + i_surf = 4*i + 3; + } + } else if (u[i] < 0.0 && ijk1[i] == 1) { + double d = std::abs(r1[i] - grid_[i][0]); + if (d < min_dist) { + min_dist = d; + i_surf = 4*i + 1; + } + } // u[i] == 0 intentionally skipped + } + + // Add the outgoing current bin. + int i_mesh = get_bin_from_indices(ijk1); + int i_bin = 4*3*i_mesh + i_surf - 1; + bins.push_back(i_bin); } // ======================================================================== @@ -959,80 +1015,43 @@ void RectilinearMesh::surface_bins_crossed(const Particle* p, // Check whether distance is the shortest distance if (distance == d[i]) { - // Check whether the current indices are within the mesh bounds - bool in_mesh = true; - for (int j = 0; j < 3; ++j) { - if (ijk0[j] < 1 || ijk0[j] > shape_[j]) { - in_mesh = false; - break; - } - } - // Check whether particle is moving in positive i direction if (u[i] > 0) { // Outward current on i max surface - if (in_mesh) { - int i_surf = 4*i + 3; - int i_mesh = get_bin_from_indices(ijk0); - int i_bin = 4*3*i_mesh + i_surf - 1; - - bins.push_back(i_bin); - } + int i_surf = 4*i + 3; + int i_mesh = get_bin_from_indices(ijk0); + int i_bin = 4*3*i_mesh + i_surf - 1; + bins.push_back(i_bin); // Advance position ++ijk0[i]; xyz_cross[i] = grid_[i][ijk0[i]]; - in_mesh = true; - for (int j = 0; j < 3; ++j) { - if (ijk0[j] < 1 || ijk0[j] > shape_[j]) { - in_mesh = false; - break; - } - } - // If the particle crossed the surface, tally the inward current on - // i min surface - if (in_mesh) { - int i_surf = 4*i + 2; - int i_mesh = get_bin_from_indices(ijk0); - int i_bin = 4*3*i_mesh + i_surf - 1; - - bins.push_back(i_bin); - } + // Inward current on i min surface + i_surf = 4*i + 2; + i_mesh = get_bin_from_indices(ijk0); + i_bin = 4*3*i_mesh + i_surf - 1; + bins.push_back(i_bin); } else { // The particle is moving in the negative i direction // Outward current on i min surface - if (in_mesh) { - int i_surf = 4*i + 1; - int i_mesh = get_bin_from_indices(ijk0); - int i_bin = 4*3*i_mesh + i_surf - 1; - - bins.push_back(i_bin); - } + int i_surf = 4*i + 1; + int i_mesh = get_bin_from_indices(ijk0); + int i_bin = 4*3*i_mesh + i_surf - 1; + bins.push_back(i_bin); // Advance position --ijk0[i]; xyz_cross[i] = grid_[i][ijk0[i] - 1]; - in_mesh = true; - for (int j = 0; j < 3; ++j) { - if (ijk0[j] < 1 || ijk0[j] > shape_[j]) { - in_mesh = false; - break; - } - } - // If the particle crossed the surface, tally the inward current on - // i max surface - if (in_mesh) { - int i_surf = 4*i + 4; - int i_mesh = get_bin_from_indices(ijk0); - int i_bin = 4*3*i_mesh + i_surf - 1; - - bins.push_back(i_bin); - } + // Inward current on i min surface + i_surf = 4*i + 4; + i_mesh = get_bin_from_indices(ijk0); + i_bin = 4*3*i_mesh + i_surf - 1; + bins.push_back(i_bin); } } } diff --git a/src/tallies/filter_meshsurface.cpp b/src/tallies/filter_meshsurface.cpp index 8f4ae4e356..aa64520139 100644 --- a/src/tallies/filter_meshsurface.cpp +++ b/src/tallies/filter_meshsurface.cpp @@ -18,8 +18,12 @@ MeshSurfaceFilter::get_all_bins(const Particle* p, int estimator, std::string MeshSurfaceFilter::text_label(int bin) const { - auto& mesh = *dynamic_cast(model::meshes[mesh_].get()); - int n_dim = mesh.n_dimension_; + int n_dim; + if (auto* mesh = dynamic_cast(model::meshes[mesh_].get())) { + n_dim = mesh->n_dimension_; + } else if (auto* mesh = dynamic_cast(model::meshes[mesh_].get())) { + n_dim = 3; + } // Get flattend mesh index and surface index. int i_mesh = bin / (4 * n_dim); @@ -75,9 +79,14 @@ void MeshSurfaceFilter::set_mesh(int32_t mesh) { mesh_ = mesh; - auto &m = *dynamic_cast(model::meshes[mesh_].get()); - n_bins_ = 4 * m.n_dimension_; - for (auto dim : m.shape_) n_bins_ *= dim; + auto* m_ptr = model::meshes[mesh_].get(); + if (auto* m = dynamic_cast(m_ptr)) { + n_bins_ = 4 * m->n_dimension_; + for (auto dim : m->shape_) n_bins_ *= dim; + } else if (auto* m = dynamic_cast(m_ptr)) { + n_bins_ = 4 * 3; + for (auto dim : m->shape_) n_bins_ *= dim; + } } //============================================================================== From 3cee8a77fcda2f5e7dec58925b6a65dff6243ec2 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 28 May 2019 16:53:49 -0400 Subject: [PATCH 052/151] Add RectilinearMesh to regression tests --- openmc/filter.py | 3 +- openmc/mesh.py | 8 +++++ .../filter_mesh/inputs_true.dat | 31 +++++++++++++++---- .../filter_mesh/results_true.dat | 2 +- tests/regression_tests/filter_mesh/test.py | 19 ++++++++++++ 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 7ad901b59d..4d14f054e8 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -777,8 +777,9 @@ class MeshSurfaceFilter(MeshFilter): self._mesh = mesh # Take the product of mesh indices and current names + n_dim = mesh.n_dimension self.bins = [mesh_tuple + (surf,) for mesh_tuple, surf in - product(mesh.indices, _CURRENT_NAMES[:4*3])] + product(mesh.indices, _CURRENT_NAMES[:4*n_dim])] def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. diff --git a/openmc/mesh.py b/openmc/mesh.py index 9370e305d0..0961b238e5 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -128,6 +128,10 @@ class Mesh(MeshBase): def dimension(self): return self._dimension + @property + def n_dimension(self): + return len(self._dimension) + @property def lower_left(self): return self._lower_left @@ -417,6 +421,10 @@ class RectilinearMesh(MeshBase): def __init__(self, mesh_id=None, name=''): super().__init__(mesh_id, name) + @property + def n_dimension(self): + return 3 + @property def indices(self): nx = len(self.x_grid) - 1 diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index 511b298482..7afd834cde 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -324,30 +324,41 @@ -182.07 -182.07 -183.0 182.07 182.07 183.0 + + -182.07 -160.65 -139.23 -117.81 -96.39 -74.97 -53.55000000000001 -32.129999999999995 -10.710000000000008 10.70999999999998 32.129999999999995 53.54999999999998 74.96999999999997 96.38999999999999 117.81 139.22999999999996 160.64999999999998 182.07 + -182.07 -160.65 -139.23 -117.81 -96.39 -74.97 -53.55000000000001 -32.129999999999995 -10.710000000000008 10.70999999999998 32.129999999999995 53.54999999999998 74.96999999999997 96.38999999999999 117.81 139.22999999999996 160.64999999999998 182.07 + 1.0 1.683624003879018 2.8345897864376153 4.772383405596668 8.034899257376447 13.52774925846868 22.77564337001445 38.34561988154435 64.55960607618856 108.69410247084474 182.99999999999991 + 1 - + 1 2 - + 2 3 - + 3 + + 4 + + + 4 + 1 total - 4 + 5 current @@ -355,7 +366,7 @@ total - 5 + 6 current @@ -363,7 +374,15 @@ total - 6 + 7 + current + + + 4 + total + + + 8 current diff --git a/tests/regression_tests/filter_mesh/results_true.dat b/tests/regression_tests/filter_mesh/results_true.dat index 227d0d608a..05efffe2fb 100644 --- a/tests/regression_tests/filter_mesh/results_true.dat +++ b/tests/regression_tests/filter_mesh/results_true.dat @@ -1 +1 @@ -cc306dfd3e3669827cb2f3bdc38005c0616f6faecb9277f3b0f869fdecf826ae662c65d67a402705956f100d2e13c7c11332364fa41ccc2e215ab100709b627f \ No newline at end of file +35f04a6f062ef64116ef4eb0e9b803cd44cff7e185e2b53c9174afad8a26ca1a436ca9b800d6a228e006a9129f4536d7dce289d7a11cd56c6949d71d6a201b31 \ No newline at end of file diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py index 8a7f7a6fca..79e2082da2 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -1,3 +1,5 @@ +import numpy as np + import openmc from tests.testing_harness import HashedPyAPITestHarness @@ -26,13 +28,20 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): mesh_3d.lower_left = [-182.07, -182.07, -183.00] mesh_3d.upper_right = [182.07, 182.07, 183.00] + recti_mesh = openmc.RectilinearMesh(mesh_id=4) + recti_mesh.x_grid = np.linspace(-182.07, 182.07, 18) + recti_mesh.y_grid = np.linspace(-182.07, 182.07, 18) + recti_mesh.z_grid = np.logspace(0, np.log10(183), 11) + # Initialize the filters mesh_1d_filter = openmc.MeshFilter(mesh_1d) mesh_2d_filter = openmc.MeshFilter(mesh_2d) mesh_3d_filter = openmc.MeshFilter(mesh_3d) + recti_mesh_filter = openmc.MeshFilter(recti_mesh) meshsurf_1d_filter = openmc.MeshSurfaceFilter(mesh_1d) meshsurf_2d_filter = openmc.MeshSurfaceFilter(mesh_2d) meshsurf_3d_filter = openmc.MeshSurfaceFilter(mesh_3d) + recti_meshsurf_filter = openmc.MeshSurfaceFilter(recti_mesh) # Initialized the tallies tally = openmc.Tally(name='tally 1') @@ -65,6 +74,16 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): tally.scores = ['current'] self._model.tallies.append(tally) + tally = openmc.Tally(name='tally 7') + tally.filters = [recti_mesh_filter] + tally.scores = ['total'] + self._model.tallies.append(tally) + + tally = openmc.Tally(name='tally 8') + tally.filters = [recti_meshsurf_filter] + tally.scores = ['current'] + self._model.tallies.append(tally) + def test_filter_mesh(): harness = FilterMeshTestHarness('statepoint.10.h5') From d1e051c8570bf8cff82e9142cdb87c7c9b0ec4e4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 23 May 2019 15:44:02 -0500 Subject: [PATCH 053/151] Update test results for Ubuntu 16.04 --- docs/source/methods/neutron_physics.rst | 2 +- .../cmfd_feed_ng/results_true.dat | 6 +++--- .../lattice_hex_coincident/inputs_true.dat | 6 +++--- .../lattice_hex_coincident/results_true.dat | 2 +- .../lattice_hex_coincident/test.py | 15 ++++++++------- tests/regression_tests/salphabeta/inputs_true.dat | 2 +- .../regression_tests/salphabeta/results_true.dat | 2 +- tests/regression_tests/salphabeta/test.py | 2 +- tests/regression_tests/triso/results_true.dat | 2 +- 9 files changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/source/methods/neutron_physics.rst b/docs/source/methods/neutron_physics.rst index 3451ae83f0..465929ba0c 100644 --- a/docs/source/methods/neutron_physics.rst +++ b/docs/source/methods/neutron_physics.rst @@ -86,7 +86,7 @@ Elastic Scattering ------------------ Note that the multi-group mode makes no distinction between elastic or -inelastic scattering reactions. The spceific multi-group scattering +inelastic scattering reactions. The specific multi-group scattering implementation is discussed in the :ref:`multi-group-scatter` section. Elastic scattering refers to the process by which a neutron scatters off a diff --git a/tests/regression_tests/cmfd_feed_ng/results_true.dat b/tests/regression_tests/cmfd_feed_ng/results_true.dat index 8308e8634f..7074114598 100644 --- a/tests/regression_tests/cmfd_feed_ng/results_true.dat +++ b/tests/regression_tests/cmfd_feed_ng/results_true.dat @@ -1,5 +1,5 @@ k-combined: -1.029540E+00 1.765356E-02 +1.029540E+00 1.765357E-02 tally 1: 1.144958E+02 1.311468E+03 @@ -93,7 +93,7 @@ tally 3: 0.000000E+00 6.939496E+01 3.014077E+02 -6.294738E-01 +6.294737E-01 2.532146E-02 4.991526E+01 1.566280E+02 @@ -129,7 +129,7 @@ tally 3: 0.000000E+00 6.885265E+01 2.965191E+02 -6.537576E-01 +6.537575E-01 2.783733E-02 4.958341E+01 1.546656E+02 diff --git a/tests/regression_tests/lattice_hex_coincident/inputs_true.dat b/tests/regression_tests/lattice_hex_coincident/inputs_true.dat index 8d73f63741..0388a4f555 100644 --- a/tests/regression_tests/lattice_hex_coincident/inputs_true.dat +++ b/tests/regression_tests/lattice_hex_coincident/inputs_true.dat @@ -39,7 +39,7 @@ - + @@ -67,8 +67,8 @@ eigenvalue 1000 - 10 - 5 + 5 + 2 -0.9899494936611666 -0.9899494936611666 0.0 0.9899494936611666 0.9899494936611666 10.0 diff --git a/tests/regression_tests/lattice_hex_coincident/results_true.dat b/tests/regression_tests/lattice_hex_coincident/results_true.dat index 8bf1c539ed..1f95edaf8d 100644 --- a/tests/regression_tests/lattice_hex_coincident/results_true.dat +++ b/tests/regression_tests/lattice_hex_coincident/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.744283E+00 1.734930E-03 +1.910374E+00 2.685991E-02 diff --git a/tests/regression_tests/lattice_hex_coincident/test.py b/tests/regression_tests/lattice_hex_coincident/test.py index 65786e2ecd..eaea74b193 100644 --- a/tests/regression_tests/lattice_hex_coincident/test.py +++ b/tests/regression_tests/lattice_hex_coincident/test.py @@ -1,4 +1,5 @@ -import numpy as np +from math import sqrt + import openmc from tests.testing_harness import PyAPITestHarness @@ -13,7 +14,7 @@ class HexLatticeCoincidentTestHarness(PyAPITestHarness): materials.append(fuel_mat) matrix = openmc.Material() - matrix.set_density('atom/b-cm', 8.7742E-02) + matrix.set_density('atom/b-cm', 1.7742E-02) matrix.add_element('C', 1.0, 'ao') matrix.add_s_alpha_beta('c_Graphite') materials.append(matrix) @@ -94,7 +95,7 @@ class HexLatticeCoincidentTestHarness(PyAPITestHarness): coolant_univ.add_cells(coolant_channel) half_width = assembly_pitch # cm - edge_length = (2./np.sqrt(3.0)) * half_width + edge_length = (2./sqrt(3.0)) * half_width inf_mat = openmc.Cell() inf_mat.fill = matrix @@ -132,19 +133,19 @@ class HexLatticeCoincidentTestHarness(PyAPITestHarness): settings.run_mode = 'eigenvalue' source = openmc.Source() - corner_dist = np.sqrt(2) * pin_rad + corner_dist = sqrt(2) * pin_rad ll = [-corner_dist, -corner_dist, 0.0] ur = [corner_dist, corner_dist, 10.0] source.space = openmc.stats.Box(ll, ur) source.strength = 1.0 settings.source = source settings.output = {'summary' : False} - settings.batches = 10 - settings.inactive = 5 + settings.batches = 5 + settings.inactive = 2 settings.particles = 1000 settings.seed = 22 settings.export_to_xml() def test_lattice_hex_coincident_surf(): - harness = HexLatticeCoincidentTestHarness('statepoint.10.h5') + harness = HexLatticeCoincidentTestHarness('statepoint.5.h5') harness.main() diff --git a/tests/regression_tests/salphabeta/inputs_true.dat b/tests/regression_tests/salphabeta/inputs_true.dat index ede96d3769..29d8065cf1 100644 --- a/tests/regression_tests/salphabeta/inputs_true.dat +++ b/tests/regression_tests/salphabeta/inputs_true.dat @@ -49,7 +49,7 @@ eigenvalue - 1000 + 400 5 0 diff --git a/tests/regression_tests/salphabeta/results_true.dat b/tests/regression_tests/salphabeta/results_true.dat index fb9d2d77b7..708e6e7269 100644 --- a/tests/regression_tests/salphabeta/results_true.dat +++ b/tests/regression_tests/salphabeta/results_true.dat @@ -1,2 +1,2 @@ k-combined: -8.757212E-01 5.686265E-02 +8.474822E-01 1.767966E-02 diff --git a/tests/regression_tests/salphabeta/test.py b/tests/regression_tests/salphabeta/test.py index e69ada5ba3..ab5305126e 100644 --- a/tests/regression_tests/salphabeta/test.py +++ b/tests/regression_tests/salphabeta/test.py @@ -66,7 +66,7 @@ def make_model(): # Settings model.settings.batches = 5 model.settings.inactive = 0 - model.settings.particles = 1000 + model.settings.particles = 400 model.settings.source = openmc.Source(space=openmc.stats.Box( [-4, -4, -4], [4, 4, 4])) diff --git a/tests/regression_tests/triso/results_true.dat b/tests/regression_tests/triso/results_true.dat index 0c5186e45b..c8e6832345 100644 --- a/tests/regression_tests/triso/results_true.dat +++ b/tests/regression_tests/triso/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.701412E+00 3.180877E-02 +1.701412E+00 3.180881E-02 From f4ae14af39972d49d9aeb9d95e0868c35323eb12 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 28 May 2019 19:57:52 -0400 Subject: [PATCH 054/151] Use RegularMesh* for entropy and ufs --- include/openmc/settings.h | 3 --- include/openmc/simulation.h | 4 ++++ openmc/filter.py | 4 +++- src/eigenvalue.cpp | 27 +++++++++----------------- src/finalize.cpp | 5 +++-- src/plot.cpp | 29 +++++++++++++++++++++++----- src/settings.cpp | 38 +++++++++++++++++++++---------------- src/simulation.cpp | 3 +++ 8 files changed, 68 insertions(+), 45 deletions(-) diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 4b76c3dc59..77127db0a4 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -59,9 +59,6 @@ extern std::string path_source; extern std::string path_sourcepoint; //!< path to a source file extern std::string path_statepoint; //!< path to a statepoint file -extern int32_t index_entropy_mesh; //!< Index of entropy mesh in global mesh array -extern int32_t index_ufs_mesh; //!< Index of UFS mesh in global mesh array - extern "C" int32_t n_batches; //!< number of (inactive+active) batches extern "C" int32_t n_inactive; //!< number of inactive batches extern "C" int32_t gen_per_batch; //!< number of generations per batch diff --git a/include/openmc/simulation.h b/include/openmc/simulation.h index fef6ebbcd1..8efac4c7f8 100644 --- a/include/openmc/simulation.h +++ b/include/openmc/simulation.h @@ -4,6 +4,7 @@ #ifndef OPENMC_SIMULATION_H #define OPENMC_SIMULATION_H +#include "openmc/mesh.h" #include "openmc/particle.h" #include @@ -39,6 +40,9 @@ extern "C" int total_gen; //!< total number of generations simulated extern double total_weight; //!< Total source weight in a batch extern int64_t work_per_rank; //!< number of particles per MPI rank +extern const RegularMesh* entropy_mesh; +extern const RegularMesh* ufs_mesh; + extern std::vector k_generation; extern std::vector work_index; diff --git a/openmc/filter.py b/openmc/filter.py index 4d14f054e8..fa1085b970 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -611,7 +611,9 @@ class MeshFilter(Filter): self.id = filter_id def __hash__(self): - return hash(repr(self)) + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tMesh ID', self.mesh.id) + return hash(string) def __repr__(self): string = type(self).__name__ + '\n' diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index 0f4b49f461..310279d369 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -532,14 +532,10 @@ int openmc_get_keff(double* k_combined) void shannon_entropy() { - // Get reference to entropy mesh - auto& m = *dynamic_cast( - model::meshes[settings::index_entropy_mesh].get()); - // Get source weight in each mesh bin bool sites_outside; - xt::xtensor p = m.count_sites(simulation::fission_bank, - &sites_outside); + xt::xtensor p = simulation::entropy_mesh->count_sites( + simulation::fission_bank, &sites_outside); // display warning message if there were sites outside entropy box if (sites_outside) { @@ -565,22 +561,19 @@ void shannon_entropy() void ufs_count_sites() { - auto& m = *dynamic_cast( - model::meshes[settings::index_entropy_mesh].get()); - if (simulation::current_batch == 1 && simulation::current_gen == 1) { // On the first generation, just assume that the source is already evenly // distributed so that effectively the production of fission sites is not // biased auto s = xt::view(simulation::source_frac, xt::all()); - s = m.volume_frac_; + s = simulation::ufs_mesh->volume_frac_; } else { // count number of source sites in each ufs mesh cell bool sites_outside; - simulation::source_frac = m.count_sites(simulation::source_bank, - &sites_outside); + simulation::source_frac = simulation::ufs_mesh->count_sites( + simulation::source_bank, &sites_outside); // Check for sites outside of the mesh if (mpi::master && sites_outside) { @@ -589,7 +582,7 @@ void ufs_count_sites() #ifdef OPENMC_MPI // Send source fraction to all processors - int n_bins = xt::prod(m.shape_)(); + int n_bins = xt::prod(simulation::ufs_mesh->shape_)(); MPI_Bcast(simulation::source_frac.data(), n_bins, MPI_DOUBLE, 0, mpi::intracomm); #endif @@ -607,18 +600,16 @@ void ufs_count_sites() double ufs_get_weight(const Particle* p) { - auto& m = *dynamic_cast( - model::meshes[settings::index_entropy_mesh].get()); - // Determine indices on ufs mesh for current location - int mesh_bin = m.get_bin(p->r()); + int mesh_bin = simulation::ufs_mesh->get_bin(p->r()); if (mesh_bin < 0) { p->write_restart(); fatal_error("Source site outside UFS mesh!"); } if (simulation::source_frac(mesh_bin) != 0.0) { - return m.volume_frac_ / simulation::source_frac(mesh_bin); + return simulation::ufs_mesh->volume_frac_ + / simulation::source_frac(mesh_bin); } else { return 1.0; } diff --git a/src/finalize.cpp b/src/finalize.cpp index 34c4b9781b..fa6b7628cc 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -71,8 +71,6 @@ int openmc_finalize() settings::energy_cutoff = {0.0, 1000.0, 0.0, 0.0}; settings::entropy_on = false; settings::gen_per_batch = 1; - settings::index_entropy_mesh = -1; - settings::index_ufs_mesh = -1; settings::legendre_to_tabular = true; settings::legendre_to_tabular_points = -1; settings::n_particles = -1; @@ -114,6 +112,9 @@ int openmc_finalize() simulation::satisfy_triggers = false; simulation::total_gen = 0; + simulation::entropy_mesh = nullptr; + simulation::ufs_mesh = nullptr; + data::energy_max = {INFTY, INFTY}; data::energy_min = {0.0, 0.0}; model::root_universe = -1; diff --git a/src/plot.cpp b/src/plot.cpp index 070726bcbb..43e7ed69cb 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -19,6 +19,7 @@ #include "openmc/progress_bar.h" #include "openmc/random_lcg.h" #include "openmc/settings.h" +#include "openmc/simulation.h" #include "openmc/string_utils.h" namespace openmc { @@ -497,20 +498,38 @@ Plot::set_meshlines(pugi::xml_node plot_node) // Set mesh based on type if ("ufs" == meshtype) { - if (settings::index_ufs_mesh < 0) { + if (!simulation::ufs_mesh) { std::stringstream err_msg; err_msg << "No UFS mesh for meshlines on plot " << id_; fatal_error(err_msg); } else { - index_meshlines_mesh_ = settings::index_ufs_mesh; + for (int i = 0; i < model::meshes.size(); ++i) { + if (const auto* m + = dynamic_cast(model::meshes[i].get())) { + if (m == simulation::ufs_mesh) { + index_meshlines_mesh_ = i; + } + } + } + if (index_meshlines_mesh_ == -1) + fatal_error("Could not find the UFS mesh for meshlines plot"); } } else if ("entropy" == meshtype) { - if (settings::index_entropy_mesh < 0) { + if (!simulation::entropy_mesh) { std::stringstream err_msg; - err_msg <<"No entropy mesh for meshlines on plot " << id_; + err_msg << "No entropy mesh for meshlines on plot " << id_; fatal_error(err_msg); } else { - index_meshlines_mesh_ = settings::index_entropy_mesh; + for (int i = 0; i < model::meshes.size(); ++i) { + if (const auto* m + = dynamic_cast(model::meshes[i].get())) { + if (m == simulation::entropy_mesh) { + index_meshlines_mesh_ = i; + } + } + } + if (index_meshlines_mesh_ == -1) + fatal_error("Could not find the entropy mesh for meshlines plot"); } } else if ("tally" == meshtype) { // Ensure that there is a mesh id if the type is tally diff --git a/src/settings.cpp b/src/settings.cpp index 27f8c31d28..63827c3f41 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -73,9 +73,6 @@ std::string path_source; std::string path_sourcepoint; std::string path_statepoint; -int32_t index_entropy_mesh {-1}; -int32_t index_ufs_mesh {-1}; - int32_t n_batches; int32_t n_inactive {0}; int32_t gen_per_batch {1}; @@ -481,6 +478,7 @@ void read_settings_xml() read_meshes(root); // Shannon Entropy mesh + int32_t i_entropy_mesh = -1; if (check_for_node(root, "entropy_mesh")) { int temp = std::stoi(get_node_value(root, "entropy_mesh")); if (model::mesh_map.find(temp) == model::mesh_map.end()) { @@ -488,7 +486,7 @@ void read_settings_xml() msg << "Mesh " << temp << " specified for Shannon entropy does not exist."; fatal_error(msg); } - index_entropy_mesh = model::mesh_map.at(temp); + i_entropy_mesh = model::mesh_map.at(temp); } else if (check_for_node(root, "entropy")) { warning("Specifying a Shannon entropy mesh via the element " @@ -500,25 +498,28 @@ void read_settings_xml() model::meshes.push_back(std::make_unique(node_entropy)); // Set entropy mesh index - index_entropy_mesh = model::meshes.size() - 1; + i_entropy_mesh = model::meshes.size() - 1; // Assign ID and set mapping model::meshes.back()->id_ = 10000; - model::mesh_map[10000] = index_entropy_mesh; + model::mesh_map[10000] = i_entropy_mesh; } - if (index_entropy_mesh >= 0) { - auto& m = *dynamic_cast(model::meshes[index_entropy_mesh].get()); - if (m.shape_.dimension() == 0) { + if (i_entropy_mesh >= 0) { + auto* m = dynamic_cast(model::meshes[i_entropy_mesh].get()); + if (!m) fatal_error("Only regular meshes can be used as an entropy mesh"); + simulation::entropy_mesh = m; + + if (m->shape_.dimension() == 0) { // If the user did not specify how many mesh cells are to be used in // each direction, we automatically determine an appropriate number of // cells int n = std::ceil(std::pow(n_particles / 20.0, 1.0/3.0)); - m.shape_ = {n, n, n}; - m.n_dimension_ = 3; + m->shape_ = {n, n, n}; + m->n_dimension_ = 3; // Calculate width - m.width_ = (m.upper_right_ - m.lower_left_) / m.shape_; + m->width_ = (m->upper_right_ - m->lower_left_) / m->shape_; } // Turn on Shannon entropy calculation @@ -526,6 +527,7 @@ void read_settings_xml() } // Uniform fission source weighting mesh + int32_t i_ufs_mesh = -1; if (check_for_node(root, "ufs_mesh")) { auto temp = std::stoi(get_node_value(root, "ufs_mesh")); if (model::mesh_map.find(temp) == model::mesh_map.end()) { @@ -534,7 +536,7 @@ void read_settings_xml() "does not exist."; fatal_error(msg); } - index_ufs_mesh = model::mesh_map.at(temp); + i_ufs_mesh = model::mesh_map.at(temp); } else if (check_for_node(root, "uniform_fs")) { warning("Specifying a UFS mesh via the element " @@ -546,14 +548,18 @@ void read_settings_xml() model::meshes.push_back(std::make_unique(node_ufs)); // Set entropy mesh index - index_ufs_mesh = model::meshes.size() - 1; + i_ufs_mesh = model::meshes.size() - 1; // Assign ID and set mapping model::meshes.back()->id_ = 10001; - model::mesh_map[10001] = index_entropy_mesh; + model::mesh_map[10001] = i_entropy_mesh; } - if (index_ufs_mesh >= 0) { + if (i_ufs_mesh >= 0) { + auto* m = dynamic_cast(model::meshes[i_ufs_mesh].get()); + if (!m) fatal_error("Only regular meshes can be used as a UFS mesh"); + simulation::ufs_mesh = m; + // Turn on uniform fission source weighting ufs_on = true; } diff --git a/src/simulation.cpp b/src/simulation.cpp index fc7a788517..f94ba232ac 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -247,6 +247,9 @@ int total_gen {0}; double total_weight; int64_t work_per_rank; +const RegularMesh* entropy_mesh {nullptr}; +const RegularMesh* ufs_mesh {nullptr}; + std::vector k_generation; std::vector work_index; From a0d3d1132813feffd0289ecdf10b17eca86e5e96 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 29 May 2019 11:57:13 -0400 Subject: [PATCH 055/151] Clean up RectilinearMesh implementation --- include/openmc/mesh.h | 20 ++++- openmc/mesh.py | 82 +++++++++++++++---- src/mesh.cpp | 127 ++++++++++++++++++----------- src/tallies/filter_mesh.cpp | 39 +++------ src/tallies/filter_meshsurface.cpp | 17 +--- 5 files changed, 175 insertions(+), 110 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index ecb7a59ca1..12158339d9 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -79,6 +79,12 @@ public: //! \param[out] ijk Mesh indices virtual void get_indices_from_bin(int bin, int* ijk) const = 0; + //! Get the number of mesh cells. + virtual int n_bins() const = 0; + + //! Get the number of mesh cell surfaces. + virtual int n_surface_bins() const = 0; + //! Write mesh data to an HDF5 group // //! \param[in] group HDF5 group @@ -87,6 +93,7 @@ public: // Data members int id_ {-1}; //!< User-specified ID + int n_dimension_; //!< Number of dimensions }; //============================================================================== @@ -116,6 +123,10 @@ public: void get_indices_from_bin(int bin, int* ijk) const override; + int n_bins() const override; + + int n_surface_bins() const override; + void to_hdf5(hid_t group) const override; // New methods @@ -133,13 +144,12 @@ public: //! \param[in] bank Array of bank sites //! \param[out] Whether any bank sites are outside the mesh //! \return Array indicating number of sites in each mesh/energy bin - virtual xt::xarray - count_sites(const std::vector& bank, bool* outside) const; + xt::xarray count_sites(const std::vector& bank, + bool* outside) const; // Data members double volume_frac_; //!< Volume fraction of each mesh element - int n_dimension_; //!< Number of dimensions xt::xarray shape_; //!< Number of mesh elements in each dimension xt::xarray lower_left_; //!< Lower-left coordinates of mesh xt::xarray upper_right_; //!< Upper-right coordinates of mesh @@ -173,6 +183,10 @@ public: void get_indices_from_bin(int bin, int* ijk) const override; + int n_bins() const override; + + int n_surface_bins() const override; + void to_hdf5(hid_t group) const override; // New methods diff --git a/openmc/mesh.py b/openmc/mesh.py index 0961b238e5..ef3212b900 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -77,7 +77,7 @@ class MeshBase(IDManagerMixin, metaclass=ABCMeta): class Mesh(MeshBase): - """A structured Cartesian mesh in one, two, or three dimensions + """A regular Cartesian mesh in one, two, or three dimensions Parameters ---------- @@ -92,10 +92,10 @@ class Mesh(MeshBase): Unique identifier for the mesh name : str Name of the mesh - type : str - Type of the mesh dimension : Iterable of int The number of mesh cells in each direction. + n_dimension : int + Number of mesh dimensions. lower_left : Iterable of float The lower-left corner of the structured mesh. If only two coordinate are given, it is assumed that the mesh is an x-y mesh. @@ -114,16 +114,11 @@ class Mesh(MeshBase): super().__init__(mesh_id, name) # Initialize Mesh class attributes - self._type = 'regular' self._dimension = None self._lower_left = None self._upper_right = None self._width = None - @property - def type(self): - return self._type - @property def dimension(self): return self._dimension @@ -166,14 +161,6 @@ class Mesh(MeshBase): nx, = self.dimension return ((x,) for x in range(1, nx + 1)) - @type.setter - def type(self, meshtype): - cv.check_type('type for mesh ID="{0}"'.format(self._id), - meshtype, str) - cv.check_value('type for mesh ID="{0}"'.format(self._id), - meshtype, ['regular']) - self._type = meshtype - @dimension.setter def dimension(self, dimension): cv.check_type('mesh dimension', dimension, Iterable, Integral) @@ -215,7 +202,6 @@ class Mesh(MeshBase): # Read and assign mesh properties mesh = cls(mesh_id) - mesh.type = group['type'][()].decode() mesh.dimension = group['dimension'][()] mesh.lower_left = group['lower_left'][()] mesh.upper_right = group['upper_right'][()] @@ -269,7 +255,7 @@ class Mesh(MeshBase): element = ET.Element("mesh") element.set("id", str(self._id)) - element.set("type", self._type) + element.set("type", "regular") subelement = ET.SubElement(element, "dimension") subelement.text = ' '.join(map(str, self._dimension)) @@ -418,13 +404,58 @@ class Mesh(MeshBase): class RectilinearMesh(MeshBase): + """A 3D rectilinear Cartesian mesh + + Parameters + ---------- + mesh_id : int + Unique identifier for the mesh + name : str + Name of the mesh + + Attributes + ---------- + id : int + Unique identifier for the mesh + name : str + Name of the mesh + n_dimension : int + Number of mesh dimensions (always 3 for a RectilinearMesh). + x_grid : Iterable of float + Mesh boundary points along the x-axis. + y_grid : Iterable of float + Mesh boundary points along the y-axis. + z_grid : Iterable of float + Mesh boundary points along the z-axis. + indices : list of tuple + A list of mesh indices for each mesh element, e.g. [(1, 1, 1), (2, 1, + 1), ...] + + """ + def __init__(self, mesh_id=None, name=''): super().__init__(mesh_id, name) + self._x_grid = None + self._y_grid = None + self._z_grid = None + @property def n_dimension(self): return 3 + @property + def x_grid(self): + return self._x_grid + + @property + def y_grid(self): + return self._y_grid + + @property + def z_grid(self): + return self._z_grid + @property def indices(self): nx = len(self.x_grid) - 1 @@ -435,6 +466,21 @@ class RectilinearMesh(MeshBase): for y in range(1, ny + 1) for x in range(1, nx + 1)) + @x_grid.setter + def x_grid(self, grid): + cv.check_type('mesh x_grid', grid, Iterable, Real) + self._x_grid = grid + + @y_grid.setter + def y_grid(self, grid): + cv.check_type('mesh y_grid', grid, Iterable, Real) + self._y_grid = grid + + @z_grid.setter + def z_grid(self, grid): + cv.check_type('mesh z_grid', grid, Iterable, Real) + self._z_grid = grid + @classmethod def from_hdf5(cls, group): mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) diff --git a/src/mesh.cpp b/src/mesh.cpp index 0e3198c63f..2cb91b98fa 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -228,6 +228,18 @@ void RegularMesh::get_indices_from_bin(int bin, int* ijk) const } } +int RegularMesh::n_bins() const +{ + int n_bins = 1; + for (auto dim : shape_) n_bins *= dim; + return n_bins; +} + +int RegularMesh::n_surface_bins() const +{ + return 4 * n_dimension_ * n_bins(); +} + bool RegularMesh::intersects(Position& r0, Position r1, int* ijk) const { switch(n_dimension_) { @@ -788,6 +800,8 @@ RegularMesh::count_sites(const std::vector& bank, RectilinearMesh::RectilinearMesh(pugi::xml_node node) : Mesh {node} { + n_dimension_ = 3; + grid_.resize(3); grid_[0] = get_node_array(node, "x_grid"); grid_[1] = get_node_array(node, "y_grid"); @@ -1099,6 +1113,18 @@ void RectilinearMesh::get_indices_from_bin(int bin, int* ijk) const ijk[2] = bin / (shape_[0] * shape_[1]) + 1; } +int RectilinearMesh::n_bins() const +{ + int n_bins = 1; + for (auto dim : shape_) n_bins *= dim; + return n_bins; +} + +int RectilinearMesh::n_surface_bins() const +{ + return 4 * n_dimension_ * n_bins(); +} + void RectilinearMesh::to_hdf5(hid_t group) const { hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); @@ -1222,6 +1248,32 @@ bool RectilinearMesh::intersects(Position& r0, Position r1, int* ijk) const return min_dist < INFTY; } +//============================================================================== +// Helper functions for the C API +//============================================================================== + +int +check_mesh(int32_t index) +{ + if (index < 0 || index >= model::meshes.size()) { + set_errmsg("Index in meshes array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + return 0; +} + +int +check_regular_mesh(int32_t index, RegularMesh** mesh) +{ + if (int err = check_mesh(index)) return err; + *mesh = dynamic_cast(model::meshes[index].get()); + if (!*mesh) { + set_errmsg("This function is only valid for regular meshes."); + return OPENMC_E_INVALID_TYPE; + } + return 0; +} + //============================================================================== // C API functions //============================================================================== @@ -1256,10 +1308,7 @@ openmc_get_mesh_index(int32_t id, int32_t* index) extern "C" int openmc_mesh_get_id(int32_t index, int32_t* id) { - if (index < 0 || index >= model::meshes.size()) { - set_errmsg("Index in meshes array is out of bounds."); - return OPENMC_E_OUT_OF_BOUNDS; - } + if (int err = check_mesh(index)) return err; *id = model::meshes[index]->id_; return 0; } @@ -1268,10 +1317,7 @@ openmc_mesh_get_id(int32_t index, int32_t* id) extern "C" int openmc_mesh_set_id(int32_t index, int32_t id) { - if (index < 0 || index >= model::meshes.size()) { - set_errmsg("Index in meshes array is out of bounds."); - return OPENMC_E_OUT_OF_BOUNDS; - } + if (int err = check_mesh(index)) return err; model::meshes[index]->id_ = id; model::mesh_map[id] = index; return 0; @@ -1281,12 +1327,10 @@ openmc_mesh_set_id(int32_t index, int32_t id) extern "C" int openmc_mesh_get_dimension(int32_t index, int** dims, int* n) { - if (index < 0 || index >= model::meshes.size()) { - set_errmsg("Index in meshes array is out of bounds."); - return OPENMC_E_OUT_OF_BOUNDS; - } - *dims = dynamic_cast(model::meshes[index].get())->shape_.data(); - *n = dynamic_cast(model::meshes[index].get())->n_dimension_; + RegularMesh* mesh; + if (int err = check_regular_mesh(index, &mesh)) return err; + *dims = mesh->shape_.data(); + *n = mesh->n_dimension_; return 0; } @@ -1294,16 +1338,13 @@ openmc_mesh_get_dimension(int32_t index, int** dims, int* n) extern "C" int openmc_mesh_set_dimension(int32_t index, int n, const int* dims) { - if (index < 0 || index >= model::meshes.size()) { - set_errmsg("Index in meshes array is out of bounds."); - return OPENMC_E_OUT_OF_BOUNDS; - } + RegularMesh* mesh; + if (int err = check_regular_mesh(index, &mesh)) return err; // Copy dimension std::vector shape = {static_cast(n)}; - auto& m = *dynamic_cast(model::meshes[index].get()); - m.shape_ = xt::adapt(dims, n, xt::no_ownership(), shape); - m.n_dimension_ = m.shape_.size(); + mesh->shape_ = xt::adapt(dims, n, xt::no_ownership(), shape); + mesh->n_dimension_ = mesh->shape_.size(); return 0; } @@ -1312,22 +1353,18 @@ openmc_mesh_set_dimension(int32_t index, int n, const int* dims) extern "C" int openmc_mesh_get_params(int32_t index, double** ll, double** ur, double** width, int* n) { - if (index < 0 || index >= model::meshes.size()) { - set_errmsg("Index in meshes array is out of bounds."); - return OPENMC_E_OUT_OF_BOUNDS; - } + RegularMesh* m; + if (int err = check_regular_mesh(index, &m)) return err; - //auto& m = model::meshes[index]; - auto& m = *dynamic_cast(model::meshes[index].get()); - if (m.lower_left_.dimension() == 0) { + if (m->lower_left_.dimension() == 0) { set_errmsg("Mesh parameters have not been set."); return OPENMC_E_ALLOCATE; } - *ll = m.lower_left_.data(); - *ur = m.upper_right_.data(); - *width = m.width_.data(); - *n = m.n_dimension_; + *ll = m->lower_left_.data(); + *ur = m->upper_right_.data(); + *width = m->width_.data(); + *n = m->n_dimension_; return 0; } @@ -1336,26 +1373,22 @@ extern "C" int openmc_mesh_set_params(int32_t index, int n, const double* ll, const double* ur, const double* width) { - if (index < 0 || index >= model::meshes.size()) { - set_errmsg("Index in meshes array is out of bounds."); - return OPENMC_E_OUT_OF_BOUNDS; - } + RegularMesh* m; + if (int err = check_regular_mesh(index, &m)) return err; - //auto& m = model::meshes[index]; - auto& m = *dynamic_cast(model::meshes[index].get()); std::vector shape = {static_cast(n)}; if (ll && ur) { - m.lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape); - m.upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape); - m.width_ = (m.upper_right_ - m.lower_left_) / m.shape_; + m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape); + m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape); + m->width_ = (m->upper_right_ - m->lower_left_) / m->shape_; } else if (ll && width) { - m.lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape); - m.width_ = xt::adapt(width, n, xt::no_ownership(), shape); - m.upper_right_ = m.lower_left_ + m.shape_ * m.width_; + m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape); + m->width_ = xt::adapt(width, n, xt::no_ownership(), shape); + m->upper_right_ = m->lower_left_ + m->shape_ * m->width_; } else if (ur && width) { - m.upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape); - m.width_ = xt::adapt(width, n, xt::no_ownership(), shape); - m.lower_left_ = m.upper_right_ - m.shape_ * m.width_; + m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape); + m->width_ = xt::adapt(width, n, xt::no_ownership(), shape); + m->lower_left_ = m->upper_right_ - m->shape_ * m->width_; } else { set_errmsg("At least two parameters must be specified."); return OPENMC_E_INVALID_ARGUMENT; diff --git a/src/tallies/filter_mesh.cpp b/src/tallies/filter_mesh.cpp index 9658e552ec..5dce9c618a 100644 --- a/src/tallies/filter_mesh.cpp +++ b/src/tallies/filter_mesh.cpp @@ -55,43 +55,26 @@ MeshFilter::to_statepoint(hid_t filter_group) const std::string MeshFilter::text_label(int bin) const { - if (auto* mesh = dynamic_cast(model::meshes[mesh_].get())) { - int n_dim = mesh->n_dimension_; + auto& mesh = *model::meshes[mesh_]; + int n_dim = mesh.n_dimension_; - int ijk[n_dim]; - mesh->get_indices_from_bin(bin, ijk); + int ijk[n_dim]; + mesh.get_indices_from_bin(bin, ijk); - std::stringstream out; - out << "Mesh Index (" << ijk[0]; - if (n_dim > 1) out << ", " << ijk[1]; - if (n_dim > 2) out << ", " << ijk[2]; - out << ")"; + std::stringstream out; + out << "Mesh Index (" << ijk[0]; + if (n_dim > 1) out << ", " << ijk[1]; + if (n_dim > 2) out << ", " << ijk[2]; + out << ")"; - return out.str(); - - } else if (auto* mesh = dynamic_cast(model::meshes[mesh_].get())) { - int ijk[3]; - mesh->get_indices_from_bin(bin, ijk); - - std::stringstream out; - out << "Mesh Index (" << ijk[0] << ", " << ijk[1] << ", " << ijk[2] << ")"; - - return out.str(); - } + return out.str(); } void MeshFilter::set_mesh(int32_t mesh) { mesh_ = mesh; - auto* m_ptr = model::meshes[mesh_].get(); - if (auto* m = dynamic_cast(m_ptr)) { - n_bins_ = 1; - for (auto dim : m->shape_) n_bins_ *= dim; - } else if (auto* m = dynamic_cast(m_ptr)) { - n_bins_ = 1; - for (auto dim : m->shape_) n_bins_ *= dim; - } + n_bins_ = model::meshes[mesh_]->n_bins(); } //============================================================================== diff --git a/src/tallies/filter_meshsurface.cpp b/src/tallies/filter_meshsurface.cpp index aa64520139..edb0492e9f 100644 --- a/src/tallies/filter_meshsurface.cpp +++ b/src/tallies/filter_meshsurface.cpp @@ -18,12 +18,8 @@ MeshSurfaceFilter::get_all_bins(const Particle* p, int estimator, std::string MeshSurfaceFilter::text_label(int bin) const { - int n_dim; - if (auto* mesh = dynamic_cast(model::meshes[mesh_].get())) { - n_dim = mesh->n_dimension_; - } else if (auto* mesh = dynamic_cast(model::meshes[mesh_].get())) { - n_dim = 3; - } + auto& mesh = *model::meshes[mesh_]; + int n_dim = mesh.n_dimension_; // Get flattend mesh index and surface index. int i_mesh = bin / (4 * n_dim); @@ -79,14 +75,7 @@ void MeshSurfaceFilter::set_mesh(int32_t mesh) { mesh_ = mesh; - auto* m_ptr = model::meshes[mesh_].get(); - if (auto* m = dynamic_cast(m_ptr)) { - n_bins_ = 4 * m->n_dimension_; - for (auto dim : m->shape_) n_bins_ *= dim; - } else if (auto* m = dynamic_cast(m_ptr)) { - n_bins_ = 4 * 3; - for (auto dim : m->shape_) n_bins_ *= dim; - } + n_bins_ = model::meshes[mesh_]->n_surface_bins(); } //============================================================================== From 7a49a29f38c6537a1346754ec66cf7d9a0aaa450 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 29 May 2019 15:29:54 -0400 Subject: [PATCH 056/151] Refactor and fix RegularMesh meshline plots --- include/openmc/mesh.h | 9 ++ src/mesh.cpp | 39 +++++ src/plot.cpp | 141 ++++++++++--------- tests/regression_tests/plot/plots.xml | 3 + tests/regression_tests/plot/results_true.dat | 2 +- 5 files changed, 127 insertions(+), 67 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 12158339d9..8233d67792 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -85,6 +85,9 @@ public: //! Get the number of mesh cell surfaces. virtual int n_surface_bins() const = 0; + virtual std::pair, std::vector> + plot(Position plot_ll, Position plot_ur) const = 0; + //! Write mesh data to an HDF5 group // //! \param[in] group HDF5 group @@ -127,6 +130,9 @@ public: int n_surface_bins() const override; + std::pair, std::vector> + plot(Position plot_ll, Position plot_ur) const override; + void to_hdf5(hid_t group) const override; // New methods @@ -187,6 +193,9 @@ public: int n_surface_bins() const override; + std::pair, std::vector> + plot(Position plot_ll, Position plot_ur) const override; + void to_hdf5(hid_t group) const override; // New methods diff --git a/src/mesh.cpp b/src/mesh.cpp index 2cb91b98fa..f398d641d6 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -729,6 +729,40 @@ void RegularMesh::surface_bins_crossed(const Particle* p, } } +std::pair, std::vector> +RegularMesh::plot(Position plot_ll, Position plot_ur) const +{ + std::array axes {-1, -1}; + if (plot_ur.z == plot_ll.z) { + axes[0] = 0; + if (n_dimension_ > 1) axes[1] = 1; + } else if (plot_ur.y == plot_ll.y) { + axes[0] = 0; + if (n_dimension_ > 2) axes[1] = 2; + } else if (plot_ur.x == plot_ll.x) { + if (n_dimension_ > 1) axes[0] = 1; + if (n_dimension_ > 2) axes[1] = 2; + } else { + fatal_error("Can only plot mesh lines on an axis-aligned plot"); + } + + std::array, 2> axis_lines; + for (int i_ax = 0; i_ax < 2; ++i_ax) { + int axis = axes[i_ax]; + if (axis == -1) continue; + std::vector& lines {axis_lines[i_ax]}; + + double coord = lower_left_[axis]; + for (int i = 0; i < shape_[axis] + 1; ++i) { + if (coord >= plot_ll[axis] && coord <= plot_ur[axis]) + lines.push_back(coord); + coord += width_[axis]; + } + } + + return {axis_lines[0], axis_lines[1]}; +} + void RegularMesh::to_hdf5(hid_t group) const { hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); @@ -1125,6 +1159,11 @@ int RectilinearMesh::n_surface_bins() const return 4 * n_dimension_ * n_bins(); } +std::pair, std::vector> +RectilinearMesh::plot(Position plot_ll, Position plot_ur) const +{ +} + void RectilinearMesh::to_hdf5(hid_t group) const { hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); diff --git a/src/plot.cpp b/src/plot.cpp index 43e7ed69cb..bfb6b335fa 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -698,19 +698,19 @@ void draw_mesh_lines(Plot pl, ImageData& data) RGBColor rgb; rgb = pl.meshlines_color_; - int outer, inner; + int ax1, ax2; switch(pl.basis_) { case PlotBasis::xy : - outer = 0; - inner = 1; + ax1 = 0; + ax2 = 1; break; case PlotBasis::xz : - outer = 0; - inner = 2; + ax1 = 0; + ax2 = 2; break; case PlotBasis::yz : - outer = 1; - inner = 2; + ax1 = 1; + ax2 = 2; break; default: UNREACHABLE(); @@ -719,70 +719,79 @@ void draw_mesh_lines(Plot pl, ImageData& data) Position ll_plot {pl.origin_}; Position ur_plot {pl.origin_}; - ll_plot[outer] -= pl.width_[0] / 2.; - ll_plot[inner] -= pl.width_[1] / 2.; - ur_plot[outer] += pl.width_[0] / 2.; - ur_plot[inner] += pl.width_[1] / 2.; + ll_plot[ax1] -= pl.width_[0] / 2.; + ll_plot[ax2] -= pl.width_[1] / 2.; + ur_plot[ax1] += pl.width_[0] / 2.; + ur_plot[ax2] += pl.width_[1] / 2.; Position width = ur_plot - ll_plot; - auto& m = *dynamic_cast(model::meshes[pl.index_meshlines_mesh_].get()); + // Find the (axis-aligned) lines of the mesh that intersect this plot. + std::pair, std::vector> axis_lines; + axis_lines = model::meshes[pl.index_meshlines_mesh_]->plot(ll_plot, ur_plot); - int ijk_ll[3], ijk_ur[3]; - bool in_mesh; - m.get_indices(ll_plot, &(ijk_ll[0]), &in_mesh); - m.get_indices(ur_plot, &(ijk_ur[0]), &in_mesh); - - // Fortran/C++ index correction - ijk_ur[0]++; ijk_ur[1]++; ijk_ur[2]++; - - Position r_ll, r_ur; - // sweep through all meshbins on this plane and draw borders - for (int i = ijk_ll[outer]; i <= ijk_ur[outer]; i++) { - for (int j = ijk_ll[inner]; j <= ijk_ur[inner]; j++) { - // check if we're in the mesh for this ijk - if (i > 0 && i <= m.shape_[outer] && j >0 && j <= m.shape_[inner] ) { - int outrange[3], inrange[3]; - // get xyz's of lower left and upper right of this mesh cell - r_ll[outer] = m.lower_left_[outer] + m.width_[outer] * (i - 1); - r_ll[inner] = m.lower_left_[inner] + m.width_[inner] * (j - 1); - r_ur[outer] = m.lower_left_[outer] + m.width_[outer] * i; - r_ur[inner] = m.lower_left_[inner] + m.width_[inner] * j; - - // map the xyz ranges to pixel ranges - double frac = (r_ll[outer] - ll_plot[outer]) / width[outer]; - outrange[0] = int(frac * double(pl.pixels_[0])); - frac = (r_ur[outer] - ll_plot[outer]) / width[outer]; - outrange[1] = int(frac * double(pl.pixels_[0])); - - frac = (r_ur[inner] - ll_plot[inner]) / width[inner]; - inrange[0] = int((1. - frac) * (double)pl.pixels_[1]); - frac = (r_ll[inner] - ll_plot[inner]) / width[inner]; - inrange[1] = int((1. - frac) * (double)pl.pixels_[1]); - - // draw lines - for (int out_ = outrange[0]; out_ <= outrange[1]; out_++) { - for (int plus = 0; plus <= pl.meshlines_width_; plus++) { - data(out_, inrange[0] + plus) = rgb; - data(out_, inrange[1] + plus) = rgb; - data(out_, inrange[0] - plus) = rgb; - data(out_, inrange[1] - plus) = rgb; - } - } - - for (int in_ = inrange[0]; in_ <= inrange[1]; in_++) { - for (int plus = 0; plus <= pl.meshlines_width_; plus++) { - data(outrange[0] + plus, in_) = rgb; - data(outrange[1] + plus, in_) = rgb; - data(outrange[0] - plus, in_) = rgb; - data(outrange[1] - plus, in_) = rgb; - } - } - - } // end if(in mesh) + // Draw lines perpendicular to the first axis. + { + // Find the bounds along the second axis (accounting for low-D meshes). + int ax2_min, ax2_max; + if (axis_lines.second.size() > 0) { + double frac = (axis_lines.second.front() - ll_plot[ax2]) / width[ax2]; + ax2_min = frac * pl.pixels_[1]; + if (ax2_min < 0) ax2_min = 0; + frac = (axis_lines.second.back() - ll_plot[ax2]) / width[ax2]; + ax2_max = frac * pl.pixels_[1]; + if (ax2_max > pl.pixels_[1]) ax2_max = pl.pixels_[1]; + } else { + ax2_min = 0; + ax2_max = pl.pixels_[1]; } - } // end outer loops -} // end draw_mesh_lines + + // Iterate across the first axis and draw lines. + for (auto ax1_val : axis_lines.first) { + double frac = (ax1_val - ll_plot[ax1]) / width[ax1]; + int ax1_ind = frac * pl.pixels_[0]; + for (int ax2_ind = ax2_min; ax2_ind < ax2_max; ++ax2_ind) { + for (int plus = 0; plus <= pl.meshlines_width_; plus++) { + if (ax1_ind+plus >= 0 && ax1_ind+plus < pl.pixels_[0]) + data(ax1_ind+plus, ax2_ind) = rgb; + if (ax1_ind-plus >= 0 && ax1_ind-plus < pl.pixels_[0]) + data(ax1_ind-plus, ax2_ind) = rgb; + } + } + } + } + + // Draw lines perpendicular to the second axis. + { + // Find the bounds along the first axis. + int ax1_min, ax1_max; + if (axis_lines.first.size() > 0) { + double frac = (axis_lines.first.front() - ll_plot[ax1]) / width[ax1]; + ax1_min = frac * pl.pixels_[0]; + if (ax1_min < 0) ax1_min = 0; + frac = (axis_lines.first.back() - ll_plot[ax1]) / width[ax1]; + ax1_max = frac * pl.pixels_[0]; + if (ax1_max > pl.pixels_[0]) ax1_max = pl.pixels_[0]; + } else { + ax1_min = 0; + ax1_max = pl.pixels_[0]; + } + + // Iterate across the second axis and draw lines. + for (auto ax2_val : axis_lines.second) { + double frac = (ax2_val - ll_plot[ax2]) / width[ax2]; + int ax2_ind = frac * pl.pixels_[1]; + for (int ax1_ind = ax1_min; ax1_ind < ax1_max; ++ax1_ind) { + for (int plus = 0; plus <= pl.meshlines_width_; plus++) { + if (ax2_ind+plus >= 0 && ax2_ind+plus < pl.pixels_[1]) + data(ax1_ind, ax2_ind+plus) = rgb; + if (ax2_ind-plus >= 0 && ax2_ind-plus < pl.pixels_[1]) + data(ax1_ind, ax2_ind-plus) = rgb; + } + } + } + } +} //============================================================================== // CREATE_VOXEL outputs a binary file that can be input into silomesh for 3D diff --git a/tests/regression_tests/plot/plots.xml b/tests/regression_tests/plot/plots.xml index ecfe69125b..075124a8f5 100644 --- a/tests/regression_tests/plot/plots.xml +++ b/tests/regression_tests/plot/plots.xml @@ -6,7 +6,10 @@ 25 25 200 200 + + diff --git a/tests/regression_tests/plot/results_true.dat b/tests/regression_tests/plot/results_true.dat index 18251e8d0c..128aa9bbef 100644 --- a/tests/regression_tests/plot/results_true.dat +++ b/tests/regression_tests/plot/results_true.dat @@ -1 +1 @@ -6b3eb36488b995b42233e6b7c0164cf6c92a1823b3c91985b97fd076f86c0aad93fbdb74e70026f5f12c61a6aee52a09588171a7fd839511ee7d9efc3952beb2 \ No newline at end of file +ae51cbf147d69804b03f9d88c03d98533665a9822438d1d8b70b40928a1db35d20e18d12ee697b5e1e217799d6e8c352524e8d2227328e719e85c4e6d96f825c \ No newline at end of file From 7f8e993e93673b58ecfd6ce961a7c93534413add Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 29 May 2019 15:48:58 -0400 Subject: [PATCH 057/151] Implement rectilinear meshline plots --- include/openmc/mesh.h | 8 ++++++ src/mesh.cpp | 27 ++++++++++++++++++++ src/plot.cpp | 10 ++++---- tests/regression_tests/plot/plots.xml | 4 +-- tests/regression_tests/plot/results_true.dat | 2 +- tests/regression_tests/plot/tallies.xml | 20 +++++++++++++++ 6 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 tests/regression_tests/plot/tallies.xml diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 8233d67792..0e8d75ea39 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -85,6 +85,14 @@ public: //! Get the number of mesh cell surfaces. virtual int n_surface_bins() const = 0; + //! Find the mesh lines that intersect an axis-aligned slice plot + // + //! \param[in] plot_ll The lower-left coordinates of the slice plot. + //! \param[in] plot_ur The upper-right coordinates of the slice plot. + //! \return A pair of vectors indicating where the mesh lines lie along each + //! of the plot's axes. For example an xy-slice plot will get back a vector + //! of x-coordinates and another of y-coordinates. These vectors may be + //! empty for low-dimensional meshes. virtual std::pair, std::vector> plot(Position plot_ll, Position plot_ur) const = 0; diff --git a/src/mesh.cpp b/src/mesh.cpp index f398d641d6..870a58e6c6 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -732,6 +732,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, std::pair, std::vector> RegularMesh::plot(Position plot_ll, Position plot_ur) const { + // Figure out which axes lie in the plane of the plot. std::array axes {-1, -1}; if (plot_ur.z == plot_ll.z) { axes[0] = 0; @@ -746,6 +747,7 @@ RegularMesh::plot(Position plot_ll, Position plot_ur) const fatal_error("Can only plot mesh lines on an axis-aligned plot"); } + // Get the coordinates of the mesh lines along both of the axes. std::array, 2> axis_lines; for (int i_ax = 0; i_ax < 2; ++i_ax) { int axis = axes[i_ax]; @@ -1162,6 +1164,31 @@ int RectilinearMesh::n_surface_bins() const std::pair, std::vector> RectilinearMesh::plot(Position plot_ll, Position plot_ur) const { + // Figure out which axes lie in the plane of the plot. + std::array axes {-1, -1}; + if (plot_ur.z == plot_ll.z) { + axes = {0, 1}; + } else if (plot_ur.y == plot_ll.y) { + axes = {0, 2}; + } else if (plot_ur.x == plot_ll.x) { + axes = {1, 2}; + } else { + fatal_error("Can only plot mesh lines on an axis-aligned plot"); + } + + // Get the coordinates of the mesh lines along both of the axes. + std::array, 2> axis_lines; + for (int i_ax = 0; i_ax < 2; ++i_ax) { + int axis = axes[i_ax]; + std::vector& lines {axis_lines[i_ax]}; + + for (auto coord : grid_[axis]) { + if (coord >= plot_ll[axis] && coord <= plot_ur[axis]) + lines.push_back(coord); + } + } + + return {axis_lines[0], axis_lines[1]}; } void RectilinearMesh::to_hdf5(hid_t group) const diff --git a/src/plot.cpp b/src/plot.cpp index bfb6b335fa..1ad4f83174 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -735,11 +735,11 @@ void draw_mesh_lines(Plot pl, ImageData& data) // Find the bounds along the second axis (accounting for low-D meshes). int ax2_min, ax2_max; if (axis_lines.second.size() > 0) { - double frac = (axis_lines.second.front() - ll_plot[ax2]) / width[ax2]; - ax2_min = frac * pl.pixels_[1]; + double frac = (axis_lines.second.back() - ll_plot[ax2]) / width[ax2]; + ax2_min = (1.0 - frac) * pl.pixels_[1]; if (ax2_min < 0) ax2_min = 0; - frac = (axis_lines.second.back() - ll_plot[ax2]) / width[ax2]; - ax2_max = frac * pl.pixels_[1]; + frac = (axis_lines.second.front() - ll_plot[ax2]) / width[ax2]; + ax2_max = (1.0 - frac) * pl.pixels_[1]; if (ax2_max > pl.pixels_[1]) ax2_max = pl.pixels_[1]; } else { ax2_min = 0; @@ -780,7 +780,7 @@ void draw_mesh_lines(Plot pl, ImageData& data) // Iterate across the second axis and draw lines. for (auto ax2_val : axis_lines.second) { double frac = (ax2_val - ll_plot[ax2]) / width[ax2]; - int ax2_ind = frac * pl.pixels_[1]; + int ax2_ind = (1.0 - frac) * pl.pixels_[1]; for (int ax1_ind = ax1_min; ax1_ind < ax1_max; ++ax1_ind) { for (int plus = 0; plus <= pl.meshlines_width_; plus++) { if (ax2_ind+plus >= 0 && ax2_ind+plus < pl.pixels_[1]) diff --git a/tests/regression_tests/plot/plots.xml b/tests/regression_tests/plot/plots.xml index 075124a8f5..ce63da1442 100644 --- a/tests/regression_tests/plot/plots.xml +++ b/tests/regression_tests/plot/plots.xml @@ -6,10 +6,7 @@ 25 25 200 200 - - @@ -17,6 +14,7 @@ 25 25 200 200 + diff --git a/tests/regression_tests/plot/results_true.dat b/tests/regression_tests/plot/results_true.dat index 128aa9bbef..db6beb9cdb 100644 --- a/tests/regression_tests/plot/results_true.dat +++ b/tests/regression_tests/plot/results_true.dat @@ -1 +1 @@ -ae51cbf147d69804b03f9d88c03d98533665a9822438d1d8b70b40928a1db35d20e18d12ee697b5e1e217799d6e8c352524e8d2227328e719e85c4e6d96f825c \ No newline at end of file +fd61f6a5de632f06a39e2a938480ba3dc314810655e684291e4b255bb8c142a16f10fec414c587ecd727fa559d3760d6e5b43f53fe86e90238a69d9c5698db8e \ No newline at end of file diff --git a/tests/regression_tests/plot/tallies.xml b/tests/regression_tests/plot/tallies.xml new file mode 100644 index 0000000000..b7e678ca0f --- /dev/null +++ b/tests/regression_tests/plot/tallies.xml @@ -0,0 +1,20 @@ + + + + + -10 10 + -10 10 + -10 0 5 7.5 8.75 10 + + + + mesh + 2 + + + + 1 + total + + + From 7cf1822410b87c0e0674d9388fb5b6ffcc50aa49 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 29 May 2019 22:02:43 -0400 Subject: [PATCH 058/151] Add RectilinearMesh to documentation --- docs/source/io_formats/tallies.rst | 19 ++- docs/source/pythonapi/base.rst | 1 + src/relaxng/tallies.rnc | 33 +++-- src/relaxng/tallies.rng | 206 +++++++++++++++++++---------- 4 files changed, 172 insertions(+), 87 deletions(-) diff --git a/docs/source/io_formats/tallies.rst b/docs/source/io_formats/tallies.rst index 23a0201ca9..0e1cd16f4e 100644 --- a/docs/source/io_formats/tallies.rst +++ b/docs/source/io_formats/tallies.rst @@ -312,21 +312,30 @@ a separate element with the tag name ````. This element has the following attributes/sub-elements: :type: - The type of structured mesh. The only valid option is "regular". + The type of structured mesh. This can be either "regular" or "rectilinear". :dimension: - The number of mesh cells in each direction. + The number of mesh cells in each direction. (For regular mesh only.) :lower_left: The lower-left corner of the structured mesh. If only two coordinates are - given, it is assumed that the mesh is an x-y mesh. + given, it is assumed that the mesh is an x-y mesh. (For regular mesh only.) :upper_right: The upper-right corner of the structured mesh. If only two coordinates are - given, it is assumed that the mesh is an x-y mesh. + given, it is assumed that the mesh is an x-y mesh. (For regular mesh only.) :width: - The width of mesh cells in each direction. + The width of mesh cells in each direction. (For regular mesh only.) + + :x_grid: + The mesh divisions along the x-axis. (For rectilinear mesh only.) + + :y_grid: + The mesh divisions along the y-axis. (For rectilinear mesh only.) + + :z_grid: + The mesh divisions along the z-axis. (For rectilinear mesh only.) .. note:: One of ```` or ```` must be specified, but not both diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 8cf65a997f..0a6fbbbc07 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -125,6 +125,7 @@ Constructing Tallies openmc.ZernikeRadialFilter openmc.ParticleFilter openmc.Mesh + openmc.RectilinearMesh openmc.Trigger openmc.TallyDerivative openmc.Tally diff --git a/src/relaxng/tallies.rnc b/src/relaxng/tallies.rnc index 204284c480..ec511481f6 100644 --- a/src/relaxng/tallies.rnc +++ b/src/relaxng/tallies.rnc @@ -1,17 +1,30 @@ element tallies { element mesh { (element id { xsd:int } | attribute id { xsd:int }) & - (element type { ( "regular" ) } | - attribute type { ( "regular" ) }) & - (element dimension { list { xsd:positiveInteger+ } } | - attribute dimension { list { xsd:positiveInteger+ } }) & - (element lower_left { list { xsd:double+ } } | - attribute lower_left { list { xsd:double+ } }) & ( - (element upper_right { list { xsd:double+ } } | - attribute upper_right { list { xsd:double+ } }) | - (element width { list { xsd:double+ } } | - attribute width { list { xsd:double+ } }) + ( + (element type { ( "regular" ) } | + attribute type { ( "regular" ) }) & + (element dimension { list { xsd:positiveInteger+ } } | + attribute dimension { list { xsd:positiveInteger+ } }) & + (element lower_left { list { xsd:double+ } } | + attribute lower_left { list { xsd:double+ } }) & + ( + (element upper_right { list { xsd:double+ } } | + attribute upper_right { list { xsd:double+ } }) | + (element width { list { xsd:double+ } } | + attribute width { list { xsd:double+ } }) + ) + ) | ( + (element type { ( "rectilinear" ) } | + attribute type { ( "rectilinear" ) }) & + (element x_grid { list { xsd:double+ } } | + attribute x_grid { list { xsd:double+ } }) & + (element y_grid { list { xsd:double+ } } | + attribute y_grid { list { xsd:double+ } }) & + (element z_grid { list { xsd:double+ } } | + attribute z_grid { list { xsd:double+ } }) + ) ) }* & diff --git a/src/relaxng/tallies.rng b/src/relaxng/tallies.rng index 15b6f5b249..98a48eeb85 100644 --- a/src/relaxng/tallies.rng +++ b/src/relaxng/tallies.rng @@ -13,78 +13,140 @@ - - regular - - - regular - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + regular + + + regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rectilinear + + + rectilinear + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 79f792c09a7e0ef72584f350fe79ad96bdeae847 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Thu, 30 May 2019 10:54:08 +0300 Subject: [PATCH 059/151] New regression test for lattice with OX orient --- .../lattice_hex_OX/__init__.py | 0 .../lattice_hex_OX/inputs_true.dat | 126 +++++++++++++ .../lattice_hex_OX/results_true.dat | 2 + tests/regression_tests/lattice_hex_OX/test.py | 171 ++++++++++++++++++ 4 files changed, 299 insertions(+) create mode 100644 tests/regression_tests/lattice_hex_OX/__init__.py create mode 100644 tests/regression_tests/lattice_hex_OX/inputs_true.dat create mode 100644 tests/regression_tests/lattice_hex_OX/results_true.dat create mode 100644 tests/regression_tests/lattice_hex_OX/test.py diff --git a/tests/regression_tests/lattice_hex_OX/__init__.py b/tests/regression_tests/lattice_hex_OX/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/lattice_hex_OX/inputs_true.dat b/tests/regression_tests/lattice_hex_OX/inputs_true.dat new file mode 100644 index 0000000000..9130cf284d --- /dev/null +++ b/tests/regression_tests/lattice_hex_OX/inputs_true.dat @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + 1.235 5.0 + 4 +
0.0 0.0 5.0
+ + 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 + 1 1 1 1 1 3 1 1 1 1 3 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 3 1 1 1 1 3 1 1 1 3 1 1 1 1 + 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 +1 1 1 1 1 3 1 1 1 1 2 1 1 1 1 3 1 1 1 1 1 + 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 + 1 1 1 1 3 1 1 1 3 1 1 1 1 3 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 3 1 1 1 1 3 1 1 1 1 1 + 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 + 1 1 1 1 1 3 1 1 1 1 3 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 3 1 1 1 1 3 1 1 1 3 1 1 1 1 + 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 +1 1 1 1 1 3 1 1 1 1 2 1 1 1 1 3 1 1 1 1 1 + 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 + 1 1 1 1 3 1 1 1 3 1 1 1 1 3 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 3 1 1 1 1 3 1 1 1 1 1 + 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 10 + 5 + + + -13.62546635287517 -13.62546635287517 0.0 13.62546635287517 13.62546635287517 10.0 + + + + false + + 22 + diff --git a/tests/regression_tests/lattice_hex_OX/results_true.dat b/tests/regression_tests/lattice_hex_OX/results_true.dat new file mode 100644 index 0000000000..a742aa0d54 --- /dev/null +++ b/tests/regression_tests/lattice_hex_OX/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.355663E+00 2.896562E-02 diff --git a/tests/regression_tests/lattice_hex_OX/test.py b/tests/regression_tests/lattice_hex_OX/test.py new file mode 100644 index 0000000000..2eab458dca --- /dev/null +++ b/tests/regression_tests/lattice_hex_OX/test.py @@ -0,0 +1,171 @@ +from tests.testing_harness import PyAPITestHarness +import openmc +import numpy as np + +class HexLatticeOXTestHarness(PyAPITestHarness): + def _build_inputs(self): + materials = openmc.Materials() + + fuel_mat = openmc.Material(material_id=1,name="UO2") + fuel_mat.set_density('sum') + fuel_mat.add_nuclide('U235',0.87370e-03) + fuel_mat.add_nuclide('U238',1.87440e-02) + fuel_mat.add_nuclide('O16',3.92350e-02) + materials.append(fuel_mat) + + coolant = openmc.Material(material_id=2,name="borated H2O") + coolant.set_density('sum') + coolant.add_nuclide('H1',0.06694) + coolant.add_nuclide('O16',0.03347) + coolant.add_nuclide('B10',6.6262e-6) + coolant.add_nuclide('B11',2.6839e-5) + materials.append(coolant) + + absorber = openmc.Material(material_id=3,name="pellet B4C") + absorber.set_density('sum') + absorber.add_nuclide('C0',0.01966) + absorber.add_nuclide('B11',4.7344e-6) + absorber.add_nuclide('B10',1.9177e-5) + materials.append(absorber) + + zirc = openmc.Material(material_id=4,name="Zirc4") + zirc.set_density('sum') + zirc.add_element('Zr',4.23e-2) + materials.append(zirc) + + materials.export_to_xml() + + ### Geometry ### + pin_rad = 0.7 # cm + assembly_pitch = 1.235 # cm + hexagonal_pitch = 23.6 # cm + length = 10.0 # cm + #Fuel pin surfaces + cylfuelin = openmc.ZCylinder(surface_id=1,r=0.386) + cylfuelout = openmc.ZCylinder(surface_id=2,r=0.4582) + #Fuel cells + infcell = openmc.Cell(cell_id=1) + infcell.region = -cylfuelin + infcell.fill = fuel_mat + + clfcell = openmc.Cell(cell_id=2) + clfcell.region = -cylfuelout & +cylfuelin + clfcell.fill = zirc + + outfcell = openmc.Cell(cell_id=3) + outfcell.region = +cylfuelout + outfcell.fill = coolant + #Fuel universe + fuel_ch_univ = openmc.Universe(universe_id=1,name="Fuel channel",cells=[infcell, clfcell,outfcell]) + + #Central tube surfaces + cyltubein = openmc.ZCylinder(surface_id=3,r=0.45) + cyltubeout = openmc.ZCylinder(surface_id=4,r=0.5177) + #Central tube cells + inctcell = openmc.Cell(cell_id=4) + inctcell.region = -cyltubein + inctcell.fill = coolant + + clctcell = openmc.Cell(cell_id=5) + clctcell.region = -cyltubeout & +cyltubein + clctcell.fill = zirc + + outctcell = openmc.Cell(cell_id=6) + outctcell.region = +cyltubeout + outctcell.fill = coolant + #Central tubel universe + tube_ch_univ = openmc.Universe(universe_id=2,name="Central tube channel",cells=[inctcell, clctcell,outctcell]) + + #Absorber tube surfaces + cylabsin = openmc.ZCylinder(surface_id=5,r=0.35) + cylabsout = openmc.ZCylinder(surface_id=6,r=0.41) + cylabsclin = openmc.ZCylinder(surface_id=7,r=0.545) + cylabsclout = openmc.ZCylinder(surface_id=8,r=0.6323) + #Absorber tube cells + inabscell = openmc.Cell(cell_id=7) + inabscell.region = -cylabsin + inabscell.fill = absorber + + clabscell = openmc.Cell(cell_id=8) + clabscell.region = -cylabsout & +cylabsin + clabscell.fill = zirc + + interabscell = openmc.Cell(cell_id=9) + interabscell.region = -cylabsclin & +cylabsout + interabscell.fill = coolant + + clatcell = openmc.Cell(cell_id=10) + clatcell.region = -cylabsclout & +cylabsclin + clatcell.fill = zirc + + outabscell = openmc.Cell(cell_id=11) + outabscell.region = +cylabsclout + outabscell.fill = coolant + #Absorber tube universe + abs_ch_univ = openmc.Universe(universe_id=3,name="Central tube channel",cells=[inabscell,clabscell,interabscell,clatcell,outabscell]) + #Assembly surfaces + edge_length = (1./np.sqrt(3.0)) * hexagonal_pitch + fuel_bottom = openmc.ZPlane(surface_id=9,z0=0.0, boundary_type = 'reflective') + fuel_top = openmc.ZPlane(surface_id=10,z0=length, boundary_type = 'reflective') + # a hex surface for the core to go inside of + hexprism = openmc.model.get_hexagonal_prism(edge_length=edge_length, + origin=(0.0, 0.0), + boundary_type = 'reflective', + orientation='x') + region = hexprism & +fuel_bottom & -fuel_top + + inf_mat = openmc.Cell(cell_id=12) + inf_mat.fill = coolant + inf_mat_univ = openmc.Universe(universe_id=4,cells=[inf_mat,]) + # Fill lattice by channels + nring = 11 + universes = [] # array of rings + for ring in range(nring - 1,-1,-1): + arr = [] + arr.append(fuel_ch_univ) + for cell in range(ring * 6 - 1): + arr.append(fuel_ch_univ) + universes.append(arr) + universes[-1] = [tube_ch_univ] + channels = [(7,16),(7,13),(7,10),(7,7),(7,4),(7,1),(5,0),\ + (4,33),(5,25),(4,27),(5,20),(4,21),(5,15),\ + (4,15),(5,10),(4,9),(5,5),(4,3)] + for i,j in channels: + universes[i][j] = abs_ch_univ + lattice = openmc.HexLattice(name="regular fuel assembly") + lattice.orientation = "ox" + lattice.center = (0., 0., length/2.0) + lattice.pitch = (assembly_pitch,length/2.0) + lattice.universes = 2*[universes] + lattice.outer = inf_mat_univ + + assembly_cell = openmc.Cell(cell_id=13,name="container assembly cell") + assembly_cell.region = region + assembly_cell.fill = lattice + + root_univ = openmc.Universe(universe_id=5,name="root universe", cells=[assembly_cell,]) + + geom = openmc.Geometry(root_univ) + geom.export_to_xml() + + ### Settings ### + + settings = openmc.Settings() + settings.run_mode = 'eigenvalue' + + source = openmc.Source() + ll = [-edge_length, -edge_length, 0.0] + ur = [edge_length, edge_length, 10.0] + source.space = openmc.stats.Box(ll, ur) + source.strength = 1.0 + settings.source = source + settings.output = {'summary' : False} + settings.batches = 10 + settings.inactive = 5 + settings.particles = 1000 + settings.seed = 22 + settings.export_to_xml() + +def test_lattice_hex_ox_surf(): + harness = HexLatticeOXTestHarness('statepoint.10.h5') + harness.main() From 818a6c245b92a749b5b7d1ddd8279a4d34fe9bd9 Mon Sep 17 00:00:00 2001 From: dryuri92 <39188804+dryuri92@users.noreply.github.com> Date: Thu, 30 May 2019 11:02:42 +0300 Subject: [PATCH 060/151] Update constants.h --- include/openmc/constants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 7abb3d7b9d..d9307abe20 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -25,7 +25,7 @@ constexpr int VERSION_RELEASE {0}; constexpr std::array VERSION {VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE}; // HDF5 data format -constexpr int HDF5_VERSION[] {2, 0};//DR change version HDF5 to work +constexpr int HDF5_VERSION[] {2, 0}; // Version numbers for binary files constexpr std::array VERSION_STATEPOINT {17, 0}; From 86dc85621bd809b58124bb11d079dfe29618e494 Mon Sep 17 00:00:00 2001 From: dryuri92 <39188804+dryuri92@users.noreply.github.com> Date: Thu, 30 May 2019 11:26:49 +0300 Subject: [PATCH 061/151] Update lattice.py with sharper recomendation --- openmc/lattice.py | 79 +++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 45a3c1b815..9931f2bd27 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -136,15 +136,13 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): lattice.universes = uarray elif lattice_type == 'hexagonal': - n_rings = group['n_rings'][()] n_axial = group['n_axial'][()] center = group['center'][()] pitch = group['pitch'][()] outer = group['outer'][()] - #DR Temp added for compatibility with previous version if ('orientation' in group.keys()): - orientation = group['orientation'].value + orientation = group['orientation'][()] else: orientation = "oy" @@ -155,7 +153,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): lattice = openmc.HexLattice(lattice_id, name) lattice.center = center lattice.pitch = pitch - + lattice.orientation = orientation # If the Universe specified outer the Lattice is not void if outer >= 0: lattice.outer = universes[outer] @@ -880,11 +878,11 @@ class HexLattice(Lattice): Most methods for this class use a natural indexing scheme wherein elements are assigned an index corresponding to their position relative to skewed - :math:`(x,\alpha,z)` - OY orientation or :math:`(\alpha,y,z)` - OX orientation //DR - axes as described fully in :ref:`hexagonal_indexing`. However, note that when - universes are assigned to lattice elements using the :attr:`HexLattice.universes` property, - the array indices do not correspond to natural indices. - + :math:`(x,\alpha,z)` or :math:`(\alpha,y,z)` bases, depending on the lattice + orientation, as described fully in :ref:`hexagonal_indexing`. However, note + that when universes are assigned to lattice elements using the + :attr:`HexLattice.universes` property, the array indices do not correspond + to natural indices. Parameters ---------- lattice_id : int, optional @@ -921,7 +919,7 @@ class HexLattice(Lattice): from the outermost ring), and i is the index with a ring starting from the top and proceeding clockwise. orientation : str by default 'OY' orientation of main lattice diagonal another option - - 'OX'//DR + - 'OX' num_rings : int Number of radial ring positions in the xy-plane num_axial : int @@ -970,12 +968,14 @@ class HexLattice(Lattice): @property def num_rings(self): return self._num_rings + @property - def orientation(self):#//DR + def orientation(self): if self._hextype: return "OX" else: return "OY" + @property def num_axial(self): return self._num_axial @@ -1031,7 +1031,7 @@ class HexLattice(Lattice): @orientation.setter def orientation(self, orientation): - cv.check_type('orientation', orientation, str) + cv.check_value('orientation', orientation.lower(), ('ox', 'oy')) if orientation.lower() == "ox": self._hextype = 1 @@ -1125,7 +1125,7 @@ class HexLattice(Lattice): ------- 3-tuple of int Indices of corresponding lattice element in :math:`(x,\alpha,z)` - or :math:`(\alpha,y,z)`basesis + or :math:`(\alpha,y,z)`bases numpy.ndarray Carestian coordinates of the point in the corresponding lattice element coordinate system @@ -1139,27 +1139,20 @@ class HexLattice(Lattice): else: z = point[2] - self.center[2] iz = floor(z/self.pitch[1] + 0.5*self.num_axial) - if self._hextype:#//DR - alpha = y - x*sqrt(3.) - ix = 0 - ia = floor(-alpha/(sqrt(3.0) * self.pitch[0])) - iy = floor(y/(sqrt(0.75) * self.pitch[0])) + if self._hextype: + alpha = y - x*sqrt(3.) + i1 = floor(-alpha/(sqrt(3.0) * self.pitch[0])) + i2 = floor(y/(sqrt(0.75) * self.pitch[0])) else: - alpha = y - x/sqrt(3.) - ix = floor(x/(sqrt(0.75) * self.pitch[0])) - ia = floor(alpha/self.pitch[0]) - iy = 0 - - + alpha = y - x/sqrt(3.) + i1 = floor(x/(sqrt(0.75) * self.pitch[0])) + i2 = floor(alpha/self.pitch[0] # Check four lattice elements to see which one is closest based on local # coordinates - #//DR - numbersOY=[(ix, ia, iz), (ix + 1, ia, iz), (ix, ia + 1, iz), \ - (ix + 1, ia + 1, iz)]# in case OY default orientation - numbersOX=[(ia, iy, iz), (ia + 1, iy, iz), (ia, iy + 1, iz), \ - (ia + 1, iy + 1, iz)]# in case OX orientation + numbers=[(i1, i2, iz), (i1 + 1, i2, iz), (i1, i2 + 1, iz), \ + (i1 + 1, i2 + 1, iz)] d_min = np.inf - for idx in (numbersOX if self._hextype else numbersOY): #//DR + for idx in numbers: p = self.get_local_coordinates(point, idx) d = p[0]**2 + p[1]**2 if d < d_min: @@ -1187,12 +1180,16 @@ class HexLattice(Lattice): system """ - if self._hextype:#//DR - x = point[0] - (self.center[0] + self.pitch[0]*idx[0] + 0.5*self.pitch[0]*idx[1]) - y = point[1] - (self.center[1] + (sqrt(0.75)*self.pitch[0]*idx[1])) + if self._hextype: + x = point[0] - (self.center[0] + self.pitch[0]*idx[0] + + 0.5*self.pitch[0]*idx[1]) + y = point[1] - (self.center[1] + + (sqrt(0.75)*self.pitch[0]*idx[1])) else: - x = point[0] - (self.center[0] + sqrt(0.75)*self.pitch[0]*idx[0]) - y = point[1] - (self.center[1] + (0.5*idx[0] + idx[1])*self.pitch[0]) + x = point[0] - (self.center[0] + + sqrt(0.75)*self.pitch[0]*idx[0]) + y = point[1] - (self.center[1] + + (0.5*idx[0] + idx[1])*self.pitch[0]) if self._num_axial is None: z = point[2] @@ -1200,7 +1197,8 @@ class HexLattice(Lattice): z = point[2] - (self.center[2] + (idx[2] + 0.5 - 0.5*self.num_axial)* self.pitch[1]) return (x, y, z) - def get_universe_index(self, idx): #//DR + + def get_universe_index(self, idx): r"""Return index in the universes array corresponding to a lattice element index Parameters @@ -1217,10 +1215,11 @@ class HexLattice(Lattice): """ if self._hextype: - return self.get_universe_index_ox(idx) + return self._get_universe_index_ox(idx) else: - return self.get_universe_index_oy(idx) - def get_universe_index_oy(self, idx):#//DR + return self._get_universe_index_oy(idx) + + def _get_universe_index_oy(self, idx): r"""Return index in the universes array corresponding to a lattice element index Parameters @@ -1261,7 +1260,7 @@ class HexLattice(Lattice): return (idx[2], i_ring, i_within) - def get_universe_index_ox(self, idx):#//DR + def _get_universe_index_ox(self, idx): r"""Return index in the universes array corresponding to a lattice element index numeration opposite clockwise From 8ae6c30d6b2880a12125d6e85bed00015ebfc878 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Thu, 30 May 2019 12:29:33 +0300 Subject: [PATCH 062/151] correct python code --- openmc/lattice.py | 8 ++++---- tests/regression_tests/lattice_hex_OX/test.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 9931f2bd27..281cc3addd 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -1146,13 +1146,13 @@ class HexLattice(Lattice): else: alpha = y - x/sqrt(3.) i1 = floor(x/(sqrt(0.75) * self.pitch[0])) - i2 = floor(alpha/self.pitch[0] + i2 = floor(alpha/self.pitch[0]) # Check four lattice elements to see which one is closest based on local # coordinates - numbers=[(i1, i2, iz), (i1 + 1, i2, iz), (i1, i2 + 1, iz), \ - (i1 + 1, i2 + 1, iz)] + numbersaxy=[(i1, i2, iz), (i1 + 1, i2, iz), (i1, i2 + 1, iz), + (i1 + 1, i2 + 1, iz)] d_min = np.inf - for idx in numbers: + for idx in numbersaxy: p = self.get_local_coordinates(point, idx) d = p[0]**2 + p[1]**2 if d < d_min: diff --git a/tests/regression_tests/lattice_hex_OX/test.py b/tests/regression_tests/lattice_hex_OX/test.py index 2eab458dca..0af36e6f70 100644 --- a/tests/regression_tests/lattice_hex_OX/test.py +++ b/tests/regression_tests/lattice_hex_OX/test.py @@ -56,7 +56,8 @@ class HexLatticeOXTestHarness(PyAPITestHarness): outfcell.region = +cylfuelout outfcell.fill = coolant #Fuel universe - fuel_ch_univ = openmc.Universe(universe_id=1,name="Fuel channel",cells=[infcell, clfcell,outfcell]) + fuel_ch_univ = openmc.Universe(universe_id=1,name="Fuel channel", + cells=[infcell, clfcell,outfcell]) #Central tube surfaces cyltubein = openmc.ZCylinder(surface_id=3,r=0.45) @@ -74,7 +75,8 @@ class HexLatticeOXTestHarness(PyAPITestHarness): outctcell.region = +cyltubeout outctcell.fill = coolant #Central tubel universe - tube_ch_univ = openmc.Universe(universe_id=2,name="Central tube channel",cells=[inctcell, clctcell,outctcell]) + tube_ch_univ = openmc.Universe(universe_id=2,name="Central tube channel", + cells=[inctcell, clctcell,outctcell]) #Absorber tube surfaces cylabsin = openmc.ZCylinder(surface_id=5,r=0.35) @@ -102,11 +104,14 @@ class HexLatticeOXTestHarness(PyAPITestHarness): outabscell.region = +cylabsclout outabscell.fill = coolant #Absorber tube universe - abs_ch_univ = openmc.Universe(universe_id=3,name="Central tube channel",cells=[inabscell,clabscell,interabscell,clatcell,outabscell]) + abs_ch_univ = openmc.Universe(universe_id=3,name="Central tube channel",\ + cells=[inabscell,clabscell,interabscell,clatcell,outabscell]) #Assembly surfaces edge_length = (1./np.sqrt(3.0)) * hexagonal_pitch - fuel_bottom = openmc.ZPlane(surface_id=9,z0=0.0, boundary_type = 'reflective') - fuel_top = openmc.ZPlane(surface_id=10,z0=length, boundary_type = 'reflective') + fuel_bottom = openmc.ZPlane(surface_id=9,z0=0.0, + boundary_type = 'reflective') + fuel_top = openmc.ZPlane(surface_id=10,z0=length, + boundary_type = 'reflective') # a hex surface for the core to go inside of hexprism = openmc.model.get_hexagonal_prism(edge_length=edge_length, origin=(0.0, 0.0), From 00f346d40cb6f4602756e90a62f278adca4891fd Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Thu, 30 May 2019 13:55:32 +0300 Subject: [PATCH 063/151] add a decoding method to transform bytes to string --- openmc/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 281cc3addd..429d8b691b 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -142,7 +142,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): pitch = group['pitch'][()] outer = group['outer'][()] if ('orientation' in group.keys()): - orientation = group['orientation'][()] + orientation = group['orientation'][()].decode() else: orientation = "oy" From fb7a895ce9fa5212f0ef9be161241d3571b92687 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Thu, 30 May 2019 14:13:27 +0300 Subject: [PATCH 064/151] removed unused marks --- openmc/lattice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 429d8b691b..cec9a67258 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -940,7 +940,7 @@ class HexLattice(Lattice): string = 'HexLattice\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) - string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', "OX" if self._hextype else "OY" )#//DR + string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', "OX" if self._hextype else "OY" ) string += '{0: <16}{1}{2}\n'.format('\t# Rings', '=\t', self._num_rings) string += '{0: <16}{1}{2}\n'.format('\t# Axial', '=\t', self._num_axial) string += '{0: <16}{1}{2}\n'.format('\tCenter', '=\t', @@ -1802,7 +1802,7 @@ class HexLattice(Lattice): # Join the rows together and return the string. return '\n'.join(rows) @staticmethod - def show_indices_ox(num_rings):#//DR + def show_indices_ox(num_rings): """Return a diagram of the hexagonal lattice with OX orientation layout with indices. This method can be used to show the proper indices to be used when From 4e0c52a62826770325e9484b09f956fd262e0ce8 Mon Sep 17 00:00:00 2001 From: dryuri92 <39188804+dryuri92@users.noreply.github.com> Date: Thu, 30 May 2019 22:06:36 +0300 Subject: [PATCH 065/151] rename HexOrientation to Orientation --- include/openmc/lattice.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index 6b30486b88..b79e77660f 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -27,9 +27,6 @@ enum class LatticeType { rect, hex }; -enum class HexOrientation { - oy, ox -}; //============================================================================== // Global variables @@ -269,6 +266,9 @@ public: void to_hdf5_inner(hid_t group_id) const; private: + enum class Orientation { + y, x + }; //! Fill universes_ vector for OY orientation void fill_lattice_oy(std::vector univ_words); @@ -277,7 +277,7 @@ private: int n_rings_; //!< Number of radial tile positions int n_axial_; //!< Number of axial tile positions - HexOrientation orientation_; //!< Flat side up (oy) vs. sharp side up (ox) + Orientation orientation_; //!< Flat side up (oy) vs. sharp side up (ox) Position center_; //!< Global center of lattice std::array pitch_; //!< Lattice tile width and height }; From 0333c5d500d5e205ad48aafdc2a448be91717c6e Mon Sep 17 00:00:00 2001 From: dryuri92 <39188804+dryuri92@users.noreply.github.com> Date: Thu, 30 May 2019 22:10:07 +0300 Subject: [PATCH 066/151] call function by ref --- include/openmc/lattice.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index b79e77660f..195f627857 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -270,14 +270,14 @@ private: y, x }; //! Fill universes_ vector for OY orientation - void fill_lattice_oy(std::vector univ_words); + void fill_lattice_oy(const std::vector& univ_words); //! Fill universes_ vector for OX orientation - void fill_lattice_ox(std::vector univ_words); + void fill_lattice_ox(const std::vector& univ_words); int n_rings_; //!< Number of radial tile positions int n_axial_; //!< Number of axial tile positions - Orientation orientation_; //!< Flat side up (oy) vs. sharp side up (ox) + Orientation orientation_; //!< Flat side up (oy) vs. sharp side up (ox) Position center_; //!< Global center of lattice std::array pitch_; //!< Lattice tile width and height }; From 40b5c23aabdf9bb8968d1d6afb846d908d71199e Mon Sep 17 00:00:00 2001 From: dryuri92 <39188804+dryuri92@users.noreply.github.com> Date: Thu, 30 May 2019 22:35:35 +0300 Subject: [PATCH 067/151] add call fucntion by reference --- src/lattice.cpp | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index b98ba6366f..ec9fa908c1 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -444,15 +444,15 @@ HexLattice::HexLattice(pugi::xml_node lat_node) if (check_for_node(lat_node, "orient")) { std::string orientation = get_node_value(lat_node, "orient"); if (orientation == "OY") { - orientation_ = HexOrientation::oy; + orientation_ = Orientation::oy; } else if (orientation == "OX") { - orientation_ = HexOrientation::ox; + orientation_ = Orientation::ox; } else { fatal_error("Unrecognized orientation '" + orientation + "' for lattice " + std::to_string(id_)); } } else { - orientation_ = HexOrientation::oy; + orientation_ = Orientation::oy; } // Read the lattice center. @@ -504,22 +504,18 @@ HexLattice::HexLattice(pugi::xml_node lat_node) // the following code walks a set of index values across the skewed array // in a manner that matches the input order. Note that i_x = 0, i_a = 0 // or i_a = 0, i_y = 0 corresponds to the center of the hexagonal lattice. - universes_.resize((2*n_rings_-1) * (2*n_rings_-1) * n_axial_, C_NONE); - - if (orientation_ == HexOrientation::oy) { + if (orientation_ == Orientation::oy) { fill_lattice_oy(univ_words); } else { fill_lattice_ox(univ_words); } - - } - + //============================================================================== - + void -HexLattice::fill_lattice_ox(std::vector univ_words) +HexLattice::fill_lattice_ox(const std::vector& univ_words) { int input_index = 0; for (int m = 0; m < n_axial_; m++) { @@ -572,7 +568,7 @@ HexLattice::fill_lattice_ox(std::vector univ_words) //============================================================================== void -HexLattice::fill_lattice_oy(std::vector univ_words) +HexLattice::fill_lattice_oy(const std::vector& univ_words) { int input_index = 0; for (int m = 0; m < n_axial_; m++) { @@ -712,7 +708,7 @@ const double beta_dir; double gamma_dir; double delta_dir; - if (orientation_ == HexOrientation::oy) { + if (orientation_ == Orientation::oy) { beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0; gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0; delta_dir = u.y; @@ -741,7 +737,7 @@ const r_t = get_local_position(r, i_xyz_t); } double beta; - if (orientation_ == HexOrientation::oy) { + if (orientation_ == Orientation::oy) { beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0; } else { beta = r_t.x; @@ -765,7 +761,7 @@ const r_t = get_local_position(r, i_xyz_t); } double gamma; - if (orientation_ == HexOrientation::oy) { + if (orientation_ == Orientation::oy) { gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0; } else { gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0; @@ -792,7 +788,7 @@ const r_t = get_local_position(r, i_xyz_t); } double delta; - if (orientation_ == HexOrientation::oy) { + if (orientation_ == Orientation::oy) { delta = r_t.y; } else { delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0; @@ -852,7 +848,7 @@ HexLattice::get_indices(Position r, Direction u) const } int i1, i2; - if (orientation_ == HexOrientation::oy) { + if (orientation_ == Orientation::oy) { // Convert coordinates into skewed bases. The (x, alpha) basis is used to // find the index of the global coordinates to within 4 cells. double alpha = r_o.y - r_o.x / std::sqrt(3.0); @@ -930,7 +926,7 @@ Position HexLattice::get_local_position(Position r, const std::array i_xyz) const { - if (orientation_ == HexOrientation::oy) { + if (orientation_ == Orientation::oy) { // x_l = x_g - (center + pitch_x*cos(30)*index_x) r.x -= center_.x + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; @@ -1010,7 +1006,7 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_string(lat_group, "type", "hexagonal", false); write_dataset(lat_group, "n_rings", n_rings_); write_dataset(lat_group, "n_axial", n_axial_); - if (orientation_ == HexOrientation::oy) { + if (orientation_ == Orientation::oy) { write_string(lat_group, "orientation", "oy", false); } else { write_string(lat_group, "orientation", "ox", false); From feac9c83a8ff251a96652ec93cb90b6679beca9b Mon Sep 17 00:00:00 2001 From: dryuri92 <39188804+dryuri92@users.noreply.github.com> Date: Thu, 30 May 2019 22:38:54 +0300 Subject: [PATCH 068/151] is3d separately --- src/lattice.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index ec9fa908c1..d2e29e1067 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -933,10 +933,6 @@ const // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] + (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0); - if (is_3d_) { - r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; - } - } else { // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y) r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] @@ -944,10 +940,11 @@ const // y_l = y_g - (center + pitch_y*cos(30)*index_y) r.y -= center_.y + std::sqrt(3.0)/2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0]; - if (is_3d_) { + } + + if (is_3d_) { r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; } - } return r; } From 40e8f3c22cfbb830c42470a43ca651d584ec7271 Mon Sep 17 00:00:00 2001 From: dryuri92 <39188804+dryuri92@users.noreply.github.com> Date: Thu, 30 May 2019 22:40:09 +0300 Subject: [PATCH 069/151] simple change --- src/initialize.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index 58d96bf408..061025745f 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -36,11 +36,9 @@ int openmc_init(int argc, char* argv[], const void* intracomm) { using namespace openmc; - - + char cCurrentPath[256]; - - + #ifdef OPENMC_MPI // Check if intracomm was passed MPI_Comm comm; From cc98d13793a7dd1fac4d42a260bd49403889becc Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 30 May 2019 17:55:09 -0400 Subject: [PATCH 070/151] Read chain file for deplete.Operator from OPENMC_CROSS_SECTIONS If a chain_file is not passed when initializing an openmc.deplete.Operator object, then the chain_file will be read from the depletion_chain node from $OPENMC_CROSS_SECTIONS. A deprecation warning is raised if the environment variable OPENMC_DEPLETE_CHAIN is still set, informing the users that this information can instead be placed in their cross section file. Documentation is updated describing the logic in the chain_file search to reflect this in - TransportOperator - Operator - Chain . Closes #1239 --- openmc/deplete/abc.py | 26 ++++++++++++++++++++++---- openmc/deplete/chain.py | 3 ++- openmc/deplete/operator.py | 5 +++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 8502909a21..14dd3638ce 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -8,6 +8,8 @@ from collections import namedtuple import os from pathlib import Path from abc import ABCMeta, abstractmethod +from xml.etree import ElementTree as ET +from warnings import warn from .chain import Chain @@ -43,8 +45,9 @@ class TransportOperator(metaclass=ABCMeta): Parameters ---------- chain_file : str, optional - Path to the depletion chain XML file. Defaults to the - :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. + Path to the depletion chain XML file. Defaults to the file + listed under ``depletion_chain`` in + :envvar:`OPENMC_CROSS_SECTIONS` environment variable. Attributes ---------- @@ -62,8 +65,23 @@ class TransportOperator(metaclass=ABCMeta): if chain_file is None: chain_file = os.environ.get("OPENMC_DEPLETE_CHAIN", None) if chain_file is None: - raise IOError("No chain specified, either manually or in " - "environment variable OPENMC_DEPLETE_CHAIN.") + xs_file = os.environ.get("OPENMC_CROSS_SECTIONS", None) + if xs_file is None: + raise IOError( + "OPENMC_CROSS_SECTIONS environment variable " + "not set.") + root = ET.parse(xs_file).getroot() + node = root.find("depletion_chain") + if node is None: + raise IOError( + "No chain specified, either manually or " + "under depletion_chain in environment variable " + "OPENMC_CROSS_SECTIONS.") + chain_file = node.get("path") + else: + warn("Use of OPENMC_DEPLETE_CHAIN is deprecated in favor " + "of adding depletion_chain to OPENMC_CROSS_SECTIONS", + DeprecationWarning) self.chain = Chain.from_xml(chain_file) @abstractmethod diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 4635d99d15..b8b6a3644a 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -107,7 +107,8 @@ class Chain(object): yield sublibrary files. The depletion chain used during a depletion simulation is indicated by either an argument to :class:`openmc.deplete.Operator` or through the - :envvar:`OPENMC_DEPLETE_CHAIN` environment variable. + ``depletion_chain`` item in the :envvar:`OPENMC_CROSS_SECTIONS` + environment variable. Attributes ---------- diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 66cac392d9..d284d98567 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -64,8 +64,9 @@ class Operator(TransportOperator): settings : openmc.Settings OpenMC Settings object chain_file : str, optional - Path to the depletion chain XML file. Defaults to the - :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. + Path to the depletion chain XML file. Defaults to the file + listed under ``depletion_chain`` in + :envvar:`OPENMC_CROSS_SECTIONS` environment variable. prev_results : ResultsList, optional Results from a previous depletion calculation. If this argument is specified, the depletion calculation will start from the latest state From de0ffecdd517e6ca77de3d2a1ee0bcb6d2843857 Mon Sep 17 00:00:00 2001 From: dryuri92 <39188804+dryuri92@users.noreply.github.com> Date: Fri, 31 May 2019 00:59:54 +0300 Subject: [PATCH 071/151] Accept changes by P.Romano --- openmc/lattice.py | 179 ++++++++++++++++++---------------------------- 1 file changed, 70 insertions(+), 109 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index cec9a67258..8d2d82ed04 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -141,7 +141,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): center = group['center'][()] pitch = group['pitch'][()] outer = group['outer'][()] - if ('orientation' in group.keys()): + if ('orientation' in group): orientation = group['orientation'][()].decode() else: orientation = "oy" @@ -157,7 +157,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): # If the Universe specified outer the Lattice is not void if outer >= 0: lattice.outer = universes[outer] - if (orientation.lower() is "oy"): + if (orientation.lower() == "oy"): # Build array of Universe pointers for the Lattice. Note that # we need to convert between the HDF5's square array of # (x, alpha, z) to the Python API's format of a ragged nested @@ -883,6 +883,7 @@ class HexLattice(Lattice): that when universes are assigned to lattice elements using the :attr:`HexLattice.universes` property, the array indices do not correspond to natural indices. + Parameters ---------- lattice_id : int, optional @@ -918,8 +919,9 @@ class HexLattice(Lattice): possible, where z is the axial index, r is in the ring index (starting from the outermost ring), and i is the index with a ring starting from the top and proceeding clockwise. - orientation : str by default 'OY' orientation of main lattice diagonal another option - - 'OX' + orientation : {'x', 'y'} + str by default 'y' orientation of main lattice diagonal another option + - 'x' num_rings : int Number of radial ring positions in the xy-plane num_axial : int @@ -934,13 +936,13 @@ class HexLattice(Lattice): self._num_rings = None self._num_axial = None self._center = None - self._hextype = 0 # by Default orientaion is OY + self._orientation = 'y' def __repr__(self): string = 'HexLattice\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) - string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', "OX" if self._hextype else "OY" ) + string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', "OX" if (self._orientation== 'x') else "OY" ) string += '{0: <16}{1}{2}\n'.format('\t# Rings', '=\t', self._num_rings) string += '{0: <16}{1}{2}\n'.format('\t# Axial', '=\t', self._num_axial) string += '{0: <16}{1}{2}\n'.format('\tCenter', '=\t', @@ -971,10 +973,7 @@ class HexLattice(Lattice): @property def orientation(self): - if self._hextype: - return "OX" - else: - return "OY" + return self._orientation @property def num_axial(self): @@ -1032,8 +1031,7 @@ class HexLattice(Lattice): @orientation.setter def orientation(self, orientation): cv.check_value('orientation', orientation.lower(), ('ox', 'oy')) - if orientation.lower() == "ox": - self._hextype = 1 + self._orientation = orientation.lower() @Lattice.pitch.setter def pitch(self, pitch): @@ -1125,7 +1123,7 @@ class HexLattice(Lattice): ------- 3-tuple of int Indices of corresponding lattice element in :math:`(x,\alpha,z)` - or :math:`(\alpha,y,z)`bases + or :math:`(\alpha,y,z)` bases numpy.ndarray Carestian coordinates of the point in the corresponding lattice element coordinate system @@ -1139,7 +1137,7 @@ class HexLattice(Lattice): else: z = point[2] - self.center[2] iz = floor(z/self.pitch[1] + 0.5*self.num_axial) - if self._hextype: + if self._orientation == 'x': alpha = y - x*sqrt(3.) i1 = floor(-alpha/(sqrt(3.0) * self.pitch[0])) i2 = floor(y/(sqrt(0.75) * self.pitch[0])) @@ -1149,10 +1147,10 @@ class HexLattice(Lattice): i2 = floor(alpha/self.pitch[0]) # Check four lattice elements to see which one is closest based on local # coordinates - numbersaxy=[(i1, i2, iz), (i1 + 1, i2, iz), (i1, i2 + 1, iz), - (i1 + 1, i2 + 1, iz)] + indices = [(i1, i2, iz), (i1 + 1, i2, iz), (i1, i2 + 1, iz), + (i1 + 1, i2 + 1, iz)] d_min = np.inf - for idx in numbersaxy: + for idx in indices: p = self.get_local_coordinates(point, idx) d = p[0]**2 + p[1]**2 if d < d_min: @@ -1171,7 +1169,7 @@ class HexLattice(Lattice): Cartesian coordinates of point idx : Iterable of int Indices of lattice element in :math:`(x,\alpha,z)` - or :math:`(\alpha,y,z)` basesis + or :math:`(\alpha,y,z)` bases Returns ------- @@ -1180,17 +1178,17 @@ class HexLattice(Lattice): system """ - if self._hextype: + if self._orientation == 'x': x = point[0] - (self.center[0] + self.pitch[0]*idx[0] + 0.5*self.pitch[0]*idx[1]) y = point[1] - (self.center[1] + - (sqrt(0.75)*self.pitch[0]*idx[1])) + sqrt(0.75)*self.pitch[0]*idx[1]) else: x = point[0] - (self.center[0] + sqrt(0.75)*self.pitch[0]*idx[0]) y = point[1] - (self.center[1] + (0.5*idx[0] + idx[1])*self.pitch[0]) - + if self._num_axial is None: z = point[2] else: @@ -1210,31 +1208,11 @@ class HexLattice(Lattice): Returns ------- - 2- or 3-tuple of int + 2- or 3-tuple of int Indices used when setting the :attr:`HexLattice.universes` property """ - if self._hextype: - return self._get_universe_index_ox(idx) - else: - return self._get_universe_index_oy(idx) - - def _get_universe_index_oy(self, idx): - r"""Return index in the universes array corresponding to a lattice element index - - Parameters - ---------- - idx : Iterable of int - Lattice element indices in the :math:`(x,\alpha,z)` coordinate - system - - Returns - ------- - 2- or 3-tuple of int - Indices used when setting the :attr:`HexLattice.universes` property - - """ - + # First we determine which ring the index corresponds to. x = idx[0] a = idx[1] @@ -1253,54 +1231,15 @@ class HexLattice(Lattice): i_within = 3*g - x else: i_within = 5*g - z - + + if (self._orientation == 'x') and (i_within > 0): + i_within = 6*(self._num_rings - i_ring - 1) - i_within_ + if self.num_axial is None: return (i_ring, i_within) else: return (idx[2], i_ring, i_within) - - - def _get_universe_index_ox(self, idx): - r"""Return index in the universes array corresponding to a lattice element index - numeration opposite clockwise - - Parameters - ---------- - idx : Iterable of int - Lattice element indices in the :math:`(\alpha,y,z)` coordinate - system - - Returns - ------- - 2- or 3-tuple of int - Indices used when setting the :attr:`HexLattice.universes` property - - """ - - # First we determine which ring the index corresponds to. - a = idx[0] - y = idx[1] - z = -a - y - g = max(abs(y), abs(a), abs(z)) - - # Next we use a clever method to figure out where along the ring we are. - i_ring = self._num_rings - 1 - g - if y >= 0: - if a >= 0: - i_within = y - else: - i_within = 2*g + z - else: - if a <= 0: - i_within = 3*g - y - else: - i_within = 5*g - z - - if self.num_axial is None: - return (i_ring, i_within) - else: - return (idx[2], i_ring, i_within) - + def is_valid_index(self, idx): r"""Determine whether lattice element index is within defined range @@ -1352,7 +1291,7 @@ class HexLattice(Lattice): lattice_subelement.set("n_rings", str(self._num_rings)) # If orientation is "OX" export it to XML - if self._hextype: + if self._orientation== 'x': lattice_subelement.set("orient", "OX") if self._num_axial is not None: @@ -1483,10 +1422,10 @@ class HexLattice(Lattice): each sub-list represents a single ring. The first list should be the outer ring. """ - if self._hextype: - return self._repr_axial_slice_ox(universes) + if self._orientation== 'x': + return self._repr_axial_slice_ox(universes) else: - return self._repr_axial_slice_oy(universes) + return self._repr_axial_slice_oy(universes) def _repr_axial_slice_ox(self, universes): """Return string representation for the given 2D group of universes in 'OX' orientation case. @@ -1533,7 +1472,7 @@ class HexLattice(Lattice): for i in range(r): # Add the universe. universe = universes[r_prime][theta] - rows[y].insert(0,id_form.format(universe._id)) + rows[y].insert(0, id_form.format(universe._id)) # Translate the indices. theta += 1 @@ -1542,7 +1481,7 @@ class HexLattice(Lattice): for i in range(r): # Add the universe. universe = universes[r_prime][theta] - rows[y].insert(0,id_form.format(universe._id)) + rows[y].insert(0, id_form.format(universe._id)) # Translate the indices. y -= 1 @@ -1584,12 +1523,11 @@ class HexLattice(Lattice): for y in range(self._num_rings - 1): rows[y] = (self._num_rings - 1 - y)*pad + rows[y] rows[-1 - y] = (self._num_rings - 1 - y)*pad + rows[-1 - y] - + # Join the rows together and return the string. universe_ids = '\n'.join(rows) return universe_ids - - + def _repr_axial_slice_oy(self, universes): """Return string representation for the given 2D group of universes in 'OY' orientation case.. @@ -1697,9 +1635,8 @@ class HexLattice(Lattice): # Join the rows together and return the string. universe_ids = '\n'.join(rows) return universe_ids - - @staticmethod - def show_indices(num_rings): + + def _show_indices_y(num_rings): """Return a diagram of the hexagonal lattice layout with indices. This method can be used to show the proper indices to be used when @@ -1801,24 +1738,24 @@ class HexLattice(Lattice): # Join the rows together and return the string. return '\n'.join(rows) - @staticmethod - def show_indices_ox(num_rings): + + def _show_indices_x(num_rings): """Return a diagram of the hexagonal lattice with OX orientation layout with indices. This method can be used to show the proper indices to be used when setting the :attr:`HexLattice.universes` property. For example, running this method with num_rings=3 will return the similar diagram:: - (0, 4) (0, 3) (0, 2) + (0, 4) (0, 3) (0, 2) + + (0, 5) (1, 2) (1, 1) (0, 1) + + (0, 6) (1, 3) (2, 0) (1, 0) (0, 0) - (0, 5) (1, 2) (1, 1) (0, 1) - - (0, 6) (1, 3) (2, 0) (1, 0) (0, 0) - - (0, 7) (1, 4) (1, 5) (0,11) - - (0, 8) (0, 9) (0,10) + (0, 7) (1, 4) (1, 5) (0,11) + (0, 8) (0, 9) (0,10) + Parameters ---------- num_rings : int @@ -1897,4 +1834,28 @@ class HexLattice(Lattice): # Join the rows together and return the string. return '\n\n'.join(rows) + + @staticmethod + def show_indices(num_rings, orientation="y"): + """Return a diagram of the hexagonal lattice layout with indices. + + Parameters + ---------- + num_rings : int + Number of rings in the hexagonal lattice + orientation : {"x", "y"} + str type of hex lattice orientation + + Returns + ------- + str + Diagram of the hexagonal lattice showing indices + + """ + + if self._orientation == 'x': + return self._show_indices_x(num_rings) + else: + return self._show_indices_y(num_rings) + From f7008616465267906dd7ca75e0339812549e2988 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 30 May 2019 18:01:33 -0400 Subject: [PATCH 072/151] Add test for the instantiation of deplete.Operator Construct a minimal cross sections xml file that only contains the depletion_chain node required by the Operator. The path to this file is set to the OPENMC_CROSS_SECTIONS file, and reverted after the test. The depletion_chain points towards the chain_simple.xml files, and a reference Chain is produced in the test. The test involves creating a bare Operator instance, that constructs a chain based on the temporary OPENMC_CROSS_SECTIONS file. This chain is compared to that produced by reading the chain_simple.xml file stored in the test directory. --- tests/unit_tests/test_deplete_operator.py | 72 +++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/unit_tests/test_deplete_operator.py diff --git a/tests/unit_tests/test_deplete_operator.py b/tests/unit_tests/test_deplete_operator.py new file mode 100644 index 0000000000..c188ec94c2 --- /dev/null +++ b/tests/unit_tests/test_deplete_operator.py @@ -0,0 +1,72 @@ +"""Basic unit tests for openmc.deplete.Operator instantiation + +Modifies and resets environment variable OPENMC_CROSS_SECTIONS +to a custom file with new depletion_chain node +""" + +from os import remove +from os import environ +from unittest import mock +from pathlib import Path +import pytest + +from openmc.deplete.abc import TransportOperator +from openmc.deplete.chain import Chain + +BARE_XS_FILE = "bare_cross_sections.xml" +CHAIN_PATH = Path().cwd() / "tests" / "chain_simple.xml" + + +@pytest.fixture(scope="module") +def bare_xs(): + """Create a very basic cross_sections file, return simple Chain. + + """ + + bare_xs_contents = """ + + + +""".format(CHAIN_PATH) + + with open(BARE_XS_FILE, "w") as out: + out.write(bare_xs_contents) + + yield + remove(BARE_XS_FILE) + + +class BareDepleteOperator(TransportOperator): + """Very basic class for testing the initialization.""" + + # declare abstract methods so object can be created + def __call__(self, *args, **kwargs): + pass + + def initial_condition(self): + pass + + def get_results_info(self): + pass + + +@mock.patch.dict(environ, {"OPENMC_CROSS_SECTIONS": BARE_XS_FILE}) +def test_operator_init(bare_xs): + """The test will set and unset environment variable OPENMC_CROSS_SECTIONS + to point towards a temporary dummy file. This file will be removed + at the end of the test, and only contains a + depletion_chain node.""" + # force operator to read from OPENMC_CROSS_SECTIONS + bare_op = BareDepleteOperator(chain_file=None) + act_chain = bare_op.chain + ref_chain = Chain.from_xml(CHAIN_PATH) + assert len(act_chain) == len(ref_chain) + for name in ref_chain.nuclide_dict: + # compare openmc.deplete.Nuclide objects + ref_nuc = ref_chain[name] + act_nuc = act_chain[name] + for prop in [ + 'name', 'half_life', 'decay_energy', 'reactions', + 'decay_modes', 'yield_data', 'yield_energies', + ]: + assert getattr(act_nuc, prop) == getattr(ref_nuc, prop), prop From d86026689254fb1f36e1f80a9d5eaa13c4fac87b Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Fri, 31 May 2019 02:38:06 +0300 Subject: [PATCH 073/151] test lattice OX according to PEP8 --- include/openmc/lattice.h | 4 +- openmc/lattice.py | 309 +++++++++--------- tests/regression_tests/lattice_hex_OX/test.py | 157 +++++---- 3 files changed, 255 insertions(+), 215 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index 195f627857..c06920759e 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -266,8 +266,8 @@ public: void to_hdf5_inner(hid_t group_id) const; private: - enum class Orientation { - y, x + enum class Orientation { + oy, ox }; //! Fill universes_ vector for OY orientation void fill_lattice_oy(const std::vector& univ_words); diff --git a/openmc/lattice.py b/openmc/lattice.py index 8d2d82ed04..fed12d1925 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -36,7 +36,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): outer : openmc.Universe A universe to fill all space outside the lattice universes : Iterable of Iterable of openmc.Universe - A two- or three-dimensional list/array of universes filling each element + A two-or three-dimensional list/array of universes filling each element of the lattice """ @@ -142,10 +142,9 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): pitch = group['pitch'][()] outer = group['outer'][()] if ('orientation' in group): - orientation = group['orientation'][()].decode() + orientation = group['orientation'][()].decode() else: - orientation = "oy" - + orientation = "y" universe_ids = group['universes'][()] @@ -158,120 +157,119 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): if outer >= 0: lattice.outer = universes[outer] if (orientation.lower() == "oy"): - # Build array of Universe pointers for the Lattice. Note that - # we need to convert between the HDF5's square array of - # (x, alpha, z) to the Python API's format of a ragged nested - # list of (z, ring, theta). - uarray = [] - for z in range(n_axial): - # Add a list for this axial level. - uarray.append([]) - x = n_rings - 1 - a = 2*n_rings - 2 - for r in range(n_rings - 1, 0, -1): - # Add a list for this ring. - uarray[-1].append([]) + # Build array of Universe pointers for the Lattice. Note that + # we need to convert between the HDF5's square array of + # (x, alpha, z) to the Python API's format of a ragged nested + # list of (z, ring, theta). + uarray = [] + for z in range(n_axial): + # Add a list for this axial level. + uarray.append([]) + x = n_rings - 1 + a = 2*n_rings - 2 + for r in range(n_rings - 1, 0, -1): + # Add a list for this ring. + uarray[-1].append([]) - # Climb down the top-right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - x += 1 - a -= 1 + # Climb down the top-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + x += 1 + a -= 1 - # Climb down the right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - a -= 1 + # Climb down the right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + a -= 1 - # Climb down the bottom-right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - x -= 1 + # Climb down the bottom-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + x -= 1 - # Climb up the bottom-left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - x -= 1 - a += 1 + # Climb up the bottom-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + x -= 1 + a += 1 - # Climb up the left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - a += 1 + # Climb up the left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + a += 1 - # Climb up the top-left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, a, x]) - x += 1 + # Climb up the top-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, a, x]) + x += 1 - # Move down to the next ring. - a -= 1 + # Move down to the next ring. + a -= 1 - # Convert the ids into Universe objects. - uarray[-1][-1] = [universes[u_id] - for u_id in uarray[-1][-1]] + # Convert the ids into Universe objects. + uarray[-1][-1] = [universes[u_id] + for u_id in uarray[-1][-1]] - # Handle the degenerate center ring separately. - u_id = universe_ids[z, a, x] - uarray[-1].append([universes[u_id]]) + # Handle the degenerate center ring separately. + u_id = universe_ids[z, a, x] + uarray[-1].append([universes[u_id]]) else: - # Build array of Universe pointers for the Lattice. Note that - # we need to convert between the HDF5's square array of - # (alpha, y, z) to the Python API's format of a ragged nested - # list of (z, ring, theta). - uarray = [] - for z in range(n_axial): - # Add a list for this axial level. - uarray.append([]) - a = 2*n_rings - 2 - y = n_rings - 1 - for r in range(n_rings - 1, 0, -1): - # Add a list for this ring. - uarray[-1].append([]) + # Build array of Universe pointers for the Lattice. Note that + # we need to convert between the HDF5's square array of + # (alpha, y, z) to the Python API's format of a ragged nested + # list of (z, ring, theta). + uarray = [] + for z in range(n_axial): + # Add a list for this axial level. + uarray.append([]) + a = 2*n_rings - 2 + y = n_rings - 1 + for r in range(n_rings - 1, 0, -1): + # Add a list for this ring. + uarray[-1].append([]) - # Climb up the top-left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - a -= 1 - y += 1 - + # Climb up the top-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a -= 1 + y += 1 - # Climb the left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - a -= 1 + # Climb the left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a -= 1 - # Climb down the bottom-left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - y -= 1 + # Climb down the bottom-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + y -= 1 - # Climb down the bottom-right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - a += 1 - y -= 1 + # Climb down the bottom-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a += 1 + y -= 1 - # Climb up the right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - a += 1 + # Climb up the right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a += 1 - # Climb up the top-right. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - y += 1 + # Climb up the top-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + y += 1 - # Move down to the next ring. - a -= 1 + # Move down to the next ring. + a -= 1 - # Convert the ids into Universe objects. - uarray[-1][-1] = [universes[u_id] - for u_id in uarray[-1][-1]] + # Convert the ids into Universe objects. + uarray[-1][-1] = [universes[u_id] + for u_id in uarray[-1][-1]] - # Handle the degenerate center ring separately. - u_id = universe_ids[z, y, a] - uarray[-1].append([universes[u_id]]) + # Handle the degenerate center ring separately. + u_id = universe_ids[z, y, a] + uarray[-1].append([universes[u_id]]) # Add the universes to the lattice. if len(pitch) == 2: @@ -407,7 +405,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): ---------- idx : Iterable of int Lattice element indices. For a rectangular lattice, the indices are - given in the :math:`(x,y)` or :math:`(x,y,z)` coordinate system. For + given in the :math:`(x,y)` or :math:`(x,y,z)` coordinate system.For hexagonal lattices, they are given in the :math:`x,\alpha` or :math:`x,\alpha,z` coordinate systems. @@ -709,7 +707,8 @@ class RectLattice(Lattice): return (x, y, z) def get_universe_index(self, idx): - """Return index in the universes array corresponding to a lattice element index + """Return index in the universes array corresponding + to a lattice element index Parameters ---------- @@ -853,7 +852,8 @@ class RectLattice(Lattice): lat_id = int(get_text(elem, 'id')) name = get_text(elem, 'name') lat = cls(lat_id, name) - lat.lower_left = [float(i) for i in get_text(elem, 'lower_left').split()] + lat.lower_left = [float(i) + for i in get_text(elem, 'lower_left').split()] lat.pitch = [float(i) for i in get_text(elem, 'pitch').split()] outer = get_text(elem, 'outer') if outer is not None: @@ -863,7 +863,7 @@ class RectLattice(Lattice): dimension = get_text(elem, 'dimension').split() shape = np.array(dimension, dtype=int)[::-1] uarray = np.array([get_universe(int(i)) for i in - get_text(elem, 'universes').split()]) + get_text(elem, 'universes').split()]) uarray.shape = shape lat.universes = uarray return lat @@ -883,7 +883,7 @@ class HexLattice(Lattice): that when universes are assigned to lattice elements using the :attr:`HexLattice.universes` property, the array indices do not correspond to natural indices. - + Parameters ---------- lattice_id : int, optional @@ -942,7 +942,9 @@ class HexLattice(Lattice): string = 'HexLattice\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) - string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', "OX" if (self._orientation== 'x') else "OY" ) + string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', + "OX" if (self._orientation == 'x') + else "OY") string += '{0: <16}{1}{2}\n'.format('\t# Rings', '=\t', self._num_rings) string += '{0: <16}{1}{2}\n'.format('\t# Axial', '=\t', self._num_axial) string += '{0: <16}{1}{2}\n'.format('\tCenter', '=\t', @@ -970,11 +972,11 @@ class HexLattice(Lattice): @property def num_rings(self): return self._num_rings - + @property def orientation(self): return self._orientation - + @property def num_axial(self): return self._num_axial @@ -1031,7 +1033,7 @@ class HexLattice(Lattice): @orientation.setter def orientation(self, orientation): cv.check_value('orientation', orientation.lower(), ('ox', 'oy')) - self._orientation = orientation.lower() + self._orientation = orientation.lower() @Lattice.pitch.setter def pitch(self, pitch): @@ -1078,7 +1080,7 @@ class HexLattice(Lattice): # Check the center ring. if len(axial_slice[-1]) != 1: msg = 'HexLattice ID={0:d} has the wrong number of ' \ - 'elements in the innermost ring. Only 1 element is ' \ + 'elements in the innermost ring.Only 1 element is ' \ 'allowed in the innermost ring.'.format(self._id) raise ValueError(msg) @@ -1086,8 +1088,8 @@ class HexLattice(Lattice): for r in range(self._num_rings-1): if len(axial_slice[r]) != 6*(self._num_rings - 1 - r): msg = 'HexLattice ID={0:d} has the wrong number of ' \ - 'elements in ring number {1:d} (counting from the '\ - 'outermost ring). This ring should have {2:d} ' \ + 'elements in ring number{1:d}(counting from the '\ + 'outermost ring). This ring should have {2:d} ' \ 'elements.'.format(self._id, r, 6*(self._num_rings - 1 - r)) raise ValueError(msg) @@ -1148,9 +1150,10 @@ class HexLattice(Lattice): # Check four lattice elements to see which one is closest based on local # coordinates indices = [(i1, i2, iz), (i1 + 1, i2, iz), (i1, i2 + 1, iz), - (i1 + 1, i2 + 1, iz)] + (i1 + 1, i2 + 1, iz)] d_min = np.inf - for idx in indices: + + for idx in indices: p = self.get_local_coordinates(point, idx) d = p[0]**2 + p[1]**2 if d < d_min: @@ -1168,7 +1171,7 @@ class HexLattice(Lattice): point : Iterable of float Cartesian coordinates of point idx : Iterable of int - Indices of lattice element in :math:`(x,\alpha,z)` + Indices of lattice element in :math:`(x,\alpha,z)` or :math:`(\alpha,y,z)` bases Returns @@ -1179,40 +1182,42 @@ class HexLattice(Lattice): """ if self._orientation == 'x': - x = point[0] - (self.center[0] + self.pitch[0]*idx[0] + + x = point[0] - (self.center[0] + self.pitch[0]*idx[0] + 0.5*self.pitch[0]*idx[1]) - y = point[1] - (self.center[1] + + y = point[1] - (self.center[1] + sqrt(0.75)*self.pitch[0]*idx[1]) else: - x = point[0] - (self.center[0] + x = point[0] - (self.center[0] + sqrt(0.75)*self.pitch[0]*idx[0]) - y = point[1] - (self.center[1] + y = point[1] - (self.center[1] + (0.5*idx[0] + idx[1])*self.pitch[0]) - + if self._num_axial is None: z = point[2] else: z = point[2] - (self.center[2] + (idx[2] + 0.5 - 0.5*self.num_axial)* self.pitch[1]) return (x, y, z) - + def get_universe_index(self, idx): - r"""Return index in the universes array corresponding to a lattice element index + r"""Return index in the universes array corresponding + to a lattice element index Parameters ---------- idx : Iterable of int Lattice element indices in the :math:`(x,\alpha,z)` coordinate - system in 'OY' orientation case, or indices in the :math:`(\alpha,y,z)` coordinate - system in 'OX' one + system in 'OY' orientation case, or indices in the + :math:`(\alpha,y,z)` coordinate system in 'OX' one Returns ------- 2- or 3-tuple of int - Indices used when setting the :attr:`HexLattice.universes` property + Indices used when setting the :attr:`HexLattice.universes` + property """ - + # First we determine which ring the index corresponds to. x = idx[0] a = idx[1] @@ -1231,23 +1236,23 @@ class HexLattice(Lattice): i_within = 3*g - x else: i_within = 5*g - z - + if (self._orientation == 'x') and (i_within > 0): i_within = 6*(self._num_rings - i_ring - 1) - i_within_ - + if self.num_axial is None: return (i_ring, i_within) else: return (idx[2], i_ring, i_within) - + def is_valid_index(self, idx): r"""Determine whether lattice element index is within defined range Parameters ---------- idx : Iterable of int - Lattice element indices in the both :math:`(x,\alpha,z)` and :math:`(\alpha,y,z)` coordinate - system + Lattice element indices in the both :math:`(x,\alpha,z)` + and :math:`(\alpha,y,z)` coordinate system Returns ------- @@ -1291,7 +1296,7 @@ class HexLattice(Lattice): lattice_subelement.set("n_rings", str(self._num_rings)) # If orientation is "OX" export it to XML - if self._orientation== 'x': + if self._orientation == 'x': lattice_subelement.set("orient", "OX") if self._num_axial is not None: @@ -1377,13 +1382,13 @@ class HexLattice(Lattice): # Create empty nested lists for one axial level univs = [[None for _ in range(max(6*(n_rings - 1 - r), 1))] - for r in range(n_rings)] + for r in range(n_rings)] if n_axial > 1: univs = [deepcopy(univs) for i in range(n_axial)] # Get flat array of universes numbers uarray = np.array([get_universe(int(i)) for i in - get_text(elem, 'universes').split()]) + get_text(elem, 'universes').split()]) # Fill nested lists j = 0 @@ -1415,6 +1420,7 @@ class HexLattice(Lattice): j += 1 lat.universes = univs return lat + def _repr_axial_slice(self, universes): """Return string representation for the given 2D group of universes. @@ -1422,13 +1428,14 @@ class HexLattice(Lattice): each sub-list represents a single ring. The first list should be the outer ring. """ - if self._orientation== 'x': + if self._orientation == 'x': return self._repr_axial_slice_ox(universes) else: return self._repr_axial_slice_oy(universes) def _repr_axial_slice_ox(self, universes): - """Return string representation for the given 2D group of universes in 'OX' orientation case. + """Return string representation for the given 2D group of universes + in 'OX' orientation case. The 'universes' argument should be a list of lists of universes where each sub-list represents a single ring. The first list should be the @@ -1523,13 +1530,14 @@ class HexLattice(Lattice): for y in range(self._num_rings - 1): rows[y] = (self._num_rings - 1 - y)*pad + rows[y] rows[-1 - y] = (self._num_rings - 1 - y)*pad + rows[-1 - y] - + # Join the rows together and return the string. universe_ids = '\n'.join(rows) return universe_ids - + def _repr_axial_slice_oy(self, universes): - """Return string representation for the given 2D group of universes in 'OY' orientation case.. + """Return string representation for the given 2D group of universes in + 'OY' orientation case.. The 'universes' argument should be a list of lists of universes where each sub-list represents a single ring. The first list should be the @@ -1635,7 +1643,7 @@ class HexLattice(Lattice): # Join the rows together and return the string. universe_ids = '\n'.join(rows) return universe_ids - + def _show_indices_y(num_rings): """Return a diagram of the hexagonal lattice layout with indices. @@ -1738,24 +1746,25 @@ class HexLattice(Lattice): # Join the rows together and return the string. return '\n'.join(rows) - + def _show_indices_x(num_rings): - """Return a diagram of the hexagonal lattice with OX orientation layout with indices. + """Return a diagram of the hexagonal lattice with OX orientation + layout with indices. This method can be used to show the proper indices to be used when - setting the :attr:`HexLattice.universes` property. For example, running + setting the :attr:`HexLattice.universes` property. For example,running this method with num_rings=3 will return the similar diagram:: (0, 4) (0, 3) (0, 2) - + (0, 5) (1, 2) (1, 1) (0, 1) - + (0, 6) (1, 3) (2, 0) (1, 0) (0, 0) (0, 7) (1, 4) (1, 5) (0,11) (0, 8) (0, 9) (0,10) - + Parameters ---------- num_rings : int @@ -1798,12 +1807,12 @@ class HexLattice(Lattice): for i in range(r): # Climb left the top-left. - rows[y].insert(0,str_form.format(r_prime, theta)) + rows[y].insert(0, str_form.format(r_prime, theta)) theta += 1 for i in range(r): # Climb down the middle left. - rows[y].insert(0,str_form.format(r_prime, theta)) + rows[y].insert(0, str_form.format(r_prime, theta)) y -= 1 theta += 1 @@ -1834,11 +1843,11 @@ class HexLattice(Lattice): # Join the rows together and return the string. return '\n\n'.join(rows) - + @staticmethod def show_indices(num_rings, orientation="y"): """Return a diagram of the hexagonal lattice layout with indices. - + Parameters ---------- num_rings : int @@ -1857,5 +1866,3 @@ class HexLattice(Lattice): return self._show_indices_x(num_rings) else: return self._show_indices_y(num_rings) - - diff --git a/tests/regression_tests/lattice_hex_OX/test.py b/tests/regression_tests/lattice_hex_OX/test.py index 0af36e6f70..bcef2357db 100644 --- a/tests/regression_tests/lattice_hex_OX/test.py +++ b/tests/regression_tests/lattice_hex_OX/test.py @@ -2,48 +2,56 @@ from tests.testing_harness import PyAPITestHarness import openmc import numpy as np + class HexLatticeOXTestHarness(PyAPITestHarness): + def _build_inputs(self): + materials = openmc.Materials() - fuel_mat = openmc.Material(material_id=1,name="UO2") + fuel_mat = openmc.Material(material_id=1, name="UO2") fuel_mat.set_density('sum') - fuel_mat.add_nuclide('U235',0.87370e-03) - fuel_mat.add_nuclide('U238',1.87440e-02) - fuel_mat.add_nuclide('O16',3.92350e-02) + fuel_mat.add_nuclide('U235', 0.87370e-03) + fuel_mat.add_nuclide('U238', 1.87440e-02) + fuel_mat.add_nuclide('O16', 3.92350e-02) materials.append(fuel_mat) - coolant = openmc.Material(material_id=2,name="borated H2O") + coolant = openmc.Material(material_id=2, name="borated H2O") coolant.set_density('sum') - coolant.add_nuclide('H1',0.06694) - coolant.add_nuclide('O16',0.03347) - coolant.add_nuclide('B10',6.6262e-6) - coolant.add_nuclide('B11',2.6839e-5) + coolant.add_nuclide('H1', 0.06694) + coolant.add_nuclide('O16', 0.03347) + coolant.add_nuclide('B10', 6.6262e-6) + coolant.add_nuclide('B11', 2.6839e-5) materials.append(coolant) - absorber = openmc.Material(material_id=3,name="pellet B4C") + absorber = openmc.Material(material_id=3, name="pellet B4C") absorber.set_density('sum') - absorber.add_nuclide('C0',0.01966) - absorber.add_nuclide('B11',4.7344e-6) - absorber.add_nuclide('B10',1.9177e-5) + absorber.add_nuclide('C0', 0.01966) + absorber.add_nuclide('B11', 4.7344e-6) + absorber.add_nuclide('B10', 1.9177e-5) materials.append(absorber) - zirc = openmc.Material(material_id=4,name="Zirc4") + zirc = openmc.Material(material_id=4, name="Zirc4") zirc.set_density('sum') - zirc.add_element('Zr',4.23e-2) + zirc.add_element('Zr', 4.23e-2) materials.append(zirc) materials.export_to_xml() - ### Geometry ### + # Geometry # + pin_rad = 0.7 # cm assembly_pitch = 1.235 # cm hexagonal_pitch = 23.6 # cm length = 10.0 # cm - #Fuel pin surfaces - cylfuelin = openmc.ZCylinder(surface_id=1,r=0.386) - cylfuelout = openmc.ZCylinder(surface_id=2,r=0.4582) - #Fuel cells + + # Fuel pin surfaces + + cylfuelin = openmc.ZCylinder(surface_id=1, r=0.386) + cylfuelout = openmc.ZCylinder(surface_id=2, r=0.4582) + + # Fuel cells + infcell = openmc.Cell(cell_id=1) infcell.region = -cylfuelin infcell.fill = fuel_mat @@ -55,14 +63,19 @@ class HexLatticeOXTestHarness(PyAPITestHarness): outfcell = openmc.Cell(cell_id=3) outfcell.region = +cylfuelout outfcell.fill = coolant - #Fuel universe - fuel_ch_univ = openmc.Universe(universe_id=1,name="Fuel channel", - cells=[infcell, clfcell,outfcell]) - - #Central tube surfaces - cyltubein = openmc.ZCylinder(surface_id=3,r=0.45) - cyltubeout = openmc.ZCylinder(surface_id=4,r=0.5177) - #Central tube cells + + # Fuel universe + + fuel_ch_univ = openmc.Universe(universe_id=1, name="Fuel channel", + cells=[infcell, clfcell, outfcell]) + + # Central tube surfaces + + cyltubein = openmc.ZCylinder(surface_id=3, r=0.45) + cyltubeout = openmc.ZCylinder(surface_id=4, r=0.5177) + + # Central tube cells + inctcell = openmc.Cell(cell_id=4) inctcell.region = -cyltubein inctcell.fill = coolant @@ -74,16 +87,22 @@ class HexLatticeOXTestHarness(PyAPITestHarness): outctcell = openmc.Cell(cell_id=6) outctcell.region = +cyltubeout outctcell.fill = coolant - #Central tubel universe - tube_ch_univ = openmc.Universe(universe_id=2,name="Central tube channel", - cells=[inctcell, clctcell,outctcell]) - #Absorber tube surfaces - cylabsin = openmc.ZCylinder(surface_id=5,r=0.35) - cylabsout = openmc.ZCylinder(surface_id=6,r=0.41) - cylabsclin = openmc.ZCylinder(surface_id=7,r=0.545) - cylabsclout = openmc.ZCylinder(surface_id=8,r=0.6323) - #Absorber tube cells + # Central tubel universe + + tube_ch_univ = openmc.Universe(universe_id=2, + name="Central tube channel", + cells=[inctcell, clctcell, outctcell]) + + # Absorber tube surfaces + + cylabsin = openmc.ZCylinder(surface_id=5, r=0.35) + cylabsout = openmc.ZCylinder(surface_id=6, r=0.41) + cylabsclin = openmc.ZCylinder(surface_id=7, r=0.545) + cylabsclout = openmc.ZCylinder(surface_id=8, r=0.6323) + + # Absorber tube cells + inabscell = openmc.Cell(cell_id=7) inabscell.region = -cylabsin inabscell.fill = absorber @@ -95,7 +114,7 @@ class HexLatticeOXTestHarness(PyAPITestHarness): interabscell = openmc.Cell(cell_id=9) interabscell.region = -cylabsclin & +cylabsout interabscell.fill = coolant - + clatcell = openmc.Cell(cell_id=10) clatcell.region = -cylabsclout & +cylabsclin clatcell.fill = zirc @@ -103,57 +122,69 @@ class HexLatticeOXTestHarness(PyAPITestHarness): outabscell = openmc.Cell(cell_id=11) outabscell.region = +cylabsclout outabscell.fill = coolant - #Absorber tube universe - abs_ch_univ = openmc.Universe(universe_id=3,name="Central tube channel",\ - cells=[inabscell,clabscell,interabscell,clatcell,outabscell]) - #Assembly surfaces - edge_length = (1./np.sqrt(3.0)) * hexagonal_pitch - fuel_bottom = openmc.ZPlane(surface_id=9,z0=0.0, - boundary_type = 'reflective') - fuel_top = openmc.ZPlane(surface_id=10,z0=length, - boundary_type = 'reflective') + + # Absorber tube universe + + abs_ch_univ = openmc.Universe(universe_id=3, + name="Central tube channel", + cells=[inabscell, clabscell, + interabscell, + clatcell, outabscell]) + # Assembly surfaces + + edge_length = (1./np.sqrt(3.0)) * hexagonal_pitch + fuel_bottom = openmc.ZPlane(surface_id=9, z0=0.0, + boundary_type='reflective') + fuel_top = openmc.ZPlane(surface_id=10, z0=length, + boundary_type='reflective') + # a hex surface for the core to go inside of + hexprism = openmc.model.get_hexagonal_prism(edge_length=edge_length, - origin=(0.0, 0.0), - boundary_type = 'reflective', + origin=(0.0, 0.0), + boundary_type='reflective', orientation='x') region = hexprism & +fuel_bottom & -fuel_top inf_mat = openmc.Cell(cell_id=12) inf_mat.fill = coolant - inf_mat_univ = openmc.Universe(universe_id=4,cells=[inf_mat,]) + inf_mat_univ = openmc.Universe(universe_id=4, cells=[inf_mat, ]) + # Fill lattice by channels + nring = 11 - universes = [] # array of rings - for ring in range(nring - 1,-1,-1): + universes = [] + for ring in range(nring - 1, -1, -1): arr = [] arr.append(fuel_ch_univ) for cell in range(ring * 6 - 1): arr.append(fuel_ch_univ) universes.append(arr) universes[-1] = [tube_ch_univ] - channels = [(7,16),(7,13),(7,10),(7,7),(7,4),(7,1),(5,0),\ - (4,33),(5,25),(4,27),(5,20),(4,21),(5,15),\ - (4,15),(5,10),(4,9),(5,5),(4,3)] - for i,j in channels: + channels = [(7, 16), (7, 13), (7, 10), (7, 7), (7, 4), (7, 1), (5, 0), + (4, 33), (5, 25), (4, 27), (5, 20), (4, 21), (5, 15), + (4, 15), (5, 10), (4, 9), (5, 5), (4, 3)] + for i, j in channels: universes[i][j] = abs_ch_univ lattice = openmc.HexLattice(name="regular fuel assembly") lattice.orientation = "ox" lattice.center = (0., 0., length/2.0) - lattice.pitch = (assembly_pitch,length/2.0) + lattice.pitch = (assembly_pitch, length/2.0) lattice.universes = 2*[universes] lattice.outer = inf_mat_univ - - assembly_cell = openmc.Cell(cell_id=13,name="container assembly cell") + + assembly_cell = openmc.Cell(cell_id=13, + name="container assembly cell") assembly_cell.region = region assembly_cell.fill = lattice - root_univ = openmc.Universe(universe_id=5,name="root universe", cells=[assembly_cell,]) + root_univ = openmc.Universe(universe_id=5, name="root universe", + cells=[assembly_cell, ]) geom = openmc.Geometry(root_univ) geom.export_to_xml() - ### Settings ### + # Settings # settings = openmc.Settings() settings.run_mode = 'eigenvalue' @@ -164,13 +195,15 @@ class HexLatticeOXTestHarness(PyAPITestHarness): source.space = openmc.stats.Box(ll, ur) source.strength = 1.0 settings.source = source - settings.output = {'summary' : False} + settings.output = {'summary': False} settings.batches = 10 settings.inactive = 5 settings.particles = 1000 settings.seed = 22 settings.export_to_xml() + def test_lattice_hex_ox_surf(): + harness = HexLatticeOXTestHarness('statepoint.10.h5') harness.main() From 98a95d048a39f0d666a8524e38a6928753e77c66 Mon Sep 17 00:00:00 2001 From: "dr.yuri" Date: Fri, 31 May 2019 10:30:40 +0300 Subject: [PATCH 074/151] test works with x --- openmc/lattice.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index fed12d1925..43d7eb5250 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -707,7 +707,7 @@ class RectLattice(Lattice): return (x, y, z) def get_universe_index(self, idx): - """Return index in the universes array corresponding + """Return index in the universes array corresponding to a lattice element index Parameters @@ -852,7 +852,7 @@ class RectLattice(Lattice): lat_id = int(get_text(elem, 'id')) name = get_text(elem, 'name') lat = cls(lat_id, name) - lat.lower_left = [float(i) + lat.lower_left = [float(i) for i in get_text(elem, 'lower_left').split()] lat.pitch = [float(i) for i in get_text(elem, 'pitch').split()] outer = get_text(elem, 'outer') @@ -943,8 +943,8 @@ class HexLattice(Lattice): string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', - "OX" if (self._orientation == 'x') - else "OY") + "x" if (self._orientation == 'x') + else "y") string += '{0: <16}{1}{2}\n'.format('\t# Rings', '=\t', self._num_rings) string += '{0: <16}{1}{2}\n'.format('\t# Axial', '=\t', self._num_axial) string += '{0: <16}{1}{2}\n'.format('\tCenter', '=\t', @@ -1032,7 +1032,7 @@ class HexLattice(Lattice): @orientation.setter def orientation(self, orientation): - cv.check_value('orientation', orientation.lower(), ('ox', 'oy')) + cv.check_value('orientation', orientation.lower(), ('x', 'y')) self._orientation = orientation.lower() @Lattice.pitch.setter @@ -1382,7 +1382,7 @@ class HexLattice(Lattice): # Create empty nested lists for one axial level univs = [[None for _ in range(max(6*(n_rings - 1 - r), 1))] - for r in range(n_rings)] + for r in range(n_rings)] if n_axial > 1: univs = [deepcopy(univs) for i in range(n_axial)] @@ -1434,7 +1434,7 @@ class HexLattice(Lattice): return self._repr_axial_slice_oy(universes) def _repr_axial_slice_ox(self, universes): - """Return string representation for the given 2D group of universes + """Return string representation for the given 2D group of universes in 'OX' orientation case. The 'universes' argument should be a list of lists of universes where @@ -1536,7 +1536,7 @@ class HexLattice(Lattice): return universe_ids def _repr_axial_slice_oy(self, universes): - """Return string representation for the given 2D group of universes in + """Return string representation for the given 2D group of universes in 'OY' orientation case.. The 'universes' argument should be a list of lists of universes where @@ -1644,6 +1644,7 @@ class HexLattice(Lattice): universe_ids = '\n'.join(rows) return universe_ids + @staticmethod def _show_indices_y(num_rings): """Return a diagram of the hexagonal lattice layout with indices. @@ -1747,8 +1748,9 @@ class HexLattice(Lattice): # Join the rows together and return the string. return '\n'.join(rows) + @staticmethod def _show_indices_x(num_rings): - """Return a diagram of the hexagonal lattice with OX orientation + """Return a diagram of the hexagonal lattice with OX orientation layout with indices. This method can be used to show the proper indices to be used when @@ -1862,7 +1864,7 @@ class HexLattice(Lattice): """ - if self._orientation == 'x': - return self._show_indices_x(num_rings) + if orientation == 'x': + return HexLattice._show_indices_x(num_rings) else: - return self._show_indices_y(num_rings) + return HexLattice._show_indices_y(num_rings) From a465dcdb1666bf15c0a6c9f859e06b800c0738cd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 4 Jun 2019 14:15:12 -0500 Subject: [PATCH 075/151] Consistently use 'x' / 'y' for hex lattice orientation --- include/openmc/lattice.h | 16 ++--- openmc/lattice.py | 36 ++++++------ src/lattice.cpp | 58 +++++++++---------- .../lattice_hex_OX/inputs_true.dat | 2 +- tests/regression_tests/lattice_hex_OX/test.py | 6 +- 5 files changed, 60 insertions(+), 58 deletions(-) diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index c06920759e..8f2b6c4a1e 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -267,17 +267,19 @@ public: private: enum class Orientation { - oy, ox - }; - //! Fill universes_ vector for OY orientation - void fill_lattice_oy(const std::vector& univ_words); + y, //!< Flat side perpendicular to y-axis + x //!< Flat side perpendicular to x-axis + }; - //! Fill universes_ vector for OX orientation - void fill_lattice_ox(const std::vector& univ_words); + //! Fill universes_ vector for 'y' orientation + void fill_lattice_y(const std::vector& univ_words); + + //! Fill universes_ vector for 'x' orientation + void fill_lattice_x(const std::vector& univ_words); int n_rings_; //!< Number of radial tile positions int n_axial_; //!< Number of axial tile positions - Orientation orientation_; //!< Flat side up (oy) vs. sharp side up (ox) + Orientation orientation_; //!< Orientation of lattice Position center_; //!< Global center of lattice std::array pitch_; //!< Lattice tile width and height }; diff --git a/openmc/lattice.py b/openmc/lattice.py index 43d7eb5250..4f7caa20ad 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -141,7 +141,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): center = group['center'][()] pitch = group['pitch'][()] outer = group['outer'][()] - if ('orientation' in group): + if 'orientation' in group: orientation = group['orientation'][()].decode() else: orientation = "y" @@ -156,7 +156,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): # If the Universe specified outer the Lattice is not void if outer >= 0: lattice.outer = universes[outer] - if (orientation.lower() == "oy"): + if orientation == "y": # Build array of Universe pointers for the Lattice. Note that # we need to convert between the HDF5's square array of # (x, alpha, z) to the Python API's format of a ragged nested @@ -1207,8 +1207,8 @@ class HexLattice(Lattice): ---------- idx : Iterable of int Lattice element indices in the :math:`(x,\alpha,z)` coordinate - system in 'OY' orientation case, or indices in the - :math:`(\alpha,y,z)` coordinate system in 'OX' one + system in 'y' orientation case, or indices in the + :math:`(\alpha,y,z)` coordinate system in 'x' one Returns ------- @@ -1297,7 +1297,7 @@ class HexLattice(Lattice): lattice_subelement.set("n_rings", str(self._num_rings)) # If orientation is "OX" export it to XML if self._orientation == 'x': - lattice_subelement.set("orient", "OX") + lattice_subelement.set("orientation", "x") if self._num_axial is not None: lattice_subelement.set("n_axial", str(self._num_axial)) @@ -1429,13 +1429,13 @@ class HexLattice(Lattice): outer ring. """ if self._orientation == 'x': - return self._repr_axial_slice_ox(universes) + return self._repr_axial_slice_x(universes) else: - return self._repr_axial_slice_oy(universes) + return self._repr_axial_slice_y(universes) - def _repr_axial_slice_ox(self, universes): + def _repr_axial_slice_x(self, universes): """Return string representation for the given 2D group of universes - in 'OX' orientation case. + in 'x' orientation case. The 'universes' argument should be a list of lists of universes where each sub-list represents a single ring. The first list should be the @@ -1535,9 +1535,9 @@ class HexLattice(Lattice): universe_ids = '\n'.join(rows) return universe_ids - def _repr_axial_slice_oy(self, universes): + def _repr_axial_slice_y(self, universes): """Return string representation for the given 2D group of universes in - 'OY' orientation case.. + 'y' orientation case.. The 'universes' argument should be a list of lists of universes where each sub-list represents a single ring. The first list should be the @@ -1750,22 +1750,22 @@ class HexLattice(Lattice): @staticmethod def _show_indices_x(num_rings): - """Return a diagram of the hexagonal lattice with OX orientation + """Return a diagram of the hexagonal lattice with x orientation layout with indices. This method can be used to show the proper indices to be used when setting the :attr:`HexLattice.universes` property. For example,running this method with num_rings=3 will return the similar diagram:: - (0, 4) (0, 3) (0, 2) + (0, 4) (0, 3) (0, 2) - (0, 5) (1, 2) (1, 1) (0, 1) + (0, 5) (1, 2) (1, 1) (0, 1) - (0, 6) (1, 3) (2, 0) (1, 0) (0, 0) + (0, 6) (1, 3) (2, 0) (1, 0) (0, 0) - (0, 7) (1, 4) (1, 5) (0,11) + (0, 7) (1, 4) (1, 5) (0,11) - (0, 8) (0, 9) (0,10) + (0, 8) (0, 9) (0,10) Parameters ---------- @@ -1855,7 +1855,7 @@ class HexLattice(Lattice): num_rings : int Number of rings in the hexagonal lattice orientation : {"x", "y"} - str type of hex lattice orientation + Orientation of the hexagonal lattice Returns ------- diff --git a/src/lattice.cpp b/src/lattice.cpp index d2e29e1067..1838df133c 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -440,19 +440,19 @@ HexLattice::HexLattice(pugi::xml_node lat_node) is_3d_ = false; } - // Read the lattice orientation. Default to OY. - if (check_for_node(lat_node, "orient")) { - std::string orientation = get_node_value(lat_node, "orient"); - if (orientation == "OY") { - orientation_ = Orientation::oy; - } else if (orientation == "OX") { - orientation_ = Orientation::ox; + // Read the lattice orientation. Default to 'y'. + if (check_for_node(lat_node, "orientation")) { + std::string orientation = get_node_value(lat_node, "orientation"); + if (orientation == "y") { + orientation_ = Orientation::y; + } else if (orientation == "x") { + orientation_ = Orientation::x; } else { fatal_error("Unrecognized orientation '" + orientation + "' for lattice " + std::to_string(id_)); } } else { - orientation_ = Orientation::oy; + orientation_ = Orientation::y; } // Read the lattice center. @@ -497,25 +497,25 @@ HexLattice::HexLattice(pugi::xml_node lat_node) // Parse the universes. // Universes in hexagonal lattices are stored in a manner that represents - // a skewed coordinate system: (x, alpha) in case of OY orientation - // and (alpha,y) in OX one rather than (x, y). There is + // a skewed coordinate system: (x, alpha) in case of 'y' orientation + // and (alpha,y) in 'x' one rather than (x, y). There is // no obvious, direct relationship between the order of universes in the // input and the order that they will be stored in the skewed array so // the following code walks a set of index values across the skewed array // in a manner that matches the input order. Note that i_x = 0, i_a = 0 // or i_a = 0, i_y = 0 corresponds to the center of the hexagonal lattice. universes_.resize((2*n_rings_-1) * (2*n_rings_-1) * n_axial_, C_NONE); - if (orientation_ == Orientation::oy) { - fill_lattice_oy(univ_words); + if (orientation_ == Orientation::y) { + fill_lattice_y(univ_words); } else { - fill_lattice_ox(univ_words); + fill_lattice_x(univ_words); } } - + //============================================================================== - + void -HexLattice::fill_lattice_ox(const std::vector& univ_words) +HexLattice::fill_lattice_x(const std::vector& univ_words) { int input_index = 0; for (int m = 0; m < n_axial_; m++) { @@ -568,7 +568,7 @@ HexLattice::fill_lattice_ox(const std::vector& univ_words) //============================================================================== void -HexLattice::fill_lattice_oy(const std::vector& univ_words) +HexLattice::fill_lattice_y(const std::vector& univ_words) { int input_index = 0; for (int m = 0; m < n_axial_; m++) { @@ -692,13 +692,13 @@ const { // Short description of the direction vectors used here. The beta, gamma, and // delta vectors point towards the flat sides of each hexagonal tile. - // OY - orientation: + // Y - orientation: // basis0 = (1, 0) // basis1 = (-1/sqrt(3), 1) = +120 degrees from basis0 // beta = (sqrt(3)/2, 1/2) = +30 degrees from basis0 // gamma = (sqrt(3)/2, -1/2) = -60 degrees from beta // delta = (0, 1) = +60 degrees from beta - // OX - orientation: + // X - orientation: // basis0 = (1/sqrt(3), -1) // basis1 = (0, 1) = +120 degrees from basis0 // beta = (1, 0) = +30 degrees from basis0 @@ -708,7 +708,7 @@ const double beta_dir; double gamma_dir; double delta_dir; - if (orientation_ == Orientation::oy) { + if (orientation_ == Orientation::y) { beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0; gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0; delta_dir = u.y; @@ -737,7 +737,7 @@ const r_t = get_local_position(r, i_xyz_t); } double beta; - if (orientation_ == Orientation::oy) { + if (orientation_ == Orientation::y) { beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0; } else { beta = r_t.x; @@ -761,7 +761,7 @@ const r_t = get_local_position(r, i_xyz_t); } double gamma; - if (orientation_ == Orientation::oy) { + if (orientation_ == Orientation::y) { gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0; } else { gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0; @@ -788,7 +788,7 @@ const r_t = get_local_position(r, i_xyz_t); } double delta; - if (orientation_ == Orientation::oy) { + if (orientation_ == Orientation::y) { delta = r_t.y; } else { delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0; @@ -848,7 +848,7 @@ HexLattice::get_indices(Position r, Direction u) const } int i1, i2; - if (orientation_ == Orientation::oy) { + if (orientation_ == Orientation::y) { // Convert coordinates into skewed bases. The (x, alpha) basis is used to // find the index of the global coordinates to within 4 cells. double alpha = r_o.y - r_o.x / std::sqrt(3.0); @@ -926,7 +926,7 @@ Position HexLattice::get_local_position(Position r, const std::array i_xyz) const { - if (orientation_ == Orientation::oy) { + if (orientation_ == Orientation::y) { // x_l = x_g - (center + pitch_x*cos(30)*index_x) r.x -= center_.x + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0]; @@ -941,7 +941,7 @@ const r.y -= center_.y + std::sqrt(3.0)/2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0]; } - + if (is_3d_) { r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1]; } @@ -1003,10 +1003,10 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_string(lat_group, "type", "hexagonal", false); write_dataset(lat_group, "n_rings", n_rings_); write_dataset(lat_group, "n_axial", n_axial_); - if (orientation_ == Orientation::oy) { - write_string(lat_group, "orientation", "oy", false); + if (orientation_ == Orientation::y) { + write_string(lat_group, "orientation", "y", false); } else { - write_string(lat_group, "orientation", "ox", false); + write_string(lat_group, "orientation", "x", false); } if (is_3d_) { write_dataset(lat_group, "pitch", pitch_); diff --git a/tests/regression_tests/lattice_hex_OX/inputs_true.dat b/tests/regression_tests/lattice_hex_OX/inputs_true.dat index 9130cf284d..9c0b102a93 100644 --- a/tests/regression_tests/lattice_hex_OX/inputs_true.dat +++ b/tests/regression_tests/lattice_hex_OX/inputs_true.dat @@ -13,7 +13,7 @@ - + 1.235 5.0 4
0.0 0.0 5.0
diff --git a/tests/regression_tests/lattice_hex_OX/test.py b/tests/regression_tests/lattice_hex_OX/test.py index bcef2357db..c958387ddf 100644 --- a/tests/regression_tests/lattice_hex_OX/test.py +++ b/tests/regression_tests/lattice_hex_OX/test.py @@ -148,7 +148,7 @@ class HexLatticeOXTestHarness(PyAPITestHarness): inf_mat = openmc.Cell(cell_id=12) inf_mat.fill = coolant - inf_mat_univ = openmc.Universe(universe_id=4, cells=[inf_mat, ]) + inf_mat_univ = openmc.Universe(universe_id=4, cells=[inf_mat]) # Fill lattice by channels @@ -167,7 +167,7 @@ class HexLatticeOXTestHarness(PyAPITestHarness): for i, j in channels: universes[i][j] = abs_ch_univ lattice = openmc.HexLattice(name="regular fuel assembly") - lattice.orientation = "ox" + lattice.orientation = "x" lattice.center = (0., 0., length/2.0) lattice.pitch = (assembly_pitch, length/2.0) lattice.universes = 2*[universes] @@ -179,7 +179,7 @@ class HexLatticeOXTestHarness(PyAPITestHarness): assembly_cell.fill = lattice root_univ = openmc.Universe(universe_id=5, name="root universe", - cells=[assembly_cell, ]) + cells=[assembly_cell]) geom = openmc.Geometry(root_univ) geom.export_to_xml() From 3794c3eec44ba69a67b0028cb76954aa3f7c20a6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 4 Jun 2019 14:42:57 -0500 Subject: [PATCH 076/151] Update documentation for hex lattice --- docs/source/io_formats/geometry.rst | 7 +++++++ openmc/lattice.py | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/source/io_formats/geometry.rst b/docs/source/io_formats/geometry.rst index 94511842dc..a89bfb1e34 100644 --- a/docs/source/io_formats/geometry.rst +++ b/docs/source/io_formats/geometry.rst @@ -318,6 +318,13 @@ the following attributes or sub-elements: *Default*: None + :orientation: + The orientation of the hexagonal lattice. The string "x" indicates that two + sides of the lattice are perpendicular to the x-axis, whereas the string "y" + indicates that two sides are perpendicular to the y-axis. + + *Default*: "y" + :center: The coordinates of the center of the lattice. If the lattice does not have axial sections then only the x- and y-coordinates are specified. diff --git a/openmc/lattice.py b/openmc/lattice.py index 4f7caa20ad..b2bdc8a564 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -884,6 +884,10 @@ class HexLattice(Lattice): :attr:`HexLattice.universes` property, the array indices do not correspond to natural indices. + .. versionchanged:: 0.11 + The orientation of the lattice can now be changed with the + :attr:`orientation` attribute. + Parameters ---------- lattice_id : int, optional @@ -1295,7 +1299,7 @@ class HexLattice(Lattice): self._outer.create_xml_subelement(xml_element) lattice_subelement.set("n_rings", str(self._num_rings)) - # If orientation is "OX" export it to XML + # If orientation is "x" export it to XML if self._orientation == 'x': lattice_subelement.set("orientation", "x") From d1a2640ac350cc4acde81193c6ef9362e9237a92 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 4 Jun 2019 16:00:50 -0500 Subject: [PATCH 077/151] Rename lattice_hex_OX -> lattice_hex_x --- .../{lattice_hex_OX => lattice_hex_x}/__init__.py | 0 .../{lattice_hex_OX => lattice_hex_x}/inputs_true.dat | 0 .../{lattice_hex_OX => lattice_hex_x}/results_true.dat | 0 tests/regression_tests/{lattice_hex_OX => lattice_hex_x}/test.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename tests/regression_tests/{lattice_hex_OX => lattice_hex_x}/__init__.py (100%) rename tests/regression_tests/{lattice_hex_OX => lattice_hex_x}/inputs_true.dat (100%) rename tests/regression_tests/{lattice_hex_OX => lattice_hex_x}/results_true.dat (100%) rename tests/regression_tests/{lattice_hex_OX => lattice_hex_x}/test.py (100%) diff --git a/tests/regression_tests/lattice_hex_OX/__init__.py b/tests/regression_tests/lattice_hex_x/__init__.py similarity index 100% rename from tests/regression_tests/lattice_hex_OX/__init__.py rename to tests/regression_tests/lattice_hex_x/__init__.py diff --git a/tests/regression_tests/lattice_hex_OX/inputs_true.dat b/tests/regression_tests/lattice_hex_x/inputs_true.dat similarity index 100% rename from tests/regression_tests/lattice_hex_OX/inputs_true.dat rename to tests/regression_tests/lattice_hex_x/inputs_true.dat diff --git a/tests/regression_tests/lattice_hex_OX/results_true.dat b/tests/regression_tests/lattice_hex_x/results_true.dat similarity index 100% rename from tests/regression_tests/lattice_hex_OX/results_true.dat rename to tests/regression_tests/lattice_hex_x/results_true.dat diff --git a/tests/regression_tests/lattice_hex_OX/test.py b/tests/regression_tests/lattice_hex_x/test.py similarity index 100% rename from tests/regression_tests/lattice_hex_OX/test.py rename to tests/regression_tests/lattice_hex_x/test.py From c16154e84afb7347c1aa48f564b633ae175e8d1a Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 4 Jun 2019 18:28:19 -0400 Subject: [PATCH 078/151] Remove openmc_load_cmfd_tallies, address comments from @paulromano --- include/openmc/capi.h | 2 - openmc/capi/core.py | 2 - openmc/cmfd.py | 272 +++++++++--------- src/state_point.cpp | 34 --- tests/regression_tests/cmfd_feed/test.py | 32 +-- tests/regression_tests/cmfd_feed_2g/test.py | 10 +- .../cmfd_feed_expanding_window/test.py | 8 +- tests/regression_tests/cmfd_feed_ng/test.py | 10 +- .../regression_tests/cmfd_feed_ref_d/test.py | 8 +- .../cmfd_feed_rolling_window/test.py | 8 +- tests/regression_tests/cmfd_nofeed/test.py | 8 +- tests/regression_tests/cmfd_restart/test.py | 8 +- 12 files changed, 175 insertions(+), 227 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 7e4f4253fa..2a0385e6dc 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -45,8 +45,6 @@ extern "C" { bool openmc_is_statepoint_batch(); int openmc_legendre_filter_get_order(int32_t index, int* order); int openmc_legendre_filter_set_order(int32_t index, int order); - void openmc_load_cmfd_tallies(const int tally_ids[4], - const int sp_tally_ids[4]); int openmc_load_nuclide(const char* name); int openmc_material_add_nuclide(int32_t index, const char name[], double density); int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n); diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 9884ee0277..88e8f88175 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -49,8 +49,6 @@ _init_linsolver_argtypes = [_array_1d_int, c_int, _array_1d_int, c_int, c_int, _dll.openmc_initialize_linsolver.argtypes = _init_linsolver_argtypes _dll.openmc_initialize_linsolver.restype = None _dll.openmc_is_statepoint_batch.restype = c_bool -_dll.openmc_load_cmfd_tallies.restype = None -_dll.openmc_load_cmfd_tallies.argtypes = [POINTER(c_int), POINTER(c_int)] _dll.openmc_master.restype = c_bool _dll.openmc_next_batch.argtypes = [POINTER(c_int)] _dll.openmc_next_batch.restype = c_int diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 700d1c0bd1..c7dd331559 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -198,8 +198,8 @@ class CMFDRun(object): Batch number at which CMFD tallies should begin accummulating feedback_begin: int Batch number at which CMFD feedback should be turned on - reference_d : bool - Reference diffusion coefficients to fix CMFD parameters to + ref_d : list of floats + List of reference diffusion coefficients to fix CMFD parameters to dhat_reset : bool Indicate whether :math:`\widehat{D}` nonlinear CMFD parameters should be reset to zero before solving CMFD eigenproblem. @@ -262,12 +262,12 @@ class CMFDRun(object): Specifies type of tally window scheme to use to accumulate CMFD tallies. Options are: - * "expanding" - Have an expanding rolling window that doubles in size + * "expanding" - Have an expanding window that doubles in size to give more weight to more recent tallies as more generations are simulated * "rolling" - Have a fixed window size that aggregates tallies from the same number of previous generations tallied - * 'none" - Don't use a windowing scheme so that all tallies from last + * "none" - Don't use a windowing scheme so that all tallies from last time they were reset are used for the CMFD algorithm. window_size : int @@ -827,6 +827,14 @@ class CMFDRun(object): self._write_cmfd_timing_stats() def statepoint_write(self, filename=None): + """Write all simulation parameters to statepoint + + Parameters + ---------- + filename : str + Filename of statepoint + + """ if filename is None: batch_str_len = len(str(openmc.capi.settings.batches)) batch_str = str(openmc.capi.current_batch()).zfill(batch_str_len) @@ -840,13 +848,21 @@ class CMFDRun(object): self._write_cmfd_statepoint(filename) def _write_cmfd_statepoint(self, filename): + """Append all CNFD simulation parameters to existing statepoint + + Parameters + ---------- + filename : str + Filename of statepoint + + """ if openmc.capi.master(): - with h5py.File(filename, 'a') as h5f: - if 'cmfd' not in h5f: + with h5py.File(filename, 'a') as f: + if 'cmfd' not in f: if openmc.capi.settings.verbosity >= 5: print(' Writing CMFD data to {}...'.format(filename)) sys.stdout.flush() - cmfd_group = h5f.create_group("cmfd") + cmfd_group = f.create_group("cmfd") cmfd_group.attrs['cmfd_on'] = self._cmfd_on cmfd_group.attrs['feedback'] = self._feedback cmfd_group.attrs['feedback_begin'] = self._feedback_begin @@ -900,7 +916,6 @@ class CMFDRun(object): temp_loss = sparse.csr_matrix((temp_data, (loss_row, loss_col)), shape=(n, n)) - # Pass coremap as 1-d array of 32-bit integers coremap = np.swapaxes(self._coremap, 0, 2).flatten().astype(np.int32) @@ -954,12 +969,9 @@ class CMFDRun(object): self._set_coremap() # Extract spatial and energy indices - nx = self._indices[0] - ny = self._indices[1] - nz = self._indices[2] - ng = self._indices[3] + nx, ny, nz, ng = self._indices - # Allocate parameters that need to stored for rolling window + # Allocate parameters that need to stored for tally window self._openmc_src_rate = np.zeros((nx, ny, nz, ng, 0)) self._flux_rate = np.zeros((nx, ny, nz, ng, 0)) self._total_rate = np.zeros((nx, ny, nz, ng, 0)) @@ -1038,60 +1050,68 @@ class CMFDRun(object): # Create tally objects self._create_cmfd_tally() - def _reset_cmfd(self, sp_filepath): - with h5py.File(sp_filepath, 'r') as h5f: - if 'cmfd' not in h5f: + def _reset_cmfd(self, filename): + """Reset all CMFD parameters from statepoint + + Parameters + ---------- + filename : str + Filename of statepoint to read from + + """ + with h5py.File(filename, 'r') as f: + if 'cmfd' not in f: raise OpenMCError('Could not find CMFD parameters in ', - 'file {}'.format(sp_filepath)) + 'file {}'.format(filename)) else: # Overwrite CMFD values from statepoint if (openmc.capi.master() and openmc.capi.settings.verbosity >= 5): - print(' Loading CMFD data from {}...'.format(sp_filepath)) + print(' Loading CMFD data from {}...'.format(filename)) sys.stdout.flush() - self._cmfd_on = h5f['cmfd'].attrs['cmfd_on'] - self._feedback = h5f['cmfd'].attrs['feedback'] - self._feedback_begin = h5f['cmfd'].attrs['feedback_begin'] - self._tally_begin = h5f['cmfd'].attrs['tally_begin'] - self._time_cmfd = h5f['cmfd'].attrs['time_cmfd'] - self._time_cmfdbuild = h5f['cmfd'].attrs['time_cmfdbuild'] - self._time_cmfdsolve = h5f['cmfd'].attrs['time_cmfdsolve'] - self._window_size = h5f['cmfd'].attrs['window_size'] - self._window_type = h5f['cmfd'].attrs['window_type'] - self._k_cmfd = list(h5f['cmfd']['k_cmfd']) - self._dom = list(h5f['cmfd']['dom']) - self._src_cmp = list(h5f['cmfd']['src_cmp']) - self._balance = list(h5f['cmfd']['balance']) - self._entropy = list(h5f['cmfd']['entropy']) - self._reset = list(h5f['cmfd']['reset']) - self._albedo = h5f['cmfd']['albedo'][:] - self._coremap = h5f['cmfd']['coremap'][:] - self._egrid = h5f['cmfd']['egrid'][:] - self._indices = h5f['cmfd']['indices'][:] - self._current_rate = h5f['cmfd']['current_rate'][:] - self._flux_rate = h5f['cmfd']['flux_rate'][:] - self._nfiss_rate = h5f['cmfd']['nfiss_rate'][:] - self._openmc_src_rate = h5f['cmfd']['openmc_src_rate'][:] - self._p1scatt_rate = h5f['cmfd']['p1scatt_rate'][:] - self._scatt_rate = h5f['cmfd']['scatt_rate'][:] - self._total_rate = h5f['cmfd']['total_rate'][:] + cmfd_group = f['cmfd'] + self._cmfd_on = cmfd_group.attrs['cmfd_on'] + self._feedback = cmfd_group.attrs['feedback'] + self._feedback_begin = cmfd_group.attrs['feedback_begin'] + self._tally_begin = cmfd_group.attrs['tally_begin'] + self._time_cmfd = cmfd_group.attrs['time_cmfd'] + self._time_cmfdbuild = cmfd_group.attrs['time_cmfdbuild'] + self._time_cmfdsolve = cmfd_group.attrs['time_cmfdsolve'] + self._window_size = cmfd_group.attrs['window_size'] + self._window_type = cmfd_group.attrs['window_type'] + self._k_cmfd = list(cmfd_group['k_cmfd']) + self._dom = list(cmfd_group['dom']) + self._src_cmp = list(cmfd_group['src_cmp']) + self._balance = list(cmfd_group['balance']) + self._entropy = list(cmfd_group['entropy']) + self._reset = list(cmfd_group['reset']) + self._albedo = cmfd_group['albedo'][()] + self._coremap = cmfd_group['coremap'][()] + self._egrid = cmfd_group['egrid'][()] + self._indices = cmfd_group['indices'][()] + self._current_rate = cmfd_group['current_rate'][()] + self._flux_rate = cmfd_group['flux_rate'][()] + self._nfiss_rate = cmfd_group['nfiss_rate'][()] + self._openmc_src_rate = cmfd_group['openmc_src_rate'][()] + self._p1scatt_rate = cmfd_group['p1scatt_rate'][()] + self._scatt_rate = cmfd_group['scatt_rate'][()] + self._total_rate = cmfd_group['total_rate'][()] # Overwrite CMFD mesh properties - cmfd_mesh_name = 'mesh ' + str(h5f['cmfd'].attrs['mesh_id']) - cmfd_mesh = h5f['tallies']['meshes'][cmfd_mesh_name] - self._mesh.dimension = cmfd_mesh['dimension'][:] - self._mesh.lower_left = cmfd_mesh['lower_left'][:] - self._mesh.upper_right = cmfd_mesh['upper_right'][:] - self._mesh.width = cmfd_mesh['width'][:] + cmfd_mesh_name = 'mesh ' + str(cmfd_group.attrs['mesh_id']) + cmfd_mesh = f['tallies']['meshes'][cmfd_mesh_name] + self._mesh.dimension = cmfd_mesh['dimension'][()] + self._mesh.lower_left = cmfd_mesh['lower_left'][()] + self._mesh.upper_right = cmfd_mesh['upper_right'][()] + self._mesh.width = cmfd_mesh['width'][()] # Store tally ids from statepoint run - sp_tally_ids = list(h5f['cmfd']['tally_ids']) + sp_tally_ids = list(cmfd_group['tally_ids']) # Set CMFD variables not in statepoint file default_egrid = np.array([_ENERGY_MIN_NEUTRON, _ENERGY_MAX_NEUTRON]) self._energy_filters = not np.array_equal(self._egrid, default_egrid) self._n_resets = len(self._reset) - openmc.capi.settings.run_CE = True self._mat_dim = np.max(self._coremap) + 1 self._reset_every = (self._window_type == 'expanding' or self._window_type == 'rolling') @@ -1099,22 +1119,14 @@ class CMFDRun(object): # Recreate CMFD tallies in memory self._create_cmfd_tally() - # Restore tally results and n_realizations from statepoint data - arg1 = (c_int*4)(*self._tally_ids) - arg2 = (c_int*4)(*sp_tally_ids) - openmc.capi._dll.openmc_load_cmfd_tallies(arg1, arg2) - def _allocate_cmfd(self): """Allocates all numpy arrays and lists used in CMFD algorithm""" # Extract spatial and energy indices - nx = self._indices[0] - ny = self._indices[1] - nz = self._indices[2] - ng = self._indices[3] + nx, ny, nz, ng = self._indices # Allocate dimensions for each mesh cell self._hxyz = np.zeros((nx, ny, nz, 3)) - self._hxyz[:,:,:,:] = openmc.capi.meshes[self._mesh_id].width + self._hxyz[:] = openmc.capi.meshes[self._mesh_id].width # Allocate flux, cross sections and diffusion coefficient self._flux = np.zeros((nx, ny, nz, ng)) @@ -1129,7 +1141,7 @@ class CMFDRun(object): self._dhat = np.zeros((nx, ny, nz, ng, 6)) # Set reference diffusion parameters - if len(self._ref_d) > 0: + if self._ref_d: self._set_reference_params = True # Check length of reference diffusion parameters equal to number of # energy groups @@ -1139,7 +1151,7 @@ class CMFDRun(object): def _set_tally_window(self): """Sets parameters to handle different tally window options""" - # Set parameters for rolling window + # Set parameters for expanding window if self._window_type == 'expanding': self._reset_every = True self._window_size = 1 @@ -1216,7 +1228,7 @@ class CMFDRun(object): """Configures CMFD object for a CMFD eigenvalue calculation """ - # Calculate all cross sections based on rolling window averages + # Calculate all cross sections based on tally window averages self._compute_xs() # Compute effective downscatter cross section @@ -1360,10 +1372,7 @@ class CMFDRun(object): """ # Extract number of groups and number of accelerated regions - nx = self._indices[0] - ny = self._indices[1] - nz = self._indices[2] - ng = self._indices[3] + nx, ny, nz, ng = self._indices n = self._mat_dim # Compute cmfd_src in a vecotorized manner by phi to the spatial @@ -1426,10 +1435,7 @@ class CMFDRun(object): if new_weights: # Get spatial dimensions and energy groups - nx = self._indices[0] - ny = self._indices[1] - nz = self._indices[2] - ng = self._indices[3] + nx, ny, nz, ng = self._indices # Count bank site in mesh and reverse due to egrid structured outside = self._count_bank_sites() @@ -1459,11 +1465,10 @@ class CMFDRun(object): # Compute weight factors div_condition = np.logical_and(sourcecounts > 0, self._cmfd_src > 0) - with np.errstate(divide='ignore', invalid='ignore'): - self._weightfactors = (np.divide(self._cmfd_src * norm, - sourcecounts, where=div_condition, - out=np.ones_like(self._cmfd_src), - dtype=np.float32)) + self._weightfactors = (np.divide(self._cmfd_src * norm, + sourcecounts, where=div_condition, + out=np.ones_like(self._cmfd_src), + dtype=np.float32)) if (not self._feedback or openmc.capi.current_batch() < self._feedback_begin): @@ -1850,9 +1855,8 @@ class CMFDRun(object): kerr = abs(k_o - k_n) / k_n # Calculate max error in source - with np.errstate(divide='ignore', invalid='ignore'): - serr = np.sqrt(np.sum(np.where(s_n > 0, ((s_n-s_o) / s_n)**2, 0)) - / len(s_n)) + serr = np.sqrt(np.sum(np.where(s_n > 0, ((s_n-s_o) / s_n)**2, 0)) + / len(s_n)) # Check for convergence iconv = kerr < self._cmfd_ktol and serr < self._stol @@ -1888,9 +1892,7 @@ class CMFDRun(object): # Reshape coremap to three dimensional array # Indices of coremap in user input switched in x and z axes - nx = self._indices[0] - ny = self._indices[1] - nz = self._indices[2] + nx, ny, nz = self._indices[:3] self._coremap = self._coremap.reshape(nz, ny, nx) self._coremap = np.swapaxes(self._coremap, 0, 2) @@ -1900,7 +1902,7 @@ class CMFDRun(object): a tally window scheme """ - # Update window size for rolling window if necessary + # Update window size for expanding window if necessary num_cmfd_batches = openmc.capi.current_batch() - self._tally_begin + 1 if (self._window_type == 'expanding' and num_cmfd_batches == self._window_size * 2): @@ -1909,20 +1911,17 @@ class CMFDRun(object): # Discard tallies from oldest batch if window limit reached tally_windows = self._flux_rate.shape[-1] + 1 if tally_windows > self._window_size: - self._flux_rate = self._flux_rate[:,:,:,:,1:] - self._total_rate = self._total_rate[:,:,:,:,1:] - self._p1scatt_rate = self._p1scatt_rate[:,:,:,:,1:] - self._scatt_rate = self._scatt_rate[:,:,:,:,:,1:] - self._nfiss_rate = self._nfiss_rate[:,:,:,:,:,1:] - self._current_rate = self._current_rate[:,:,:,:,:,1:] - self._openmc_src_rate = self._openmc_src_rate[:,:,:,:,1:] + self._flux_rate = self._flux_rate[...,1:] + self._total_rate = self._total_rate[...,1:] + self._p1scatt_rate = self._p1scatt_rate[...,1:] + self._scatt_rate = self._scatt_rate[...,1:] + self._nfiss_rate = self._nfiss_rate[...,1:] + self._current_rate = self._current_rate[...,1:] + self._openmc_src_rate = self._openmc_src_rate[...,1:] tally_windows -= 1 # Extract spatial and energy indices - nx = self._indices[0] - ny = self._indices[1] - nz = self._indices[2] - ng = self._indices[3] + nx, ny, nz, ng = self._indices # Get tallies in-memory tallies = openmc.capi.tallies @@ -1991,10 +1990,9 @@ class CMFDRun(object): # Compute total xs as aggregate of banked total_rate over tally window # divided by flux - with np.errstate(divide='ignore', invalid='ignore'): - self._totalxs = np.divide(np.sum(self._total_rate, axis=4), - self._flux, where=self._flux > 0, - out=np.zeros_like(self._totalxs)) + self._totalxs = np.divide(np.sum(self._total_rate, axis=4), + self._flux, where=self._flux > 0, + out=np.zeros_like(self._totalxs)) # Get scattering rr from CMFD tally 1 # flux is repeated to account for extra dimensionality of scattering xs @@ -2023,10 +2021,9 @@ class CMFDRun(object): # window divided by flux. Flux dimensionality increased to account for # extra dimensionality of scattering xs extended_flux = self._flux[:,:,:,:,np.newaxis] - with np.errstate(divide='ignore', invalid='ignore'): - self._scattxs = np.divide(np.sum(self._scatt_rate, axis=5), - extended_flux, where=extended_flux > 0, - out=np.zeros_like(self._scattxs)) + self._scattxs = np.divide(np.sum(self._scatt_rate, axis=5), + extended_flux, where=extended_flux > 0, + out=np.zeros_like(self._scattxs)) # Get nu-fission rr from CMFD tally 1 nfissrr = tallies[tally_id].results[:,1,1] @@ -2049,10 +2046,9 @@ class CMFDRun(object): # Compute nu-fission xs as aggregate of banked nfiss_rate over tally # window divided by flux. Flux dimensionality increased to account for # extra dimensionality of nu-fission xs - with np.errstate(divide='ignore', invalid='ignore'): - self._nfissxs = np.divide(np.sum(self._nfiss_rate, axis=5), - extended_flux, where=extended_flux > 0, - out=np.zeros_like(self._nfissxs)) + self._nfissxs = np.divide(np.sum(self._nfiss_rate, axis=5), + extended_flux, where=extended_flux > 0, + out=np.zeros_like(self._nfissxs)) # Openmc source distribution is sum of nu-fission rr in incoming # energies @@ -2121,10 +2117,9 @@ class CMFDRun(object): # Compute p1-scatter xs as aggregate of banked p1scatt_rate over tally # window divided by flux - with np.errstate(divide='ignore', invalid='ignore'): - self._p1scattxs = np.divide(np.sum(self._p1scatt_rate, axis=4), - self._flux, where=self._flux > 0, - out=np.zeros_like(self._p1scattxs)) + self._p1scattxs = np.divide(np.sum(self._p1scatt_rate, axis=4), + self._flux, where=self._flux > 0, + out=np.zeros_like(self._p1scattxs)) if self._set_reference_params: # Set diffusion coefficients based on reference value @@ -2162,10 +2157,9 @@ class CMFDRun(object): siga2 = sigt2 - sigs22 - sigs21 # Compute effective downscatter XS - with np.errstate(divide='ignore', invalid='ignore'): - sigs12_eff = sigs12 - sigs21 * np.divide(flux2, flux1, - where=flux1 > 0, - out=np.zeros_like(flux2)) + sigs12_eff = sigs12 - sigs21 * np.divide(flux2, flux1, + where=flux1 > 0, + out=np.zeros_like(flux2)) # Recompute total cross sections and record self._totalxs[:,:,:,0] = siga1 + sigs11 + sigs12_eff @@ -2232,9 +2226,7 @@ class CMFDRun(object): """ # Extract spatial indices - nx = self._indices[0] - ny = self._indices[1] - nz = self._indices[2] + nx, ny, nz = self._indices[:3] # Logical for determining whether region of interest is accelerated # region @@ -2607,10 +2599,9 @@ class CMFDRun(object): # a cell borders a reflector region on the left current_in_left = self._current[:,:,:,_CURRENTS['in_left'],:] current_out_left = self._current[:,:,:,_CURRENTS['out_left'],:] - with np.errstate(divide='ignore', invalid='ignore'): - ref_albedo = np.divide(current_in_left, current_out_left, - where=current_out_left > 1.0e-10, - out=np.ones_like(current_out_left)) + ref_albedo = np.divide(current_in_left, current_out_left, + where=current_out_left > 1.0e-10, + out=np.ones_like(current_out_left)) # Diffusion coefficient of neighbor to left neig_dc = np.roll(self._diffcof, 1, axis=0) @@ -2637,10 +2628,9 @@ class CMFDRun(object): # a cell borders a reflector region on the right current_in_right = self._current[:,:,:,_CURRENTS['in_right'],:] current_out_right = self._current[:,:,:,_CURRENTS['out_right'],:] - with np.errstate(divide='ignore', invalid='ignore'): - ref_albedo = np.divide(current_in_right, current_out_right, - where=current_out_right > 1.0e-10, - out=np.ones_like(current_out_right)) + ref_albedo = np.divide(current_in_right, current_out_right, + where=current_out_right > 1.0e-10, + out=np.ones_like(current_out_right)) # Diffusion coefficient of neighbor to right neig_dc = np.roll(self._diffcof, -1, axis=0) @@ -2667,10 +2657,9 @@ class CMFDRun(object): # a cell borders a reflector region on the back current_in_back = self._current[:,:,:,_CURRENTS['in_back'],:] current_out_back = self._current[:,:,:,_CURRENTS['out_back'],:] - with np.errstate(divide='ignore', invalid='ignore'): - ref_albedo = np.divide(current_in_back, current_out_back, - where=current_out_back > 1.0e-10, - out=np.ones_like(current_out_back)) + ref_albedo = np.divide(current_in_back, current_out_back, + where=current_out_back > 1.0e-10, + out=np.ones_like(current_out_back)) # Diffusion coefficient of neighbor to back neig_dc = np.roll(self._diffcof, 1, axis=1) @@ -2697,10 +2686,9 @@ class CMFDRun(object): # a cell borders a reflector region in the front current_in_front = self._current[:,:,:,_CURRENTS['in_front'],:] current_out_front = self._current[:,:,:,_CURRENTS['out_front'],:] - with np.errstate(divide='ignore', invalid='ignore'): - ref_albedo = np.divide(current_in_front, current_out_front, - where=current_out_front > 1.0e-10, - out=np.ones_like(current_out_front)) + ref_albedo = np.divide(current_in_front, current_out_front, + where=current_out_front > 1.0e-10, + out=np.ones_like(current_out_front)) # Diffusion coefficient of neighbor to front neig_dc = np.roll(self._diffcof, -1, axis=1) @@ -2727,10 +2715,9 @@ class CMFDRun(object): # a cell borders a reflector region on the bottom current_in_bottom = self._current[:,:,:,_CURRENTS['in_bottom'],:] current_out_bottom = self._current[:,:,:,_CURRENTS['out_bottom'],:] - with np.errstate(divide='ignore', invalid='ignore'): - ref_albedo = np.divide(current_in_bottom, current_out_bottom, - where=current_out_bottom > 1.0e-10, - out=np.ones_like(current_out_bottom)) + ref_albedo = np.divide(current_in_bottom, current_out_bottom, + where=current_out_bottom > 1.0e-10, + out=np.ones_like(current_out_bottom)) # Diffusion coefficient of neighbor to bottom neig_dc = np.roll(self._diffcof, 1, axis=2) @@ -2757,10 +2744,9 @@ class CMFDRun(object): # a cell borders a reflector region on the top current_in_top = self._current[:,:,:,_CURRENTS['in_top'],:] current_out_top = self._current[:,:,:,_CURRENTS['out_top'],:] - with np.errstate(divide='ignore', invalid='ignore'): - ref_albedo = np.divide(current_in_top, current_out_top, - where=current_out_top > 1.0e-10, - out=np.ones_like(current_out_top)) + ref_albedo = np.divide(current_in_top, current_out_top, + where=current_out_top > 1.0e-10, + out=np.ones_like(current_out_top)) # Diffusion coefficient of neighbor to top neig_dc = np.roll(self._diffcof, -1, axis=2) diff --git a/src/state_point.cpp b/src/state_point.cpp index 485b95ca46..7516b0d485 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -299,40 +299,6 @@ openmc_statepoint_write(const char* filename, bool* write_source) return 0; } -extern "C" void -openmc_load_cmfd_tallies(const int* tally_ids, const int* sp_tally_ids) -{ - hid_t file_id = file_open(settings::path_statepoint.c_str(), 'r', true); - -#ifdef PHDF5 - if (true) { -#else - if (mpi::master) { -#endif - - hid_t tallies_group = open_group(file_id, "tallies"); - - int n_tallies = 4; - for (int i = 0; i < n_tallies; i++) { - std::string name = "tally " + std::to_string(sp_tally_ids[i]); - hid_t tally_group = open_group(tallies_group, name.c_str()); - // Get tally from tally_ids - auto& tally = model::tallies[model::tally_map[tally_ids[i]]]; - tally->init_results(); - auto& results = tally->results_; - read_tally_results(tally_group, results.shape()[0], - results.shape()[1], results.data()); - read_dataset(tally_group, "n_realizations", tally->n_realizations_); - close_group(tally_group); - } - - close_group(tallies_group); - } - // Close file - file_close(file_id); -} - - void restart_set_keff() { if (simulation::restart_batch > settings::n_inactive) { diff --git a/tests/regression_tests/cmfd_feed/test.py b/tests/regression_tests/cmfd_feed/test.py index d45f316c87..906c631ecc 100644 --- a/tests/regression_tests/cmfd_feed/test.py +++ b/tests/regression_tests/cmfd_feed/test.py @@ -15,10 +15,10 @@ def test_cmfd_physical_adjoint(): """ # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() @@ -45,10 +45,10 @@ def test_cmfd_math_adjoint(): """ # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() @@ -74,10 +74,10 @@ def test_cmfd_write_matrices(): """ # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() @@ -122,10 +122,10 @@ def test_cmfd_feed(): """Test 1 group CMFD solver with CMFD feedback""" # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() diff --git a/tests/regression_tests/cmfd_feed_2g/test.py b/tests/regression_tests/cmfd_feed_2g/test.py index 095dbf53a8..ba4d21609a 100644 --- a/tests/regression_tests/cmfd_feed_2g/test.py +++ b/tests/regression_tests/cmfd_feed_2g/test.py @@ -7,11 +7,11 @@ def test_cmfd_feed_2g(): """Test 2 group CMFD solver results with CMFD feedback""" # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -1.25984, -1.25984, -1.0 - cmfd_mesh.upper_right = 1.25984, 1.25984, 1.0 - cmfd_mesh.dimension = 2, 2, 1 - cmfd_mesh.energy = [0.0, 0.625, 20000000] - cmfd_mesh.albedo = 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-1.25984, -1.25984, -1.0) + cmfd_mesh.upper_right = (1.25984, 1.25984, 1.0) + cmfd_mesh.dimension = (2, 2, 1) + cmfd_mesh.energy = (0.0, 0.625, 20000000) + cmfd_mesh.albedo = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() diff --git a/tests/regression_tests/cmfd_feed_expanding_window/test.py b/tests/regression_tests/cmfd_feed_expanding_window/test.py index 9a2697dff8..e8671e95c7 100644 --- a/tests/regression_tests/cmfd_feed_expanding_window/test.py +++ b/tests/regression_tests/cmfd_feed_expanding_window/test.py @@ -6,10 +6,10 @@ def test_cmfd_feed_rolling_window(): """Test 1 group CMFD solver with CMFD feedback""" # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() diff --git a/tests/regression_tests/cmfd_feed_ng/test.py b/tests/regression_tests/cmfd_feed_ng/test.py index 266772054f..dce674f905 100644 --- a/tests/regression_tests/cmfd_feed_ng/test.py +++ b/tests/regression_tests/cmfd_feed_ng/test.py @@ -7,11 +7,11 @@ def test_cmfd_feed_ng(): """Test n group CMFD solver with CMFD feedback""" # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -1.25984, -1.25984, -1.0 - cmfd_mesh.upper_right = 1.25984, 1.25984, 1.0 - cmfd_mesh.dimension = 2, 2, 1 - cmfd_mesh.energy = [0.0, 0.625, 5.53080, 20000000] - cmfd_mesh.albedo = 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-1.25984, -1.25984, -1.0) + cmfd_mesh.upper_right = (1.25984, 1.25984, 1.0) + cmfd_mesh.dimension = (2, 2, 1) + cmfd_mesh.energy = (0.0, 0.625, 5.53080, 20000000) + cmfd_mesh.albedo = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() diff --git a/tests/regression_tests/cmfd_feed_ref_d/test.py b/tests/regression_tests/cmfd_feed_ref_d/test.py index 4facabb5f8..d033ab8ddf 100644 --- a/tests/regression_tests/cmfd_feed_ref_d/test.py +++ b/tests/regression_tests/cmfd_feed_ref_d/test.py @@ -6,10 +6,10 @@ def test_cmfd_feed_rolling_window(): """Test 1 group CMFD solver with CMFD feedback""" # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() diff --git a/tests/regression_tests/cmfd_feed_rolling_window/test.py b/tests/regression_tests/cmfd_feed_rolling_window/test.py index 67861be4ac..802b5b7003 100644 --- a/tests/regression_tests/cmfd_feed_rolling_window/test.py +++ b/tests/regression_tests/cmfd_feed_rolling_window/test.py @@ -6,10 +6,10 @@ def test_cmfd_feed_rolling_window(): """Test 1 group CMFD solver with CMFD feedback""" # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() diff --git a/tests/regression_tests/cmfd_nofeed/test.py b/tests/regression_tests/cmfd_nofeed/test.py index f99fcb64bc..7d0895c2fb 100644 --- a/tests/regression_tests/cmfd_nofeed/test.py +++ b/tests/regression_tests/cmfd_nofeed/test.py @@ -7,10 +7,10 @@ def test_cmfd_nofeed(): """Test 1 group CMFD solver without CMFD feedback""" # Initialize and set CMFD mesh cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) # Initialize and run CMFDRun object cmfd_run = cmfd.CMFDRun() diff --git a/tests/regression_tests/cmfd_restart/test.py b/tests/regression_tests/cmfd_restart/test.py index 6f89947f3d..7ed92d024c 100644 --- a/tests/regression_tests/cmfd_restart/test.py +++ b/tests/regression_tests/cmfd_restart/test.py @@ -43,10 +43,10 @@ def test_cmfd_restart(): """Test 1 group CMFD solver with restart run""" # Initialize and set CMFD mesh, create a copy for second run cmfd_mesh = cmfd.CMFDMesh() - cmfd_mesh.lower_left = -10.0, -1.0, -1.0 - cmfd_mesh.upper_right = 10.0, 1.0, 1.0 - cmfd_mesh.dimension = 10, 1, 1 - cmfd_mesh.albedo = 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 + cmfd_mesh.lower_left = (-10.0, -1.0, -1.0) + cmfd_mesh.upper_right = (10.0, 1.0, 1.0) + cmfd_mesh.dimension = (10, 1, 1) + cmfd_mesh.albedo = (0.0, 0.0, 1.0, 1.0, 1.0, 1.0) cmfd_mesh2 = copy.deepcopy(cmfd_mesh) # Initialize and run first CMFDRun object From 0bfbd2c823f2c574fd666f8bacfd77142a1b198f Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 4 Jun 2019 20:13:21 -0400 Subject: [PATCH 079/151] numpy errstate required for serr --- openmc/cmfd.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index c7dd331559..76f6fe3b19 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -1855,8 +1855,9 @@ class CMFDRun(object): kerr = abs(k_o - k_n) / k_n # Calculate max error in source - serr = np.sqrt(np.sum(np.where(s_n > 0, ((s_n-s_o) / s_n)**2, 0)) - / len(s_n)) + with np.errstate(divide='ignore', invalid='ignore'): + serr = np.sqrt(np.sum(np.where(s_n > 0, ((s_n-s_o) / s_n)**2, 0)) + / len(s_n)) # Check for convergence iconv = kerr < self._cmfd_ktol and serr < self._stol From afc23d0cf1631dbe310c92bd49f69903602df8de Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 4 Jun 2019 20:51:48 -0400 Subject: [PATCH 080/151] Update results_true.dat for cmfd_feed_2g and cmfd_feed_ng --- tests/regression_tests/cmfd_feed_2g/results_true.dat | 4 ++-- tests/regression_tests/cmfd_feed_ng/results_true.dat | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/regression_tests/cmfd_feed_2g/results_true.dat b/tests/regression_tests/cmfd_feed_2g/results_true.dat index c8c23eb947..f16d82309f 100644 --- a/tests/regression_tests/cmfd_feed_2g/results_true.dat +++ b/tests/regression_tests/cmfd_feed_2g/results_true.dat @@ -90,7 +90,7 @@ tally 3: 9.818871E+01 4.822799E+02 8.401346E-01 -3.664037E-02 +3.664038E-02 6.130607E+01 1.882837E+02 0.000000E+00 @@ -383,7 +383,7 @@ cmfd balance 1.08402E-03 1.09177E-03 5.45977E-04 -4.45555E-04 +4.45554E-04 4.01147E-04 3.71025E-04 3.57715E-04 diff --git a/tests/regression_tests/cmfd_feed_ng/results_true.dat b/tests/regression_tests/cmfd_feed_ng/results_true.dat index bf04296117..d62fb5b4b6 100644 --- a/tests/regression_tests/cmfd_feed_ng/results_true.dat +++ b/tests/regression_tests/cmfd_feed_ng/results_true.dat @@ -190,7 +190,7 @@ tally 3: 0.000000E+00 0.000000E+00 1.082199E-01 -9.467141E-04 +9.467140E-04 3.168219E-01 6.900253E-03 0.000000E+00 From 970aa6ea9a63d264596463645c17566cc5d26047 Mon Sep 17 00:00:00 2001 From: jingang Date: Wed, 5 Jun 2019 11:26:39 -0400 Subject: [PATCH 081/151] fixed compiling errors with visual studio, mostly VLAs --- include/openmc/error.h | 2 +- include/openmc/hdf5_interface.h | 23 ++++++++++++--- src/cell.cpp | 2 +- src/finalize.cpp | 4 +-- src/hdf5_interface.cpp | 9 ++++-- src/initialize.cpp | 2 +- src/lattice.cpp | 12 ++++---- src/math_functions.cpp | 13 +++++---- src/mesh.cpp | 43 ++++++++++++++-------------- src/mgxs.cpp | 3 +- src/surface.cpp | 1 + src/tallies/filter_legendre.cpp | 4 +-- src/tallies/filter_mesh.cpp | 4 +-- src/tallies/filter_sph_harm.cpp | 8 +++--- src/tallies/filter_sptl_legendre.cpp | 4 +-- src/tallies/filter_zernike.cpp | 8 +++--- 16 files changed, 81 insertions(+), 61 deletions(-) diff --git a/include/openmc/error.h b/include/openmc/error.h index 5f30f0252d..32ed36c8b8 100644 --- a/include/openmc/error.h +++ b/include/openmc/error.h @@ -44,7 +44,7 @@ void fatal_error(const std::stringstream& message) [[noreturn]] inline void fatal_error(const char* message) { - fatal_error({message, std::strlen(message)}); + fatal_error(std::string{message, std::strlen(message)}); } void warning(const std::string& message); diff --git a/include/openmc/hdf5_interface.h b/include/openmc/hdf5_interface.h index 0be98718ce..e52d6c8054 100644 --- a/include/openmc/hdf5_interface.h +++ b/include/openmc/hdf5_interface.h @@ -187,11 +187,12 @@ read_attribute(hid_t obj_id, const char* name, std::string& str) { // Create buffer to read data into auto n = attribute_typesize(obj_id, name); - char buffer[n]; + char* buffer = new char[n]; // Read attribute and set string read_attr_string(obj_id, name, n, buffer); str = std::string{buffer, n}; + delete[] buffer; } // overload for std::vector @@ -203,7 +204,10 @@ read_attribute(hid_t obj_id, const char* name, std::vector& vec) // Allocate a C char array to get strings auto n = attribute_typesize(obj_id, name); - char buffer[m][n]; + char** buffer = new char*[m]; + for (int i = 0; i < m; i++) { + buffer[i] = new char[n]; + } // Read char data in attribute read_attr_string(obj_id, name, n, buffer[0]); @@ -216,7 +220,9 @@ read_attribute(hid_t obj_id, const char* name, std::vector& vec) // Create string based on (char*, size_t) constructor vec.emplace_back(&buffer[i][0], k); + delete[] buffer[i]; } + delete[] buffer; } //============================================================================== @@ -240,7 +246,7 @@ read_dataset(hid_t obj_id, const char* name, std::string& str, bool indep=false) { // Create buffer to read data into auto n = dataset_typesize(obj_id, name); - char buffer[n]; + char* buffer = new char[n]; // Read attribute and set string read_string(obj_id, name, n, buffer, indep); @@ -458,7 +464,10 @@ write_dataset(hid_t obj_id, const char* name, const std::vector& bu } // Copy data into contiguous buffer - char temp[n][m]; + char** temp = new char*[n]; + for (int i = 0; i < n; i++) { + temp[i] = new char[m]; + } std::fill(temp[0], temp[0] + n*m, '\0'); for (int i = 0; i < n; ++i) { std::copy(buffer[i].begin(), buffer[i].end(), temp[i]); @@ -466,6 +475,12 @@ write_dataset(hid_t obj_id, const char* name, const std::vector& bu // Write 2D data write_string(obj_id, 1, dims, m, name, temp[0], false); + + // Free temp array + for (int i = 0; i < n; i++) { + delete[] temp[i]; + } + delete[] temp; } template inline void diff --git a/src/cell.cpp b/src/cell.cpp index 650a18a8a0..157d0d1a2c 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -560,7 +560,7 @@ CSGCell::contains_complex(Position r, Direction u, int32_t on_surface) const { // Make a stack of booleans. We don't know how big it needs to be, but we do // know that rpn.size() is an upper-bound. - bool stack[rpn_.size()]; + std::vector stack(rpn_.size()); int i_stack = -1; for (int32_t token : rpn_) { diff --git a/src/finalize.cpp b/src/finalize.cpp index 34c4b9781b..81d7559b8e 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -117,7 +117,7 @@ int openmc_finalize() data::energy_max = {INFTY, INFTY}; data::energy_min = {0.0, 0.0}; model::root_universe = -1; - openmc_set_seed(DEFAULT_SEED); + openmc::openmc_set_seed(DEFAULT_SEED); // Deallocate arrays free_memory(); @@ -159,6 +159,6 @@ int openmc_hard_reset() simulation::total_gen = 0; // Reset the random number generator state - openmc_set_seed(DEFAULT_SEED); + openmc::openmc_set_seed(DEFAULT_SEED); return 0; } diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 1fb10b7066..729f008914 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -366,10 +366,11 @@ member_names(hid_t group_id, H5O_type_t type) i, nullptr, 0, H5P_DEFAULT); // Read name - char buffer[size]; + char* buffer = new char[size]; H5Lget_name_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, i, buffer, size, H5P_DEFAULT); names.emplace_back(&buffer[0]); + delete[] buffer; } return names; } @@ -404,11 +405,13 @@ object_name(hid_t obj_id) { // Determine size and create buffer size_t size = 1 + H5Iget_name(obj_id, nullptr, 0); - char buffer[size]; + char* buffer = new char[size]; // Read and return name H5Iget_name(obj_id, buffer, size); - return buffer; + std::string str = buffer; + delete[] buffer; + return str; } diff --git a/src/initialize.cpp b/src/initialize.cpp index f8a4f4a39a..fd524a2de9 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -68,7 +68,7 @@ int openmc_init(int argc, char* argv[], const void* intracomm) // Initialize random number generator -- if the user specifies a seed, it // will be re-initialized later - openmc_set_seed(DEFAULT_SEED); + openmc::openmc_set_seed(DEFAULT_SEED); // Read XML input files read_input_xml(); diff --git a/src/lattice.cpp b/src/lattice.cpp index b16fecc6e3..5e679042ad 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -388,7 +388,7 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const hsize_t nx {static_cast(n_cells_[0])}; hsize_t ny {static_cast(n_cells_[1])}; hsize_t nz {static_cast(n_cells_[2])}; - int out[nx*ny*nz]; + std::vector out(nx*ny*nz); for (int m = 0; m < nz; m++) { for (int k = 0; k < ny; k++) { @@ -401,12 +401,12 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const } hsize_t dims[3] {nz, ny, nx}; - write_int(lat_group, 3, dims, "universes", out, false); + write_int(lat_group, 3, dims, "universes", &out[0], false); } else { hsize_t nx {static_cast(n_cells_[0])}; hsize_t ny {static_cast(n_cells_[1])}; - int out[nx*ny]; + std::vector out(nx*ny); for (int k = 0; k < ny; k++) { for (int j = 0; j < nx; j++) { @@ -417,7 +417,7 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const } hsize_t dims[3] {1, ny, nx}; - write_int(lat_group, 3, dims, "universes", out, false); + write_int(lat_group, 3, dims, "universes", &out[0], false); } } @@ -877,7 +877,7 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const hsize_t nx {static_cast(2*n_rings_ - 1)}; hsize_t ny {static_cast(2*n_rings_ - 1)}; hsize_t nz {static_cast(n_axial_)}; - int out[nx*ny*nz]; + std::vector out(nx*ny*nz); for (int m = 0; m < nz; m++) { for (int k = 0; k < ny; k++) { @@ -897,7 +897,7 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const } hsize_t dims[3] {nz, ny, nx}; - write_int(lat_group, 3, dims, "universes", out, false); + write_int(lat_group, 3, dims, "universes", &out[0], false); } //============================================================================== diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 54d0e93441..ec5d64eac7 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -110,12 +110,13 @@ void calc_pn_c(int n, double x, double pnx[]) double evaluate_legendre(int n, const double data[], double x) { - double pnx[n + 1]; + double* pnx = new double[n + 1]; double val = 0.0; calc_pn_c(n, x, pnx); for (int l = 0; l <= n; l++) { val += (l + 0.5) * data[l] * pnx[l]; } + delete[] pnx; return val; } @@ -536,8 +537,8 @@ void calc_zn(int n, double rho, double phi, double zn[]) { double sin_phi = std::sin(phi); double cos_phi = std::cos(phi); - double sin_phi_vec[n + 1]; // Sin[n * phi] - double cos_phi_vec[n + 1]; // Cos[n * phi] + std::vector sin_phi_vec(n + 1); // Sin[n * phi] + std::vector cos_phi_vec(n + 1); // Cos[n * phi] sin_phi_vec[0] = 1.0; cos_phi_vec[0] = 1.0; sin_phi_vec[1] = 2.0 * cos_phi; @@ -554,8 +555,8 @@ void calc_zn(int n, double rho, double phi, double zn[]) { // =========================================================================== // Calculate R_pq(rho) - double zn_mat[n + 1][n + 1]; // Matrix forms of the coefficients which are - // easier to work with + // Matrix forms of the coefficients which are easier to work with + std::vector> zn_mat(n + 1, std::vector(n + 1)); // Fill the main diagonal first (Eq 3.9 in Chong) for (int p = 0; p <= n; p++) { @@ -763,7 +764,7 @@ void broaden_wmp_polynomials(double E, double dopp, int n, double factors[]) void spline(int n, const double x[], const double y[], double z[]) { - double c_new[n-1]; + std::vector c_new(n-1); // Set natural boundary conditions c_new[0] = 0.0; diff --git a/src/mesh.cpp b/src/mesh.cpp index ddfa272a6d..075e58de24 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -181,13 +181,13 @@ int RegularMesh::get_bin(Position r) const } // Determine indices - int ijk[n_dimension_]; + std::vector ijk(n_dimension_); bool in_mesh; - get_indices(r, ijk, &in_mesh); + get_indices(r, &ijk[0], &in_mesh); if (!in_mesh) return -1; // Convert indices to bin - return get_bin_from_indices(ijk); + return get_bin_from_indices(&ijk[0]); } int RegularMesh::get_bin_from_indices(const int* ijk) const @@ -495,11 +495,11 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, // Determine the mesh indices for the starting and ending coords. int n = n_dimension_; - int ijk0[n], ijk1[n]; + std::vector ijk0(n), ijk1(n); bool start_in_mesh; - get_indices(r0, ijk0, &start_in_mesh); + get_indices(r0, &ijk0[0], &start_in_mesh); bool end_in_mesh; - get_indices(r1, ijk1, &end_in_mesh); + get_indices(r1, &ijk1[0], &end_in_mesh); // Reset coordinates and check for a mesh intersection if necessary. if (start_in_mesh) { @@ -509,7 +509,7 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, // The initial coords do not lie in the mesh. Check to see if the particle // eventually intersects the mesh and compute the relevant coords and // indices. - if (!intersects(r0, r1, ijk0)) return; + if (!intersects(r0, r1, &ijk0[0])) return; } r1 = r; @@ -517,17 +517,17 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, // Find which mesh cells are traversed and the length of each traversal. while (true) { - if (std::equal(ijk0, ijk0+n, ijk1)) { + if (ijk0 == ijk1) { // The track ends in this cell. Use the particle end location rather // than the mesh surface and stop iterating. double distance = (r1 - r0).norm(); - bins.push_back(get_bin_from_indices(ijk0)); + bins.push_back(get_bin_from_indices(&ijk0[0])); lengths.push_back(distance / total_distance); break; } // The track exits this cell. Determine the distance to each mesh surface. - double d[n]; + std::vector d(n); for (int k = 0; k < n; ++k) { if (std::fabs(u[k]) < FP_PRECISION) { d[k] = INFTY; @@ -541,9 +541,9 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, } // Pick the closest mesh surface and append this traversal to the output. - auto j = std::min_element(d, d+n) - d; + auto j = std::min_element(d.begin(), d.end()) - d.begin(); double distance = d[j]; - bins.push_back(get_bin_from_indices(ijk0)); + bins.push_back(get_bin_from_indices(&ijk0[0])); lengths.push_back(distance / total_distance); // Translate to the oncoming mesh surface. @@ -582,18 +582,17 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // Determine indices for starting and ending location. int n = n_dimension_; - int ijk0[n], ijk1[n]; + std::vector ijk0(n), ijk1(n); bool start_in_mesh; - get_indices(r0, ijk0, &start_in_mesh); + get_indices(r0, &ijk0[0], &start_in_mesh); bool end_in_mesh; - get_indices(r1, ijk1, &end_in_mesh); + get_indices(r1, &ijk1[0], &end_in_mesh); // Check if the track intersects any part of the mesh. if (!start_in_mesh) { Position r0_copy = r0; - int ijk0_copy[n]; - for (int i = 0; i < n; ++i) ijk0_copy[i] = ijk0[i]; - if (!intersects(r0_copy, r1, ijk0_copy)) return; + std::vector ijk0_copy(ijk0); + if (!intersects(r0_copy, r1, &ijk0_copy[0])) return; } // ======================================================================== @@ -651,7 +650,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // Outward current on i max surface if (in_mesh) { int i_surf = 4*i + 3; - int i_mesh = get_bin_from_indices(ijk0); + int i_mesh = get_bin_from_indices(&ijk0[0]); int i_bin = 4*n*i_mesh + i_surf - 1; bins.push_back(i_bin); @@ -672,7 +671,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // i min surface if (in_mesh) { int i_surf = 4*i + 2; - int i_mesh = get_bin_from_indices(ijk0); + int i_mesh = get_bin_from_indices(&ijk0[0]); int i_bin = 4*n*i_mesh + i_surf - 1; bins.push_back(i_bin); @@ -684,7 +683,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // Outward current on i min surface if (in_mesh) { int i_surf = 4*i + 1; - int i_mesh = get_bin_from_indices(ijk0); + int i_mesh = get_bin_from_indices(&ijk0[0]); int i_bin = 4*n*i_mesh + i_surf - 1; bins.push_back(i_bin); @@ -705,7 +704,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // i max surface if (in_mesh) { int i_surf = 4*i + 4; - int i_mesh = get_bin_from_indices(ijk0); + int i_mesh = get_bin_from_indices(&ijk0[0]); int i_bin = 4*n*i_mesh + i_surf - 1; bins.push_back(i_bin); diff --git a/src/mgxs.cpp b/src/mgxs.cpp index efe562fa28..3aac41f35b 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -96,7 +96,7 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, const std::vector& temperature, // Determine the available temperatures hid_t kT_group = open_group(xs_id, "kTs"); int num_temps = get_num_datasets(kT_group); - char* dset_names[num_temps]; + char** dset_names = new char*[num_temps]; for (int i = 0; i < num_temps; i++) { dset_names[i] = new char[151]; } @@ -111,6 +111,7 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, const std::vector& temperature, // Done with dset_names, so delete it delete[] dset_names[i]; } + delete[] dset_names; std::sort(available_temps.begin(), available_temps.end()); // If only one temperature is available, lets just use nearest temperature diff --git a/src/surface.cpp b/src/surface.cpp index f30b444310..9361fa4241 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "openmc/error.h" #include "openmc/hdf5_interface.h" diff --git a/src/tallies/filter_legendre.cpp b/src/tallies/filter_legendre.cpp index 1d1d440b28..0446cb2955 100644 --- a/src/tallies/filter_legendre.cpp +++ b/src/tallies/filter_legendre.cpp @@ -18,8 +18,8 @@ void LegendreFilter::get_all_bins(const Particle* p, int estimator, FilterMatch& match) const { - double wgt[n_bins_]; - calc_pn_c(order_, p->mu_, wgt); + std::vector wgt(n_bins_); + calc_pn_c(order_, p->mu_, &wgt[0]); for (int i = 0; i < n_bins_; i++) { match.bins_.push_back(i); match.weights_.push_back(wgt[i]); diff --git a/src/tallies/filter_mesh.cpp b/src/tallies/filter_mesh.cpp index f69a966523..c62578b47e 100644 --- a/src/tallies/filter_mesh.cpp +++ b/src/tallies/filter_mesh.cpp @@ -58,8 +58,8 @@ MeshFilter::text_label(int bin) const auto& mesh = *model::meshes[mesh_]; int n_dim = mesh.n_dimension_; - int ijk[n_dim]; - mesh.get_indices_from_bin(bin, ijk); + std::vector ijk(n_dim); + mesh.get_indices_from_bin(bin, &ijk[0]); std::stringstream out; out << "Mesh Index (" << ijk[0]; diff --git a/src/tallies/filter_sph_harm.cpp b/src/tallies/filter_sph_harm.cpp index 1e97e64c1b..bbb34b16cc 100644 --- a/src/tallies/filter_sph_harm.cpp +++ b/src/tallies/filter_sph_harm.cpp @@ -35,16 +35,16 @@ SphericalHarmonicsFilter::get_all_bins(const Particle* p, int estimator, FilterMatch& match) const { // Determine cosine term for scatter expansion if necessary - double wgt[order_ + 1]; + std::vector wgt(order_ + 1); if (cosine_ == SphericalHarmonicsCosine::scatter) { - calc_pn_c(order_, p->mu_, wgt); + calc_pn_c(order_, p->mu_, &wgt[0]); } else { for (int i = 0; i < order_ + 1; i++) wgt[i] = 1; } // Find the Rn,m values - double rn[n_bins_]; - calc_rn(order_, p->u_last_, rn); + std::vector rn(n_bins_); + calc_rn(order_, p->u_last_, &rn[0]); int j = 0; for (int n = 0; n < order_ + 1; n++) { diff --git a/src/tallies/filter_sptl_legendre.cpp b/src/tallies/filter_sptl_legendre.cpp index 616dbbbd4d..6727bfed10 100644 --- a/src/tallies/filter_sptl_legendre.cpp +++ b/src/tallies/filter_sptl_legendre.cpp @@ -50,8 +50,8 @@ SpatialLegendreFilter::get_all_bins(const Particle* p, int estimator, double x_norm = 2.0*(x - min_) / (max_ - min_) - 1.0; // Compute and return the Legendre weights. - double wgt[order_ + 1]; - calc_pn_c(order_, x_norm, wgt); + std::vector wgt(order_ + 1); + calc_pn_c(order_, x_norm, &wgt[0]); for (int i = 0; i < order_ + 1; i++) { match.bins_.push_back(i); match.weights_.push_back(wgt[i]); diff --git a/src/tallies/filter_zernike.cpp b/src/tallies/filter_zernike.cpp index 9f66a68f98..9ab7b4bcda 100644 --- a/src/tallies/filter_zernike.cpp +++ b/src/tallies/filter_zernike.cpp @@ -36,8 +36,8 @@ ZernikeFilter::get_all_bins(const Particle* p, int estimator, if (r <= 1.0) { // Compute and return the Zernike weights. - double zn[n_bins_]; - calc_zn(order_, r, theta, zn); + std::vector zn(n_bins_); + calc_zn(order_, r, theta, &zn[0]); for (int i = 0; i < n_bins_; i++) { match.bins_.push_back(i); match.weights_.push_back(zn[i]); @@ -93,8 +93,8 @@ ZernikeRadialFilter::get_all_bins(const Particle* p, int estimator, if (r <= 1.0) { // Compute and return the Zernike weights. - double zn[n_bins_]; - calc_zn_rad(order_, r, zn); + std::vector zn(n_bins_); + calc_zn_rad(order_, r, &zn[0]); for (int i = 0; i < n_bins_; i++) { match.bins_.push_back(i); match.weights_.push_back(zn[i]); From 12b5f337d45e533c9eb6a0e6cfeab01d0a529508 Mon Sep 17 00:00:00 2001 From: jingang Date: Wed, 5 Jun 2019 15:19:24 -0400 Subject: [PATCH 082/151] HDF5 linking errors --- CMakeLists.txt | 6 ++++++ src/hdf5_interface.cpp | 2 ++ 2 files changed, 8 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index fea014f12d..7448713663 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,6 +272,12 @@ if(GIT_SHA1_SUCCESS EQUAL 0) target_compile_definitions(libopenmc PRIVATE -DGIT_SHA1="${GIT_SHA1}") endif() +# To use the shared HDF5 libraries on Windows with Visual Studio, +# the H5_BUILT_AS_DYNAMIC_LIB compile definition must be specified. +if(MSVC) + target_compile_definitions(libopenmc PRIVATE -DH5_BUILT_AS_DYNAMIC_LIB) +endif() + # target_link_libraries treats any arguments starting with - but not -l as # linker flags. Thus, we can pass both linker flags and libraries together. target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES} diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 729f008914..b1a90cb8f7 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -792,6 +792,8 @@ const hid_t H5TypeMap::type_id = H5T_NATIVE_INT; template<> const hid_t H5TypeMap::type_id = H5T_NATIVE_ULONG; template<> +const hid_t H5TypeMap::type_id = H5T_NATIVE_ULLONG; +template<> const hid_t H5TypeMap::type_id = H5T_NATIVE_UINT; template<> const hid_t H5TypeMap::type_id = H5T_NATIVE_INT64; From 97b8b6bef71b812c7e0376ef91a776ca10d09b96 Mon Sep 17 00:00:00 2001 From: jingang Date: Wed, 5 Jun 2019 16:22:23 -0400 Subject: [PATCH 083/151] read windows cross section libaries --- src/cross_sections.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cross_sections.cpp b/src/cross_sections.cpp index 5d8d18c219..8ea59c1f08 100644 --- a/src/cross_sections.cpp +++ b/src/cross_sections.cpp @@ -402,6 +402,10 @@ void read_ce_cross_sections_xml() // If no directory is listed in cross_sections.xml, by default select the // directory in which the cross_sections.xml file resides auto pos = filename.rfind("/"); + if (pos == std::string::npos) { + // no '/' found, probably a Windows directory + pos = filename.rfind("\\"); + } directory = filename.substr(0, pos); } From e5b108be7a14a1bc6a42034dc5e0bd801a4219fe Mon Sep 17 00:00:00 2001 From: jingang Date: Thu, 6 Jun 2019 10:36:15 -0400 Subject: [PATCH 084/151] fix write 2D char arrays in HDF5 --- include/openmc/hdf5_interface.h | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/include/openmc/hdf5_interface.h b/include/openmc/hdf5_interface.h index e52d6c8054..c87abd84a1 100644 --- a/include/openmc/hdf5_interface.h +++ b/include/openmc/hdf5_interface.h @@ -464,22 +464,16 @@ write_dataset(hid_t obj_id, const char* name, const std::vector& bu } // Copy data into contiguous buffer - char** temp = new char*[n]; - for (int i = 0; i < n; i++) { - temp[i] = new char[m]; - } - std::fill(temp[0], temp[0] + n*m, '\0'); + char* temp = new char[n*m]; + std::fill(temp, temp + n*m, '\0'); for (int i = 0; i < n; ++i) { - std::copy(buffer[i].begin(), buffer[i].end(), temp[i]); + std::copy(buffer[i].begin(), buffer[i].end(), temp + i*m); } // Write 2D data - write_string(obj_id, 1, dims, m, name, temp[0], false); + write_string(obj_id, 1, dims, m, name, temp, false); // Free temp array - for (int i = 0; i < n; i++) { - delete[] temp[i]; - } delete[] temp; } From 950c684e6ce92b9c7b39cf0bdab548813ba21b4e Mon Sep 17 00:00:00 2001 From: jingang Date: Thu, 6 Jun 2019 10:46:01 -0400 Subject: [PATCH 085/151] Set libopenmc as static library in CMake for MSVC: Built openmc.exe successfully with visual studio --- CMakeLists.txt | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7448713663..70ff63e978 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,7 +159,7 @@ target_compile_options(faddeeva PRIVATE ${cxxflags}) # libopenmc #=============================================================================== -add_library(libopenmc SHARED +list(APPEND libopenmc_SOURCES src/bank.cpp src/bremsstrahlung.cpp src/dagmc.cpp @@ -246,6 +246,18 @@ add_library(libopenmc SHARED src/xml_interface.cpp src/xsdata.cpp) +# For Visual Studio compilers +if(MSVC) + # Use static library (otherwise explicit symbol portings are needed) + add_library(libopenmc STATIC ${libopenmc_SOURCES}) + + # To use the shared HDF5 libraries on Windows, the H5_BUILT_AS_DYNAMIC_LIB + # compile definition must be specified. + target_compile_definitions(libopenmc PRIVATE -DH5_BUILT_AS_DYNAMIC_LIB) +else() + add_library(libopenmc SHARED ${libopenmc_SOURCES}) +endif() + set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc) @@ -272,12 +284,6 @@ if(GIT_SHA1_SUCCESS EQUAL 0) target_compile_definitions(libopenmc PRIVATE -DGIT_SHA1="${GIT_SHA1}") endif() -# To use the shared HDF5 libraries on Windows with Visual Studio, -# the H5_BUILT_AS_DYNAMIC_LIB compile definition must be specified. -if(MSVC) - target_compile_definitions(libopenmc PRIVATE -DH5_BUILT_AS_DYNAMIC_LIB) -endif() - # target_link_libraries treats any arguments starting with - but not -l as # linker flags. Thus, we can pass both linker flags and libraries together. target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES} From 59046be677cd4526bb568330e385da07d718b485 Mon Sep 17 00:00:00 2001 From: Jingang Liang Date: Thu, 6 Jun 2019 17:20:14 +0000 Subject: [PATCH 086/151] fix reading 2D string vector from attribute in hdf5 --- include/openmc/hdf5_interface.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/include/openmc/hdf5_interface.h b/include/openmc/hdf5_interface.h index c87abd84a1..d0474c8344 100644 --- a/include/openmc/hdf5_interface.h +++ b/include/openmc/hdf5_interface.h @@ -204,23 +204,19 @@ read_attribute(hid_t obj_id, const char* name, std::vector& vec) // Allocate a C char array to get strings auto n = attribute_typesize(obj_id, name); - char** buffer = new char*[m]; - for (int i = 0; i < m; i++) { - buffer[i] = new char[n]; - } + char* buffer = new char[m*n]; // Read char data in attribute - read_attr_string(obj_id, name, n, buffer[0]); + read_attr_string(obj_id, name, n, buffer); for (int i = 0; i < m; ++i) { // Determine proper length of string -- strlen doesn't work because // buffer[i] might not have any null characters std::size_t k = 0; - for (; k < n; ++k) if (buffer[i][k] == '\0') break; + for (; k < n; ++k) if (buffer[i*n + k] == '\0') break; // Create string based on (char*, size_t) constructor - vec.emplace_back(&buffer[i][0], k); - delete[] buffer[i]; + vec.emplace_back(&buffer[i*n], k); } delete[] buffer; } From f57c4b919c6f067668eb1691942089fcb24488bc Mon Sep 17 00:00:00 2001 From: jingang Date: Thu, 6 Jun 2019 11:36:39 -0400 Subject: [PATCH 087/151] do not set compile/link flags for visual studio generator --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70ff63e978..5c0734d23f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,9 @@ endif() # Set compile/link flags based on which compiler is being used #=============================================================================== +# Skip for Visual Stduio which has its own configurations through GUI +if(NOT MSVC) + if(openmp) # Requires CMake 3.1+ find_package(OpenMP) @@ -104,6 +107,8 @@ endif() message(STATUS "OpenMC C++ flags: ${cxxflags}") message(STATUS "OpenMC Linker flags: ${ldflags}") +endif() + #=============================================================================== # pugixml library #=============================================================================== From b804ddc952696ea00b75ef4748f36650f4ca7370 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 6 Jun 2019 15:19:15 -0400 Subject: [PATCH 088/151] Address comments on #1246 --- include/openmc/mesh.h | 14 +++--- openmc/mesh.py | 12 ++--- src/mesh.cpp | 16 ++++++- src/plot.cpp | 102 ++++++++++++++++++++---------------------- src/settings.cpp | 15 ++++--- 5 files changed, 83 insertions(+), 76 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 0e8d75ea39..6d59826aaf 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -10,7 +10,7 @@ #include "hdf5.h" #include "pugixml.hpp" -#include "xtensor/xarray.hpp" +#include "xtensor/xtensor.hpp" #include "openmc/particle.h" #include "openmc/position.h" @@ -105,6 +105,8 @@ public: int id_ {-1}; //!< User-specified ID int n_dimension_; //!< Number of dimensions + xt::xtensor lower_left_; //!< Lower-left coordinates of mesh + xt::xtensor upper_right_; //!< Upper-right coordinates of mesh }; //============================================================================== @@ -158,16 +160,14 @@ public: //! \param[in] bank Array of bank sites //! \param[out] Whether any bank sites are outside the mesh //! \return Array indicating number of sites in each mesh/energy bin - xt::xarray count_sites(const std::vector& bank, + xt::xtensor count_sites(const std::vector& bank, bool* outside) const; // Data members double volume_frac_; //!< Volume fraction of each mesh element - xt::xarray shape_; //!< Number of mesh elements in each dimension - xt::xarray lower_left_; //!< Lower-left coordinates of mesh - xt::xarray upper_right_; //!< Upper-right coordinates of mesh - xt::xarray width_; //!< Width of each mesh element + xt::xtensor shape_; //!< Number of mesh elements in each dimension + xt::xtensor width_; //!< Width of each mesh element private: bool intersects_1d(Position& r0, Position r1, int* ijk) const; @@ -218,7 +218,7 @@ public: // Data members - xt::xarray shape_; //!< Number of mesh elements in each dimension + xt::xtensor shape_; //!< Number of mesh elements in each dimension private: std::vector> grid_; diff --git a/openmc/mesh.py b/openmc/mesh.py index ef3212b900..5b6b8ad3e9 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -104,9 +104,9 @@ class Mesh(MeshBase): are given, it is assumed that the mesh is an x-y mesh. width : Iterable of float The width of mesh cells in each direction. - indices : list of tuple - A list of mesh indices for each mesh element, e.g. [(1, 1, 1), (2, 1, - 1), ...] + indices : Iterable of tuple + An iterable of mesh indices for each mesh element, e.g. [(1, 1, 1), + (2, 1, 1), ...] """ @@ -427,9 +427,9 @@ class RectilinearMesh(MeshBase): Mesh boundary points along the y-axis. z_grid : Iterable of float Mesh boundary points along the z-axis. - indices : list of tuple - A list of mesh indices for each mesh element, e.g. [(1, 1, 1), (2, 1, - 1), ...] + indices : Iterable of tuple + An iterable of mesh indices for each mesh element, e.g. [(1, 1, 1), + (2, 1, 1), ...] """ diff --git a/src/mesh.cpp b/src/mesh.cpp index 870a58e6c6..315381706e 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -752,7 +752,7 @@ RegularMesh::plot(Position plot_ll, Position plot_ur) const for (int i_ax = 0; i_ax < 2; ++i_ax) { int axis = axes[i_ax]; if (axis == -1) continue; - std::vector& lines {axis_lines[i_ax]}; + auto& lines {axis_lines[i_ax]}; double coord = lower_left_[axis]; for (int i = 0; i < shape_[axis] + 1; ++i) { @@ -778,7 +778,7 @@ void RegularMesh::to_hdf5(hid_t group) const close_group(mesh_group); } -xt::xarray +xt::xtensor RegularMesh::count_sites(const std::vector& bank, bool* outside) const { @@ -846,6 +846,18 @@ RectilinearMesh::RectilinearMesh(pugi::xml_node node) shape_ = {static_cast(grid_[0].size()) - 1, static_cast(grid_[1].size()) - 1, static_cast(grid_[2].size()) - 1}; + + for (const auto& g : grid_) { + if (g.size() < 2) fatal_error("x-, y-, and z- grids for rectilinear meshes " + "must each have at least 2 points"); + for (int i = 1; i < g.size(); ++i) { + if (g[i] <= g[i-1]) fatal_error("Values in for x-, y-, and z- grids for " + "rectilinear meshes must be sorted and unique."); + } + } + + lower_left_ = {grid_[0].front(), grid_[1].front(), grid_[2].front()}; + upper_right_ = {grid_[0].back(), grid_[1].back(), grid_[2].back()}; } void RectilinearMesh::bins_crossed(const Particle* p, std::vector& bins, diff --git a/src/plot.cpp b/src/plot.cpp index 1ad4f83174..6bc82e1641 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -727,67 +727,61 @@ void draw_mesh_lines(Plot pl, ImageData& data) Position width = ur_plot - ll_plot; // Find the (axis-aligned) lines of the mesh that intersect this plot. - std::pair, std::vector> axis_lines; - axis_lines = model::meshes[pl.index_meshlines_mesh_]->plot(ll_plot, ur_plot); + auto axis_lines = model::meshes[pl.index_meshlines_mesh_] + ->plot(ll_plot, ur_plot); - // Draw lines perpendicular to the first axis. - { - // Find the bounds along the second axis (accounting for low-D meshes). - int ax2_min, ax2_max; - if (axis_lines.second.size() > 0) { - double frac = (axis_lines.second.back() - ll_plot[ax2]) / width[ax2]; - ax2_min = (1.0 - frac) * pl.pixels_[1]; - if (ax2_min < 0) ax2_min = 0; - frac = (axis_lines.second.front() - ll_plot[ax2]) / width[ax2]; - ax2_max = (1.0 - frac) * pl.pixels_[1]; - if (ax2_max > pl.pixels_[1]) ax2_max = pl.pixels_[1]; - } else { - ax2_min = 0; - ax2_max = pl.pixels_[1]; - } + // Find the bounds along the second axis (accounting for low-D meshes). + int ax2_min, ax2_max; + if (axis_lines.second.size() > 0) { + double frac = (axis_lines.second.back() - ll_plot[ax2]) / width[ax2]; + ax2_min = (1.0 - frac) * pl.pixels_[1]; + if (ax2_min < 0) ax2_min = 0; + frac = (axis_lines.second.front() - ll_plot[ax2]) / width[ax2]; + ax2_max = (1.0 - frac) * pl.pixels_[1]; + if (ax2_max > pl.pixels_[1]) ax2_max = pl.pixels_[1]; + } else { + ax2_min = 0; + ax2_max = pl.pixels_[1]; + } - // Iterate across the first axis and draw lines. - for (auto ax1_val : axis_lines.first) { - double frac = (ax1_val - ll_plot[ax1]) / width[ax1]; - int ax1_ind = frac * pl.pixels_[0]; - for (int ax2_ind = ax2_min; ax2_ind < ax2_max; ++ax2_ind) { - for (int plus = 0; plus <= pl.meshlines_width_; plus++) { - if (ax1_ind+plus >= 0 && ax1_ind+plus < pl.pixels_[0]) - data(ax1_ind+plus, ax2_ind) = rgb; - if (ax1_ind-plus >= 0 && ax1_ind-plus < pl.pixels_[0]) - data(ax1_ind-plus, ax2_ind) = rgb; - } + // Iterate across the first axis and draw lines. + for (auto ax1_val : axis_lines.first) { + double frac = (ax1_val - ll_plot[ax1]) / width[ax1]; + int ax1_ind = frac * pl.pixels_[0]; + for (int ax2_ind = ax2_min; ax2_ind < ax2_max; ++ax2_ind) { + for (int plus = 0; plus <= pl.meshlines_width_; plus++) { + if (ax1_ind+plus >= 0 && ax1_ind+plus < pl.pixels_[0]) + data(ax1_ind+plus, ax2_ind) = rgb; + if (ax1_ind-plus >= 0 && ax1_ind-plus < pl.pixels_[0]) + data(ax1_ind-plus, ax2_ind) = rgb; } } } - // Draw lines perpendicular to the second axis. - { - // Find the bounds along the first axis. - int ax1_min, ax1_max; - if (axis_lines.first.size() > 0) { - double frac = (axis_lines.first.front() - ll_plot[ax1]) / width[ax1]; - ax1_min = frac * pl.pixels_[0]; - if (ax1_min < 0) ax1_min = 0; - frac = (axis_lines.first.back() - ll_plot[ax1]) / width[ax1]; - ax1_max = frac * pl.pixels_[0]; - if (ax1_max > pl.pixels_[0]) ax1_max = pl.pixels_[0]; - } else { - ax1_min = 0; - ax1_max = pl.pixels_[0]; - } + // Find the bounds along the first axis. + int ax1_min, ax1_max; + if (axis_lines.first.size() > 0) { + double frac = (axis_lines.first.front() - ll_plot[ax1]) / width[ax1]; + ax1_min = frac * pl.pixels_[0]; + if (ax1_min < 0) ax1_min = 0; + frac = (axis_lines.first.back() - ll_plot[ax1]) / width[ax1]; + ax1_max = frac * pl.pixels_[0]; + if (ax1_max > pl.pixels_[0]) ax1_max = pl.pixels_[0]; + } else { + ax1_min = 0; + ax1_max = pl.pixels_[0]; + } - // Iterate across the second axis and draw lines. - for (auto ax2_val : axis_lines.second) { - double frac = (ax2_val - ll_plot[ax2]) / width[ax2]; - int ax2_ind = (1.0 - frac) * pl.pixels_[1]; - for (int ax1_ind = ax1_min; ax1_ind < ax1_max; ++ax1_ind) { - for (int plus = 0; plus <= pl.meshlines_width_; plus++) { - if (ax2_ind+plus >= 0 && ax2_ind+plus < pl.pixels_[1]) - data(ax1_ind, ax2_ind+plus) = rgb; - if (ax2_ind-plus >= 0 && ax2_ind-plus < pl.pixels_[1]) - data(ax1_ind, ax2_ind-plus) = rgb; - } + // Iterate across the second axis and draw lines. + for (auto ax2_val : axis_lines.second) { + double frac = (ax2_val - ll_plot[ax2]) / width[ax2]; + int ax2_ind = (1.0 - frac) * pl.pixels_[1]; + for (int ax1_ind = ax1_min; ax1_ind < ax1_max; ++ax1_ind) { + for (int plus = 0; plus <= pl.meshlines_width_; plus++) { + if (ax2_ind+plus >= 0 && ax2_ind+plus < pl.pixels_[1]) + data(ax1_ind, ax2_ind+plus) = rgb; + if (ax2_ind-plus >= 0 && ax2_ind-plus < pl.pixels_[1]) + data(ax1_ind, ax2_ind-plus) = rgb; } } } diff --git a/src/settings.cpp b/src/settings.cpp index 63827c3f41..ac31d26706 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -478,7 +478,7 @@ void read_settings_xml() read_meshes(root); // Shannon Entropy mesh - int32_t i_entropy_mesh = -1; + int32_t index_entropy_mesh = -1; if (check_for_node(root, "entropy_mesh")) { int temp = std::stoi(get_node_value(root, "entropy_mesh")); if (model::mesh_map.find(temp) == model::mesh_map.end()) { @@ -486,7 +486,7 @@ void read_settings_xml() msg << "Mesh " << temp << " specified for Shannon entropy does not exist."; fatal_error(msg); } - i_entropy_mesh = model::mesh_map.at(temp); + index_entropy_mesh = model::mesh_map.at(temp); } else if (check_for_node(root, "entropy")) { warning("Specifying a Shannon entropy mesh via the element " @@ -498,15 +498,16 @@ void read_settings_xml() model::meshes.push_back(std::make_unique(node_entropy)); // Set entropy mesh index - i_entropy_mesh = model::meshes.size() - 1; + index_entropy_mesh = model::meshes.size() - 1; // Assign ID and set mapping model::meshes.back()->id_ = 10000; - model::mesh_map[10000] = i_entropy_mesh; + model::mesh_map[10000] = index_entropy_mesh; } - if (i_entropy_mesh >= 0) { - auto* m = dynamic_cast(model::meshes[i_entropy_mesh].get()); + if (index_entropy_mesh >= 0) { + auto* m = dynamic_cast( + model::meshes[index_entropy_mesh].get()); if (!m) fatal_error("Only regular meshes can be used as an entropy mesh"); simulation::entropy_mesh = m; @@ -552,7 +553,7 @@ void read_settings_xml() // Assign ID and set mapping model::meshes.back()->id_ = 10001; - model::mesh_map[10001] = i_entropy_mesh; + model::mesh_map[10001] = index_entropy_mesh; } if (i_ufs_mesh >= 0) { From 9ddd8b3067e09dfd35b1b52460df6de55de9bde2 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 6 Jun 2019 17:04:07 -0400 Subject: [PATCH 089/151] Change Python Mesh class to RegularMesh --- docs/source/pythonapi/base.rst | 2 +- examples/jupyter/mdgxs-part-ii.ipynb | 81 +++++-------------- examples/jupyter/mg-mode-part-i.ipynb | 45 +++-------- examples/jupyter/mg-mode-part-ii.ipynb | 9 +-- examples/jupyter/mg-mode-part-iii.ipynb | 9 +-- examples/jupyter/mgxs-part-iii.ipynb | 9 +-- examples/jupyter/pandas-dataframes.ipynb | 5 +- examples/jupyter/post-processing.ipynb | 2 +- examples/jupyter/tally-arithmetic.ipynb | 74 +++++------------ examples/python/lattice/nested/build-xml.py | 6 +- examples/python/lattice/simple/build-xml.py | 3 +- examples/python/pincell/build-xml.py | 5 +- .../pincell_depletion/restart_depletion.py | 2 +- .../python/pincell_depletion/run_depletion.py | 2 +- .../python/pincell_multigroup/build-xml.py | 3 +- openmc/capi/filter.py | 6 +- openmc/capi/mesh.py | 10 +-- openmc/cmfd.py | 8 +- openmc/filter.py | 26 +++--- openmc/mesh.py | 19 +++-- openmc/mgxs/library.py | 14 ++-- openmc/mgxs/mdgxs.py | 30 +++---- openmc/mgxs/mgxs.py | 66 +++++++-------- openmc/plots.py | 4 +- openmc/settings.py | 10 +-- openmc/statepoint.py | 4 +- openmc/trigger.py | 1 - .../filter_mesh/inputs_true.dat | 6 +- tests/regression_tests/filter_mesh/test.py | 9 +-- .../mg_tallies/inputs_true.dat | 2 +- tests/regression_tests/mg_tallies/test.py | 3 +- .../mgxs_library_mesh/inputs_true.dat | 2 +- .../mgxs_library_mesh/test.py | 3 +- .../regression_tests/tallies/inputs_true.dat | 2 +- tests/regression_tests/tallies/test.py | 4 +- .../tally_arithmetic/inputs_true.dat | 2 +- .../regression_tests/tally_arithmetic/test.py | 3 +- .../tally_slice_merge/inputs_true.dat | 2 +- .../tally_slice_merge/test.py | 3 +- tests/unit_tests/test_capi.py | 6 +- tests/unit_tests/test_mesh_from_lattice.py | 8 +- tests/unit_tests/test_settings.py | 2 +- 42 files changed, 204 insertions(+), 308 deletions(-) diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 0a6fbbbc07..2c0ac8797d 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -124,7 +124,7 @@ Constructing Tallies openmc.ZernikeFilter openmc.ZernikeRadialFilter openmc.ParticleFilter - openmc.Mesh + openmc.RegularMesh openmc.RectilinearMesh openmc.Trigger openmc.TallyDerivative diff --git a/examples/jupyter/mdgxs-part-ii.ipynb b/examples/jupyter/mdgxs-part-ii.ipynb index 4c2cc40486..23ee4d2514 100644 --- a/examples/jupyter/mdgxs-part-ii.ipynb +++ b/examples/jupyter/mdgxs-part-ii.ipynb @@ -22,9 +22,7 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -82,9 +80,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a materials collection and export to XML\n", @@ -102,9 +98,7 @@ { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create cylinders for the fuel and clad\n", @@ -167,9 +161,7 @@ { "cell_type": "code", "execution_count": 6, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a Universe to encapsulate a control rod guide tube\n", @@ -204,9 +196,7 @@ { "cell_type": "code", "execution_count": 7, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create fuel assembly Lattice\n", @@ -225,9 +215,7 @@ { "cell_type": "code", "execution_count": 8, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create array indices for guide tube locations in lattice\n", @@ -254,9 +242,7 @@ { "cell_type": "code", "execution_count": 9, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create root Cell\n", @@ -280,9 +266,7 @@ { "cell_type": "code", "execution_count": 10, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create Geometry and export to XML\n", @@ -300,9 +284,7 @@ { "cell_type": "code", "execution_count": 11, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# OpenMC simulation parameters\n", @@ -336,9 +318,7 @@ { "cell_type": "code", "execution_count": 12, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -383,9 +363,7 @@ { "cell_type": "code", "execution_count": 13, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a 20-group EnergyGroups object\n", @@ -407,14 +385,11 @@ { "cell_type": "code", "execution_count": 14, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a tally mesh \n", - "mesh = openmc.Mesh(mesh_id=1)\n", - "mesh.type = 'regular'\n", + "mesh = openmc.RegularMesh(mesh_id=1)\n", "mesh.dimension = [17, 17, 1]\n", "mesh.lower_left = [-10.71, -10.71, -10000.]\n", "mesh.width = [1.26, 1.26, 20000.]\n", @@ -464,9 +439,7 @@ { "cell_type": "code", "execution_count": 15, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -635,9 +608,7 @@ { "cell_type": "code", "execution_count": 16, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Load the last statepoint file\n", @@ -654,9 +625,7 @@ { "cell_type": "code", "execution_count": 17, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Initialize MGXS Library with OpenMC statepoint data\n", @@ -687,9 +656,7 @@ { "cell_type": "code", "execution_count": 18, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", @@ -923,9 +890,7 @@ { "cell_type": "code", "execution_count": 19, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1110,9 +1075,7 @@ { "cell_type": "code", "execution_count": 20, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", @@ -1189,7 +1152,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python [default]", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -1203,9 +1166,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.7" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/jupyter/mg-mode-part-i.ipynb b/examples/jupyter/mg-mode-part-i.ipynb index 8e52787879..23227b1ed3 100644 --- a/examples/jupyter/mg-mode-part-i.ipynb +++ b/examples/jupyter/mg-mode-part-i.ipynb @@ -86,9 +86,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# The scattering matrix is ordered with incoming groups as rows and outgoing groups as columns\n", @@ -158,9 +156,7 @@ { "cell_type": "code", "execution_count": 5, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# For every cross section data set in the library, assign an openmc.Macroscopic object to a material\n", @@ -207,9 +203,7 @@ { "cell_type": "code", "execution_count": 7, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create the surface used for each pin\n", @@ -249,9 +243,7 @@ { "cell_type": "code", "execution_count": 8, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "lattices = {}\n", @@ -352,9 +344,7 @@ { "cell_type": "code", "execution_count": 9, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "lattices['Core'] = openmc.RectLattice(name='3x3 core lattice')\n", @@ -396,9 +386,7 @@ { "cell_type": "code", "execution_count": 10, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -426,9 +414,7 @@ { "cell_type": "code", "execution_count": 11, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create Geometry and set root Universe\n", @@ -456,8 +442,7 @@ "tallies_file = openmc.Tallies()\n", "\n", "# Instantiate a tally Mesh\n", - "mesh = openmc.Mesh()\n", - "mesh.type = 'regular'\n", + "mesh = openmc.RegularMesh()\n", "mesh.dimension = [17 * 2, 17 * 2]\n", "mesh.lower_left = [-32.13, -10.71]\n", "mesh.upper_right = [+10.71, +32.13]\n", @@ -489,9 +474,7 @@ { "cell_type": "code", "execution_count": 13, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# OpenMC simulation parameters\n", @@ -533,9 +516,7 @@ { "cell_type": "code", "execution_count": 14, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -649,9 +630,7 @@ { "cell_type": "code", "execution_count": 15, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -714,7 +693,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.0" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/examples/jupyter/mg-mode-part-ii.ipynb b/examples/jupyter/mg-mode-part-ii.ipynb index e2df6fd3db..85a8d73675 100644 --- a/examples/jupyter/mg-mode-part-ii.ipynb +++ b/examples/jupyter/mg-mode-part-ii.ipynb @@ -564,8 +564,7 @@ ], "source": [ "# Instantiate a tally Mesh\n", - "mesh = openmc.Mesh()\n", - "mesh.type = 'regular'\n", + "mesh = openmc.RegularMesh()\n", "mesh.dimension = [17, 17]\n", "mesh.lower_left = [-10.71, -10.71]\n", "mesh.upper_right = [+10.71, +10.71]\n", @@ -1518,9 +1517,9 @@ ], "metadata": { "kernelspec": { - "display_name": "openmc", + "display_name": "Python 3", "language": "python", - "name": "openmc" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1532,7 +1531,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/examples/jupyter/mg-mode-part-iii.ipynb b/examples/jupyter/mg-mode-part-iii.ipynb index 7487b842c6..2cf9981889 100644 --- a/examples/jupyter/mg-mode-part-iii.ipynb +++ b/examples/jupyter/mg-mode-part-iii.ipynb @@ -545,8 +545,7 @@ "outputs": [], "source": [ "# Instantiate a tally Mesh\n", - "mesh = openmc.Mesh()\n", - "mesh.type = 'regular'\n", + "mesh = openmc.RegularMesh()\n", "mesh.dimension = [10, 10]\n", "mesh.lower_left = [0., 0.]\n", "mesh.upper_right = [length, length]\n", @@ -1417,9 +1416,9 @@ ], "metadata": { "kernelspec": { - "display_name": "openmc", + "display_name": "Python 3", "language": "python", - "name": "openmc" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1431,7 +1430,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/examples/jupyter/mgxs-part-iii.ipynb b/examples/jupyter/mgxs-part-iii.ipynb index d3e90d0946..5ffb66d3a0 100644 --- a/examples/jupyter/mgxs-part-iii.ipynb +++ b/examples/jupyter/mgxs-part-iii.ipynb @@ -586,8 +586,7 @@ "outputs": [], "source": [ "# Instantiate a tally Mesh\n", - "mesh = openmc.Mesh(mesh_id=1)\n", - "mesh.type = 'regular'\n", + "mesh = openmc.RegularMesh(mesh_id=1)\n", "mesh.dimension = [17, 17]\n", "mesh.lower_left = [-10.71, -10.71]\n", "mesh.upper_right = [+10.71, +10.71]\n", @@ -1527,9 +1526,9 @@ "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "openmc", + "display_name": "Python 3", "language": "python", - "name": "openmc" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1541,7 +1540,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/examples/jupyter/pandas-dataframes.ipynb b/examples/jupyter/pandas-dataframes.ipynb index b8b10311c4..80a0b880f9 100644 --- a/examples/jupyter/pandas-dataframes.ipynb +++ b/examples/jupyter/pandas-dataframes.ipynb @@ -341,8 +341,7 @@ "outputs": [], "source": [ "# Instantiate a tally Mesh\n", - "mesh = openmc.Mesh(mesh_id=1)\n", - "mesh.type = 'regular'\n", + "mesh = openmc.RegularMesh(mesh_id=1)\n", "mesh.dimension = [17, 17]\n", "mesh.lower_left = [-10.71, -10.71]\n", "mesh.width = [1.26, 1.26]\n", @@ -1986,7 +1985,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.6" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/examples/jupyter/post-processing.ipynb b/examples/jupyter/post-processing.ipynb index 1223f67f33..a6d134b54b 100644 --- a/examples/jupyter/post-processing.ipynb +++ b/examples/jupyter/post-processing.ipynb @@ -319,7 +319,7 @@ "outputs": [], "source": [ "# Create mesh which will be used for tally\n", - "mesh = openmc.Mesh()\n", + "mesh = openmc.RegularMesh()\n", "mesh.dimension = [100, 100]\n", "mesh.lower_left = [-0.63, -0.63]\n", "mesh.upper_right = [0.63, 0.63]\n", diff --git a/examples/jupyter/tally-arithmetic.ipynb b/examples/jupyter/tally-arithmetic.ipynb index 2bccf73f2a..35c096ba4d 100644 --- a/examples/jupyter/tally-arithmetic.ipynb +++ b/examples/jupyter/tally-arithmetic.ipynb @@ -284,9 +284,7 @@ { "cell_type": "code", "execution_count": 11, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Run openmc in plotting mode\n", @@ -296,9 +294,7 @@ { "cell_type": "code", "execution_count": 12, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -376,8 +372,7 @@ "tallies_file.append(tally)\n", "\n", "# Instantiate a tally mesh\n", - "mesh = openmc.Mesh(mesh_id=1)\n", - "mesh.type = 'regular'\n", + "mesh = openmc.RegularMesh(mesh_id=1)\n", "mesh.dimension = [1, 1, 1]\n", "mesh.lower_left = [-0.63, -0.63, -100.]\n", "mesh.width = [1.26, 1.26, 200.]\n", @@ -485,9 +480,7 @@ { "cell_type": "code", "execution_count": 20, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", @@ -518,7 +511,6 @@ "cell_type": "code", "execution_count": 21, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [ @@ -674,9 +666,7 @@ { "cell_type": "code", "execution_count": 23, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -740,9 +730,7 @@ { "cell_type": "code", "execution_count": 24, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -807,9 +795,7 @@ { "cell_type": "code", "execution_count": 25, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -873,9 +859,7 @@ { "cell_type": "code", "execution_count": 26, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -939,9 +923,7 @@ { "cell_type": "code", "execution_count": 27, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1004,9 +986,7 @@ { "cell_type": "code", "execution_count": 28, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1066,9 +1046,7 @@ { "cell_type": "code", "execution_count": 29, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1128,9 +1106,7 @@ { "cell_type": "code", "execution_count": 30, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1210,9 +1186,7 @@ { "cell_type": "code", "execution_count": 32, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1358,9 +1332,7 @@ { "cell_type": "code", "execution_count": 33, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1390,9 +1362,7 @@ { "cell_type": "code", "execution_count": 34, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1414,9 +1384,7 @@ { "cell_type": "code", "execution_count": 35, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1445,9 +1413,7 @@ { "cell_type": "code", "execution_count": 36, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1539,9 +1505,7 @@ { "cell_type": "code", "execution_count": 37, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1709,7 +1673,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.0" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/examples/python/lattice/nested/build-xml.py b/examples/python/lattice/nested/build-xml.py index 37483a9397..06641f5ac5 100644 --- a/examples/python/lattice/nested/build-xml.py +++ b/examples/python/lattice/nested/build-xml.py @@ -150,8 +150,7 @@ plot_file.export_to_xml() ############################################################################### # Instantiate a tally mesh -mesh = openmc.Mesh(mesh_id=1) -mesh.type = 'regular' +mesh = openmc.RegularMesh(mesh_id=1) mesh.dimension = [4, 4] mesh.lower_left = [-2, -2] mesh.width = [1, 1] @@ -164,6 +163,7 @@ tally = openmc.Tally(tally_id=1) tally.filters = [mesh_filter] tally.scores = ['total'] -# Instantiate a Tallies collection, register Tally/Mesh, and export to XML +# Instantiate a Tallies collection, register Tally/RegularMesh, and export to +# XML tallies_file = openmc.Tallies([tally]) tallies_file.export_to_xml() diff --git a/examples/python/lattice/simple/build-xml.py b/examples/python/lattice/simple/build-xml.py index 1cdfa24f3b..17544b87f5 100644 --- a/examples/python/lattice/simple/build-xml.py +++ b/examples/python/lattice/simple/build-xml.py @@ -143,8 +143,7 @@ plot_file.export_to_xml() ############################################################################### # Instantiate a tally mesh -mesh = openmc.Mesh(mesh_id=1) -mesh.type = 'regular' +mesh = openmc.RegularMesh(mesh_id=1) mesh.dimension = [4, 4] mesh.lower_left = [-2, -2] mesh.width = [1, 1] diff --git a/examples/python/pincell/build-xml.py b/examples/python/pincell/build-xml.py index c728d5f36c..072cc911f0 100644 --- a/examples/python/pincell/build-xml.py +++ b/examples/python/pincell/build-xml.py @@ -106,7 +106,7 @@ bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) settings_file.source = openmc.source.Source(space=uniform_dist) -entropy_mesh = openmc.Mesh() +entropy_mesh = openmc.RegularMesh() entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] entropy_mesh.dimension = [10, 10, 1] @@ -119,8 +119,7 @@ settings_file.export_to_xml() ############################################################################### # Instantiate a tally mesh -mesh = openmc.Mesh() -mesh.type = 'regular' +mesh = openmc.RegularMesh() mesh.dimension = [100, 100, 1] mesh.lower_left = [-0.62992, -0.62992, -1.e50] mesh.upper_right = [0.62992, 0.62992, 1.e50] diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index f9324a8cb8..d5a0e2234a 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -47,7 +47,7 @@ bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) settings_file.source = openmc.source.Source(space=uniform_dist) -entropy_mesh = openmc.Mesh() +entropy_mesh = openmc.RegularMesh() entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] entropy_mesh.dimension = [10, 10, 1] diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index c1ad9d20aa..5a018cda10 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -119,7 +119,7 @@ bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) settings_file.source = openmc.source.Source(space=uniform_dist) -entropy_mesh = openmc.Mesh() +entropy_mesh = openmc.RegularMesh() entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] entropy_mesh.dimension = [10, 10, 1] diff --git a/examples/python/pincell_multigroup/build-xml.py b/examples/python/pincell_multigroup/build-xml.py index 995cd87db5..bb9fc78606 100644 --- a/examples/python/pincell_multigroup/build-xml.py +++ b/examples/python/pincell_multigroup/build-xml.py @@ -155,8 +155,7 @@ settings_file.export_to_xml() ############################################################################### # Instantiate a tally mesh -mesh = openmc.Mesh(mesh_id=1) -mesh.type = 'regular' +mesh = openmc.RegularMesh(mesh_id=1) mesh.dimension = [100, 100, 1] mesh.lower_left = [-0.63, -0.63, -1.e50] mesh.upper_right = [0.63, 0.63, 1.e50] diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 05ec85d010..c0716bcef6 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -11,7 +11,7 @@ from . import _dll from .core import _FortranObjectWithID from .error import _error_handler from .material import Material -from .mesh import Mesh +from .mesh import RegularMesh __all__ = ['Filter', 'AzimuthalFilter', 'CellFilter', @@ -255,7 +255,7 @@ class MeshFilter(Filter): def mesh(self): index_mesh = c_int32() _dll.openmc_mesh_filter_get_mesh(self._index, index_mesh) - return Mesh(index=index_mesh.value) + return RegularMesh(index=index_mesh.value) @mesh.setter def mesh(self, mesh): @@ -274,7 +274,7 @@ class MeshSurfaceFilter(Filter): def mesh(self): index_mesh = c_int32() _dll.openmc_meshsurface_filter_get_mesh(self._index, index_mesh) - return Mesh(index=index_mesh.value) + return RegularMesh(index=index_mesh.value) @mesh.setter def mesh(self, mesh): diff --git a/openmc/capi/mesh.py b/openmc/capi/mesh.py index 70a4345b09..a1161f48f3 100644 --- a/openmc/capi/mesh.py +++ b/openmc/capi/mesh.py @@ -11,7 +11,7 @@ from .core import _FortranObjectWithID from .error import _error_handler from .material import Material -__all__ = ['Mesh', 'meshes'] +__all__ = ['RegularMesh', 'meshes'] # Mesh functions _dll.openmc_extend_meshes.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)] @@ -45,8 +45,8 @@ _dll.n_meshes.argtypes = [] _dll.n_meshes.restype = c_int -class Mesh(_FortranObjectWithID): - """Mesh stored internally. +class RegularMesh(_FortranObjectWithID): + """RegularMesh stored internally. This class exposes a mesh that is stored internally in the OpenMC library. To obtain a view of a mesh with a given ID, use the @@ -170,11 +170,11 @@ class _MeshMapping(Mapping): except (AllocationError, InvalidIDError) as e: # __contains__ expects a KeyError to work correctly raise KeyError(str(e)) - return Mesh(index=index.value) + return RegularMesh(index=index.value) def __iter__(self): for i in range(len(self)): - yield Mesh(index=i).id + yield RegularMesh(index=i).id def __len__(self): return _dll.n_meshes() diff --git a/openmc/cmfd.py b/openmc/cmfd.py index d1ea77fa06..244ceadcf0 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -2605,16 +2605,16 @@ class CMFDRun(object): def _create_cmfd_tally(self): """Creates all tallies in-memory that are used to solve CMFD problem""" # Create Mesh object based on CMFDMesh, stored internally - cmfd_mesh = openmc.capi.Mesh() - # Store id of Mesh object + cmfd_mesh = openmc.capi.RegularMesh() + # Store id of mesh object self._mesh_id = cmfd_mesh.id - # Set dimension and parameters of Mesh object + # Set dimension and parameters of mesh object cmfd_mesh.dimension = self._mesh.dimension cmfd_mesh.set_parameters(lower_left=self._mesh.lower_left, upper_right=self._mesh.upper_right, width=self._mesh.width) - # Create Mesh Filter object, stored internally + # Create mesh Filter object, stored internally mesh_filter = openmc.capi.MeshFilter() # Set mesh for Mesh Filter mesh_filter.mesh = cmfd_mesh diff --git a/openmc/filter.py b/openmc/filter.py index fa1085b970..dbdaf2e19e 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -161,8 +161,8 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): Keyword arguments ----------------- meshes : dict - Dictionary mapping integer IDs to openmc.Mesh objects. Only used - for openmc.MeshFilter objects. + Dictionary mapping integer IDs to openmc.MeshBase objects. Only + used for openmc.MeshFilter objects. """ @@ -587,15 +587,15 @@ class MeshFilter(Filter): Parameters ---------- - mesh : openmc.Mesh - The Mesh object that events will be tallied onto + mesh : openmc.MeshBase + The mesh object that events will be tallied onto filter_id : int Unique identifier for the filter Attributes ---------- - mesh : openmc.Mesh - The Mesh object that events will be tallied onto + mesh : openmc.MeshBase + The mesh object that events will be tallied onto id : int Unique identifier for the filter bins : list of tuple @@ -688,7 +688,7 @@ class MeshFilter(Filter): # Initialize dictionary to build Pandas Multi-index column filter_dict = {} - # Append Mesh ID as outermost index of multi-index + # Append mesh ID as outermost index of multi-index mesh_key = 'mesh {}'.format(self.mesh.id) # Find mesh dimensions - use 3D indices for simplicity @@ -750,17 +750,17 @@ class MeshSurfaceFilter(MeshFilter): Parameters ---------- - mesh : openmc.Mesh - The Mesh object that events will be tallied onto + mesh : openmc.MeshBase + The mesh object that events will be tallied onto filter_id : int Unique identifier for the filter Attributes ---------- bins : Integral - The Mesh ID - mesh : openmc.Mesh - The Mesh object that events will be tallied onto + The mesh ID + mesh : openmc.MeshBase + The mesh object that events will be tallied onto id : int Unique identifier for the filter bins : list of tuple @@ -817,7 +817,7 @@ class MeshSurfaceFilter(MeshFilter): # Initialize dictionary to build Pandas Multi-index column filter_dict = {} - # Append Mesh ID as outermost index of multi-index + # Append mesh ID as outermost index of multi-index mesh_key = 'mesh {}'.format(self.mesh.id) # Find mesh dimensions - use 3D indices for simplicity diff --git a/openmc/mesh.py b/openmc/mesh.py index 5b6b8ad3e9..861afbd02e 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -3,6 +3,7 @@ from collections.abc import Iterable from numbers import Real, Integral from xml.etree import ElementTree as ET import sys +import warnings import numpy as np @@ -69,14 +70,14 @@ class MeshBase(IDManagerMixin, metaclass=ABCMeta): mesh_type = group['type'][()].decode() if mesh_type == 'regular': - return Mesh.from_hdf5(group) + return RegularMesh.from_hdf5(group) elif mesh_type == 'rectilinear': return RectilinearMesh.from_hdf5(group) else: raise ValueError('Unrecognized mesh type: "' + mesh_type + '"') -class Mesh(MeshBase): +class RegularMesh(MeshBase): """A regular Cartesian mesh in one, two, or three dimensions Parameters @@ -113,7 +114,6 @@ class Mesh(MeshBase): def __init__(self, mesh_id=None, name=''): super().__init__(mesh_id, name) - # Initialize Mesh class attributes self._dimension = None self._lower_left = None self._upper_right = None @@ -186,7 +186,7 @@ class Mesh(MeshBase): self._width = width def __repr__(self): - string = 'Mesh\n' + string = 'RegularMesh\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self._type) @@ -227,8 +227,8 @@ class Mesh(MeshBase): Returns ------- - openmc.Mesh - Mesh instance + openmc.RegularMesh + RegularMesh instance """ cv.check_type('rectangular lattice', lattice, openmc.RectLattice) @@ -255,7 +255,6 @@ class Mesh(MeshBase): element = ET.Element("mesh") element.set("id", str(self._id)) - element.set("type", "regular") subelement = ET.SubElement(element, "dimension") subelement.text = ' '.join(map(str, self._dimension)) @@ -403,6 +402,12 @@ class Mesh(MeshBase): return root_cell, cells +def Mesh(*args, **kwargs): + warnings.warn("Mesh has been renamed RegularMesh. Future versions of " + "OpenMC will not accept the name Mesh.") + return RegularMesh(*args, **kwargs) + + class RectilinearMesh(MeshBase): """A 3D rectilinear Cartesian mesh diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index 7b75d90fff..fbf08f85f7 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -51,7 +51,7 @@ class Library(object): The types of cross sections in the library (e.g., ['total', 'scatter']) domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization - domains : Iterable of openmc.Material, openmc.Cell, openmc.Universe or openmc.Mesh + domains : Iterable of openmc.Material, openmc.Cell, openmc.Universe or openmc.RegularMesh The spatial domain(s) for which MGXS in the Library are computed correction : {'P0', None} Apply the P0 correction to scattering matrices if set to 'P0' @@ -324,7 +324,7 @@ class Library(object): cv.check_type('domain', domains, Iterable, openmc.Universe) all_domains = self.geometry.get_all_universes().values() elif self.domain_type == 'mesh': - cv.check_type('domain', domains, Iterable, openmc.Mesh) + cv.check_type('domain', domains, Iterable, openmc.RegularMesh) # The mesh and geometry are independent, so set all_domains # to the input domains @@ -606,7 +606,7 @@ class Library(object): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh or Integral + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh or Integral The material, cell, or universe object of interest (or its ID) mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', chi', 'chi-prompt', 'inverse-velocity', 'prompt-nu-fission', 'prompt-nu-fission matrix', 'delayed-nu-fission', 'delayed-nu-fission matrix', 'chi-delayed', 'beta'} The type of multi-group cross section object to return @@ -631,7 +631,7 @@ class Library(object): elif self.domain_type == 'universe': cv.check_type('domain', domain, (openmc.Universe, Integral)) elif self.domain_type == 'mesh': - cv.check_type('domain', domain, (openmc.Mesh, Integral)) + cv.check_type('domain', domain, (openmc.RegularMesh, Integral)) # Check that requested domain is included in library if isinstance(domain, Integral): @@ -916,7 +916,7 @@ class Library(object): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization xsdata_name : str Name to apply to the "xsdata" entry produced by this method @@ -930,7 +930,7 @@ class Library(object): subdomain : iterable of int This parameter is not used unless using a mesh domain. In that case, the subdomain is an [i,j,k] index (1-based indexing) of the - mesh cell of interest in the openmc.Mesh object. Note: + mesh cell of interest in the openmc.RegularMesh object. Note: this parameter currently only supports subdomains within a mesh, and not the subdomains of a distribcell. @@ -952,7 +952,7 @@ class Library(object): """ cv.check_type('domain', domain, (openmc.Material, openmc.Cell, - openmc.Universe, openmc.Mesh)) + openmc.Universe, openmc.RegularMesh)) cv.check_type('xsdata_name', xsdata_name, str) cv.check_type('nuclide', nuclide, str) cv.check_value('xs_type', xs_type, ['macro', 'micro']) diff --git a/openmc/mgxs/mdgxs.py b/openmc/mgxs/mdgxs.py index 2d278d62d7..14bc524ee9 100644 --- a/openmc/mgxs/mdgxs.py +++ b/openmc/mgxs/mdgxs.py @@ -40,7 +40,7 @@ class MDGXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -68,7 +68,7 @@ class MDGXS(MGXS): Reaction type (e.g., 'chi-delayed', 'beta', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -238,7 +238,7 @@ class MDGXS(MGXS): mdgxs_type : {'delayed-nu-fission', 'chi-delayed', 'beta', 'decay-rate', 'delayed-nu-fission matrix'} The type of multi-delayed-group cross section object to return domain : openmc.Material or openmc.Cell or openmc.Universe or - openmc.Mesh + openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -917,7 +917,7 @@ class ChiDelayed(MDGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -945,7 +945,7 @@ class ChiDelayed(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -1429,7 +1429,7 @@ class DelayedNuFissionXS(MDGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -1457,7 +1457,7 @@ class DelayedNuFissionXS(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -1563,7 +1563,7 @@ class Beta(MDGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -1591,7 +1591,7 @@ class Beta(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -1746,7 +1746,7 @@ class DecayRate(MDGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -1774,7 +1774,7 @@ class DecayRate(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -1921,7 +1921,7 @@ class MatrixMDGXS(MDGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -1949,7 +1949,7 @@ class MatrixMDGXS(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2512,7 +2512,7 @@ class DelayedNuFissionMatrixXS(MatrixMDGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -2540,7 +2540,7 @@ class DelayedNuFissionMatrixXS(MatrixMDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 01a03f6506..d3d39e0f67 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -56,7 +56,7 @@ _DOMAIN_TO_FILTER = {'cell': openmc.CellFilter, _DOMAINS = (openmc.Cell, openmc.Universe, openmc.Material, - openmc.Mesh) + openmc.RegularMesh) # Supported ScatterMatrixXS angular distribution types MU_TREATMENTS = ('legendre', 'histogram') @@ -124,7 +124,7 @@ class MGXS(metaclass=ABCMeta): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -150,7 +150,7 @@ class MGXS(metaclass=ABCMeta): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -606,7 +606,7 @@ class MGXS(metaclass=ABCMeta): self._domain_type = 'cell' elif isinstance(domain, openmc.Universe): self._domain_type = 'universe' - elif isinstance(domain, openmc.Mesh): + elif isinstance(domain, openmc.RegularMesh): self._domain_type = 'mesh' @domain_type.setter @@ -675,7 +675,7 @@ class MGXS(metaclass=ABCMeta): ---------- mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', 'chi', 'chi-prompt', 'inverse-velocity', 'prompt-nu-fission', 'prompt-nu-fission matrix'} The type of multi-group cross section object to return - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -1985,7 +1985,7 @@ class MatrixMGXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -2011,7 +2011,7 @@ class MatrixMGXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2478,7 +2478,7 @@ class TotalXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -2504,7 +2504,7 @@ class TotalXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2609,7 +2609,7 @@ class TransportXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -2640,7 +2640,7 @@ class TransportXS(MGXS): If True, the cross section data will include neutron multiplication by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2834,7 +2834,7 @@ class AbsorptionXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -2860,7 +2860,7 @@ class AbsorptionXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2961,7 +2961,7 @@ class CaptureXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -2987,7 +2987,7 @@ class CaptureXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -3103,7 +3103,7 @@ class FissionXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -3140,7 +3140,7 @@ class FissionXS(MGXS): If true, computes cross sections which only includes prompt neutrons by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -3282,7 +3282,7 @@ class KappaFissionXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -3308,7 +3308,7 @@ class KappaFissionXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -3408,7 +3408,7 @@ class ScatterXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -3439,7 +3439,7 @@ class ScatterXS(MGXS): If True, the cross section data will include neutron multiplication by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -3602,7 +3602,7 @@ class ScatterMatrixXS(MatrixMGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -3656,7 +3656,7 @@ class ScatterMatrixXS(MatrixMGXS): If True, the cross section data will include neutron multiplication by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -4715,7 +4715,7 @@ class MultiplicityMatrixXS(MatrixMGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -4741,7 +4741,7 @@ class MultiplicityMatrixXS(MatrixMGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -4880,7 +4880,7 @@ class ScatterProbabilityMatrix(MatrixMGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -4906,7 +4906,7 @@ class ScatterProbabilityMatrix(MatrixMGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -5042,7 +5042,7 @@ class NuFissionMatrixXS(MatrixMGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -5073,7 +5073,7 @@ class NuFissionMatrixXS(MatrixMGXS): If true, computes cross sections which only includes prompt neutrons by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -5198,7 +5198,7 @@ class Chi(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -5229,7 +5229,7 @@ class Chi(MGXS): If true, computes cross sections which only includes prompt neutrons by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -5780,7 +5780,7 @@ class InverseVelocity(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh The domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization @@ -5806,7 +5806,7 @@ class InverseVelocity(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization diff --git a/openmc/plots.py b/openmc/plots.py index eb582502e4..d3359f5ac1 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -214,8 +214,8 @@ class Plot(IDManagerMixin): level : int Universe depth to plot at meshlines : dict - Dictionary defining type, id, linewidth and color of a regular mesh - to be plotted on top of a plot + Dictionary defining type, id, linewidth and color of a mesh to be + plotted on top of a plot """ diff --git a/openmc/settings.py b/openmc/settings.py index f348bc6cb0..a93dd345d9 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -9,7 +9,7 @@ import numpy as np from openmc._xml import clean_indentation import openmc.checkvalue as cv -from openmc import VolumeCalculation, Source, Mesh +from openmc import VolumeCalculation, Source, RegularMesh _RUN_MODES = ['eigenvalue', 'fixed source', 'plot', 'volume', 'particle restart'] _RES_SCAT_METHODS = ['dbrc', 'rvs'] @@ -45,7 +45,7 @@ class Settings(object): secondary bremsstrahlung photons ('ttb'). energy_mode : {'continuous-energy', 'multi-group'} Set whether the calculation should be continuous-energy or multi-group. - entropy_mesh : openmc.Mesh + entropy_mesh : openmc.RegularMesh Mesh to be used to calculate Shannon entropy. If the mesh dimensions are not specified. OpenMC assigns a mesh such that 20 source sites per mesh cell are to be expected on average. @@ -145,7 +145,7 @@ class Settings(object): Maximum number of batches simulated. If this is set, the number of batches specified via ``batches`` is interpreted as the minimum number of batches - ufs_mesh : openmc.Mesh + ufs_mesh : openmc.RegularMesh Mesh to be used for redistributing source sites via the uniform fision site (UFS) method. verbosity : int @@ -551,7 +551,7 @@ class Settings(object): @entropy_mesh.setter def entropy_mesh(self, entropy): - cv.check_type('entropy mesh', entropy, Mesh) + cv.check_type('entropy mesh', entropy, RegularMesh) cv.check_length('entropy mesh dimension', entropy.dimension, 3) cv.check_length('entropy mesh lower-left corner', entropy.lower_left, 3) cv.check_length('entropy mesh upper-right corner', entropy.upper_right, 3) @@ -640,7 +640,7 @@ class Settings(object): @ufs_mesh.setter def ufs_mesh(self, ufs_mesh): - cv.check_type('UFS mesh', ufs_mesh, Mesh) + cv.check_type('UFS mesh', ufs_mesh, RegularMesh) cv.check_length('UFS mesh dimension', ufs_mesh.dimension, 3) cv.check_length('UFS mesh lower-left corner', ufs_mesh.lower_left, 3) cv.check_length('UFS mesh upper-right corner', ufs_mesh.upper_right, 3) diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 1991c5be68..c9a8f9f9a8 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -72,7 +72,7 @@ class StatePoint(object): k_generation : numpy.ndarray Estimate of k-effective for each batch/generation meshes : dict - Dictionary whose keys are mesh IDs and whose values are Mesh objects + Dictionary whose keys are mesh IDs and whose values are MeshBase objects n_batches : int Number of batches n_inactive : int @@ -292,7 +292,7 @@ class StatePoint(object): if not self._meshes_read: mesh_group = self._f['tallies/meshes'] - # Iterate over all Meshes + # Iterate over all meshes for group in mesh_group.values(): mesh = openmc.MeshBase.from_hdf5(group) self._meshes[mesh.id] = mesh diff --git a/openmc/trigger.py b/openmc/trigger.py index 71f9c0b92b..98557aab58 100644 --- a/openmc/trigger.py +++ b/openmc/trigger.py @@ -31,7 +31,6 @@ class Trigger(object): """ def __init__(self, trigger_type, threshold): - # Initialize Mesh class attributes self.trigger_type = trigger_type self.threshold = threshold self._scores = [] diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index 7afd834cde..6755a64c82 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -309,17 +309,17 @@ - + 17 -182.07 182.07 - + 17 17 -182.07 -182.07 182.07 182.07 - + 17 17 17 -182.07 -182.07 -183.0 182.07 182.07 183.0 diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py index 79e2082da2..ed2e9b35b4 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -10,20 +10,17 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): super().__init__(*args, **kwargs) # Initialize Meshes - mesh_1d = openmc.Mesh(mesh_id=1) - mesh_1d.type = 'regular' + mesh_1d = openmc.RegularMesh(mesh_id=1) mesh_1d.dimension = [17] mesh_1d.lower_left = [-182.07] mesh_1d.upper_right = [182.07] - mesh_2d = openmc.Mesh(mesh_id=2) - mesh_2d.type = 'regular' + mesh_2d = openmc.RegularMesh(mesh_id=2) mesh_2d.dimension = [17, 17] mesh_2d.lower_left = [-182.07, -182.07] mesh_2d.upper_right = [182.07, 182.07] - mesh_3d = openmc.Mesh(mesh_id=3) - mesh_3d.type = 'regular' + mesh_3d = openmc.RegularMesh(mesh_id=3) mesh_3d.dimension = [17, 17, 17] mesh_3d.lower_left = [-182.07, -182.07, -183.00] mesh_3d.upper_right = [182.07, 182.07, 183.00] diff --git a/tests/regression_tests/mg_tallies/inputs_true.dat b/tests/regression_tests/mg_tallies/inputs_true.dat index b75573b0a7..5629587222 100644 --- a/tests/regression_tests/mg_tallies/inputs_true.dat +++ b/tests/regression_tests/mg_tallies/inputs_true.dat @@ -33,7 +33,7 @@ - + 10 1 1 0.0 0.0 0.0 929.45 1000 1000 diff --git a/tests/regression_tests/mg_tallies/test.py b/tests/regression_tests/mg_tallies/test.py index b0a8987108..e1c19e6b7e 100644 --- a/tests/regression_tests/mg_tallies/test.py +++ b/tests/regression_tests/mg_tallies/test.py @@ -62,8 +62,7 @@ def test_mg_tallies(): model = slab_mg() # Instantiate a tally mesh - mesh = openmc.Mesh(mesh_id=1) - mesh.type = 'regular' + mesh = openmc.RegularMesh(mesh_id=1) mesh.dimension = [10, 1, 1] mesh.lower_left = [0.0, 0.0, 0.0] mesh.upper_right = [929.45, 1000, 1000] diff --git a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat index 299da0713c..4f138b24f6 100644 --- a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat @@ -309,7 +309,7 @@ - + 2 2 -100.0 -100.0 100.0 100.0 diff --git a/tests/regression_tests/mgxs_library_mesh/test.py b/tests/regression_tests/mgxs_library_mesh/test.py index a9d24da934..b72fdf2f1e 100644 --- a/tests/regression_tests/mgxs_library_mesh/test.py +++ b/tests/regression_tests/mgxs_library_mesh/test.py @@ -27,8 +27,7 @@ class MGXSTestHarness(PyAPITestHarness): self.mgxs_lib.domain_type = 'mesh' # Instantiate a tally mesh - mesh = openmc.Mesh(mesh_id=1) - mesh.type = 'regular' + mesh = openmc.RegularMesh(mesh_id=1) mesh.dimension = [2, 2] mesh.lower_left = [-100., -100.] mesh.width = [100., 100.] diff --git a/tests/regression_tests/tallies/inputs_true.dat b/tests/regression_tests/tallies/inputs_true.dat index 3ec8d8d4ce..9409ca4a71 100644 --- a/tests/regression_tests/tallies/inputs_true.dat +++ b/tests/regression_tests/tallies/inputs_true.dat @@ -309,7 +309,7 @@ - + 2 2 -182.07 -182.07 182.07 182.07 diff --git a/tests/regression_tests/tallies/test.py b/tests/regression_tests/tallies/test.py index 543d0acd21..d4857b3ec7 100644 --- a/tests/regression_tests/tallies/test.py +++ b/tests/regression_tests/tallies/test.py @@ -1,6 +1,6 @@ from openmc.filter import * from openmc.filter_expansion import * -from openmc import Mesh, Tally +from openmc import RegularMesh, Tally from tests.testing_harness import HashedPyAPITestHarness @@ -28,7 +28,7 @@ def test_tallies(): azimuthal_tally2.scores = ['flux'] azimuthal_tally2.estimator = 'analog' - mesh_2x2 = Mesh(mesh_id=1) + mesh_2x2 = RegularMesh(mesh_id=1) mesh_2x2.lower_left = [-182.07, -182.07] mesh_2x2.upper_right = [182.07, 182.07] mesh_2x2.dimension = [2, 2] diff --git a/tests/regression_tests/tally_arithmetic/inputs_true.dat b/tests/regression_tests/tally_arithmetic/inputs_true.dat index 3605005add..842b0df623 100644 --- a/tests/regression_tests/tally_arithmetic/inputs_true.dat +++ b/tests/regression_tests/tally_arithmetic/inputs_true.dat @@ -309,7 +309,7 @@ - + 2 2 2 -160.0 -160.0 -183.0 160.0 160.0 183.0 diff --git a/tests/regression_tests/tally_arithmetic/test.py b/tests/regression_tests/tally_arithmetic/test.py index 3adf0c5bef..236f5cc384 100644 --- a/tests/regression_tests/tally_arithmetic/test.py +++ b/tests/regression_tests/tally_arithmetic/test.py @@ -10,8 +10,7 @@ class TallyArithmeticTestHarness(PyAPITestHarness): super().__init__(*args, **kwargs) # Initialize Mesh - mesh = openmc.Mesh(mesh_id=1) - mesh.type = 'regular' + mesh = openmc.RegularMesh(mesh_id=1) mesh.dimension = [2, 2, 2] mesh.lower_left = [-160.0, -160.0, -183.0] mesh.upper_right = [160.0, 160.0, 183.0] diff --git a/tests/regression_tests/tally_slice_merge/inputs_true.dat b/tests/regression_tests/tally_slice_merge/inputs_true.dat index ccd3655f9e..6f421b1d95 100644 --- a/tests/regression_tests/tally_slice_merge/inputs_true.dat +++ b/tests/regression_tests/tally_slice_merge/inputs_true.dat @@ -310,7 +310,7 @@ - + 2 2 -50.0 -50.0 50.0 50.0 diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index 20ab57a077..090e314483 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -24,8 +24,7 @@ class TallySliceMergeTestHarness(PyAPITestHarness): cell_27 = openmc.CellFilter(27) distribcell_filter = openmc.DistribcellFilter(21) - mesh = openmc.Mesh(name='mesh') - mesh.type = 'regular' + mesh = openmc.RegularMesh(name='mesh') mesh.dimension = [2, 2] mesh.lower_left = [-50., -50.] mesh.upper_right = [+50., +50.] diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index be88072d9f..742563d517 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -325,11 +325,11 @@ def test_find_material(capi_init): def test_mesh(capi_init): - mesh = openmc.capi.Mesh() + mesh = openmc.capi.RegularMesh() mesh.dimension = (2, 3, 4) assert mesh.dimension == (2, 3, 4) with pytest.raises(exc.AllocationError): - mesh2 = openmc.capi.Mesh(mesh.id) + mesh2 = openmc.capi.RegularMesh(mesh.id) # Make sure each combination of parameters works ll = (0., 0., 0.) @@ -349,7 +349,7 @@ def test_mesh(capi_init): assert isinstance(meshes, Mapping) assert len(meshes) == 1 for mesh_id, mesh in meshes.items(): - assert isinstance(mesh, openmc.capi.Mesh) + assert isinstance(mesh, openmc.capi.RegularMesh) assert mesh_id == mesh.id mf = openmc.capi.MeshFilter(mesh) diff --git a/tests/unit_tests/test_mesh_from_lattice.py b/tests/unit_tests/test_mesh_from_lattice.py index feac873260..0a01387cde 100644 --- a/tests/unit_tests/test_mesh_from_lattice.py +++ b/tests/unit_tests/test_mesh_from_lattice.py @@ -90,12 +90,12 @@ def test_mesh2d(rlat2): shape = np.array(rlat2.shape) width = shape*rlat2.pitch - mesh1 = openmc.Mesh.from_rect_lattice(rlat2) + mesh1 = openmc.RegularMesh.from_rect_lattice(rlat2) assert np.array_equal(mesh1.dimension, (3, 3)) assert np.array_equal(mesh1.lower_left, rlat2.lower_left) assert np.array_equal(mesh1.upper_right, rlat2.lower_left + width) - mesh2 = openmc.Mesh.from_rect_lattice(rlat2, division=3) + mesh2 = openmc.RegularMesh.from_rect_lattice(rlat2, division=3) assert np.array_equal(mesh2.dimension, (9, 9)) assert np.array_equal(mesh2.lower_left, rlat2.lower_left) assert np.array_equal(mesh2.upper_right, rlat2.lower_left + width) @@ -105,12 +105,12 @@ def test_mesh3d(rlat3): shape = np.array(rlat3.shape) width = shape*rlat3.pitch - mesh1 = openmc.Mesh.from_rect_lattice(rlat3) + mesh1 = openmc.RegularMesh.from_rect_lattice(rlat3) assert np.array_equal(mesh1.dimension, (3, 3, 2)) assert np.array_equal(mesh1.lower_left, rlat3.lower_left) assert np.array_equal(mesh1.upper_right, rlat3.lower_left + width) - mesh2 = openmc.Mesh.from_rect_lattice(rlat3, division=3) + mesh2 = openmc.RegularMesh.from_rect_lattice(rlat3, division=3) assert np.array_equal(mesh2.dimension, (9, 9, 6)) assert np.array_equal(mesh2.lower_left, rlat3.lower_left) assert np.array_equal(mesh2.upper_right, rlat3.lower_left + width) diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index e42f6240f5..5c5452d63e 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -24,7 +24,7 @@ def test_export_to_xml(run_in_tmpdir): s.seed = 17 s.survival_biasing = True s.cutoff = {'weight': 0.25, 'weight_avg': 0.5, 'energy': 1.0e-5} - mesh = openmc.Mesh() + mesh = openmc.RegularMesh() mesh.lower_left = (-10., -10., -10.) mesh.upper_right = (10., 10., 10.) mesh.dimension = (5, 5, 5) From 837796add055e4ca606d0449b32d17cceeacee19 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 7 Jun 2019 12:47:30 -0500 Subject: [PATCH 090/151] Including making TL RayHistory, last_dir available in surface.cpp and adding a reflection method to DAGSurface. --- include/openmc/dagmc.h | 8 ++++++-- include/openmc/surface.h | 1 + src/cell.cpp | 6 +----- src/dagmc.cpp | 3 +++ src/surface.cpp | 7 +++++++ 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/include/openmc/dagmc.h b/include/openmc/dagmc.h index 508b617d1e..adc4f47f1f 100644 --- a/include/openmc/dagmc.h +++ b/include/openmc/dagmc.h @@ -9,11 +9,15 @@ extern "C" const bool dagmc_enabled; #ifdef DAGMC #include "DagMC.hpp" -#include "openmc/cell.h" -#include "openmc/surface.h" +#include "openmc/xml_interface.h" +#include "openmc/position.h" namespace openmc { +extern moab::DagMC::RayHistory history; +extern Direction last_dir; +#pragma omp threadprivate(history, last_dir) + namespace model { extern moab::DagMC* DAG; } diff --git a/include/openmc/surface.h b/include/openmc/surface.h index 4db397a593..7a70f4767c 100644 --- a/include/openmc/surface.h +++ b/include/openmc/surface.h @@ -136,6 +136,7 @@ public: double evaluate(Position r) const; double distance(Position r, Direction u, bool coincident) const; Direction normal(Position r) const; + Direction reflect(Position r, Direction u) const; //! Get the bounding box of this surface. BoundingBox bounding_box() const; diff --git a/src/cell.cpp b/src/cell.cpp index e5d0f2f007..cf53b6e518 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -7,6 +7,7 @@ #include "openmc/capi.h" #include "openmc/constants.h" +#include "openmc/dagmc.h" #include "openmc/error.h" #include "openmc/geometry.h" #include "openmc/hdf5_interface.h" @@ -609,11 +610,6 @@ CSGCell::contains_complex(Position r, Direction u, int32_t on_surface) const //============================================================================== #ifdef DAGMC -extern moab::DagMC::RayHistory history; -extern Direction last_dir; -#pragma omp threadprivate(history, last_dir) -moab::DagMC::RayHistory history; -Direction last_dir; DAGCell::DAGCell() : Cell{} {}; diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 8add1f4f37..05e0659415 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -9,6 +9,7 @@ #include "openmc/material.h" #include "openmc/string_utils.h" #include "openmc/settings.h" +#include "openmc/surface.h" #ifdef DAGMC @@ -37,6 +38,8 @@ const bool dagmc_enabled = false; const std::string DAGMC_FILENAME = "dagmc.h5m"; namespace openmc { +moab::DagMC::RayHistory history; +Direction last_dir; namespace model { diff --git a/src/surface.cpp b/src/surface.cpp index f30b444310..83ee218cb0 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -6,6 +6,7 @@ #include #include "openmc/error.h" +#include "openmc/dagmc.h" #include "openmc/hdf5_interface.h" #include "openmc/settings.h" #include "openmc/string_utils.h" @@ -264,6 +265,12 @@ Direction DAGSurface::normal(Position r) const return u; } +Direction DAGSurface::reflect(Position r, Direction u) const +{ + history.rollback_last_intersection(); + return Surface::reflect(r, u); +} + BoundingBox DAGSurface::bounding_box() const { moab::ErrorCode rval; From 57c581e4b5444c4c14e1374788a184dec95b081e Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 7 Jun 2019 13:17:17 -0500 Subject: [PATCH 091/151] Update direction on reflection and use a different call to reset instersections. --- src/surface.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/surface.cpp b/src/surface.cpp index 83ee218cb0..69da679574 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -267,8 +267,9 @@ Direction DAGSurface::normal(Position r) const Direction DAGSurface::reflect(Position r, Direction u) const { - history.rollback_last_intersection(); - return Surface::reflect(r, u); + history.reset_to_last_intersection(); + last_dir = Surface::reflect(r, u); + return last_dir; } BoundingBox DAGSurface::bounding_box() const From d870734b5c571e51c3f0ab5a7aab1c4da8101e15 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 7 Jun 2019 13:32:33 -0500 Subject: [PATCH 092/151] Placing dagmc tests under their own directory. --- .../regression_tests/dagmc/{ => legacy}/__init__.py | 0 tests/regression_tests/dagmc/{ => legacy}/dagmc.h5m | Bin .../dagmc/{ => legacy}/inputs_true.dat | 0 .../dagmc/{ => legacy}/results_true.dat | 0 tests/regression_tests/dagmc/{ => legacy}/test.py | 0 tests/regression_tests/{ => dagmc}/uwuw/__init__.py | 0 tests/regression_tests/{ => dagmc}/uwuw/dagmc.h5m | Bin .../{ => dagmc}/uwuw/inputs_true.dat | 0 tests/regression_tests/dagmc/uwuw/results_true.dat | 1 + tests/regression_tests/{ => dagmc}/uwuw/test.py | 0 tests/regression_tests/uwuw/results_true.dat | 1 - 11 files changed, 1 insertion(+), 1 deletion(-) rename tests/regression_tests/dagmc/{ => legacy}/__init__.py (100%) rename tests/regression_tests/dagmc/{ => legacy}/dagmc.h5m (100%) rename tests/regression_tests/dagmc/{ => legacy}/inputs_true.dat (100%) rename tests/regression_tests/dagmc/{ => legacy}/results_true.dat (100%) rename tests/regression_tests/dagmc/{ => legacy}/test.py (100%) rename tests/regression_tests/{ => dagmc}/uwuw/__init__.py (100%) rename tests/regression_tests/{ => dagmc}/uwuw/dagmc.h5m (100%) rename tests/regression_tests/{ => dagmc}/uwuw/inputs_true.dat (100%) create mode 120000 tests/regression_tests/dagmc/uwuw/results_true.dat rename tests/regression_tests/{ => dagmc}/uwuw/test.py (100%) delete mode 120000 tests/regression_tests/uwuw/results_true.dat diff --git a/tests/regression_tests/dagmc/__init__.py b/tests/regression_tests/dagmc/legacy/__init__.py similarity index 100% rename from tests/regression_tests/dagmc/__init__.py rename to tests/regression_tests/dagmc/legacy/__init__.py diff --git a/tests/regression_tests/dagmc/dagmc.h5m b/tests/regression_tests/dagmc/legacy/dagmc.h5m similarity index 100% rename from tests/regression_tests/dagmc/dagmc.h5m rename to tests/regression_tests/dagmc/legacy/dagmc.h5m diff --git a/tests/regression_tests/dagmc/inputs_true.dat b/tests/regression_tests/dagmc/legacy/inputs_true.dat similarity index 100% rename from tests/regression_tests/dagmc/inputs_true.dat rename to tests/regression_tests/dagmc/legacy/inputs_true.dat diff --git a/tests/regression_tests/dagmc/results_true.dat b/tests/regression_tests/dagmc/legacy/results_true.dat similarity index 100% rename from tests/regression_tests/dagmc/results_true.dat rename to tests/regression_tests/dagmc/legacy/results_true.dat diff --git a/tests/regression_tests/dagmc/test.py b/tests/regression_tests/dagmc/legacy/test.py similarity index 100% rename from tests/regression_tests/dagmc/test.py rename to tests/regression_tests/dagmc/legacy/test.py diff --git a/tests/regression_tests/uwuw/__init__.py b/tests/regression_tests/dagmc/uwuw/__init__.py similarity index 100% rename from tests/regression_tests/uwuw/__init__.py rename to tests/regression_tests/dagmc/uwuw/__init__.py diff --git a/tests/regression_tests/uwuw/dagmc.h5m b/tests/regression_tests/dagmc/uwuw/dagmc.h5m similarity index 100% rename from tests/regression_tests/uwuw/dagmc.h5m rename to tests/regression_tests/dagmc/uwuw/dagmc.h5m diff --git a/tests/regression_tests/uwuw/inputs_true.dat b/tests/regression_tests/dagmc/uwuw/inputs_true.dat similarity index 100% rename from tests/regression_tests/uwuw/inputs_true.dat rename to tests/regression_tests/dagmc/uwuw/inputs_true.dat diff --git a/tests/regression_tests/dagmc/uwuw/results_true.dat b/tests/regression_tests/dagmc/uwuw/results_true.dat new file mode 120000 index 0000000000..f09f75fa58 --- /dev/null +++ b/tests/regression_tests/dagmc/uwuw/results_true.dat @@ -0,0 +1 @@ +../legacy/results_true.dat \ No newline at end of file diff --git a/tests/regression_tests/uwuw/test.py b/tests/regression_tests/dagmc/uwuw/test.py similarity index 100% rename from tests/regression_tests/uwuw/test.py rename to tests/regression_tests/dagmc/uwuw/test.py diff --git a/tests/regression_tests/uwuw/results_true.dat b/tests/regression_tests/uwuw/results_true.dat deleted file mode 120000 index 3711895aba..0000000000 --- a/tests/regression_tests/uwuw/results_true.dat +++ /dev/null @@ -1 +0,0 @@ -../dagmc/results_true.dat \ No newline at end of file From 853438083ae01fae6b18c5860e8e7157943c75ee Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 7 Jun 2019 15:25:09 -0500 Subject: [PATCH 093/151] Change order of x-oriented hex lattice universes to clockwise --- openmc/lattice.py | 96 +++++++++++++------- tests/regression_tests/lattice_hex_x/test.py | 6 +- 2 files changed, 64 insertions(+), 38 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index b2bdc8a564..30d9ad6de3 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -1241,8 +1241,8 @@ class HexLattice(Lattice): else: i_within = 5*g - z - if (self._orientation == 'x') and (i_within > 0): - i_within = 6*(self._num_rings - i_ring - 1) - i_within_ + if self._orientation == 'x' and g > 0: + i_within = (i_within + 5*g) % (6*g) if self.num_axial is None: return (i_ring, i_within) @@ -1376,6 +1376,7 @@ class HexLattice(Lattice): lat = cls(lat_id, name) lat.center = [float(i) for i in get_text(elem, 'center').split()] lat.pitch = [float(i) for i in get_text(elem, 'pitch').split()] + lat.orientation = get_text(elem, 'orientation') outer = get_text(elem, 'outer') if outer is not None: lat.outer = get_universe(int(outer)) @@ -1390,7 +1391,7 @@ class HexLattice(Lattice): if n_axial > 1: univs = [deepcopy(univs) for i in range(n_axial)] - # Get flat array of universes numbers + # Get flat array of universes uarray = np.array([get_universe(int(i)) for i in get_text(elem, 'universes').split()]) @@ -1400,28 +1401,53 @@ class HexLattice(Lattice): # Get list for a single axial level axial_level = univs[z] if n_axial > 1 else univs - # Start iterating from top - x, alpha = 0, n_rings - 1 - while True: - # Set entry in list based on (x,alpha,z) coordinates - _, i_ring, i_within = lat.get_universe_index((x, alpha, z)) - axial_level[i_ring][i_within] = uarray[j] + if lat.orientation == 'y': + # Start iterating from top + x, alpha = 0, n_rings - 1 + while True: + # Set entry in list based on (x,alpha,z) coordinates + _, i_ring, i_within = lat.get_universe_index((x, alpha, z)) + axial_level[i_ring][i_within] = uarray[j] - # Move to the right - x += 2 - alpha -= 1 - if not lat.is_valid_index((x, alpha, z)): - # Move down in y direction - alpha += x - 1 - x = 1 - x + # Move to the right + x += 2 + alpha -= 1 if not lat.is_valid_index((x, alpha, z)): - # Move to the right - x += 2 - alpha -= 1 + # Move down in y direction + alpha += x - 1 + x = 1 - x if not lat.is_valid_index((x, alpha, z)): - # Reached the bottom + # Move to the right + x += 2 + alpha -= 1 + if not lat.is_valid_index((x, alpha, z)): + # Reached the bottom + break + j += 1 + else: + # Start iterating from top + alpha, y = 1 - n_rings, n_rings - 1 + while True: + # Set entry in list based on (alpha,y,z) coordinates + _, i_ring, i_within = lat.get_universe_index((alpha, y, z)) + axial_level[i_ring][i_within] = uarray[j] + + # Move to the right + alpha += 1 + if not lat.is_valid_index((alpha, y, z)): + # Move down to next row + alpha = 1 - n_rings + y -= 1 + + # Check if we've reached the bottom + if y == -n_rings: break - j += 1 + + while not lat.is_valid_index((alpha, y, z)): + # Move to the right + alpha += 1 + j += 1 + lat.universes = univs return lat @@ -1469,7 +1495,7 @@ class HexLattice(Lattice): theta = 0 y = middle - # Climb up the top-right. + # Climb down the bottom-right for i in range(r): # Add the universe. universe = universes[r_prime][theta] @@ -1479,7 +1505,7 @@ class HexLattice(Lattice): y += 1 theta += 1 - # Climb left the top-left. + # Climb left across the bottom for i in range(r): # Add the universe. universe = universes[r_prime][theta] @@ -1488,7 +1514,7 @@ class HexLattice(Lattice): # Translate the indices. theta += 1 - # Climb down the middle left. + # Climb up the bottom-left for i in range(r): # Add the universe. universe = universes[r_prime][theta] @@ -1498,7 +1524,7 @@ class HexLattice(Lattice): y -= 1 theta += 1 - # Climb down the down left. + # Climb up the top-left for i in range(r): # Add the universe. universe = universes[r_prime][theta] @@ -1508,7 +1534,7 @@ class HexLattice(Lattice): y -= 1 theta += 1 - # Climb right the down right. + # Climb right across the top for i in range(r): # Add the universe. universe = universes[r_prime][theta] @@ -1517,7 +1543,7 @@ class HexLattice(Lattice): # Translate the indices. theta += 1 - # Climb up the middle-right. + # Climb down the top-right for i in range(r): # Add the universe. universe = universes[r_prime][theta] @@ -1528,7 +1554,7 @@ class HexLattice(Lattice): theta += 1 # Flip the rows and join each row into a single string. - rows = [pad.join(x) for x in rows[::-1]] + rows = [pad.join(x) for x in rows] # Pad the beginning of the rows so they line up properly. for y in range(self._num_rings - 1): @@ -1806,41 +1832,41 @@ class HexLattice(Lattice): y = middle for i in range(r): - # Climb up the top-right. + # Climb down the bottom-right rows[y].append(str_form.format(r_prime, theta)) y += 1 theta += 1 for i in range(r): - # Climb left the top-left. + # Climb left across the bottom rows[y].insert(0, str_form.format(r_prime, theta)) theta += 1 for i in range(r): - # Climb down the middle left. + # Climb up the bottom-left rows[y].insert(0, str_form.format(r_prime, theta)) y -= 1 theta += 1 for i in range(r): - # Climb down the down left. + # Climb up the top-left rows[y].insert(0, str_form.format(r_prime, theta)) y -= 1 theta += 1 for i in range(r): - # Climb right the down right. + # Climb right across the top rows[y].append(str_form.format(r_prime, theta)) theta += 1 for i in range(r): - # Climb up the middle-right. + # Climb down the top-right rows[y].append(str_form.format(r_prime, theta)) y += 1 theta += 1 # Flip the rows and join each row into a single string. - rows = [pad.join(x) for x in rows[::-1]] + rows = [pad.join(x) for x in rows] # Pad the beginning of the rows so they line up properly. for y in range(num_rings - 1): diff --git a/tests/regression_tests/lattice_hex_x/test.py b/tests/regression_tests/lattice_hex_x/test.py index c958387ddf..405e9d1e9c 100644 --- a/tests/regression_tests/lattice_hex_x/test.py +++ b/tests/regression_tests/lattice_hex_x/test.py @@ -161,9 +161,9 @@ class HexLatticeOXTestHarness(PyAPITestHarness): arr.append(fuel_ch_univ) universes.append(arr) universes[-1] = [tube_ch_univ] - channels = [(7, 16), (7, 13), (7, 10), (7, 7), (7, 4), (7, 1), (5, 0), - (4, 33), (5, 25), (4, 27), (5, 20), (4, 21), (5, 15), - (4, 15), (5, 10), (4, 9), (5, 5), (4, 3)] + channels = [(7, 2), (7, 5), (7, 8), (7, 11), (7, 14), (7, 17), (5, 0), + (4, 3), (5, 5), (4, 9), (5, 10), (4, 15), (5, 15), + (4, 21), (5, 20), (4, 27), (5, 25), (4, 33)] for i, j in channels: universes[i][j] = abs_ch_univ lattice = openmc.HexLattice(name="regular fuel assembly") From d072adc05064556df10628c68c0866fe30570a47 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 7 Jun 2019 17:29:41 -0500 Subject: [PATCH 094/151] Fixes for reflecting DagMC BCs --- include/openmc/surface.h | 2 +- src/cell.cpp | 6 ------ src/particle.cpp | 12 +++++++----- src/surface.cpp | 5 ++--- tests/regression_tests/dagmc/refl/dagmc.h5m | Bin 0 -> 1250249 bytes 5 files changed, 10 insertions(+), 15 deletions(-) create mode 100644 tests/regression_tests/dagmc/refl/dagmc.h5m diff --git a/include/openmc/surface.h b/include/openmc/surface.h index 7a70f4767c..6fd0a73ae1 100644 --- a/include/openmc/surface.h +++ b/include/openmc/surface.h @@ -82,7 +82,7 @@ public: //! \param[in] r The point at which the ray is incident. //! \param[in] u Incident direction of the ray //! \return Outgoing direction of the ray - Direction reflect(Position r, Direction u) const; + virtual Direction reflect(Position r, Direction u) const; //! Evaluate the equation describing the surface. //! diff --git a/src/cell.cpp b/src/cell.cpp index cf53b6e518..17ad62c88c 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -616,18 +616,12 @@ DAGCell::DAGCell() : Cell{} {}; std::pair DAGCell::distance(Position r, Direction u, int32_t on_surface) const { - // if we've changed direction, reset the history and update last direction if (u != last_dir || on_surface == 0) { history.reset(); last_dir = u; } - // always reset the history if we're on the surface - // if (on_surface == 0) { - // history.reset(); - // } - moab::ErrorCode rval; moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_); moab::EntityHandle hit_surf; diff --git a/src/particle.cpp b/src/particle.cpp index cbc6217caa..52ac941de4 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -461,11 +461,13 @@ Particle::cross_surface() // boundary, it is necessary to redetermine the particle's coordinates in // the lower universes. - n_coord_ = 1; - if (!find_cell(this, true)) { - this->mark_as_lost("Couldn't find particle after reflecting from surface " - + std::to_string(surf->id_) + "."); - return; + if (!settings::dagmc) { + n_coord_ = 1; + if (!find_cell(this, true)) { + this->mark_as_lost("Couldn't find particle after reflecting from surface " + + std::to_string(surf->id_) + "."); + return; + } } // Set previous coordinate going slightly past surface crossing diff --git a/src/surface.cpp b/src/surface.cpp index 69da679574..a643d52730 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -256,13 +256,12 @@ DAGSurface::distance(Position r, Direction u, bool coincident) const Direction DAGSurface::normal(Position r) const { moab::ErrorCode rval; - Direction u; moab::EntityHandle surf = dagmc_ptr_->entity_by_index(2, dag_index_); double pnt[3] = {r.x, r.y, r.z}; - double dir[3] = {u.x, u.y, u.z}; + double dir[3]; rval = dagmc_ptr_->get_angle(surf, pnt, dir); MB_CHK_ERR_CONT(rval); - return u; + return dir; } Direction DAGSurface::reflect(Position r, Direction u) const diff --git a/tests/regression_tests/dagmc/refl/dagmc.h5m b/tests/regression_tests/dagmc/refl/dagmc.h5m new file mode 100644 index 0000000000000000000000000000000000000000..ac788d2debfb797d46e0b269ae09e90b9a2c29c8 GIT binary patch literal 1250249 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ï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

^gPMa!u6hinr31gNz61)xSD4S2E6Xuy$O1j`7*U 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 rxe&%Jp5Gkc}N zJ@?}IW%f#jd+x>aE9{jB_uPx;SJ^8b?ztDwud!Dw+;cCUUuUmqxaVFxzrkLSaL>JX z{tJ7B!#(%n`LFC13isTL=fAO6Fx+!5p8w8XfpE{gc>V`_`NKW;;`yKKw7tjA_xfjp>WiNZU z=UzO&!(O&<&%Jnlm%Xgvo_p~;L5}}kXUY=pxfjpxVLvnb-g7UW-^>07?0e6>czz%I znb`N9d-42!_TOjUd+x>a2iVWZzW3aV=MS=c%G2`_t^KId-42H_S3NMJ@?}IW9+A9-+S)G^T*kLmwoTK7tfzy{~h+d z=UzO2lKoWdd(XXi{uKKu+4r7%@%(A_Q?Tzn_u~08?7z*v_uPx;&$9m(``&Xeo86Mr7Jli`VQJ30OwZoh>;i`#GG&){|n{At`yi9dzg zsqiOp`yKoV+BkKuM2{88L~4^N2OY4JyJJ01QoZl}i|!tD(BgSed$e*m}N z$M47OO!$4c{Q-V2ZfC~t!R;(~0^H7u-{m3{xAWnD;&y)g58N(*|Bl-Q@!xQ}5dJG}7sh|V?IQRM+%Ae=$L(VH zHQX+aU&ZYb_!ZnPiC@O;QuxofT^hfH+hy>JxLp?i3AfAPKjLW zZV$lM+9&S&<=i>Hcd=73;!Dr+4RD2e0Ps3;8 z_H_IOo)fZX;L~&ccVN%Nr{VT2d@63w#;4%+9DFiv&&4O<_B?zdZqLUj;PwK1JZ>+< z$Km!Od@OD+#>e3H5_~jnFU3dU_A-1VZZF42;Pwi9IBu`RhvD`rd?;?O#)shc8hkKr zuf+%9_BwnZZm-7&;PwW*KW=Zt`{DK`yf1EV#{1y*7Q8oZZ^e7z_BOmHZg0nX;Pwu@ zJ8tj9yW#dOyen?+#=GG59=tPde~5R&?T_$|xcxES0k=QF+vE19cstzQi?_w?eRvz( z-jBD&?E`o#+&+l6#O=@U7P$R6-W<2Tz?H?ZbFu+&+Rg!tJAYL)`uf zZ-Coh|3*+{$cp=>W4KIk>zvBgP`wu)nZvTnr z!|j`RUfli*&x6~4mPKZC!G+t1=};r4TQa@di){W&VWCN+Zpi(aQl7ye%#K4--p{D;P>KoX8a!9 z&Vnbv?X37+rk#zx!?d&0|1#|y^gm2HCw-e~=b~>h?cDU=Ogj(#7t_v5-(=eP=s%fu ze)u2Uzv7c`WL2MgucPFi_+Jbb}{-I(=JY5W!fd^D@?m2eVJ*O zqJL)ErRhsdy9|AiX_uvcV%p{CADMP}`U2CgK%ZyY73p(KyAu5a)2>XPW!hEfGfcZG zeVS=kqfasI>hwvbU4uTsv}@AGnRYGu7}Kszf6uh*(BCoby7ae9yB_@w)2>f{&9ocP zUoq{5^iihWh(5x!8`FoGb`$y#({4(C$+Vl%Uoh?F^yf^w1^pS*Zb=_x+O6mVOuIF` zpJ}(D_c86Z^j@ajj{cNsx2Hd0+8yYRnRZ9|Bc|Pn{*Y;RruQ)IF7$4u-Id1|BA7rm8f_olZn?LPEorrnp`#I*a-8<}>0dIQrQK(A-o1L<{4 zdl0>rX%D8?Fzq4qYNkDuUd6PB(JPtuaC!yP9zic>+9T;@OnVf)lxdHqmoV)y^kSwx zmR`iP$I%O!_IP>$)1E-jXWA3#c}#l}J(p=urspv2DfDcnJ(Zrtw5QQCnf7#gMmX2q z_v?Aj_FvzB9ov7u{`<21&*y&*+y6fN&u#nPpZ`7C{`c*FueN`E{Oe%**VDglwtxNo z>ume?i+^9({{7|ON49@I`uCmf-@pESYWw%Qe_z}F`S8zy?Vl(A+}Qs4^Us;>pI84} z+Wz_W&#~?I1OMFHe*f_MgzfhmzpvPSfAaf~?e{akZ`pqT^ZT6b_e;Mo+J1la`>5^r zW54g(e*gCSwC(qMzpvXn`E$^nY44)DG40)SSEjv(?!vS`q&qY1kLXTJ`(wHz)Bc3+ zz_dT5+cWLGbUUWKk8aDf_tR~d_5r#z(>_SIV%ne4Et&S`bPJ~a1>Kx!e@QoE+K1?- zO#3k1glQk48#C>rbR(wy72S|&e@!=F+TYOinfAAIJ*NE~U6*NpPuF4E$LQKj`#4>T zX`i5LGVPOe4W@mHuFkYi)76;v8M-RdK1)|&+CR{hnf5ul64O3US7h23=n72xN4h-I z{)sNfv@guWx0>ZC?-j0JnYpEE8_~dfWTB?dx+HaogAP zGT^qa|E0%mUoT9D+rGY-7Poyp@;%)4^~*H4?dzSXaog8N-^FcTPkjfsef>2RZu@#| zO5FDK-4wX(>%nj1wyz(*h1*ZY&;w(k$Tg4@2I@G@@u{zGEi_Wg>NaNGAcUc_zR4|xH%egEWn-1hyJ zM7ZtyGtc3+@8>*=+rIzv3~u{=(bKr?`%6#Zw(mzhiQB$^^#pGFe%Ir;?fYYo;kNIm zJ&N1D|CSK9eZTGz-1hywhjH8Y10TX|-#>g1w|&3y0o?Ze$@_8J_cQOqZQuXA7q_$U zy8b=5?fa_{7VTP1vPzpR=&b;69gOm&JXK!!C#W+=pEr_c;-}0`7Aqc17Ih zQ0z*$&#l;%ai4RstKdEtV^_s}j>fKr``nFP9rrmMy9VxaJ$6mp=YZ^5xX%sQwQ-*_ zvg_bJmt@z)eU8blhx^={htX&DpJSpR=>u;69gUx5a&q&u)kN+@IYZ_c=kk z1MYK$c1PUj5baL5&n?=Wai4RvyWl<-X?Mkaj?(Ui@8UTeyF0!Uw|n3_aJwhI9k+Yo z+i<%#z7@Co;9GFJFTNSK`{A2#yFb1Ww+G-GaC;!W9=8YK>u`H8z81HK;A?PuD83rE zhvBPmdpN!lw@2VBaC;=a9Jfc|%W!)%z7)5|;7f3OEWQ}G$Ki``dpy1nw=JN}%H9e*y!jz33a$Dcd0MyR+lZ>Dlq;`t0~~fOh=3K|B7O zp&frN(T+dIXvd#>wByf7+VSTq?f7$;cKo?bJN}%f9e*y=jz33g$Dcd3{+z8He=gUKKgVmwpZm2>@pEF;_}>NlG+q_A&)`*X`z&4=w|~GZ;r2Pa zB5t3@E8zA8ygY9Ih?m3dpYXD{eGxB%+n4atxcxI;3b!xgC2{)-UIMqT;>B_M8eR;y zuj55=`vzVFw|~J495AFNoW};{|a04?I6^|B2_r?VEUB-2MyCgWG@OxpDgz zo(s2c<2iBrA3O(c|BGkG?K^lj+`fxv#qIbx6n6aF3Ojzzg&jW^!;YV$VaLziu;b@+ z*zt2c?D#n#cKqBBJATfH9Y2@Ej-O*<$Im^nKNrT1 zpCe<(&z-U3=hWEob8YPSIXHIw+#EZ8&W`Qp^333$`xEhVy-Aq*)I$G(=lV?UdG6Gw zaL=jF;@)J=?b-Mw+@6C^#O=BG1l*p7kH_u#_&D5NfRDxPh4>iUUWAXv?Zx;g++Ko@ z#OE+}?us#Oh_mbiTYZ-Ltf@#eVw z8Qu)HKgXNm_7`{)-2M`8jN6CsM!0<#Z;0DR@CLYj6t9olU*Yv|`)j-|ZhwQGH z+PM84UJJLs$7|yDF}wzDAIGcX_6fWiZlA=f;`S-L3T~gqE93SVyb^Ao#Vg|W4|oOK zK8Kgb?elm!+`fR9#qA&QGPwN{UK+PA;-zr=5?&Iwf5uDT_GP>{ZePKR;r3O$C~jZF zi{SQkyfALxzzgB_FL*)R{uM8P+rQ!Yar<{XA8!AF=f&+m@jSSF6VHv?f8n`s`)@oa zZr{Rl;P!1iJ8u7jXT$A(@vOLg2hW1rck#@)ognvr|D5#!Zr_7v!tHzU_i_6^JR@%3 zk7vN`2k`W`{UDwWw;#gO;`YP%d$|1wo(8uQ;;C`_QT$!pehhyHw;#t-;r0`FO5A=D zPl4M{;cw&i)A(Ds{S2NQx1Yt6;r4U*o4B0_e*?Fl$6v?o7x33``$aq{Zoh=Tirb0t zB)I)D{tEuT#$U!?@jf##o`imt`4Vm?#b3nj*YFo``*r+z+G20}I|F_{ZfC^r!|nI+dvQAxeh+ScfG5E1%=q1c3I6Z@>@4^l+|G*s zi`&`oe{efHejB%Q;J0u)C;m5X=feNO?cDfH+|GmliQ9ScKX5xA{yT2x$A81^0{E}E zT@e2Tw+rDnaJw*m9k+|%*KoTieigTi;a70GIDQ$oOW;4_c1ipaZkNI@;&y5LC)_TB z|A^aV@e8<|Aw=3djak~8Uy9o!@g=xD3SW%dqwz(!JqBNh+hg$sxIGS^kK5z%dAL0RpNrcQ z@j19X37?JIlkr)&Jq4eM+f(rwxIGP@j{mRmY4~*SGpFJ+=$XtZxIGJ>jN7yENw_@+ zpNQLY@d>y+4+ycLy#eow+Z*vdxV;JQjoX{? zUbwvl?}^)6@gBIn4eyTI+wpF=y#w!x+dJ_txV;PSjN7~MPPn}X?}*zU;vI1NBfLFs ze~h=o?N9Kwxcw>K2DkU(t#NxF-U_$(<1KOf0Nw((58};n`!l>5Zhww9#qBTfCb<12 z-WazJ;f-+nFy0WikKhe(`zT%?x4**c;r7>fUEKZ#uY=p);Tde~;J1?PGWi z+&+$1$L$k%HQYXlSHv&<@zJV9Q?O*VM zxcw_$0Jnd`^W*mKcs|_z1J8@wf8u#?`zD?nxBtR(;r8EnPTanQ=fLgTcy`?W2hWDv z|KeG3`wpH3x9{SaaXUet1poJcAK>;qcqZJw7k?kO@53|V_WgJU+2UiY zJS}cNjK7E5kKk!=J0YGLw;#pd#qG!NcX0c0JQZ#~fv3dnC-D@x{S^K-ZabrpU3St@I<)%CjK05C&Qn`?d13~xcwIXG;Y6*KZV;V z@F#IQCH@3%r@|k{?RW6UaQj{SQQS_AC&cYE_#?Re9{wOE%4v>Q9BEM2e-50|KfHw{2$!Tj^D=Z9QZBV z&WZnx+qv++a631C6Swo=f8ust{14pDhyRY-`SIUyy8!+xZWqLV!Rin zIu8V(-+x76TaJxQ!6t^4TM{v6#ei*kK;fHX$G5#fPH^INa?WXwWxZMo@ z47Z!(2XVUvegL;y;`?#C6}}I*TjP6iyAA#+Znwoh!R>bV$GF`d{|L7`;2+|4M|=-% zcfxn$c4vGSZg;_V;&xYj2X1%6x8rtqd>d}}z_;RdPkak*_rf>hc5i$WZuh}A;&xwr z18(=j*W-47d>w8Nz}MpTKzt2u55iaD_F#M!ZV$m%;`UH{1#S<+m*e(ud>L+!z?b6o zNPG!ykHQz@_Go+&ZjZqi;`Ug40d9}O=i~Nxd>(F3z~|!jM0^fzPr_&8_GEk(Zco8y z;`UU025wKor{n)?d>TI8`^>5M402yU;!2jli? zd=PH0!3W~@T6_R*ufzM}_IkV@Zg0T*;`Tz4Rya{f9i8sdWLwF z|G@L&_Mdnj+`ftD#_hlGT)6!=o)fok;W=>oHl7`~|G~52_P=;m+`fZn!R@{Vbjgx1Yn`#O*}*8@T;E z{yJ{IfWL;@FXBmY`z8EU+)j)q!R?pvSMdKe{xbfG_nC?DB=oDymvB2N{vvL_hQEN@ zuj9|-_8WL2+Y7{89&Meu95T@=5H+r{uJxLq8-jN2vf zpK-e+ehIfr;TLhcH2xEAm%)F;?Xvg<+%AWo$L;d?Ioz&*|A5;S@w2#H2|t6|mGRTK zT?Idd+g0(CxLplDf!o#b*T%oa?K=23xLp_j8n^4=U*UFr z{3vcWz>nZ|L;NsqH^L9$c4PcY+-`z@f!j^-&vCmM{uypJ#}DFm3;Y0Xx5W43b}M`z zZnwtw;&vPSQ`~Nge}dcX@Q-o3J^m4Hcfdcy?T+{!-0p<$#_i7dF5K>d@5Jq{_zv9e zhHuC1?)Wy`?tyQ`?Vk7+-0p>M#_ittCfx3WZ^Z4s_y*kWhp)%&{`flF9)PdK?Sc3j z+#ZCl#_hrQD%>7|uf*-4_zK(}hA+qM;rKG#9)T~#?UDEr+#ZE5#_iGgBHSK>FU0M! z_yXJ>htJ3D@%TL4o`BEA?TPpt+@6Hb#_h@YEZm-g&&2Ji_zc{hhEK=;*Z4Giy7!q= z@fq|?<`mqXg-^!q+4v;fo`X-s?YZ~_+@6Pz$L;y}INV-J)g%8H<)%YOXUV{(B?X~y-++K(G$L;la zKiuAc_r>jvcpu!}g!jhn&3G@|-h%hU?X7qZ+}?(F$L;NSH{9NVcg5|Uco*E>g?Gm7 z-FPS5-h+3=(hnL6g^LRPjzJQm-?H}zv20D`*%DaZvTPj#qB@wJh*)m&yCxE;kj`8 zZ#*Y%-@E+!!zRc z{dfl4egIF8+YjRDaQh)VEp9)IzlYn8;AwC>A)XqyAI0Cr?Z@zUaQksQ6>dL)r^M|i z@f5iI6#h1DKaIbI+t1+1ar;?38E!v^zlqz4@HcS#dHi+UegS_Cw_n7Q;`U4UtGJyQ zPlDSo<4NdOnJ?jXQv5~Sehq&Cw_nGf$L%-pM7aGX{v2*6!=J_N z`(6A|+)j-r#O*ZrBe?w@{xEK* z#UH}$bohg~ogRMxw=>}P<90^;KHPpEzZbVN;rHP72Y3S9&Wzt>+F9s3Ogk(6FVoIO z|HHJi)3=#+4*C|;&Po5xv~$saG40&+O{SfP{*!6vrT<{s`RL!7c7FOdrd@#km1!5G ze_`5%=o?JCFnyhA7oo2)?V|Kmrd^D_!nBLimzj16`e&wHlD@>WOVJmZc4_)2rd@{q zk!hEuFEH(L^m(RTo<7I4E6_hM?TYkSrd^3X!?Y{Yr(JjY?Yi{WOuHWa71ORyA7$DN=p#(KA$^!>H=++Q z?Z)(%OuGsF1=DUyf6la<(VsEx=JY|P-GV;Av|G~qnRY9BAJcA4?`7I;=ueq;Tly2G z-H!g4X}708V%i<(51DpHdJog?MDJ$Wo#|any9>ROX?LY}Fzs&icBb8(-o~_h&|8^y zPkIZ}?nQ5A+P&#bOuG-gk!km(H!$sf^m?Y`_@CSMzd!$bviJeXzmIJHe)R7<+rNMP`_%UDcmKY&{qy0U1KU4O{<*RJ^XH#4+dr@VxwQTB?Vn@Y z?+5<5xBdR%_X*qYH-2BS{r=?lA=~d~e&4eF{^$2O+wYftU$p)H>i1FG@5g@Mwf+9> z_i5Yj_kLfuxAW(tJJa4lcVpT+>8?zB7u|(v@1{F5?LBlSru`w^k!gQKcVOBd)9sn| zCv-ce{VCm+Y44@mFztPGYo@)QZpE|@&@GwvLAnLg{)}$Uv_GetG3_ttrcC=wx(U-h zL^o#Ihv`O4`v~2TX&z8S8+t)i&DlzKh$wp85`M`}%7t-1ha_l(_BdyD4zn z*Mr~2ZC^ir3%7l}IXQ0o`gAhf_Vw&Haog9w-@t8OFMl1keSQ5k-1hbOq`2+t_pjo% zulFaxZQmbw1-E@a;bq+R{fET3?fVrk;kNH@yolSrAMyfj`~J!Exb6EbiE!KZXP(1t z-_Lm#w|)QT8Qk{$qNj1&_m`f+ZQqZ261RQ->IvNT{jSGx+xN#F!)@PBdla{Q|1BYI z`+nUcxb6FU597A)2R?+`zJK^2Zu@@Y1Gw$`llSAc?`PhJ+rIyKFK&Op`vCXgw(qYd zz-`};^?g^{_iufl*7p5g-`BN$f7thdZQoD!ePi4ApM9U%_Wf$#m$q~BpUd~L?L4^e zd)s+&-zT^8;l8hK=f`~?-Y$UqzP()#_kDi55bkpUc46G-2<#%b&mGuBai3GLi{U=k zU>C=I4#F;h``m^iv5J=t|}pOdod;XYSo*T;Ph%Wi=C+?L%C_c<@S5$FywAo)dlfzew^!qXaeECu2)Ebb195vDJ^;7ZRUx zaeE8i3%9r8J#l**-UGL{6aeEKm3AaDQJL2|7cn946 z7;lf;pWy9q`%}CvZtun0;PyVeHE!?6TjBNryd`cQ#9QF@XLxhm{v2Kar;ZW z32q<48{_t2yb*36!5iZCQM>_ee}&h_?XU5Axcv=Y7q`E~>)`fxcx~MN92cf7 z3rUCDe!fUr-1hTG-otG_za$NA`*|m+aof*Fc^9|+Je7BF+s|J~h1-5!OG@1K^IcNl zwx0*{Hg5a*F>m3vpEr{nxBYyYWVrqRXnV^jFUvRJ76XtHkP=X_8@m++q`SMjySux) zySux)yHiTR!tOY0z3%=0aIN*ObG{tE?6voGU(XB*GyG=8=kb0vPtkSnfAb_=_kKA~ z&~@*x^Eh4iemq&|y7%vSjIMjXpGWDs_Xm1}u6sYB%yixR4`rh3-mfSlUHAS*57Tw; zhm?V?d;g^Lblv+crK9WKpD8U}_kK=k=(_iRdWf!jzo^u7-TO)wAWDP8w|T}kM=_xE~$u6sYQ#B|;Jhb5xx-ft`+UHATE z_tSOnXLcW5_x@-1()D+_570ey-TSL0pzGd`&HJwD-oMTJwCUdO&HK9P-XG5U!0Fyk z&ilsc-ha;f%<0~*&im5o+4*&OA3Hq<-TU6@IqBXfPydweef9KQbnnBb=caq#K0OcJ z`~2y7>E0Jm&qwz@f_i?s_Z`#=(7jKgUXbp64fR5F?}MlprhDH+y$Id=Eb2w+-j`7? zM)y9BdU3kSgKP7gH}s_dc3>dAj%A z)GN@vPp4jy?tMM=N_6i7s#m6a-%z~@-TRE{Rq5WBRIf(&KBjtgy7xWRYtX$E0JsuSfSjvU+{G_np-n(7jKs-jMEnZS_WU?}Mv1 zrhDI9y$Rj>?CMSF-j`QzM)y9xdULw>{ncC0y-%>-lJ0$l^;UH6L#(%^d*5Qc4c+@3 z>uu@Y7g=vd_dd#cd-_)H!>M6__#C;BG3-kH9Uu6LntpzB@f>*;zo`Z~Ja zoxYZ?_n@z#>pkhK>3T2vD!SgAzLKu@p|7Cped){TdO!Lyy566@l&%k;FQMxL>5J+5 zAo?P@KA66ct`DIvpzA~F^Xd99`aHTmoIaPXkD$+?>m%v2>G~-8EV@3LK9jDGq0gY} zW9ifB`Z)SDx;~yhm99^qPoe7*>67XDB>E(}KAAp|u1}#)pzBlVoe)2>G~}CD7rqIK9a7_p^u>JbLqqB`aJqDx;~#il&&wJ525P|>4WL|BKjb@ zzL-9ct}mevpzBNN{ptELdOy0poZgqNub}s#>nrKK>G~>qFS@>(-jlAcq4%KcYw6wT z`Z{_yy1t&?m9B4~ccJSW>7D8NCVD5jzM0;Uu5Y1tpzB-d?dke9dONzlo!*wN@1VD# z>pSVK>H02uE4sd$-jc5Gp|_yxd+E*T`aXIyy1t*@l&&A3H=*kX>5b|7A$lXaewf~n zt{nG{8>G~;pExLZ1UX!k$q1T}6XX(}H`Z;6PjFC3+>gewkj8u3w>7pzBxZ<>~ro^m26l8oexCzfLbh*Kg2E z)AgJ5QgrQB|C(Niu75)>NY}rm z7oh8R>G|pUcl3O8{d;;|y8Z(_4_*I}o|~@!M9)Rnf2My**MFhsr0c)ZbI|qQ=-KJ| z@AOaT`XBU<>H44aY;^rE`bTvAZ~BLH{U7=Vbp2oY`*hvsTfRrveIDk!blvA?zC+i2 z-sanM-RE<@Mb~|v=bLoh=YPII*L_~->vY}ci@rwJeIDtnblvBdzCzc1-f334?(prjb1-kC@U7x4xJ`eUey6*F1pQY_9?pV z^KYM|>pm~{3A*m{bswkeK94sGUHAFDkI{9X_xmVa_xZq&&~=|DoSCls{NYS=-RBi& zr0YK4_+h&4^N=&pb)TP{p04}6<#cr2=QF3J>pssp4PE#7&kxacpBJ5)uKRrHRCL|v zQKzKqKEL`wy6*F?Q_yvvkDZ*Z`#kMrblvA~C#CB?uR94{_xaur&~={&o|vxt{P09{ z-RF%br0YJP{C>Lb^UUv~>puVdUb^n{((j?`K3_cnUH5tHK6hRB`RzWZUH5tKKG$9M z`S3mmUiW$OJ~v+X`SU(!UiW$RK9^q4&hw3Zj=i3P?sM<;ob+F^|Mx%W>z~qprt7)r zKhgEv^dISZ9{LY-Jum%xx}J~z9bL~)zf0E((7&bY1?k_=^+NQo>3U)MS9HAy{Y$!D zlzxY<7o*>%>&59`(Df4Z&*^$e`YpO%ihh%>m!{vK>t*QI>3UiEHM(Ao{ux~_Prpjn zE6}gd^@{Y%biES&5?!xMzev}s&@a&Ss`T@8y&C--U9V0*OV?}A&(QUn^wV^`7X1`m zuT4Km*Xz(v(Dl0X<8-|q{TN-ZPd`f68_WH&^OTauJrYEy&HWUUGGj`OV@kQ*Uxx=g{?$^x1TM6nz$5A5EW0*T>Lj(DkwO>2!S@eHvXKPoGNHC(x(R^@;S! zbbS(i5?!B6pGeoI&?nIKsr2!5eHwinU7t=LOV?-6$I$he^wD&E7JU?5pG_Z0*XPhj z(Dk|W;dFf-eHdMzPajIx7tn{$^@a4obbS$h5M5tPA4u1i&`_T23^xkxR6}=Z-Urp~x*VoW{(Dk+S?sR<}y&GL$Pwz_CH_*G#^^Nq-bbS-O z6J6g-??~6T&^yret@QSEeH*PV8qxAZ8{TRI-T|Z8*OV>}( z>(KR+^xAa&6ulN*KTWSm*U!*v(Dk$Q>U8}ay&7FVPp?YXFVL&d^^5e%bo~;&5?#Mc zuSnOg&@0gOtMu}8{WE$wx_*sbmabo?m!a!7=%wlUO?oN1ev4j`u76H1LD#>a7pLpD z>BZ>!9ePo^{w2K#UH^(+n67_KFGSbBp%bVJp)~TlAfNfKSfVR*Po`RrR&eo)6n&2=?~HM z=jf^F`t$Tubo~W-O1l0c{Xx3^5?~u4knuqwBBGlhXB9=}GANYxD=``s?(> zbo~u_BD(%2Jt19xi+(>{f17?EU4MsuFI|6^eh*!LkDh?8zfb@7lLY_gAN>RRKXm;= z`rmZ@Bl=%-JsbT`y8bc!54!#d{dc;ao&FnL&q4o{uIHrxLf1c~|4i3&(SM@rx#>UB z^*r<+=z3oI_jElU{X4pzpMICF7odMj*9+3Wq3ea{U(@x%^snf85&D;Oy(s+-T`xwz zP1lRlzo6?S=%3T|lJr}2y%hZ>T`x_)LD$RBuhaFi^lNmz9Q`x8UY>rHu2-O6q3adt zm+5*X`X#zvnSPP3SD{~^>s9IJ>3TK#Il5k*ewMD+pr4`ZHR-46dM)}Xx?Y=plCIaG zpP=h?>Bs4MJ^C@aUY~xHt~a0`q3aFlhv|AF`XRdBn0}D1H=!S(>rLtV>3TEzKDyqV zzL&1IpzopUE$O@IdMo-ay55?;ldiX+@1W~#>D%dgJNh=d-k!ddu6Lksq3a##o9TKd z`X;*GnZA*(ccE{f>s{&V>3TQ%I=bGSzLu`{ps%6pJ?X3IdN2Aay55_C5SQKl(Dd-k-jdt`DFuq3Z+bi|P6x`XahMn7)v%51}uh>qF`D>H0AGJi0!dK9{bK zpwFS}Bk8m0`Y8G=x;~mdldg}U&!FpL>C@@@IQlfYKAt|6p7y`L1|>)eGY8s}&?mA# zuIPUuL+F#j+;jG^>`D7%_Qo*Rr_e{!^{MnxbbT6qBwe3QA3@h=(1+9Yne<_FeHMKv zU7t-KLf7Ze2h;Vr^g(oe9(^EPpHCk^*B8+H)Afb)esp~iy)Ru~Oz%V2m(Y9D^`-P) zbbT4UCtY7o??KmB(7V(1mGo|OeHFbcU0+S_Lf6;OJJa>G^iFhr9lax6Ur+Bq*Ei7H z)Afz?c65Ccy)9keOm9Qix6oVD^{w<)bbTAWC0*Z6Z$a00(3{iso%CjOeHXncUEfV_ zLf7}u8`Jf@^hR`jAH5-6-%oEq*ALL^)AfV&dUX8|y)IopOs_-NkI-w=^`rD!bp05; zCS5;HuR+&O(5ut+lk{qI{S>__T|Z5)Lf6mGE7SF}^h$L79K9l4KToef*Duh^)Aft= za&-L?y)0e7OfN&%uh2`=^{ez!bp11WNxFWGUV^S)rx&N|H|WLa`b~OKx_*mZgsy*1 zFHG0JpckU+x9J7x`W<=!y8b0SKVAQdo{z47P0vf$zoF-$>)+CI)AhUbTy*_A`lodL zdwNc~{sTP+UH_5(Q<%q|ojvzG|1*2erTr83r2QB6eud6s|2XV>{x|lVOM5o4?QgOt?TOiYgSq|yJxQ3y z{yKZ^dp;?9&ZYe|_M|--d&!~m*k29%o=?G^b7_BtJ!yZCy_C>->{-LU=TougT-sk| zPuf$n_Yia*`%7Wp^J&;~F6}R}C+%t3O9!3D{zBOIe0uhrOZ)TeNqYwN9)`|ie=h8M zJ|lb1rTtmppM^c=(*7iS(*8Jm zPeA9fKN0r-ciw&G|2s#2oGzcFKLwu-=VH$i_C5a$d(Nf(G4`bWS@xcT&SQTx?0f!s z_MA)m3(Oy3Uw@IFnXbP?e>u!!&%~blp3lmjb7_Btc}Dj2SLqMa_1EaHhk5K7*mK|W zZ?NZF+TUcJo_+l-dOEuPHvOG2k3B7W?tA`S_MA)md(6|YufI=!h^~J?|1ivBPtBhD zp8tqF=hB{yc`EkxkLjO;dF(0KbKmpX*>f)KIha4lzMhkwg06o`&lTpeCuh%n&*x^( zxwPkDo{W7xFFjwF$DWiu_dTDVJ?GM1fO!)3^@8+5VIKPf?78px!t6Ph_9Dy^v#%GW z7Yp;)6S3#M=ZmxFT-r-8PsqMrl3pszW51t0_dQ>lJ?GM1hI!d=F82GlzWbgp$DVU( zFVFm5_Vo(%ieVo6J?y#f`AY0Lm-foctAum0C*b<-d%h}r&ZWH?^M8x{_qs%{POlN> zvDXZ9uVep*>q>hq_G&}tvHu)n0F56V(${>UdP^*dAD#b_U>Wsb?iNu_YCJ^?-k}=$KIQHpKvbrzG3cl?ERSc z59eYZ5awRTK9KpKa4z=2VeWP8LzoW@=VBig=3d7>ocV}wF7}aO?se>=n2!$UVjmOc zUdKL``M7W{_VHovb?g(Ezs<)%eIk8Qn8!Xj%)O3%3iGMqT~q50>)7WqpBK)>K0nO8j(q|1h2dQ6i^AON*cUTj63)fGG|at@eHruR z;au!1!rbfFS2AA}&c(hu%)O3%4fD0(T|4Uz z>)5w4-xkirzCFynj(rF7o#9;UyTaV-*mpDE6VAoHH_W|`eIN7v;auzo!rbfF4>CU# z&c%K>%)O5N2=k-iT#{wCj0sc`pGbl{ZyEH9s3(xSK3drcLq9- z{q?Z#`Lpafm-cha&xdodzsB|5_xuI+oJ;#f=C87^U!q?Q^VnZu&wbBdVb8g=UuFJT zI2U_XuJ69*ud(M`+OIQznSK2R{brcQ{t|ocd;S)C&ZYfx<}b3Ze?h+;=CQxPp8KA^ z!=7_#|C0Ih?CW3AzYg=*pJUH`&ws<7b7}vU`Lpcncj@1SdF;=y=f3B^XV1B`|G@ld z_Vpj>Pto`$`izUO~o&$+b!%KQoT_21~fhk5Lev**6&|6tF#wExLG3;X(C z^vCG>-}HaNJoZP~bKmp-vgcgd6XgHz>kE&tuirz@OxN$F-xubwXJXHN&)?6Ub7@b= zJR|#hBKpI0Ju&@(FpoV0d+vKa346|^Jt_0_?CZ(s>F9cLdWtZQJuQ3gd;UT8oJ)I3 z=4sg1Q_&xy>#6OMdF-j_?my()fzD+bdMe)@w5MgBE}V-!W!U$8diI=4`-ALBdj|F% zhR$P85%xWwkv-?qo}4{t&%|D4=sfmhVc+wQu;*Obld>o6kFxg|bRK(>u^YbA z2iTML$Ju)VI*&bZ*!TRC>^YbAMC?iXQ|vtroyVRq?0fzh_MA)m{p?Bmv+O+woyUG( z*!TSN>^YbAd)brr7ub6dI*v;YZ_MA)m zKkP~StL**FTz`%JdYH%l7kln|{tfn=OZ%VfN&B1Zy#<}e{zusN{M+m~m-gS;llFJm z`;EE&F8#eQkNsEn-1q$Z>^YbAU)YoO57_$XGZ<*`4>EF=xJoK;W zdS3chbUh#aOS+z)euu6Xpx>tJ1?gYV^+NQ|>3U)MExKNWev_^jrQe|I#pu`RdU5(S zx?Y0*8C@?)ze?9j(XY_;()7!8y$t;lT`x<&NY~5JFVOY!^z(GR0{t9auSh>j*DKM_ z(Dlmn({#NG{S;lVNo6&dB_2%^LbiDrsfeHL9GN}oyBhtX%y_2KmCbbSPU8eJbr zpGwz9(WlV$(e%l5eGGjPT^~!INY}^FC(!lr^zn3kg4YT2*vGjCCvtugbS~{bqZ%o(s(i_qBee{NO zeLuYcT|YptPuCC9>(TW?^tyEYFue|4KSHlf*N@U`(e-2Wnsog*y#`%BL9b5NPtvQ= z^;7h!bp15F3SB=#uT0m^(ks#RbM%UI{XD$_UB5stPuDNf%hB~q^s;pQGQA94ze2wn z=CPM%&wbB-#-4L&FU6j;Ut{k&bRK)ju^YbA66{I)P4?D@mK=f7vqxwPkFPuhQAFE4ZbNBU1;9(x}4-1q#?>^YbA-0VsFFYNsa zoyVRl?0fz<_MA)mr|e1l@9g~noyVRt?0f!C_MA(54)&z|FZTY1&STFW_C5a(d(Nf( z6ZWM2U-l9d`0wlB&SU>L?0fzm_MA(5Huj|bUiR*T&SU>5?0f!x_MA)mhwMpvLiQ3t z=dphf_C24NJ?GN?K6}#s0DDQG^Vr`D`<_qAo^xq`mpy4u#$IyhJob0OzUNc0=Um#~ zW>4B5WG^Lj9{XEi-}9;1b1v;~vM24S*?R~&kNu6X@A)+BIhXdd%wK0;Pe*@^uBWGG z2=mxqWzT)jKg^zUY0t>~754Q^^sIC}GyRb;kNsu#-1q#W>^YbA$C$swzMh5tB3*x+ z{zRC^{sMdMd;UrGoJ;#t%%5jpf13UrU4MrDY?#OXEPL+%@4WlW|96i5JbZ?^{sR5Q zFpvFd_T2aUOYAw9_LrGI#lD`E{v=(0h5l-o$NmI+?tA_<_MA)m>&zc#Uw?!CW|+sG zg+2E@{}y}BrTuN@kFl@6Lw`5SV}F!A_dWj}d(Nf(eddp_uYW-QFwA4m%%1z6|A;;3 z(w>cZCieA@>7Rso>>1f}-}Bkob1v;UnCA@VVt<(HyYKl=*>f)KxtM2QU(Zd?6Xvm} zXU~1l=Vi~iwC7`^YbAa?HzzbFru7 z`tEza0(;J-y(05U;auzwa((wbUzt7U(q4sm)o?EMYGLkm>?wHr(q5gt8qj&{HN)I< z_FBx7^LF&w^g3Z4d)+YiI`(?ZlW~2$KD|Mh$KEi^y^g&R^Ty#^>`8h1?t8uod(Nf3 zDf4FGTw`SfZoQu6}n0p<2JLc`fx!5~|x!19G zWZo&9i@kH0dmVch<`3}iMej=Q7Ur>c4|A_$@4>uhI2U`bF!wt4-pu=ibFnAp*WteB z6VaX96VjdA@25Mr-$!?DznAXZeh=NbJptXh{og|T{2p`rKXm8zzv<5Hf6<-W|D-#& z|3PC*lkUE1%` zrTtsFw0}dF_OI#E{uN!?zobk19lEsNrc3)5bZP&bF73DI(teXJ?KkMsew{Au*XYvz z8C}}1(xv?hUD_|xrTr3J+Aq?j{Q_Ou&(o#-99`Pa(xv?jUD{96rTr9L+E3D@{RCaw zkJF|77+u!0eFa_Gm(!(v8C}|!(xrU~UD_AZrF{`y+WYbOZ6R~LKYamRA3&c^*9X$)(e**} zxpaLneGXk8LZ407htg-!^(l5X==yZ}aJoK&K8&u;au#y!`$oG_b}fZ&c*&5@2C5o-^ZSFY2VNMKsXos!7%qa_Cw5{ z<^9kP(~pFC>_@}g>)4Mme}?Pp$LS}+Job}e?se>^m_N<+_0#k-VIKS0F!wt4bIhOO z`uchLg)oo(Vwig!`;%N(+Apzp89I;sN|<}j{sh;T_N(lD2A#)#EzCVwkI2Ze)T;F}qf61P6Y5$7(Bkb#6 z)4vJx*uM>PuVc^5b*23-d*4Clv1bbVp8uXb=hFTI^Nj54Khl2+^Vok5bFX86nCnXW zFYNsaoyVRb?0fz<_MA)m@66M)um3^+Gt6U8$DaG1|BF56(*8H|wCwBu(Eknd*we7* zzULDZ{O{|Z&ZYex<`1#2-%GzQ%wtc@p8KA^pFQW&o{)Je_Vq;c#9}oa`yEUzP&JyJ(+vHea@$Z&ZRvS^Q7$Qsp(1R z`a|?IVIKPf?78pxwCp*T_H@h>v#+P8C!*^a=nsc^>CUD7Pv+A80{z7>kNuA@_xwwA=hFT=b7_B>o;A#4|1Hct{|eo? zwExOn+Fzx=7Ur@466T(No$g%Pe`YT2Z_wWi^Vok1bI-p;cP{NeGMDzZ>F_3FL z=ij9}m-g?OOZ$8D_rpB)@50>kAJCmk`(5VJ{vrLNFpvG)F!y{mx^rp&hPkwVO#dXz zWB)qLJ)fQKT-v{4F6}w!Im0~mFT>pPpVFO6`yJ-eo{N4P>bdbeVIKPzVea|7bm!9k zIdf^xN6#PTvEK@F&ljLOm-d^?rM)2i2Gk4Tg~L4d>tXKsB6R1{evP@b7o`^q^VmNN zbI%v2JD2vW%%!~q{R-4e;-$ho_RC@J`OMihXP;ZHEg?cM|3)EZVo1xwY-vsrx_(rI=!#6;^J-#059q@Hf?})F3dMA7h z)H~y=q22{w1@*4@N~m|kS3tcxz8va3@MTc%i7$nEFMJ8qd*h3t-UnX<^}hH*sQ1Gc zK)pXcAL;|}c~BpS&xQIRd=AtHLc)JP#=j;h59Ib z3e-pAlc7Eap9J->_(Z6W!zVy}JpM30KBxO!@^L)(#kpPgdhY4Iecz7m`|JCq`|I=9 zq5JQ{U$^eRKmR@H{`>adtL}dv|2ydZ_w>J;?tg#(JL~>_@%M%9?=OEJ>HdE7_nq$V zUw@zK{(krOweFt}{~YN4dGgPV?w>#Zoaz2~_0Og5pKt#h>wY}&&%N%)4?j-me!TJH zitfiJKMv`BJoDq0?#Dkr&gp)<^y8xL$5%g&>V7=-YMPkP~VKVf%+D_HPpA_t)RXQZwdA7cnhfSz?(yTC*BO|yYQw^-;Fnc z`X0P7)c4|zpuP`p2=)DV1E?Rs>qGq@UJvSr@VZbxjMsts5xh3kkK(nUehjY(_2YOA zsGq>AL;WOP4eF=xs!%_TSAqH&yfW0!;+3F&4zCFH^LPcQU%<=5EAT2T0dK&Yupqn* z@4#H}Z}<;AGKk%|iFDUz1gp4r>Emkl%Om`%8YG%I|~u{Vu;B z=lA{m{+{1|^!uECKhy7b`+ar4f382n*RB1&z3$Hu`11m~KfmD5Dfsgp{#=Yd|DwOd z_5JxDX@8k{)^INNSHj%u*k5J-S~wT`>tXJ7>~AoCGn|Y4tuXgG_P3e86VAo{ZkT%= z`+Lma59eb4Ak4jv{X^y-g>$iI3v;hy|CsqF;au$5!`$oGb1=^t&c*&|n0p<2F6Ozz zx!CiBx!1AhWu7mbi#>msdmVcL<^{vK*b9ZZ*RdC7UL>50y=a(w9eXk6#lyMSON6=C zv6p0CDx8bGbeMY`dl}|s!@1bYg}K+UmuFrfoQu6;n0p<2CFYgGx!9|Ox!18*WnL|u zi@kc7dmVcX<~75)*lUHk*Rj`TUMHN3y>6I$9eX|I^~1T?8-%&nu{UJiD4dJEahQ7@ zdlTkO!@1a-g}K+UH)q}=oQu6>n0p<2E9R}kx!Bu;x!19`W!^5Fi@klAdmVcR<{iVi z*gJ)}*RgkI-X)xiy=$0z9eX$C-NU)qdxW{yvG-)&E1a|Uq4$N0__-XtAAKQR?@#yV zp!EUt`JC4W(&y3jLG-zFeK36vT^~Z9P1lFgXVLXxbbqc~A5Ndad3^+ZI$a-0_vhU8 zQS_;t*GJQ*(DgC&$#i`zeG+{FJ`s+m>yzl?==x;(Sh_xiK8CJOrH`iT)99n<`gHn8 zx;}$Gg09b`52x$1=)>sxZ2C~TK8HSpuFs_prt9x=09 z=>Gr2{{Pcm=>Gri{(t%P=(_g_RcwdlQbnjE*eNeK|y>E*5WqF_O zeO$ax%-eMDE8~4@UZ;DX8}Fl&m9Bf=otNp}r^ovMJx}+(LC^F5Ek8}yU!Xrle-VEP zW})jZ(;uT}#b1G$==!VljP%#=*I_!k{suiQJujXQdcVLzcwy-M6pP~}p!b_BgO`Qg zk5ey4_x_!FdAj%e)GN@vKd4@j?)^mdN_6i(s#m6azf!#l-TRyBRq5UjRj)?({;7I( zy7ybvYtX$vt6r1t{ap20bnpMF*QR^FSiKJ2`^)Ne>E4f4uSfU(wR(NJ_q){_(7ivd z-jMG7boEAb@4u@zrhC6$y$Rj>`|3^U-Va!BM)&@~dULw>8`fLUy+5(ulJ5PC^;UH6 zf2_BrZ{_}qdK>x{y55$)nXb2^Z=&n%=^N>K2l@uO-jTkZu6LrZqwAgNYw3Cy`Wm|4 zmA;y;ccZVO>)q)q>3R?P3cB8tzMQW2qA#QCz3EHodLQ}{y55(*n6CGuFQV)H=?m%l z0Qv&DK9D}2t`DNmqw9m|bLsjJ`W(7Gls=oT52Meb>%-|Y>G}xz47xs&KAoPpH3e^ z*Jsd&)AgD3VRU^KeJEX@O&>zn=gs*8NDZ6Urz5q*H_TH)Ag0~ZghPWy(?W`P47b2*U&rD^|ka) zbbTGYBVAul??Bf#(A(4Xjr4YOeG|PcUEfS^L)W*^ThsNe^j36z8@(l6-%f8q*LTpH z)AgP7W^{cQy(wMaO>aWi_s|>D^}X~)bbTMaAzj~3Z$Q@%(CgFngYOV_`l=b`K0 z(sR@GyYyW2AMu}HHu`V)@9-V^-}pc9Rl3gy_W8r9=(^7C!*^U-$A&$~`Q_xad9f7|D5>prjB=W_deZ=WCTbHaVzxX&~9 zx#hagKleH3J}=$pvHRS0pWp8D;e8Ig&y)9g^*)#0=i6uHd8^lXzW1y6Yw#ldP5do* zoIVbp0LMc6`2Wj%B7IEQ^Ez=piF2dFp6B9xGUrBxJOOg?0GKEr*m#t*z;VR&*0q9u;;lrpUJr)Vb61MK8tgM!=C5jd^YC>g+0&3`5ev- z411o7^SPWG5cWJ5=kqw%KkRuf&gXNkU)b|poG;*9->~PoIA6%QK4H&ualVLiy~Cd8 z;(RgZdWAjD#rYD>^$dHSi}R(N>k;-m7w5}3*FEfcF3y*8u3OmiT%51qT-UJYxj0|R zxh`SPb8)_kbDhJU=i+=d=Q@Qw&&Bx~&UFlXo{RIfoa+$wJQwHdIM+Vxc`nY^bFN+3 z^IV*7;9T3V=eaoF$hkIQ&vS9UiF2*Pp6B9xGv```J!=C5jd^hJBg+0&3`5w+S z411o7^SzvF5cWJ5=leKUKkRuf&i8Y!UfAF?icmj`SAhCC zygby;`!0WS;ni+CBRU&2d6{W4w(>R0fRP``?ofcj^6aj0Lzi$VQ5UKHv#@FGyZ zi5G_YExZuaKgSC~{R_MR)NkYYp?(L?2lX%UyiorN&ja=#xufK>3SymEA-6x zBk)DK{wVzg`WyJ0FfsjI{5_a}{t=!H{+{>0|Di1ho)i8+*FU9yPuFwNzoYB9>38XR z9{RU*Jum$mx}J~zHC@k7|B9{`pnpl%3)1h<^+NR9biFYB3%Xu}{yAMQO20+di_ve= z_2Tp!biD-qI$bYGzed+f(LbZ>rRi7cdKvl^x?YxknXZ?kU!v>f=@;pG1^NZLUXgyD zu2-U;qwAIFXX$zs`Wd=jm42G8SEHYz>(%Ke>3R+N3A$dBew?n?q93E{wdqIcdL8-^ zx?Y!ln6B5OAEN8^=?Ce01Ns5F-jKeZt~a9Zqw9_7d+B-;`X0L8l)js;H>2;O>&@vq z>3R$L4!YiwzMZbOqHm+?t?66odK>x{y55$)nXb2^Z=&n%=^N>K2l@uO-jTkZu6LrZ zqwAgNYw3Cy`Wm|4mA;y;ccZVO>)q)q>3R?P3cB8tzMQW2qA#QCz3EHodLQ}{y55(* zn6CGuFQV)H=?m%l0Qv&DK9D}2t`DNmqw9m|bLsjJ`W(7Gls=oT52Meb>%-|Y>G}xz z47xs&KAohgRmiJdIZhJcCyZJd0NdJcpMLJdc+PynvStyoi?xyo8qy zyo~$0RQfv9*QN3*=Y3r&eI4rSQt9hZUzbW>hx)oy`a0CtrP9}-zAlx%4)t}Z^mVAO zOQo+veO)Sj9qQ{+>FZEmmr7rU`npv5I@H&t($}HBE|tCx^>wNAb*QgPrLRMMT`GMY z>g!VJ>rh{pN?(V5!hcWc>rh{pN?(U&Gi(bzuXhdS+v6Re*Ryw``}RHObviQl z?Rwtcop~Rq_YB@EcyGK1bYG8i_nmuv>0I}EcCRO$_vP*Xf8}(}&ww-GEI1p^fpg&k zxGeN}_zE~5+Lz<*J6}p)2$w*wWB2VXV(y;4IPClO>?@gXf~%l?3%&+!gsY+F_08e@ zT6`V!diD);-@fO(&U)s)UC-OMGT#PwLi=`n7u*APK+o?DJ)Ymq+;jRq`eCRa3Vty7 z0enAnUypP5oqK)HIoG|O-Sg7<5#Ihus2>Y{JopLxD0E+sbN8Kled%2HdUmfTouA_E zpN6`B-lLxh=iT#sbk95gfAh0k|Nk#G=K4)wQ)q7%y65c8nYVx~p}ke;p0oS*blw(kFZp@OeLt@`_w$<9^}N^d^P_Li^S(W==lkh- z-!DJT_;JOLAAY>>_YBY3J?DHb=ly$ye~(x|UkDe$#c&B+8s^Jz z_m|`DdEU3Pg1Oi8yzh_K^ZoF=@ApdfSHabA4O|P?!S!$h+z8#@6y}?8uj_fQvxT{D z&-1=Lujl*edEc+C?8lD}_W1V=KVI1XZyz`O`-GqO{rnz3uOH^^AAv{VF?bxFfG6R- z+y^ibd;s1D?}3S7LU=!n`xE&3i`Q{Ji|%!uPoewvozI}hw?Cfl`{8^N-S^A+OuFyC z^T~AI5Bo&A@3(tX>Artne;VEQ-}h&FxQ=_C^X(R{`rp6EBG7%`AK#AeM^VldgT9}h z_ng-+&bboM_d|Nlw_7q?*Y0`WAAcRCIbQ~P-uKIMdf9M&|6b+iN6#nW_m^L1oXx+_ zc`5%Jl;=8Wv;XG03x;NHjbQ9Ky_4--K_$x8XbRUHBe+AASHogdf3d@MHK1%nozFobXea3+9G-U|yIH=7$AfL0AYD zhDBgeSPT}2C16Qd3YLatU|CoWmWLH!MOX<|hE-rySPfQ(HDFCx3)Y5pU|m=b)`tyX zL)Zv5hD~5o*bFv@EnrL73buxAU|ZM@wuc>HN7xB=hFxG+*bR1vJz!7R3-*S6U|-k| z_J;%DKsX2vhC|>`I1CPlBj89l3XXZ z3+{${;9j^7?uQ59L3jurhDYF0cnltgC*VnV3Z8~%;8}PMo`)CUMR*BbhF9QK_!+zg zufrSgCcFhdhhMCeg znP6u42z(Si2D8A&;S=yl_!N8^J_DbH&%x*63-Cqw5_}nEg|EO@;cM`9_y&9vz6IZg z@4$EAd+>eu0sIht1hc`9;U_RV%mH)4Phl>Y8|Hy|VLq527Jvm|Ay^m|fkk04SR9ss zC1EL88kT`&VL4bHR)7^@C0H3&fmLBOSRK}YHDN7S8`gn!VLezMHh>LbBiI-=flXmE z*c`TiEnzFz8n%ILVLR9!c7PpWC)gQwfn8xY*d6wOJz+1{8}@;HVL#X(4uAvUAUGHf zfkWXiI2?|EBjG4G8jgWu;W#)RPJk2PBsdvPfm7i$I33P_GvO>a8_t1q;XF7WE`ST+ zBDfeXflJ{sxE!v4E8!}*8m@tB;X1e;Zh#x%Cb$`Hfm`7=xE=0*JK-+48}5O7;Xb$@ z9)JhoA$S-bfk)vncpRR9C*di08lHh?;W>C7UVsFVz+d5S@OStJ{1g5K|Azm-e_;Z?fN~GK z7v2Z&hY4XKm>50)lfa}f8B7jSzz1PUm){5t5pIH;;TE_RZiCz5 z4!9HUg1g}!xEJn&`{4n25FUbu;SqQg9)ri>33w8of~Vmbcov?6=ivo-5nh6q;T3ol zeg?0>>+lA=32(vA;TP~WyaT_4U%{{8H}G3{7k&r7hd;m{;ZN{q_zV0M{sw=Cf51QC zU+{1E5BwJsTX_$qu2z7F4j zZ^F0W+wdLuE_@HZ4?ln(!jE7!_%ZwhW`{XoPWUOz1#`nZFfYsp^TPtLAS?t6!y>RK zEC!3i60jsJ1xv#+uq-SG%fkw=BCG@}!z!>UtOl#Y8n7m;1#81Pur90z>%#`HA#4O2 z!zQpPYzCXd7O*931zW>5uq|u{+rtj9BkTk_!!EEZ>;}8T9ue71y{p0a4lR1*TW5PBisZx!!2+t+y=M99dIYy1$V@GLwB&%+DwBD@4I!z=JA{0v@$*WnF# z6W)TK!!O`%cn5w7zk*-GZ{WA^F8mID4}X9^!k^&J@E7{X;Op=W_$GV{z75}j@51-s`|tz!A^ZqtgCE0BV0M@T=7gWZTrfAx z1M|XsFh48+3&KLMFf0O#!eX#EECEZxQm`~E1Ixm4uso~)E5b^!GOPlt!fLQOtO0An zTCg^(1M9+ius&=68^T7gF>C^x!e+2JYyn%sR;ZeiUa&Xp1N*{$us<9C2f{&cFdPDh!eMYY905ndQE)UI1INN~a6Fs!q4C}cpct=H{mV#Is5|NhIinX@GJN={04pt@51lk_wWb!Bm4>e41a;Y z!r$QU@DKPW{0sgK|AGI)1pIvbJ@8(5AG{wXgo$8c_y9}-lfq;$IZOc`gehSvm>NC= z)4;Sa9ZU~1z=vT*mvhi||);al)+_zrv*z6aljAHWacM=%@w7=8k?!yGUt{1oPbxnUld7v_WcVF6eW z7J`Lg5m*!!gT-M9SQ3_krC}LZ7M6qMVFg$bR)Upb6<8HkgVkXTSQFNQwP77t7uJLI zVFTC@HiC^|6WA0sgUw+J*b=satzjG37Pf=!VF%a|c7mN@7uXecgWX{d*c0}Gy5I7VLgTvtnI1-M6qv04h7LJ4C;RHAlPJ)x+6gU-5gVW&*I1|o- zv*8>#7tVw8;R3i2E`p2U61WsDgUjIxxDu{{tKk~B7OsQq;Rd)7Zi1WP7Pu8|gWKT_ zxD)PzyWt+V7w&`m;Q@FM9)gGA5qK0HgU8_scoLq1r{NiR7M_FW;RSdRUV@k56?heX z2Cu>E@CJOX^nYJqdL6z2--K_$x8XbRUHBe+AASHogdf3d@MHK1%nozFobXea3+9G- zU|yIH=7$AfL0AYDhDBgeSPT}2C16Qd3YLatU|CoWmWLH!MOX<|hE-rySPfQ(HDFCx z3)Y5pU|m=b)`tyXL)Zv5hD~5o*bFv@EnrL73buxAU|ZM@wuc>HN7xB=hFxG+*bR1v zJz!7R3-*S6U|-k|_J;%DKsX2vhC|>`I1CPlBj89l3XXZ3+{${;9j^7?uQ59L3jurhDYF0cnltgC*VnV3Z8~%;8}PMo`)CU zMR*BbhF9QK_!+zgufrSgCcFhdhhMl?upBH8E5M4d608iXz^bqstPX3yny?nE4eP+VupX=r z8^DIJ5o`>bz^1SnYz|w%mar9U4coxBupMjx6YLDTz^<)Xtp0F3}4g0{p zupjIX2f%@F5F8ALz@cy$91cgok#H0o4adN-a2y;DC%}nt5}XXDz^QN=oDOHenQ#`I z4d=kQa2}iw7r=#Z5nK$Hz@=~*Tn<;jm2eeY4cEZ6a2;F^H^7Z>6Wk29z^!l_+zxlZ zop2Z24fnvka39V2!LtDg=tQ#~{CEgs`>zRh>|F5lw`zRwT%AwS~BJjql1grD*=e$FrWCBNd= z{D$B1JATj8Ji{OOBY)!0{Do(Ej_3I+f8z!I&OdmO34TugGA3hkreI2@Vrr&gTBc)q zW?)8UVrFLH<;==#%+4Il$z06ME0~9QnUDEdfCX8Ig;|6}S&YS5f+bmsrFkXGuq?~5 zJS(swE3q=Guqv-&HCAU0)?_W#W*uJ5x~#|gY`}(W#KvsGrfkOMY{8bihOKxluVZVr zVOzFidv@UU?8qB-$j zu|EfJAaCX%4(1RJAaOQIFqwDn{#*@ z=W-tBa{(7}5f^g_Z|5Cc$~(D?%ejJg@owJ3mAsd$xSDHtAMfV_T+0W!jt_A?ALb)` zlpFXMALm9s!6&(iPjNG!<`zD~t=z_E`5d3;cJAN{+{s+@dAJ6AH2u}e=zVeCS!7@U`nQ9YNlaYrek_$ zU`A$QW@h2#%*t%c&K%6iT+GcYn1^|pkNH`E1zCuNS%gJdjKx`kC0UB4c_quREX%Px zE3hIfu`;W$Dz9QSR%Z>?WG&Wa9bV14tjGFnz=mwZ#%#i-Y{uqn!Ir#+t#~c3V{5iy zTef3+cHs5w$QyVgZ(=8QW*2s4H+E+a_GB;iW*_!tKlbMU4&==o#K9cGp&Z8H9Kn$s z#nBwYu^h+ooWO~k#L1k(TR4@|IGwk024`{>XLAm3<6O?;d@kTZF5+S?;qAPGOL-@k zaXDA;F5b<1xRUpB6<2c&@8kV^fNS|6*YP2)=fix2k8%SaZZ}KSL;xQiQ z+kA)b@;#p5`}}|(@*{rClRU*w_$fc*=lp_S@+*GLZ}=_0dG_PbCmSs7XX9ZSdC01q?R^?T!#_FuWnykgz zti!8Wm-Sem4cL&4*qBY&l+D;{FdMGd!FVQ{=gsk6MyC}Jj-)D&tLf)FYtH%!HZ1jb1)}!F*mPZ9_D2}=4SyGWFZ!25f)`J7H0{TWGR;B zl`O-uEXVS!z>2KI%B;ewyo%LWoi$jKwOE^Vcs1*?9_zCK8?q4_vk9BB8Jn{OTk;yV z;2XhFA zau|nm1V?fdM{^9vavaBV0w;13CvysK;Z#oJbl%DtoXJ_7%{jb{b2*Rmxqu6~h>N*| zxAP7z<(*u{lR;FH|M zr?{CWkJiwRuGGF1VJjmC0h=+NEuk#JQ z$)kLW$9SA?^Bum+_jrQu^8;aQ&JdH%}Zc!9t34_;(KXP^I>jLDgTDVd6?nTBbZj_H|!8JUThnT3}#E3+{> zb1)}!F*mPZ9_D2}=4SyGWFZ!25f)`J7H0{TWGR;Bl`O-uEXVS!z>2KI%B;ewyo%LW zoi$jKwOE^Vcs1*?9_zCK8?q4_vk9BB8Jn{OTk;yV;2XhFAau|nm1V?fdM{^9vavaBV0w;13 zCvysK;Z#oJbl%DtoXJ_7%{jb{b2*Rmxqu6~h>N*|xAP7z<(*u{lR;FH|Mr?{CWkJiwRuGGF1VJjmC0h=+NEuk#JQ$)kLW$9SA?^Bum+_jrQu^8;aQ&JdH%}Zc!9t34_;(K z7oY!`jLDgTDVd6?nTBbZj_H|!8JUThnT3}#E3+{>b1)}!F*mPZ9_D2}=4SyGWFZ!2 z5f)`J7H0{TWGR;Bl`O-uEXVS!z>2KI%B;ewyo%LWoi$jKwOE^Vcs1*?9_zCK8?q4_ zvk9BB8Jn{OTk;yV;dG_PbCmSs7XX9ZSdC01q?R^?T!#_FuWnykgz zti!8Wm-Sem4cL&4*qBY&l+DRBscLXZsyb6!e_XZ z+xRS>)!^mgjh$ zzw$R;;P3o{7nyL`r4KG+GA3sVrerFnW*VktI;Lj^W@IL2W)@z~tjxyj%)y+@#oWAt zd6<{^n4bk$kcC*7MOc)@SezwTlBHOhSF#MtvK-5^0xPl-E3*o#@+wwib=F`_)?#he z;nl3mdaTa|Y{*7z%qDEgW^B$DY{_fbir4Zwwq_f)WjnTK2VT#Pyn#3JCU#C^zsiKF*DNf=_Z2pWYhTrl#e$Uf9!yot~f8x*lg=cw==lLst;|2cCKX{P|$$b82GA3sV zrerFnW*VktI;Lj^W@IL2W)@z~tjxyj%)y+@#oWAtd6<{^n4bk$kcC*7MOc)@SezwT zlBHOhSF#MtvK-5^0xPl-E3*o#@+wwib=F`_)?#he;nl3mdaTa|Y{*7z%qDEgW^B$D zY{_fbir4Zwwq_f)WjnTK2VT#Pyn#3JCU#C^zsiKF*DN zf=_Z2pWn{e~O#=G`H{>Zsj&U%jft!w{r(y;7;!1Ztme;zQ}#t z&jWmkFY^_?%7c83hj^Gr_&VR@<5c#OySHs9g9e2*viK0n}x{D>d(Bv0`Ze#+1I zIlthS{EA=m8-C00_&rba41eH{{E0vF7oO!gp69RpjTiVk|KLSN2XHTIbLsJyjLDgT zDVd6?nTBbZj_H|!8JUThnT3}#E3+{>b1)}!F*mPZ9_D2}=4SyGWFZ!25f)`J7H0{T zWGR;Bl`O-uEXVS!z>2KI%B;ewyo%LWoi$jKwOE^Vcs1*?9_zCK8?q4_vk9BB8Jn{O zTk;yV; z2XhFAau|nm1V?fdM{^9vavaBV0w;13CvysK;Z#oJbl%DtoXJ_7%{jb{b2*Rmxqu6~ zh>N*|xAP7z<(*u{lR z;FH|Mr?{CWkJiwRuGGF1VJjmC0h=+NE zuk#JQ$)kLW$9SA?^Bum+_jrQu^8;aQ&JdH%}Zc!9t34_;(KTgMNWjLDgTDVd6?nTBbZj_H|!8JUThnT3}# zE3+{>b1)}!F*mPZ9_D2}=4SyGWFZ!25f)`J7H0{TWGR;Bl`O-uEXVS!z>2KI%B;ew zyo%LWoi$jKwOE^Vcs1*?9_zCK8?q4_vk9BB8Jn{OTk;yV;2XhFAau|nm1V?fdM{^9vavaBV z0w;13CvysK;Z#oJbl%DtoXJ_7%{jb{b2*Rmxqu6~h>N*|xAP7z<(*u{lR;FH|Mr?{CWkJiwRuGGF1VJjmC0h=+NEuk#JQ$)kLW$9SA?^Bum+_jrQu z^8;aQ&JdH%}Zc!9t3 z4_;(KJD>lVjLDgTDVd6?nTBbZj_H|!8JUThnT3}#E3+{>b1)}!F*mPZ9_D2}=4SyG zWFZ!25f)`J7H0{TWGR;Bl`O-uEXVS!z>2KI%B;ewyo%LWoi$jKwOE^Vcs1*?9_zCK z8?q4_vk9BB8Jn{OTk;y_zx41*csawR4_@J`JjmC0h=+NEuk#JQ$)kLW$9SA?^Bum+ z_jrQu^8;aQ&JdH%}Z zc!9t34_;(KMz0S{#^g-FluX6cOvAKH$Mnp=jLgK$%)-l=mD!k`Ihd2Vn44EH5A!k~ z^Roa8vJeZi2#c~9i?akvvJ^}6N|s?+mScHVU`1A9WmaKTUd3vx&Kj)ATCB}Fyqa}c zkM-Gr4cUl|*@R8mjLq4CEqM)F@mgNT)@;MJY{&NO!0XwOH}FQ@#7^wYF6_!~?9LwS z$zJTuKJ3eW?9Txl$eTHcgE@plIgG4~#Kl~~+j$3<@=h+}a<1TAyqouMCGX`buI3uv$NTvJ*YZKG z<3n7}hxrH}4~#Kl~~ z+j$3<@=h+}a<1TAyqouMCGX`buI3uv$NTvJ*YZKG<3n7}hxrH}4~#Kl~~+j$3<@=h+}a<1TAyqouMCGX`b zuI3uv$NTvJ*YZKG<3n7}hdHpzrPtA$If#QfghM%u!#RQ@If|n>hGRL7<2iv7If;`w zg|~1jr*S%Oqim91~X_=1cnSmLZiJ6&&moqD~F*|cGCv!13uV5bLWj^L-0TyH- z7G@C^Wib|K36^9jmgbc#!?G;L@~ps$ti;N!!m7NA)mWW1Sd+C_n{{|K>#`o}vjH2j z5gW4!o3a_3vjto78n)uKypFBehHcr7?b(6XvmG!Mk`j@8L?`%T-*>HN21a^8v2qgIvdlxSkL5 z5kATde2kBCBcI@t+{CB2nNM>IpW#++@CEMVF7DY#BGk@V(p5uA`%HMc_zw-}XWI{K`f0&HPnSv>qim91~X_=1c znSmLZiJ6&&moqD~F*|cGCv!13uV5bLWj^L-0TyH-7G@C^Wib|K36^9jmgbc#!?G;L z@~ps$ti;N!!m7NA)mWW1Sd+C_n{{|K>#`o}vjH2j5gW4!o3a_3vjto78n)uKypFBe zhHcr7?b(6XvmG!Mk`j@8L?`%T-*>HN21a^8v2qgIvdlxSkL55kATde2kBCBcI@t+{CB2nNM>I zpW#++@CEMVF7DY#BGk@V( zp5uA`%HMc_zw-}XWI}hJ|Cx-*nSv>qim91~X_=1cnSmLZiJ6&&moqD~F*|cGCv!13 zuV5bLWj^L-0TyH-7G@C^Wib|K36^9jmgbc#!?G;L@~ps$ti;N!!m7NA)mWW1Sd+C_ zn{{|K>#`o}vjH2j5gW4!o3a_3vjuZpdN?IK>+fDX$LG17JNN>3au;`V5BKs#?&E$Q z;7fd&ukckK{!#u**`3B$QQNG1vJkGcI4&UW_Ji+(*0YBtN{FokJTYksyd75YV1ApXC{F%S-EYI;gf8}qyz~A`?FESyy*CQrla;9KPrebQQ zVOpkRdS+loW@2V$;pNQAY|PFa%*kBL%`2FPd6|#-S%3vuh=o~%MOlo+S%M{5iluoa z%djlVu{1yoRlK zEw5v1wqaYgV|#Ys_3X$Scq4COCw68Rc4aqqXAkydFZO01_GLfz=Kv1m%^bwR9KxX- z#^D^nksQU*9K*33$MKxNiJZjAoWff;mD4z#w{iw&au#QE4sYXJ&f|P8;6g6qVlLtB zyn{=5Czo+KSMV<0&3m|#_i`0ia}Dp~{d|CH`5@QvA+G1ce1wm310Un#+{h>RBscLX zZsyb6!e_XZ+xRS>)!^mgjh$zw$R;;P3o{7nzX4=YJ++a;9KPrebQQVOpkRdS+loW@2V$;pNQAY|PFa z%*kBL%`2FPd6|#-S%3vuh=o~%MOlo+S%M{5iluoa%djlVu{1yoRlKEw5v1wqaYgV|#Ys_3X$Scq4CO zCw68Rc4aqqXAkydFZO01_GLfz=Kv1m%^bwR9KxX-#^D^nksQU*9K*33$MKxNiJZjA zoWff;mD4z#w{iw&au#QE4sYXJ&f|P8;6g6qVlLtByn{=5Czo+KSMV<0&3m|#_i`0i za}Dp~{d|CH`5@QvA+G1ce1wm310Un#+{h>RBscLXZsyb6!e_XZ+xRS>)!^mgjh$zw$R;;P3o{7nzXK z=YJ++a;9KPrebQQVOpkRdS+loW@2V$;pNQAY|PFa%*kBL%`2FPd6|#-S%3vuh=o~% zMOlo+S%M{5iluoa%djlVu{1yoRlKEw5v1wqaYgV|#Ys_3X$Scq4COCw68Rc4aqqXAkydFZO01_GLfz z=Kv1m%^bwR9KxX-#^D^nksQU*9K*33$MKxNiJZjAoWff;mD4z#w{iw&au#QE4sYXJ z&f|P8;6g6qVlLtByn{=5Czo+KSMV-g=kKGmW*fF;JGN&BUeAuafj9Cdc4B9CVOMrz zclKaU_F`}LVPE!Re-7Y4-poN9%pn}gVI0m89LZ4}%`qIyaU9PHoXAO>%qhHuQ#p;( zc`IjdCTDRr=kPYp-Moh@c`sLSHP`Sy-p>cP zmJf0rAL4pG%t!brH}Ek&&W(J6PjVBV;$}Y0EqsPsxsA{AIX=(r+`$*Ple@T^d$^Y` zav%5e0AJ$Ee1)&_AYbDl9_A6g&NuibkMb=Z<8i*tcla*f;|adc5BMQJ;>SG6Q~ZRV z@-u$UFZd9_3p+#^ZdO@99_3p+#^ZdO@99xdmK);At4N85WP?s}uky=pw#kLY?1#_fN!z1NIKw>R2; zOV=Cy8SX6h=b_e@KFDf0nt#%G^k+Mx`A3aMmyhN*8c%OKmvf!*%$B41hmFVmS+ImB zj7OJ`<{vR0?MF1e-gva%(d9N6kM=LK>wnC6wExlmJZ?Of^`ieB@)dE*>k3{P_#^ct z`wz21noGv7zqaJ1@#yEx`j5M`9Q_V29*)A<6MEjRJZux;S*0+Cs zCt1P%#a}*tfA6ajcljBAy?nI4(T~rsZ1&gnhphPP`Z+8{`>4zgZ!f5z{Bv|rKf9pwHU z%;qWP} zyY-^m8|_bYxoCf)+tJhdy%?<@T|QbbTJ9aU{iACx-QZ~cxaDJv=A-?UC*h(~bh*gr zc0}{u{!NJHBcpjwgMZ9N*Avah-=65lVxsw|d$ir?`l9*h{)pyNxdG99^!SP9y)6CX z_IdjKWB;P(Lv%Z%*QaQ|qWdYj9ns@5nvd>}Xg{OZi)cQ2J&E?Cp#6>Jqvu!jJSZGD zAMH=nt!UhQ^n8r=vv}NmbbZm|tYqAL5zA56(sA?A_M+QSCT_lz<>+;4} z++=q>qqlfn~%Cwj=Ov#%hB!j=_Vn%-&>fE_Ooi-w{K(e)3Gn~(0VXnsiCeAGLd9~w6w z^@`?)#mz^zH<}+FHy`b9G@mBrr4^#*addsr>*IZ17^BC<2-}P1qraD2&-m!L`3ycG z)HgmZZvK!L*ywRJA#Q$&7oLX3C&kS#H6K0hro_!JH{aCw)VTS(%{MnbJ#PM9^U>pS zM%?@w^R0}}ikpAH{B_3X#LY+FUqp}FxpDIkyL|LIH9u~CgZU1|7skzRG#@>#7st(S zGJm7-+vDb+HXpqXE{&VtYCd}0FN>Rh&V2MbwjyqRhxs1H?~a?_Wxkj3m2vZX%}1{@ ztK#POn~z>c*2K-fY(9G4+#ff8(0uedur_Y~u=(hDzAkS54fE0SYJJ@NTjrzZ=_7IT zZ<`-!d_&y)N9LpF-Q(ud@iX&dEI$!9|Bd;v#y7>y|73o=@y&7b7tBvIz9nuxS)oe< zlZ|hUn@?x{7UR#x&1W?~&G_?i^LfnQYJ5lBd?E9*jlaO?^{<5adB%6f&6hR5$oQ_f z`O4<+Fupr(zJ~eb#`iF~f9jgQ$M{}G``6g~YU3})&9^lFfbo5C^KHyOWPE?zd`I(- z8b82j|GJppX#6Ec``63-Q^sFrboppLdR#>F@t2S8_oU{d`!%WgXgo50`_Xu0G~dVm zN8^#v{`XB{KDvCre`9_?67$jahpb8X^E8yhIGiImlA}19V>p)MIGz(Yk&`%?Q+Nxf zavG=eR?gr|&f;v&;e0OOLN4NBF5&IGgG+fQmvK2)@GjoXd$^MKauru|4e#Uqe1L2D zAlLCBuIIyigpYCqALHZP$S3$DH}NTM=F{B5XSkKy_$;5}^W4rIe1SW;i@Ujpd-)>w zaX%06CBDp8_$m+bH6G$&9^vbJgKzRE-{LVI=X<>LEK2&{%Mz#lBj?!j`(HaJPx>nU z>6!n?{{3gJ`>);3e|9X^>0h2x{L^*9210>QAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct z1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lc zP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe6 z2n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=E zflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q z6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7% zLV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&P zAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct z1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lc zP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe6 z2n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=E zflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q z6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7% zLV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&P zAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct z1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lc zP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe6 z2n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=E zflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q z6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7% zLV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&P zAQT7%LV-{q6bJ=Eflwe62n9lcP#_ct1ww&PAQT7%LV-{q6!@<%kT-D`@+F@0C!Px= zo(m?P3niWlC!UKWo{J`)izS|mC!R|ro=Ya4OC_F5C!ViNJeNs4mrXpEOFWlPJSVk{ z%87Fqof1DxxGeFUEb*K&@ti90oHp^CF7cc`@th&?oH6m7De;^+@th^`e0kzIYvTFe z+TZ`v?Pg2dcJ{<`j>L1$#Pfgjc1PEnJn^#8ZAzW^@xSMGR^Cat3E|7H4w~ zZ{u9f=K?O|A};0<-p)I?ly`C&mvaU0;@!N5D|s(haW&WQKHkp zC^zsiKF*DNf=_Z2pW^pLF-|_!=8vgBr|LOCM#*7@>_iwlQ ze+`8B|GRzKj~UsoXYZu%f&bU%I}RH>W=PVzpU`aNurb5`*Td;=AN^bN|I?p;;r9K{ ze*F!vpZ|AXKSSTp_kZ*0C*hxceEh%u`8Vv}fA6oK{@MHQ-}?IN|MvX9_4V6-^?daG z-;kc8YgMiIw=X~cG4L;Au6Z=UZP%QedP^~q{^>P5>7zuj`*+5Jz}{f{0e`S0@m zb`&Z)uJfP09RK}%v>aVu{Br#F{n2u?e*AKDd?1?tx0a*h2+{5Q@3uU~<8&;?@jvT$ z;Q!x_i^Y!F#!j*0!m(4zOC-dO4aZKgY8@j+Ue2%+Yb^ z=bdIyZ}W3*nh z9L-0^SEJ*n(Qke*t7STF+(oh-|_$U8a8HVpPnNp)N0qa-{8KzM-Lp@|1YZy zAJ}_P-#&jC`pZIW`~AlB9sHNUf3XmoZ#J^$*uE2bj{Mg)6Pq70tWV#OJx324`7bUP zeK@-Bkm0qeR7~I zh5r0EWkQ-i$CD*X_U8rD*iQ1xemWid`GHG6UGV1zfBus-asAjSwsQW@e(jQwFfF$D zUw*nQ@himGJ`Ml#R{Xt3>}Mxpr`Y2wc1oV`baHm z6RM_9sG7KPLZ7}vM-3c3;m`4ZZ9egC95Sl=@V+Cv4;hyDcp5X_w|D20m z?jQGSvSbNoF5R)Qhvh#WU-9>AhChF5CALp#{`_y~nBK#Cjvnym*VWO_R>ZbjA#q}0 z;+8I5Z{oneyMOLKSB?D~N32ooae3*YmmXg!6N|)_lO^8H*nF(Z*z=cmJT{)f{J%Dr zIGy(H)Alh|wirO#~t>))P9?BJ}dmtN?i>DUWi>{Rg*;ondo6bJ=Eflwe6_#aqc{AJ%B z*_1J%P?{9=9~zPI{i%7HRcx7I*{V<5CbWLIe6Qo*^lzNE=CDH@ejoEh=De3BOOYl^ zq59Jj{_(-|HObQNE0p}FW{q06ihU;*eXsQA$BB#4cd(PD#C|^^I@<8}@#vkM`1NDo zUB*uFhJ4E?wr=$+O0@yeGr|F`W|Nn*Te664W3 zwej1pmc;teJG$}f*GOXh=$+sA^=l=ue)JA;{Q7m0SU-9vIez`RNvvNliShbLj5kPP zykQdKjglB|oWywaPIvtNH%(&w=pCE*^_wTLe)P_H{Q51ESU-A)J%0UGNvt2e6Cc0+ zbxEury<;E0ew!rLkKWmjU%y=v>$gv0yh9S>(N7A*Z@*&_>qkFP5WoJ7NvwZU662kc z81J0Kc;Z_Of1cv6w`&sP-I5sZp2T>MB*uFtG2SbQ@!m;{M@LuU_um@?e|sJ4XFJjB z!K5Pn29Fukv-{AVL;4n}Risejsp<7iS`{fD6~+ky@3?7O_I~&QuX! ze?{+WMURiT?Ooftag%lpI<$`4Ugf`R?;r0}C9XB9XRpNF=-s_}_klyZH?Q0}x*ofo za604yTg1m7gW?u0!p+G3`-?+f| z%eo%fcioJh*;5>;&|rT1d4pa*xp7~o6YqB|IDK6I10SYrF)HV%D*Y#v%-`_b`Gw7@ z&-}hr%C>*3`ucqN7n^L$z2c*G`S$kRmhXm-T6USWVMNA_`<~75!^OwX7a*er^~&t_w2dL>YXfkVL|uPWp3H~{kh~*Pd@R|)aS}PcFp+9diO|} zp81y^^BTN%S;M{OvJ|*==G?V!=F7CBLFX%4F8_8@_gud|^+CnoG9Iln^HAGk1JW#* zz3bog&ysA(5;kwjG$yg%(Jez8H^`D=*Rq4LQ{5_6s%*Zv>*99@Crw|sKHbEsIS!>* zm9FrG3(F5&vFC<^6Atd&d203T!%H06^G=P_pP#IA@8a~s3zy%sZ(E8LCoA9kN5daJ zNY`cVhT&Ij+`VO6rsJnlow#k~@G3)hKfW#F)>D~I%zb`%_3w7?*p~V1smtozHs^H3 z0=w6a&#?GZ+B$RBpRT%k_vZ1L4xh^2ZP)zqX)Bz{v3=h0)3rX>wQ_v=VW;wMpMUmr z-MYKxoJ&*iRJO_U4iA4W{n9DJr&l Date: Fri, 7 Jun 2019 17:36:06 -0500 Subject: [PATCH 095/151] Finishing test setup. --- tests/regression_tests/dagmc/__init__.py | 0 tests/regression_tests/dagmc/refl/__init__.py | 0 .../dagmc/refl/inputs_true.dat | 23 +++++++++++ .../dagmc/refl/results_true.dat | 5 +++ tests/regression_tests/dagmc/refl/test.py | 41 +++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 tests/regression_tests/dagmc/__init__.py create mode 100644 tests/regression_tests/dagmc/refl/__init__.py create mode 100644 tests/regression_tests/dagmc/refl/inputs_true.dat create mode 100644 tests/regression_tests/dagmc/refl/results_true.dat create mode 100644 tests/regression_tests/dagmc/refl/test.py diff --git a/tests/regression_tests/dagmc/__init__.py b/tests/regression_tests/dagmc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/dagmc/refl/__init__.py b/tests/regression_tests/dagmc/refl/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/dagmc/refl/inputs_true.dat b/tests/regression_tests/dagmc/refl/inputs_true.dat new file mode 100644 index 0000000000..87ceb944d8 --- /dev/null +++ b/tests/regression_tests/dagmc/refl/inputs_true.dat @@ -0,0 +1,23 @@ + + + eigenvalue + 100 + 5 + 0 + + + -4 -4 -4 4 4 4 + + + true + + + + + 1 + + + 1 + total + + diff --git a/tests/regression_tests/dagmc/refl/results_true.dat b/tests/regression_tests/dagmc/refl/results_true.dat new file mode 100644 index 0000000000..ed67e2f93b --- /dev/null +++ b/tests/regression_tests/dagmc/refl/results_true.dat @@ -0,0 +1,5 @@ +k-combined: +2.053871E+00 7.718195E-03 +tally 1: +1.171003E+01 +2.864565E+01 diff --git a/tests/regression_tests/dagmc/refl/test.py b/tests/regression_tests/dagmc/refl/test.py new file mode 100644 index 0000000000..f6713c2e4f --- /dev/null +++ b/tests/regression_tests/dagmc/refl/test.py @@ -0,0 +1,41 @@ +import openmc +import openmc.capi +from openmc.stats import Box +from openmc.material import Materials + +import pytest +from tests.testing_harness import PyAPITestHarness + +pytestmark = pytest.mark.skipif( + not openmc.capi._dagmc_enabled(), + reason="DAGMC CAD geometry is not enabled.") + +class UWUWTest(PyAPITestHarness): + + def _build_inputs(self): + model = openmc.model.Model() + + # settings + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 100 + + source = openmc.Source(space=Box([-4, -4, -4], + [ 4, 4, 4])) + model.settings.source = source + + model.settings.dagmc = True + + model.settings.export_to_xml() + + # tally + tally = openmc.Tally() + tally.scores = ['total'] + tally.filters = [openmc.CellFilter(1)] + model.tallies = [tally] + + model.tallies.export_to_xml() + +def test_uwuw(): + harness = UWUWTest('statepoint.5.h5') + harness.main() From d36adef2806947a39a1d25655f7af21683348a3e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 8 Jun 2019 06:57:57 -0500 Subject: [PATCH 096/151] Fix unit test using Geometry.from_xml on a hex lattice --- openmc/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 30d9ad6de3..0ac47b9065 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -1376,7 +1376,7 @@ class HexLattice(Lattice): lat = cls(lat_id, name) lat.center = [float(i) for i in get_text(elem, 'center').split()] lat.pitch = [float(i) for i in get_text(elem, 'pitch').split()] - lat.orientation = get_text(elem, 'orientation') + lat.orientation = get_text(elem, 'orientation', 'y') outer = get_text(elem, 'outer') if outer is not None: lat.outer = get_universe(int(outer)) From f912506b8e9bcecc5175a3d4e7b1089222f24dc0 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 8 Jun 2019 13:28:21 -0500 Subject: [PATCH 097/151] Adding comment to explain DagMC check. --- src/particle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/particle.cpp b/src/particle.cpp index 52ac941de4..5364e1d2b4 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -460,7 +460,7 @@ Particle::cross_surface() // If a reflective surface is coincident with a lattice or universe // boundary, it is necessary to redetermine the particle's coordinates in // the lower universes. - + // (unless we're using a dagmc model, which has exactly one universe) if (!settings::dagmc) { n_coord_ = 1; if (!find_cell(this, true)) { From be7022aff71fd125770e1de9fdd95b7621904121 Mon Sep 17 00:00:00 2001 From: aprilnovak Date: Sun, 9 Jun 2019 16:55:31 -0700 Subject: [PATCH 098/151] Added method to get total material density. Refs #1253 --- docs/source/capi/index.rst | 9 +++++++++ include/openmc/capi.h | 1 + openmc/capi/material.py | 12 ++++++++++++ src/material.cpp | 13 +++++++++++++ tests/unit_tests/test_capi.py | 3 +++ 5 files changed, 38 insertions(+) diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 274a5240f0..354ee28784 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -335,6 +335,15 @@ Functions :return: Return status (negative if an error occurs) :rtype: int +.. c:function:: int openmc_material_get_density(int32_t index, double* density) + + Get density of a material. + + :param int32_t index: Index in the materials array + :param double* denity: Pointer to a density + :return Return status (negative if an error occurs) + :rtype: int + .. c:function:: int openmc_material_get_id(int32_t index, int32_t* id) Get the ID of a material diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 1f6686df1a..27b7e17a9c 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -50,6 +50,7 @@ extern "C" { int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n); int openmc_material_get_id(int32_t index, int32_t* id); int openmc_material_get_fissionable(int32_t index, bool* fissionable); + int openmc_material_get_density(int32_t index, double* density); int openmc_material_get_volume(int32_t index, double* volume); int openmc_material_set_density(int32_t index, double density, const char* units); int openmc_material_set_densities(int32_t index, int n, const char** name, const double* density); diff --git a/openmc/capi/material.py b/openmc/capi/material.py index 9811853380..43959e2446 100644 --- a/openmc/capi/material.py +++ b/openmc/capi/material.py @@ -32,6 +32,9 @@ _dll.openmc_material_get_densities.argtypes = [ POINTER(c_int)] _dll.openmc_material_get_densities.restype = c_int _dll.openmc_material_get_densities.errcheck = _error_handler +_dll.openmc_material_get_density.argtypes = [c_int32, POINTER(c_double)] +_dll.openmc_material_get_density.restype = c_int +_dll.openmc_material_get_density.errcheck = _error_handler _dll.openmc_material_get_volume.argtypes = [c_int32, POINTER(c_double)] _dll.openmc_material_get_volume.restype = c_int _dll.openmc_material_get_volume.errcheck = _error_handler @@ -139,6 +142,15 @@ class Material(_FortranObjectWithID): return self._get_densities()[0] return nuclides + @property + def density(self): + density = c_double() + try: + _dll.openmc_material_get_density(self._index, density) + except OpenMCError: + return None + return density.value + @property def densities(self): return self._get_densities()[1] diff --git a/src/material.cpp b/src/material.cpp index b5cb2c47a7..ec76eb9884 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -1203,6 +1203,19 @@ openmc_material_get_densities(int32_t index, int** nuclides, double** densities, } } +extern "C" int +openmc_material_get_density(int32_t index, double* density) +{ + if (index >= 0 && index < model::materials.size()) { + auto& mat = model::materials[index]; + *density = mat->density_gpcc_; + return 0; + } else { + set_errmsg("Index in materials array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } +} + extern "C" int openmc_material_get_fissionable(int32_t index, bool* fissionable) { diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 07fef85166..418fe1d3f1 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -120,6 +120,9 @@ def test_material(capi_init): m.set_density(rho) assert sum(m.densities) == pytest.approx(rho) + m.set_density(0.1, 'g/cm3') + assert m.density == pytest.approx(0.1) + def test_new_material(capi_init): with pytest.raises(exc.AllocationError): From e7f3e34088496b2c43d9e7bbb0f3da81d1644e3c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 10 Jun 2019 13:13:52 -0500 Subject: [PATCH 099/151] Fix from_hdf5 for hex lattice with x orientation. Assorted other fixes. --- docs/source/io_formats/geometry.rst | 4 +- include/openmc/lattice.h | 4 +- openmc/lattice.py | 49 ++++++++++--------- src/initialize.cpp | 4 +- src/relaxng/geometry.rnc | 2 +- src/relaxng/geometry.rng | 14 ++++-- .../lattice_hex_x/inputs_true.dat | 3 -- tests/regression_tests/lattice_hex_x/test.py | 3 -- tests/unit_tests/test_lattice.py | 9 ++++ 9 files changed, 50 insertions(+), 42 deletions(-) diff --git a/docs/source/io_formats/geometry.rst b/docs/source/io_formats/geometry.rst index a89bfb1e34..617ac6e870 100644 --- a/docs/source/io_formats/geometry.rst +++ b/docs/source/io_formats/geometry.rst @@ -320,8 +320,8 @@ the following attributes or sub-elements: :orientation: The orientation of the hexagonal lattice. The string "x" indicates that two - sides of the lattice are perpendicular to the x-axis, whereas the string "y" - indicates that two sides are perpendicular to the y-axis. + sides of the lattice are parallel to the x-axis, whereas the string "y" + indicates that two sides are parallel to the y-axis. *Default*: "y" diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index 8f2b6c4a1e..cd73b96408 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -267,8 +267,8 @@ public: private: enum class Orientation { - y, //!< Flat side perpendicular to y-axis - x //!< Flat side perpendicular to x-axis + y, //!< Flat side of lattice parallel to y-axis + x //!< Flat side of lattice parallel to x-axis }; //! Fill universes_ vector for 'y' orientation diff --git a/openmc/lattice.py b/openmc/lattice.py index 0ac47b9065..663d051e3c 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -228,38 +228,38 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): # Add a list for this ring. uarray[-1].append([]) - # Climb up the top-left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - a -= 1 - y += 1 - - # Climb the left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - a -= 1 - - # Climb down the bottom-left. - for i in range(r): - uarray[-1][-1].append(universe_ids[z, y, a]) - y -= 1 - # Climb down the bottom-right. for i in range(r): uarray[-1][-1].append(universe_ids[z, y, a]) - a += 1 y -= 1 - # Climb up the right. + # Climb across the bottom. for i in range(r): uarray[-1][-1].append(universe_ids[z, y, a]) - a += 1 + a -= 1 - # Climb up the top-right. + # Climb up the bottom-left. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a -= 1 + y +=1 + + # Climb up the top-left. for i in range(r): uarray[-1][-1].append(universe_ids[z, y, a]) y += 1 + # Climb across the top. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a += 1 + + # Climb down the top-right. + for i in range(r): + uarray[-1][-1].append(universe_ids[z, y, a]) + a += 1 + y -= 1 + # Move down to the next ring. a -= 1 @@ -405,9 +405,11 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): ---------- idx : Iterable of int Lattice element indices. For a rectangular lattice, the indices are - given in the :math:`(x,y)` or :math:`(x,y,z)` coordinate system.For + given in the :math:`(x,y)` or :math:`(x,y,z)` coordinate system. For hexagonal lattices, they are given in the :math:`x,\alpha` or - :math:`x,\alpha,z` coordinate systems. + :math:`x,\alpha,z` coordinate systems for "y" orientations and + :math:`\alpha,y` or :math:`\alpha,y,z` coordinate systems for "x" + orientations. Returns ------- @@ -947,8 +949,7 @@ class HexLattice(Lattice): string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) string += '{0: <16}{1}{2}\n'.format('\tOrientation', '=\t', - "x" if (self._orientation == 'x') - else "y") + self._orientation) string += '{0: <16}{1}{2}\n'.format('\t# Rings', '=\t', self._num_rings) string += '{0: <16}{1}{2}\n'.format('\t# Axial', '=\t', self._num_axial) string += '{0: <16}{1}{2}\n'.format('\tCenter', '=\t', diff --git a/src/initialize.cpp b/src/initialize.cpp index 061025745f..f8a4f4a39a 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -36,9 +36,7 @@ int openmc_init(int argc, char* argv[], const void* intracomm) { using namespace openmc; - - char cCurrentPath[256]; - + #ifdef OPENMC_MPI // Check if intracomm was passed MPI_Comm comm; diff --git a/src/relaxng/geometry.rnc b/src/relaxng/geometry.rnc index 30fdf2c8ad..c349340717 100644 --- a/src/relaxng/geometry.rnc +++ b/src/relaxng/geometry.rnc @@ -36,7 +36,7 @@ element geometry { attribute dimension { list { xsd:positiveInteger+ } }) & (element lower_left { list { xsd:double+ } } | attribute lower_left { list { xsd:double+ } }) & (element pitch { list { xsd:double+ } } | attribute pitch { list { xsd:double+ } }) & - (element orient { ( "OX" ) } | attribute orient { ( "OX" ) })? & + (element orientation { ( "x" | "y" ) } | attribute orientation { ( "x" | "y" ) })? & (element universes { list { xsd:int+ } } | attribute universes { list { xsd:int+ } }) & (element outer { xsd:int } | attribute outer { xsd:int })? }* diff --git a/src/relaxng/geometry.rng b/src/relaxng/geometry.rng index e6c5f6ca16..52b9825be8 100644 --- a/src/relaxng/geometry.rng +++ b/src/relaxng/geometry.rng @@ -296,11 +296,17 @@ - - OX + + + x + y + - - OX + + + x + y + diff --git a/tests/regression_tests/lattice_hex_x/inputs_true.dat b/tests/regression_tests/lattice_hex_x/inputs_true.dat index 9c0b102a93..b528a97b7c 100644 --- a/tests/regression_tests/lattice_hex_x/inputs_true.dat +++ b/tests/regression_tests/lattice_hex_x/inputs_true.dat @@ -119,8 +119,5 @@ -13.62546635287517 -13.62546635287517 0.0 13.62546635287517 13.62546635287517 10.0 - -

false - 22 diff --git a/tests/regression_tests/lattice_hex_x/test.py b/tests/regression_tests/lattice_hex_x/test.py index 405e9d1e9c..538f790dac 100644 --- a/tests/regression_tests/lattice_hex_x/test.py +++ b/tests/regression_tests/lattice_hex_x/test.py @@ -6,7 +6,6 @@ import numpy as np class HexLatticeOXTestHarness(PyAPITestHarness): def _build_inputs(self): - materials = openmc.Materials() fuel_mat = openmc.Material(material_id=1, name="UO2") @@ -195,7 +194,6 @@ class HexLatticeOXTestHarness(PyAPITestHarness): source.space = openmc.stats.Box(ll, ur) source.strength = 1.0 settings.source = source - settings.output = {'summary': False} settings.batches = 10 settings.inactive = 5 settings.particles = 1000 @@ -204,6 +202,5 @@ class HexLatticeOXTestHarness(PyAPITestHarness): def test_lattice_hex_ox_surf(): - harness = HexLatticeOXTestHarness('statepoint.10.h5') harness.main() diff --git a/tests/unit_tests/test_lattice.py b/tests/unit_tests/test_lattice.py index fe86e2a784..c4a3517769 100644 --- a/tests/unit_tests/test_lattice.py +++ b/tests/unit_tests/test_lattice.py @@ -202,6 +202,13 @@ def test_get_universe(rlat2, rlat3, hlat2, hlat3): assert hlat2.get_universe((1, 0)) == u1 assert hlat2.get_universe((-2, 2)) == u1 + hlat2.orientation = 'x' + assert hlat2.get_universe((2, 0)) == u2 + assert hlat2.get_universe((1, 0)) == u2 + assert hlat2.get_universe((1, 1)) == u1 + assert hlat2.get_universe((-1, 1)) == u1 + hlat2.orientation = 'y' + u1, u2, u3, outer = hlat3.univs assert hlat3.get_universe((0, 0, 0)) == u2 assert hlat3.get_universe((0, 0, 1)) == u3 @@ -342,3 +349,5 @@ def test_show_indices(): for i in range(1, 11): lines = openmc.HexLattice.show_indices(i).split('\n') assert len(lines) == 4*i - 3 + lines_x = openmc.HexLattice.show_indices(i, 'x').split('\n') + assert len(lines) == 4*i - 3 From 3c223248a80043b12e700b09f1e32ebf54f6ebd3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 11 Jun 2019 11:51:34 -0400 Subject: [PATCH 100/151] Support Path objects for DataLibrary.register_filename HDF5 File object supports reading from file-like objects, which includes pathlib.Path. Retain the filename of the path in the libraries list --- openmc/data/library.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openmc/data/library.py b/openmc/data/library.py index 905dbbe4a7..e090b2062f 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -1,5 +1,6 @@ import os import xml.etree.ElementTree as ET +import pathlib import h5py @@ -48,19 +49,20 @@ class DataLibrary(EqualityMixin): Parameters ---------- - filename : str + filename : str or Path Path to the file to be registered. """ - # Support pathlib - # TODO: Remove when support is Python 3.6+ only - filename = str(filename) + if not isinstance(filename, pathlib.Path): + path = pathlib.Path(filename) + else: + path = filename - with h5py.File(filename, 'r') as h5file: + with h5py.File(path, 'r') as h5file: filetype = h5file.attrs['filetype'].decode()[5:] materials = list(h5file) - library = {'path': filename, 'type': filetype, 'materials': materials} + library = {'path': path.name, 'type': filetype, 'materials': materials} self.libraries.append(library) def export_to_xml(self, path='cross_sections.xml'): From ac4056e96d267976d48b3bae2b1a6e23e7367a2a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 11 Jun 2019 12:23:38 -0400 Subject: [PATCH 101/151] Support depletion_chain in DataLibrary to/from xml and register DataLibrary.from_xml looks for depletion_chain node in the cross sections xml file. If found the path to this file is added to the list of libraries. An empty list of materials is used for consistency with other libraries. Type and path attributes are consistent. DataLibrary.export_to_xml does not write the materials attribute if the value stored in that library evaluates to False, e.g. for the empty list stored in the depletion chain. DataLibrary.register_file has a conditional wherein a passed xml file is treated as the depletion_chain and hdf5 files are treated as material/isotopic cross section files. --- openmc/data/library.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/openmc/data/library.py b/openmc/data/library.py index e090b2062f..d1a0d778d9 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -51,6 +51,8 @@ class DataLibrary(EqualityMixin): ---------- filename : str or Path Path to the file to be registered. + If an ``xml`` file, treat as the depletion chain file without + materials. """ if not isinstance(filename, pathlib.Path): @@ -58,9 +60,17 @@ class DataLibrary(EqualityMixin): else: path = filename - with h5py.File(path, 'r') as h5file: - filetype = h5file.attrs['filetype'].decode()[5:] - materials = list(h5file) + if path.suffix == '.xml': + filetype = 'depletion_chain' + materials = [] + elif path.suffix == '.h5': + with h5py.File(path, 'r') as h5file: + filetype = h5file.attrs['filetype'].decode()[5:] + materials = list(h5file) + else: + raise ValueError( + "File type {} not supported by {}" + .format(path.name, self.__class__.__name__)) library = {'path': path.name, 'type': filetype, 'materials': materials} self.libraries.append(library) @@ -88,7 +98,8 @@ class DataLibrary(EqualityMixin): for library in self.libraries: lib_element = ET.SubElement(root, "library") - lib_element.set('materials', ' '.join(library['materials'])) + if library['materials']: + lib_element.set('materials', ' '.join(library['materials'])) lib_element.set('path', os.path.relpath(library['path'], common_dir)) lib_element.set('type', library['type']) @@ -108,7 +119,7 @@ class DataLibrary(EqualityMixin): ---------- path : str, optional Path to XML file to read. If not provided, the - `OPENMC_CROSS_SECTIONS` environment variable will be used. + :envvar:`OPENMC_CROSS_SECTIONS` environment variable will be used. Returns ------- @@ -148,4 +159,13 @@ class DataLibrary(EqualityMixin): 'materials': materials} data.libraries.append(library) + # get depletion chain data + + dep_node = root.find("depletion_chain") + if dep_node is not None: + filename = os.path.join(directory, dep_node.attrib['path']) + library = {'path': filename, 'type': 'depletion_chain', + 'materials': []} + data.libraries.append(library) + return data From 3429ab21b286a23fe091a73af9dea29a4c228314 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 11 Jun 2019 13:36:03 -0400 Subject: [PATCH 102/151] Retrieve depletion_chain for TransportOperator using DataLibrary --- openmc/deplete/abc.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 14dd3638ce..d25d1d74f2 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -11,6 +11,7 @@ from abc import ABCMeta, abstractmethod from xml.etree import ElementTree as ET from warnings import warn +from openmc.data import DataLibrary from .chain import Chain OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) @@ -65,19 +66,17 @@ class TransportOperator(metaclass=ABCMeta): if chain_file is None: chain_file = os.environ.get("OPENMC_DEPLETE_CHAIN", None) if chain_file is None: - xs_file = os.environ.get("OPENMC_CROSS_SECTIONS", None) - if xs_file is None: - raise IOError( - "OPENMC_CROSS_SECTIONS environment variable " - "not set.") - root = ET.parse(xs_file).getroot() - node = root.find("depletion_chain") - if node is None: + data = DataLibrary.from_xml() + # search for depletion_chain path from end of list + for lib in data.libraries[::-1]: + if lib['type'] == 'depletion_chain': + break + else: raise IOError( "No chain specified, either manually or " "under depletion_chain in environment variable " "OPENMC_CROSS_SECTIONS.") - chain_file = node.get("path") + chain_file = lib['path'] else: warn("Use of OPENMC_DEPLETE_CHAIN is deprecated in favor " "of adding depletion_chain to OPENMC_CROSS_SECTIONS", From 0c0e591cb749663e949284b94eae8ad6b3db0146 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 11 Jun 2019 13:54:13 -0400 Subject: [PATCH 103/151] Simple changes to address reviewer comments Re-organize some imports, use run_in_tmpdir fixture, use FutureWarning over DeprecationWarning --- openmc/deplete/abc.py | 2 +- tests/unit_tests/test_deplete_operator.py | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index d25d1d74f2..226877c149 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -80,7 +80,7 @@ class TransportOperator(metaclass=ABCMeta): else: warn("Use of OPENMC_DEPLETE_CHAIN is deprecated in favor " "of adding depletion_chain to OPENMC_CROSS_SECTIONS", - DeprecationWarning) + FutureWarning) self.chain = Chain.from_xml(chain_file) @abstractmethod diff --git a/tests/unit_tests/test_deplete_operator.py b/tests/unit_tests/test_deplete_operator.py index c188ec94c2..757b51d923 100644 --- a/tests/unit_tests/test_deplete_operator.py +++ b/tests/unit_tests/test_deplete_operator.py @@ -4,21 +4,20 @@ Modifies and resets environment variable OPENMC_CROSS_SECTIONS to a custom file with new depletion_chain node """ -from os import remove from os import environ from unittest import mock from pathlib import Path -import pytest +import pytest from openmc.deplete.abc import TransportOperator from openmc.deplete.chain import Chain BARE_XS_FILE = "bare_cross_sections.xml" -CHAIN_PATH = Path().cwd() / "tests" / "chain_simple.xml" +CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" -@pytest.fixture(scope="module") -def bare_xs(): +@pytest.fixture() +def bare_xs(run_in_tmpdir): """Create a very basic cross_sections file, return simple Chain. """ @@ -33,7 +32,6 @@ def bare_xs(): out.write(bare_xs_contents) yield - remove(BARE_XS_FILE) class BareDepleteOperator(TransportOperator): From 1aa7abd7fa5c4e3047bb4d768a8d4a4cbfe1de0e Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 12 Jun 2019 15:32:37 -0500 Subject: [PATCH 104/151] Moving some comments. --- src/cell.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 17ad62c88c..7914e9e615 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1,3 +1,4 @@ + #include "openmc/cell.h" #include @@ -616,7 +617,8 @@ DAGCell::DAGCell() : Cell{} {}; std::pair DAGCell::distance(Position r, Direction u, int32_t on_surface) const { - // if we've changed direction, reset the history and update last direction + // if we've changed direction or we're not on a surface, + // reset the history and update last direction if (u != last_dir || on_surface == 0) { history.reset(); last_dir = u; @@ -633,7 +635,8 @@ DAGCell::distance(Position r, Direction u, int32_t on_surface) const int surf_idx; if (hit_surf != 0) { surf_idx = dagmc_ptr_->index_by_handle(hit_surf); - } else { // indicate that particle is lost + } else { + // indicate that particle is lost surf_idx = -1; dist = INFINITY; } From c924606064e9673b14ccfd72fe484749e5f2d4f0 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 12 Jun 2019 16:21:24 -0500 Subject: [PATCH 105/151] Removing some needless lines in cell.cpp --- src/cell.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 7914e9e615..4ad114b7ed 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -610,8 +610,6 @@ CSGCell::contains_complex(Position r, Direction u, int32_t on_surface) const // DAGMC Cell implementation //============================================================================== #ifdef DAGMC - - DAGCell::DAGCell() : Cell{} {}; std::pair From 42b5f2aa7e407eaa0668a2e737dc31373a0f7a5c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 12 Jun 2019 16:38:35 -0500 Subject: [PATCH 106/151] Adding history reset at the end of the particle transport loop. --- include/openmc/cell.h | 2 +- src/particle.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index cc4429b44f..080dac345c 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -11,7 +11,7 @@ #include "hdf5.h" #include "pugixml.hpp" #ifdef DAGMC -#include "DagMC.hpp" +#include "dagmc.h" #endif #include "openmc/constants.h" diff --git a/src/particle.cpp b/src/particle.cpp index 5364e1d2b4..eef58a490d 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -372,6 +372,12 @@ Particle::transport() } } + #ifdef DAGMC + if(settings::dagmc) { + history.reset(); + } + #endif + // Finish particle track output. if (write_track_) { write_particle_track(*this); From 75b97b0b34acd3aadd1d171308543efc6a0526ef Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 5 Feb 2019 11:16:33 -0600 Subject: [PATCH 107/151] Add GSL Lite 0.34.0 header-only library --- CMakeLists.txt | 12 +- vendor/gsl/include/gsl/gsl | 27 + vendor/gsl/include/gsl/gsl-lite.hpp | 2857 +++++++++++++++++++++++++++ 3 files changed, 2895 insertions(+), 1 deletion(-) create mode 100644 vendor/gsl/include/gsl/gsl create mode 100644 vendor/gsl/include/gsl/gsl-lite.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fea014f12d..0146cc6e07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,6 +119,16 @@ add_subdirectory(vendor/xtl) add_subdirectory(vendor/xtensor) target_link_libraries(xtensor INTERFACE xtl) +#=============================================================================== +# GSL header-only library +#=============================================================================== + +add_library(gsl INTERFACE) +target_include_directories(gsl INTERFACE vendor/gsl/include) + +# Make sure contract violations throw exceptions +target_compile_definitions(gsl INTERFACE GSL_THROW_ON_CONTRACT_VIOLATION) + #=============================================================================== # RPATH information #=============================================================================== @@ -275,7 +285,7 @@ endif() # target_link_libraries treats any arguments starting with - but not -l as # linker flags. Thus, we can pass both linker flags and libraries together. target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES} - pugixml faddeeva xtensor) + pugixml faddeeva xtensor gsl) if(dagmc) target_compile_definitions(libopenmc PRIVATE DAGMC) diff --git a/vendor/gsl/include/gsl/gsl b/vendor/gsl/include/gsl/gsl new file mode 100644 index 0000000000..be3f2ccdb2 --- /dev/null +++ b/vendor/gsl/include/gsl/gsl @@ -0,0 +1,27 @@ +// +// gsl-lite is based on GSL: Guidelines Support Library. +// For more information see https://github.com/martinmoene/gsl-lite +// +// Copyright (c) 2015 Martin Moene +// Copyright (c) 2015 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// mimic MS include hierarchy + +#pragma once + +#ifndef GSL_GSL_H_INCLUDED +#define GSL_GSL_H_INCLUDED + +#include "gsl-lite.hpp" + +#endif // GSL_GSL_H_INCLUDED diff --git a/vendor/gsl/include/gsl/gsl-lite.hpp b/vendor/gsl/include/gsl/gsl-lite.hpp new file mode 100644 index 0000000000..825087cabd --- /dev/null +++ b/vendor/gsl/include/gsl/gsl-lite.hpp @@ -0,0 +1,2857 @@ +// +// gsl-lite is based on GSL: Guidelines Support Library. +// For more information see https://github.com/martinmoene/gsl-lite +// +// Copyright (c) 2015-2018 Martin Moene +// Copyright (c) 2015-2018 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once + +#ifndef GSL_GSL_LITE_HPP_INCLUDED +#define GSL_GSL_LITE_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define gsl_lite_MAJOR 0 +#define gsl_lite_MINOR 34 +#define gsl_lite_PATCH 0 + +#define gsl_lite_VERSION gsl_STRINGIFY(gsl_lite_MAJOR) "." gsl_STRINGIFY(gsl_lite_MINOR) "." gsl_STRINGIFY(gsl_lite_PATCH) + +// gsl-lite backward compatibility: + +#ifdef gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR +# define gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR +# pragma message ("gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR is deprecated since gsl-lite 0.7.0; replace with gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR, or consider span(with_container, cont).") +#endif + +// M-GSL compatibility: + +#if defined( GSL_THROW_ON_CONTRACT_VIOLATION ) +# define gsl_CONFIG_CONTRACT_VIOLATION_THROWS 1 +#endif + +#if defined( GSL_TERMINATE_ON_CONTRACT_VIOLATION ) +# define gsl_CONFIG_CONTRACT_VIOLATION_THROWS 0 +#endif + +#if defined( GSL_UNENFORCED_ON_CONTRACT_VIOLATION ) +# define gsl_CONFIG_CONTRACT_LEVEL_OFF 1 +#endif + +// Configuration: Features + +#ifndef gsl_FEATURE_WITH_CONTAINER_TO_STD +# define gsl_FEATURE_WITH_CONTAINER_TO_STD 99 +#endif + +#ifndef gsl_FEATURE_MAKE_SPAN_TO_STD +# define gsl_FEATURE_MAKE_SPAN_TO_STD 99 +#endif + +#ifndef gsl_FEATURE_BYTE_SPAN_TO_STD +# define gsl_FEATURE_BYTE_SPAN_TO_STD 99 +#endif + +#ifndef gsl_FEATURE_IMPLICIT_MACRO +# define gsl_FEATURE_IMPLICIT_MACRO 1 +#endif + +#ifndef gsl_FEATURE_OWNER_MACRO +# define gsl_FEATURE_OWNER_MACRO 1 +#endif + +#ifndef gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD +# define gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD 0 +#endif + +// Configuration: Other + +#ifndef gsl_CONFIG_DEPRECATE_TO_LEVEL +# define gsl_CONFIG_DEPRECATE_TO_LEVEL 0 +#endif + +#ifndef gsl_CONFIG_SPAN_INDEX_TYPE +# define gsl_CONFIG_SPAN_INDEX_TYPE size_t +#endif + +#ifndef gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR +# define gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR 0 +#endif + +#ifndef gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF +# define gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF 0 +#endif + +#ifndef gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS +# define gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS 0 +#endif + +#ifndef gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON +# define gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON 1 +#endif + +#ifndef gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR +# define gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR 0 +#endif + +#if defined( gsl_CONFIG_CONTRACT_LEVEL_ON ) +# define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x11 +#elif defined( gsl_CONFIG_CONTRACT_LEVEL_OFF ) +# define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x00 +#elif defined( gsl_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY ) +# define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x01 +#elif defined( gsl_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY ) +# define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x10 +#else +# define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x11 +#endif + +#if !defined( gsl_CONFIG_CONTRACT_VIOLATION_THROWS ) && \ + !defined( gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES ) +# define gsl_CONFIG_CONTRACT_VIOLATION_THROWS_V 0 +#elif defined( gsl_CONFIG_CONTRACT_VIOLATION_THROWS ) && \ + !defined( gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES ) +# define gsl_CONFIG_CONTRACT_VIOLATION_THROWS_V 1 +#elif !defined( gsl_CONFIG_CONTRACT_VIOLATION_THROWS ) && \ + defined( gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES ) +# define gsl_CONFIG_CONTRACT_VIOLATION_THROWS_V 0 +#else +# error only one of gsl_CONFIG_CONTRACT_VIOLATION_THROWS and gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES may be defined. +#endif + +// C++ language version detection (C++20 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef gsl_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define gsl_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define gsl_CPLUSPLUS __cplusplus +# endif +#endif + +#define gsl_CPP98_OR_GREATER ( gsl_CPLUSPLUS >= 199711L ) +#define gsl_CPP11_OR_GREATER ( gsl_CPLUSPLUS >= 201103L ) +#define gsl_CPP14_OR_GREATER ( gsl_CPLUSPLUS >= 201402L ) +#define gsl_CPP17_OR_GREATER ( gsl_CPLUSPLUS >= 201703L ) +#define gsl_CPP20_OR_GREATER ( gsl_CPLUSPLUS >= 202000L ) + +// C++ language version (represent 98 as 3): + +#define gsl_CPLUSPLUS_V ( gsl_CPLUSPLUS / 100 - (gsl_CPLUSPLUS > 200000 ? 2000 : 1994) ) + +// half-open range [lo..hi): +#define gsl_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017) + +#if defined(_MSC_VER ) && !defined(__clang__) +# define gsl_COMPILER_MSVC_VER (_MSC_VER ) +# define gsl_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define gsl_COMPILER_MSVC_VER 0 +# define gsl_COMPILER_MSVC_VERSION 0 +#endif + +#define gsl_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) + +#if defined(__clang__) +# define gsl_COMPILER_CLANG_VERSION gsl_COMPILER_VERSION( __clang_major__, __clang_minor__, __clang_patchlevel__ ) +#else +# define gsl_COMPILER_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define gsl_COMPILER_GNUC_VERSION gsl_COMPILER_VERSION( __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ ) +#else +# define gsl_COMPILER_GNUC_VERSION 0 +#endif + +// Method enabling (C++98, VC120 (VS2013) cannot use __VA_ARGS__) + +#define gsl_REQUIRES_0(VA) \ + template< bool B = (VA), typename std::enable_if::type = 0 > + +#define gsl_REQUIRES_T(VA) \ + , typename = typename std::enable_if< (VA), gsl::detail::enabler >::type + +#define gsl_REQUIRES_R(R, VA) \ + typename std::enable_if::type + +#define gsl_REQUIRES_A(VA) \ + , typename std::enable_if::type = nullptr + +// Compiler non-strict aliasing: + +#if defined(__clang__) || defined(__GNUC__) +# define gsl_may_alias __attribute__((__may_alias__)) +#else +# define gsl_may_alias +#endif + +// Presence of gsl, language and library features: + +#define gsl_IN_STD( v ) ( ((v) == 98 ? 3 : (v)) >= gsl_CPLUSPLUS_V ) + +#define gsl_DEPRECATE_TO_LEVEL( level ) ( level <= gsl_CONFIG_DEPRECATE_TO_LEVEL ) +#define gsl_FEATURE_TO_STD( feature ) ( gsl_IN_STD( gsl_FEATURE( feature##_TO_STD ) ) ) +#define gsl_FEATURE( feature ) ( gsl_FEATURE_##feature ) +#define gsl_CONFIG( feature ) ( gsl_CONFIG_##feature ) +#define gsl_HAVE( feature ) ( gsl_HAVE_##feature ) + +// Presence of wide character support: + +#ifdef __DJGPP__ +# define gsl_HAVE_WCHAR 0 +#else +# define gsl_HAVE_WCHAR 1 +#endif + +// Presence of language & library features: + +#ifdef _HAS_CPP0X +# define gsl_HAS_CPP0X _HAS_CPP0X +#else +# define gsl_HAS_CPP0X 0 +#endif + +#define gsl_CPP11_100 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1600) +#define gsl_CPP11_110 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1700) +#define gsl_CPP11_120 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1800) +#define gsl_CPP11_140 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900) + +#define gsl_CPP14_000 (gsl_CPP14_OR_GREATER) +#define gsl_CPP14_120 (gsl_CPP14_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1800) +#define gsl_CPP14_140 (gsl_CPP14_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900) + +#define gsl_CPP17_000 (gsl_CPP17_OR_GREATER) +#define gsl_CPP17_140 (gsl_CPP17_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900) + +#define gsl_CPP11_140_CPP0X_90 (gsl_CPP11_140 || (gsl_COMPILER_MSVC_VER >= 1500 && gsl_HAS_CPP0X)) +#define gsl_CPP11_140_CPP0X_100 (gsl_CPP11_140 || (gsl_COMPILER_MSVC_VER >= 1600 && gsl_HAS_CPP0X)) + +// Presence of C++11 language features: + +#define gsl_HAVE_AUTO gsl_CPP11_100 +#define gsl_HAVE_NULLPTR gsl_CPP11_100 +#define gsl_HAVE_RVALUE_REFERENCE gsl_CPP11_100 + +#define gsl_HAVE_ENUM_CLASS gsl_CPP11_110 + +#define gsl_HAVE_ALIAS_TEMPLATE gsl_CPP11_120 +#define gsl_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG gsl_CPP11_120 +#define gsl_HAVE_EXPLICIT gsl_CPP11_120 +#define gsl_HAVE_INITIALIZER_LIST gsl_CPP11_120 + +#define gsl_HAVE_CONSTEXPR_11 gsl_CPP11_140 +#define gsl_HAVE_IS_DEFAULT gsl_CPP11_140 +#define gsl_HAVE_IS_DELETE gsl_CPP11_140 +#define gsl_HAVE_NOEXCEPT gsl_CPP11_140 + +#if gsl_CPP11_OR_GREATER +// see above +#endif + +// Presence of C++14 language features: + +#define gsl_HAVE_CONSTEXPR_14 gsl_CPP14_000 +#define gsl_HAVE_DECLTYPE_AUTO gsl_CPP14_140 + +// Presence of C++17 language features: +// MSVC: template parameter deduction guides since Visual Studio 2017 v15.7 + +#define gsl_HAVE_ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE gsl_CPP17_000 +#define gsl_HAVE_DEDUCTION_GUIDES (gsl_CPP17_000 && ! gsl_BETWEEN( gsl_COMPILER_MSVC_VERSION, 1, 999 ) ) + +// Presence of C++ library features: + +#define gsl_HAVE_ADDRESSOF gsl_CPP17_000 +#define gsl_HAVE_ARRAY gsl_CPP11_110 +#define gsl_HAVE_TYPE_TRAITS gsl_CPP11_110 +#define gsl_HAVE_TR1_TYPE_TRAITS gsl_CPP11_110 + +#define gsl_HAVE_CONTAINER_DATA_METHOD gsl_CPP11_140_CPP0X_90 +#define gsl_HAVE_STD_DATA gsl_CPP17_000 + +#define gsl_HAVE_SIZED_TYPES gsl_CPP11_140 + +#define gsl_HAVE_MAKE_SHARED gsl_CPP11_140_CPP0X_100 +#define gsl_HAVE_SHARED_PTR gsl_CPP11_140_CPP0X_100 +#define gsl_HAVE_UNIQUE_PTR gsl_CPP11_140_CPP0X_100 + +#define gsl_HAVE_MAKE_UNIQUE gsl_CPP14_120 + +#define gsl_HAVE_UNCAUGHT_EXCEPTIONS gsl_CPP17_140 + +#define gsl_HAVE_ADD_CONST gsl_HAVE_TYPE_TRAITS +#define gsl_HAVE_INTEGRAL_CONSTANT gsl_HAVE_TYPE_TRAITS +#define gsl_HAVE_REMOVE_CONST gsl_HAVE_TYPE_TRAITS +#define gsl_HAVE_REMOVE_REFERENCE gsl_HAVE_TYPE_TRAITS + +#define gsl_HAVE_TR1_ADD_CONST gsl_HAVE_TR1_TYPE_TRAITS +#define gsl_HAVE_TR1_INTEGRAL_CONSTANT gsl_HAVE_TR1_TYPE_TRAITS +#define gsl_HAVE_TR1_REMOVE_CONST gsl_HAVE_TR1_TYPE_TRAITS +#define gsl_HAVE_TR1_REMOVE_REFERENCE gsl_HAVE_TR1_TYPE_TRAITS + +// C++ feature usage: + +#if gsl_HAVE( ADDRESSOF ) +# define gsl_ADDRESSOF(x) std::addressof(x) +#else +# define gsl_ADDRESSOF(x) (&x) +#endif + +#if gsl_HAVE( CONSTEXPR_11 ) +# define gsl_constexpr constexpr +#else +# define gsl_constexpr /*constexpr*/ +#endif + +#if gsl_HAVE( CONSTEXPR_14 ) +# define gsl_constexpr14 constexpr +#else +# define gsl_constexpr14 /*constexpr*/ +#endif + +#if gsl_HAVE( EXPLICIT ) +# define gsl_explicit explicit +#else +# define gsl_explicit /*explicit*/ +#endif + +#if gsl_FEATURE( IMPLICIT_MACRO ) +# define implicit /*implicit*/ +#endif + +#if gsl_HAVE( IS_DELETE ) +# define gsl_is_delete = delete +#else +# define gsl_is_delete +#endif + +#if gsl_HAVE( IS_DELETE ) +# define gsl_is_delete_access public +#else +# define gsl_is_delete_access private +#endif + +#if !gsl_HAVE( NOEXCEPT ) || gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) +# define gsl_noexcept /*noexcept*/ +#else +# define gsl_noexcept noexcept +#endif + +#if gsl_HAVE( NULLPTR ) +# define gsl_nullptr nullptr +#else +# define gsl_nullptr NULL +#endif + +#define gsl_DIMENSION_OF( a ) ( sizeof(a) / sizeof(0[a]) ) + +// Other features: + +#define gsl_HAVE_CONSTRAINED_SPAN_CONTAINER_CTOR \ + ( gsl_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG && gsl_HAVE_CONTAINER_DATA_METHOD ) + +// Note: !defined(__NVCC__) doesn't work with nvcc here: +#define gsl_HAVE_UNCONSTRAINED_SPAN_CONTAINER_CTOR \ + ( gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR && (__NVCC__== 0) ) + +// GSL API (e.g. for CUDA platform): + +#ifndef gsl_api +# ifdef __CUDACC__ +# define gsl_api __host__ __device__ +# else +# define gsl_api /*gsl_api*/ +# endif +#endif + +// Additional includes: + +#if gsl_HAVE( ARRAY ) +# include +#endif + +#if gsl_HAVE( TYPE_TRAITS ) +# include +#elif gsl_HAVE( TR1_TYPE_TRAITS ) +# include +#endif + +#if gsl_HAVE( SIZED_TYPES ) +# include +#endif + +// MSVC warning suppression macros: + +#if gsl_COMPILER_MSVC_VERSION >= 140 +# define gsl_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] +# define gsl_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress: code) ) +# define gsl_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes)) +# define gsl_RESTORE_MSVC_WARNINGS() __pragma(warning(pop )) +#else +# define gsl_SUPPRESS_MSGSL_WARNING(expr) +# define gsl_SUPPRESS_MSVC_WARNING(code, descr) +# define gsl_DISABLE_MSVC_WARNINGS(codes) +# define gsl_RESTORE_MSVC_WARNINGS() +#endif + +// Suppress the following MSVC GSL warnings: +// - C26410: gsl::r.32: the parameter 'ptr' is a reference to const unique pointer, use const T* or const T& instead +// - C26415: gsl::r.30: smart pointer parameter 'ptr' is used only to access contained pointer. Use T* or T& instead +// - C26418: gsl::r.36: shared pointer parameter 'ptr' is not copied or moved. Use T* or T& instead +// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; +// use brace initialization, gsl::narrow_cast or gsl::narow +// - C26439, gsl::f.6 : special function 'function' can be declared 'noexcept' +// - C26440, gsl::f.6 : function 'function' can be declared 'noexcept' +// - C26473: gsl::t.1 : don't cast between pointer types where the source type and the target type are the same +// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead +// - C26482, gsl::b.2 : only index into arrays using constant expressions +// - C26490: gsl::t.1 : don't use reinterpret_cast + +gsl_DISABLE_MSVC_WARNINGS( 26410 26415 26418 26472 26439 26440 26473 26481 26482 26490 ) + +namespace gsl { + +// forward declare span<>: + +template< class T > +class span; + +// C++11 emulation: + +namespace std11 { + +#if gsl_HAVE( ADD_CONST ) + +using std::add_const; + +#elif gsl_HAVE( TR1_ADD_CONST ) + +using std::tr1::add_const; + +#else + +template< class T > struct add_const { typedef const T type; }; + +#endif // gsl_HAVE( ADD_CONST ) + +#if gsl_HAVE( REMOVE_CONST ) + +using std::remove_cv; +using std::remove_const; +using std::remove_volatile; + +#elif gsl_HAVE( TR1_REMOVE_CONST ) + +using std::tr1::remove_cv; +using std::tr1::remove_const; +using std::tr1::remove_volatile; + +#else + +template< class T > struct remove_const { typedef T type; }; +template< class T > struct remove_const { typedef T type; }; + +template< class T > struct remove_volatile { typedef T type; }; +template< class T > struct remove_volatile { typedef T type; }; + +template< class T > +struct remove_cv +{ + typedef typename remove_volatile::type>::type type; +}; + +#endif // gsl_HAVE( REMOVE_CONST ) + +#if gsl_HAVE( INTEGRAL_CONSTANT ) + +using std::integral_constant; +using std::true_type; +using std::false_type; + +#elif gsl_HAVE( TR1_INTEGRAL_CONSTANT ) + +using std::tr1::integral_constant; +using std::tr1::true_type; +using std::tr1::false_type; + +#else + +template< int v > struct integral_constant { enum { value = v }; }; +typedef integral_constant< true > true_type; +typedef integral_constant< false > false_type; + +#endif + +} // namespace std11 + +namespace detail { + +/// for nsel_REQUIRES_T + +/*enum*/ class enabler{}; + +#if gsl_HAVE( TYPE_TRAITS ) + +template< class Q > +struct is_span_oracle : std11::false_type{}; + +template< class T> +struct is_span_oracle< span > : std11::true_type{}; + +template< class Q > +struct is_span : is_span_oracle< typename std11::remove_cv::type >{}; + +template< class Q > +struct is_std_array_oracle : std11::false_type{}; + +#if gsl_HAVE( ARRAY ) + +template< class T, std::size_t Extent > +struct is_std_array_oracle< std::array > : std11::true_type{}; + +#endif + +template< class Q > +struct is_std_array : is_std_array_oracle< typename std11::remove_cv::type >{}; + +template< class Q > +struct is_array : std11::false_type {}; + +template< class T > +struct is_array : std11::true_type {}; + +template< class T, std::size_t N > +struct is_array : std11::true_type {}; + +#endif // gsl_HAVE( TYPE_TRAITS ) + +} // namespace detail + +// +// GSL.util: utilities +// + +// index type for all container indexes/subscripts/sizes +typedef gsl_CONFIG_SPAN_INDEX_TYPE index; // p0122r3 uses std::ptrdiff_t + +// +// GSL.owner: ownership pointers +// +#if gsl_HAVE( SHARED_PTR ) + using std::unique_ptr; + using std::shared_ptr; + using std::make_shared; +# if gsl_HAVE( MAKE_UNIQUE ) + using std::make_unique; +# endif +#endif + +#if gsl_HAVE( ALIAS_TEMPLATE ) +# if gsl_HAVE( TYPE_TRAITS ) + template< class T + gsl_REQUIRES_T( std::is_pointer::value ) + > + using owner = T; +# else + template< class T > using owner = T; +# endif +#else + template< class T > struct owner { typedef T type; }; +#endif + +#define gsl_HAVE_OWNER_TEMPLATE gsl_HAVE_ALIAS_TEMPLATE + +#if gsl_FEATURE( OWNER_MACRO ) +# if gsl_HAVE( OWNER_TEMPLATE ) +# define Owner(t) ::gsl::owner +# else +# define Owner(t) ::gsl::owner::type +# endif +#endif + +// +// GSL.assert: assertions +// + +#define gsl_ELIDE_CONTRACT_EXPECTS ( 0 == ( gsl_CONFIG_CONTRACT_LEVEL_MASK & 0x01 ) ) +#define gsl_ELIDE_CONTRACT_ENSURES ( 0 == ( gsl_CONFIG_CONTRACT_LEVEL_MASK & 0x10 ) ) + +#if gsl_ELIDE_CONTRACT_EXPECTS +# define Expects( x ) /* Expects elided */ +#elif gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) +# define Expects( x ) ::gsl::fail_fast_assert( (x), "GSL: Precondition failure at " __FILE__ ":" gsl_STRINGIFY(__LINE__) ); +#else +# define Expects( x ) ::gsl::fail_fast_assert( (x) ) +#endif + +#if gsl_ELIDE_CONTRACT_EXPECTS +# define gsl_EXPECTS_UNUSED_PARAM( x ) /* Make param unnamed if Expects elided */ +#else +# define gsl_EXPECTS_UNUSED_PARAM( x ) x +#endif + +#if gsl_ELIDE_CONTRACT_ENSURES +# define Ensures( x ) /* Ensures elided */ +#elif gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) +# define Ensures( x ) ::gsl::fail_fast_assert( (x), "GSL: Postcondition failure at " __FILE__ ":" gsl_STRINGIFY(__LINE__) ); +#else +# define Ensures( x ) ::gsl::fail_fast_assert( (x) ) +#endif + +#define gsl_STRINGIFY( x ) gsl_STRINGIFY_( x ) +#define gsl_STRINGIFY_( x ) #x + +struct fail_fast : public std::logic_error +{ + gsl_api explicit fail_fast( char const * const message ) + : std::logic_error( message ) {} +}; + +// workaround for gcc 5 throw/terminate constexpr bug: + +#if gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 430, 600 ) && gsl_HAVE( CONSTEXPR_14 ) + +# if gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + +gsl_api inline gsl_constexpr14 auto fail_fast_assert( bool cond, char const * const message ) -> void +{ + !cond ? throw fail_fast( message ) : 0; +} + +# else + +gsl_api inline gsl_constexpr14 auto fail_fast_assert( bool cond ) -> void +{ + struct F { static gsl_constexpr14 void f(){}; }; + + !cond ? std::terminate() : F::f(); +} + +# endif + +#else // workaround + +# if gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + +gsl_api inline gsl_constexpr14 void fail_fast_assert( bool cond, char const * const message ) +{ + if ( !cond ) + throw fail_fast( message ); +} + +# else + +gsl_api inline gsl_constexpr14 void fail_fast_assert( bool cond ) gsl_noexcept +{ + if ( !cond ) + std::terminate(); +} + +# endif +#endif // workaround + +// +// GSL.util: utilities +// + +#if gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) + +// Add uncaught_exceptions for pre-2017 MSVC, GCC and Clang +// Return unsigned char to save stack space, uncaught_exceptions can only increase by 1 in a scope + +namespace detail { + +inline unsigned char to_uchar( unsigned x ) gsl_noexcept +{ + return static_cast( x ); +} + +} // namespace detail + +namespace std11 { + +#if gsl_HAVE( UNCAUGHT_EXCEPTIONS ) + +inline unsigned char uncaught_exceptions() gsl_noexcept +{ + return detail::to_uchar( std::uncaught_exceptions() ); +} + +#elif gsl_COMPILER_MSVC_VERSION + +extern "C" char * __cdecl _getptd(); +inline unsigned char uncaught_exceptions() gsl_noexcept +{ + return detail::to_uchar( *reinterpret_cast(_getptd() + (sizeof(void*) == 8 ? 0x100 : 0x90) ) ); +} + +#elif gsl_COMPILER_CLANG_VERSION || gsl_COMPILER_GNUC_VERSION + +extern "C" char * __cxa_get_globals(); +inline unsigned char uncaught_exceptions() gsl_noexcept +{ + return detail::to_uchar( *reinterpret_cast(__cxa_get_globals() + sizeof(void*) ) ); +} +#endif +} // namespace std11 +#endif + +#if gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 110 + +template< class F > +class final_action +{ +public: + gsl_api explicit final_action( F action ) gsl_noexcept + : action_( std::move( action ) ) + , invoke_( true ) + {} + + gsl_api final_action( final_action && other ) gsl_noexcept + : action_( std::move( other.action_ ) ) + , invoke_( other.invoke_ ) + { + other.invoke_ = false; + } + + gsl_api virtual ~final_action() gsl_noexcept + { + if ( invoke_ ) + action_(); + } + +gsl_is_delete_access: + gsl_api final_action( final_action const & ) gsl_is_delete; + gsl_api final_action & operator=( final_action const & ) gsl_is_delete; + gsl_api final_action & operator=( final_action && ) gsl_is_delete; + +protected: + gsl_api void dismiss() gsl_noexcept + { + invoke_ = false; + } + +private: + F action_; + bool invoke_; +}; + +template< class F > +gsl_api inline final_action finally( F const & action ) gsl_noexcept +{ + return final_action( action ); +} + +template< class F > +gsl_api inline final_action finally( F && action ) gsl_noexcept +{ + return final_action( std::forward( action ) ); +} + +#if gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) + +template< class F > +class final_action_return : public final_action +{ +public: + gsl_api explicit final_action_return( F && action ) gsl_noexcept + : final_action( std::move( action ) ) + , exception_count( std11::uncaught_exceptions() ) + {} + + gsl_api final_action_return( final_action_return && other ) gsl_noexcept + : final_action( std::move( other ) ) + , exception_count( std11::uncaught_exceptions() ) + {} + + gsl_api ~final_action_return() override + { + if ( std11::uncaught_exceptions() != exception_count ) + this->dismiss(); + } + +gsl_is_delete_access: + gsl_api final_action_return( final_action_return const & ) gsl_is_delete; + gsl_api final_action_return & operator=( final_action_return const & ) gsl_is_delete; + +private: + unsigned char exception_count; +}; + +template< class F > +gsl_api inline final_action_return on_return( F const & action ) gsl_noexcept +{ + return final_action_return( action ); +} + +template< class F > +gsl_api inline final_action_return on_return( F && action ) gsl_noexcept +{ + return final_action_return( std::forward( action ) ); +} + +template< class F > +class final_action_error : public final_action +{ +public: + gsl_api explicit final_action_error( F && action ) gsl_noexcept + : final_action( std::move( action ) ) + , exception_count( std11::uncaught_exceptions() ) + {} + + gsl_api final_action_error( final_action_error && other ) gsl_noexcept + : final_action( std::move( other ) ) + , exception_count( std11::uncaught_exceptions() ) + {} + + gsl_api ~final_action_error() override + { + if ( std11::uncaught_exceptions() == exception_count ) + this->dismiss(); + } + +gsl_is_delete_access: + gsl_api final_action_error( final_action_error const & ) gsl_is_delete; + gsl_api final_action_error & operator=( final_action_error const & ) gsl_is_delete; + +private: + unsigned char exception_count; +}; + +template< class F > +gsl_api inline final_action_error on_error( F const & action ) gsl_noexcept +{ + return final_action_error( action ); +} + +template< class F > +gsl_api inline final_action_error on_error( F && action ) gsl_noexcept +{ + return final_action_error( std::forward( action ) ); +} + +#endif // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) + +#else // gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 110 + +class final_action +{ +public: + typedef void (*Action)(); + + gsl_api final_action( Action action ) + : action_( action ) + , invoke_( true ) + {} + + gsl_api final_action( final_action const & other ) + : action_( other.action_ ) + , invoke_( other.invoke_ ) + { + other.invoke_ = false; + } + + gsl_api virtual ~final_action() + { + if ( invoke_ ) + action_(); + } + +protected: + gsl_api void dismiss() + { + invoke_ = false; + } + +private: + gsl_api final_action & operator=( final_action const & ); + +private: + Action action_; + mutable bool invoke_; +}; + +template< class F > +gsl_api inline final_action finally( F const & f ) +{ + return final_action(( f )); +} + +#if gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) + +class final_action_return : public final_action +{ +public: + gsl_api explicit final_action_return( Action action ) + : final_action( action ) + , exception_count( std11::uncaught_exceptions() ) + {} + + gsl_api ~final_action_return() + { + if ( std11::uncaught_exceptions() != exception_count ) + this->dismiss(); + } + +private: + gsl_api final_action_return & operator=( final_action_return const & ); + +private: + unsigned char exception_count; +}; + +template< class F > +gsl_api inline final_action_return on_return( F const & action ) +{ + return final_action_return( action ); +} + +class final_action_error : public final_action +{ +public: + gsl_api explicit final_action_error( Action action ) + : final_action( action ) + , exception_count( std11::uncaught_exceptions() ) + {} + + gsl_api ~final_action_error() + { + if ( std11::uncaught_exceptions() == exception_count ) + this->dismiss(); + } + +private: + gsl_api final_action_error & operator=( final_action_error const & ); + +private: + unsigned char exception_count; +}; + +template< class F > +gsl_api inline final_action_error on_error( F const & action ) +{ + return final_action_error( action ); +} + +#endif // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) + +#endif // gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION == 110 + +#if gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 120 + +template< class T, class U > +gsl_api inline gsl_constexpr T narrow_cast( U && u ) gsl_noexcept +{ + return static_cast( std::forward( u ) ); +} + +#else + +template< class T, class U > +gsl_api inline T narrow_cast( U u ) gsl_noexcept +{ + return static_cast( u ); +} + +#endif // gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 120 + +struct narrowing_error : public std::exception {}; + +#if gsl_HAVE( TYPE_TRAITS ) + +namespace detail +{ + template< class T, class U > + struct is_same_signedness : public std::integral_constant::value == std::is_signed::value> + {}; +} +#endif + +template< class T, class U > +gsl_api inline T narrow( U u ) +{ + T t = narrow_cast( u ); + + if ( static_cast( t ) != u ) + { +#if gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + throw narrowing_error(); +#else + std::terminate(); +#endif + } + +#if gsl_HAVE( TYPE_TRAITS ) +# if gsl_COMPILER_MSVC_VERSION + // Suppress MSVC level 4 warning C4127 (conditional expression is constant) + if ( 0, ! detail::is_same_signedness::value && ( ( t < T() ) != ( u < U() ) ) ) +# else + if ( ! detail::is_same_signedness::value && ( ( t < T() ) != ( u < U() ) ) ) +# endif +#else + // Don't assume T() works: + if ( ( t < 0 ) != ( u < 0 ) ) +#endif + { +#if gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + throw narrowing_error(); +#else + std::terminate(); +#endif + } + return t; +} + +// +// at() - Bounds-checked way of accessing static arrays, std::array, std::vector. +// + +template< class T, size_t N > +gsl_api inline gsl_constexpr14 T & at( T(&arr)[N], size_t index ) +{ + Expects( index < N ); + return arr[index]; +} + +#if gsl_HAVE( ARRAY ) + +template< class T, size_t N > +gsl_api inline gsl_constexpr14 T & at( std::array & arr, size_t index ) +{ + Expects( index < N ); + return arr[index]; +} +#endif + +template< class Container > +gsl_api inline gsl_constexpr14 typename Container::value_type & at( Container & cont, size_t index ) +{ + Expects( index < cont.size() ); + return cont[index]; +} + +#if gsl_HAVE( INITIALIZER_LIST ) + +template< class T > +gsl_api inline const gsl_constexpr14 T & at( std::initializer_list cont, size_t index ) +{ + Expects( index < cont.size() ); + return *( cont.begin() + index ); +} +#endif + +template< class T > +gsl_api inline gsl_constexpr T & at( span s, size_t index ) +{ + return s.at( index ); +} + +// +// GSL.views: views +// + +// +// not_null<> - Wrap any indirection and enforce non-null. +// +template< class T > +class not_null +{ +#if gsl_CONFIG( NOT_NULL_EXPLICIT_CTOR ) +# define gsl_not_null_explicit explicit +#else +# define gsl_not_null_explicit /*explicit*/ +#endif + +#if gsl_CONFIG( NOT_NULL_GET_BY_CONST_REF ) + typedef T const & get_result_t; +#else + typedef T get_result_t; +#endif + +public: +#if gsl_HAVE( TYPE_TRAITS ) + static_assert( std::is_assignable::value, "T cannot be assigned nullptr." ); +#endif + + template< class U +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + gsl_REQUIRES_T(( std::is_constructible::value )) +#endif + > + gsl_api gsl_constexpr14 gsl_not_null_explicit +#if gsl_HAVE( RVALUE_REFERENCE ) + not_null( U && u ) + : ptr_( std::forward( u ) ) +#else + not_null( U const & u ) + : ptr_( u ) +#endif + { + Expects( ptr_ != gsl_nullptr ); + } +#undef gsl_not_null_explicit + +#if gsl_HAVE( IS_DEFAULT ) + gsl_api ~not_null() = default; + gsl_api gsl_constexpr not_null( not_null && other ) = default; + gsl_api gsl_constexpr not_null( not_null const & other ) = default; + gsl_api not_null & operator=( not_null && other ) = default; + gsl_api not_null & operator=( not_null const & other ) = default; +#else + gsl_api ~not_null() {}; + gsl_api gsl_constexpr not_null( not_null const & other ) : ptr_ ( other.ptr_ ) {} + gsl_api not_null & operator=( not_null const & other ) { ptr_ = other.ptr_; return *this; } +# if gsl_HAVE( RVALUE_REFERENCE ) + gsl_api gsl_constexpr not_null( not_null && other ) : ptr_( std::move( other.get() ) ) {} + gsl_api not_null & operator=( not_null && other ) { ptr_ = std::move( other.get() ); return *this; } +# endif +#endif + + template< class U +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + gsl_REQUIRES_T(( std::is_convertible::value )) +#endif + > + gsl_api gsl_constexpr not_null( not_null const & other ) + : ptr_( other.get() ) + {} + + gsl_api gsl_constexpr14 get_result_t get() const + { + // Without cheating and changing ptr_ from the outside, this check is superfluous: + Ensures( ptr_ != gsl_nullptr ); + return ptr_; + } + + gsl_api gsl_constexpr operator get_result_t () const { return get(); } + gsl_api gsl_constexpr get_result_t operator->() const { return get(); } + +#if gsl_HAVE( DECLTYPE_AUTO ) + gsl_api gsl_constexpr decltype(auto) operator*() const { return *get(); } +#endif + +gsl_is_delete_access: + // prevent compilation when initialized with a nullptr or literal 0: +#if gsl_HAVE( NULLPTR ) + gsl_api not_null( std::nullptr_t ) gsl_is_delete; + gsl_api not_null & operator=( std::nullptr_t ) gsl_is_delete; +#else + gsl_api not_null( int ) gsl_is_delete; + gsl_api not_null & operator=( int ) gsl_is_delete; +#endif + + // unwanted operators...pointers only point to single objects! + gsl_api not_null & operator++() gsl_is_delete; + gsl_api not_null & operator--() gsl_is_delete; + gsl_api not_null operator++( int ) gsl_is_delete; + gsl_api not_null operator--( int ) gsl_is_delete; + gsl_api not_null & operator+ ( size_t ) gsl_is_delete; + gsl_api not_null & operator+=( size_t ) gsl_is_delete; + gsl_api not_null & operator- ( size_t ) gsl_is_delete; + gsl_api not_null & operator-=( size_t ) gsl_is_delete; + gsl_api not_null & operator+=( std::ptrdiff_t ) gsl_is_delete; + gsl_api not_null & operator-=( std::ptrdiff_t ) gsl_is_delete; + gsl_api void operator[]( std::ptrdiff_t ) const gsl_is_delete; + +private: + T ptr_; +}; + +// not_null with implicit constructor, allowing copy-initialization: + +template< class T > +class not_null_ic : public not_null +{ +public: + template< class U +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + gsl_REQUIRES_T(( std::is_constructible::value )) +#endif + > + gsl_api gsl_constexpr14 +#if gsl_HAVE( RVALUE_REFERENCE ) + not_null_ic( U && u ) + : not_null( std::forward( u ) ) +#else + not_null_ic( U const & u ) + : not_null( u ) +#endif + {} +}; + +// more not_null unwanted operators + +template< class T, class U > +std::ptrdiff_t operator-( not_null const &, not_null const & ) gsl_is_delete; + +template< class T > +not_null operator-( not_null const &, std::ptrdiff_t ) gsl_is_delete; + +template< class T > +not_null operator+( not_null const &, std::ptrdiff_t ) gsl_is_delete; + +template< class T > +not_null operator+( std::ptrdiff_t, not_null const & ) gsl_is_delete; + +// not_null comparisons + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator==( not_null const & l, not_null const & r ) +{ + return l.get() == r.get(); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator< ( not_null const & l, not_null const & r ) +{ + return l.get() < r.get(); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator!=( not_null const & l, not_null const & r ) +{ + return !( l == r ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator<=( not_null const & l, not_null const & r ) +{ + return !( r < l ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator> ( not_null const & l, not_null const & r ) +{ + return ( r < l ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator>=( not_null const & l, not_null const & r ) +{ + return !( l < r ); +} + +// +// Byte-specific type. +// +#if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + enum class gsl_may_alias byte : unsigned char {}; +#else + struct gsl_may_alias byte { typedef unsigned char type; type v; }; +#endif + +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) +# define gsl_ENABLE_IF_INTEGRAL_T(T) \ + gsl_REQUIRES_T(( std::is_integral::value )) +#else +# define gsl_ENABLE_IF_INTEGRAL_T(T) +#endif + +template< class T > +gsl_api inline gsl_constexpr byte to_byte( T v ) gsl_noexcept +{ +#if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + return static_cast( v ); +#elif gsl_HAVE( CONSTEXPR_11 ) + return { static_cast( v ) }; +#else + byte b = { static_cast( v ) }; return b; +#endif +} + +template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > +gsl_api inline gsl_constexpr IntegerType to_integer( byte b ) gsl_noexcept +{ +#if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + return static_cast::type>( b ); +#else + return b.v; +#endif +} + +gsl_api inline gsl_constexpr unsigned char to_uchar( byte b ) gsl_noexcept +{ + return to_integer( b ); +} + +gsl_api inline gsl_constexpr unsigned char to_uchar( int i ) gsl_noexcept +{ + return static_cast( i ); +} + +#if ! gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + +gsl_api inline gsl_constexpr bool operator==( byte l, byte r ) gsl_noexcept +{ + return l.v == r.v; +} + +gsl_api inline gsl_constexpr bool operator!=( byte l, byte r ) gsl_noexcept +{ + return !( l == r ); +} + +gsl_api inline gsl_constexpr bool operator< ( byte l, byte r ) gsl_noexcept +{ + return l.v < r.v; +} + +gsl_api inline gsl_constexpr bool operator<=( byte l, byte r ) gsl_noexcept +{ + return !( r < l ); +} + +gsl_api inline gsl_constexpr bool operator> ( byte l, byte r ) gsl_noexcept +{ + return ( r < l ); +} + +gsl_api inline gsl_constexpr bool operator>=( byte l, byte r ) gsl_noexcept +{ + return !( l < r ); +} +#endif + +template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > +gsl_api inline gsl_constexpr14 byte & operator<<=( byte & b, IntegerType shift ) gsl_noexcept +{ +#if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + return b = to_byte( to_uchar( b ) << shift ); +#else + b.v = to_uchar( b.v << shift ); return b; +#endif +} + +template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > +gsl_api inline gsl_constexpr byte operator<<( byte b, IntegerType shift ) gsl_noexcept +{ + return to_byte( to_uchar( b ) << shift ); +} + +template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > +gsl_api inline gsl_constexpr14 byte & operator>>=( byte & b, IntegerType shift ) gsl_noexcept +{ +#if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + return b = to_byte( to_uchar( b ) >> shift ); +#else + b.v = to_uchar( b.v >> shift ); return b; +#endif +} + +template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > +gsl_api inline gsl_constexpr byte operator>>( byte b, IntegerType shift ) gsl_noexcept +{ + return to_byte( to_uchar( b ) >> shift ); +} + +gsl_api inline gsl_constexpr14 byte & operator|=( byte & l, byte r ) gsl_noexcept +{ +#if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + return l = to_byte( to_uchar( l ) | to_uchar( r ) ); +#else + l.v = to_uchar( l ) | to_uchar( r ); return l; +#endif +} + +gsl_api inline gsl_constexpr byte operator|( byte l, byte r ) gsl_noexcept +{ + return to_byte( to_uchar( l ) | to_uchar( r ) ); +} + +gsl_api inline gsl_constexpr14 byte & operator&=( byte & l, byte r ) gsl_noexcept +{ +#if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + return l = to_byte( to_uchar( l ) & to_uchar( r ) ); +#else + l.v = to_uchar( l ) & to_uchar( r ); return l; +#endif +} + +gsl_api inline gsl_constexpr byte operator&( byte l, byte r ) gsl_noexcept +{ + return to_byte( to_uchar( l ) & to_uchar( r ) ); +} + +gsl_api inline gsl_constexpr14 byte & operator^=( byte & l, byte r ) gsl_noexcept +{ +#if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) + return l = to_byte( to_uchar( l ) ^ to_uchar (r ) ); +#else + l.v = to_uchar( l ) ^ to_uchar (r ); return l; +#endif +} + +gsl_api inline gsl_constexpr byte operator^( byte l, byte r ) gsl_noexcept +{ + return to_byte( to_uchar( l ) ^ to_uchar( r ) ); +} + +gsl_api inline gsl_constexpr byte operator~( byte b ) gsl_noexcept +{ + return to_byte( ~to_uchar( b ) ); +} + +#if gsl_FEATURE_TO_STD( WITH_CONTAINER ) + +// Tag to select span constructor taking a container (prevent ms-gsl warning C26426): + +struct with_container_t { gsl_constexpr with_container_t() gsl_noexcept {} }; +const gsl_constexpr with_container_t with_container; + +#endif + +#if gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + +namespace detail { + +// Can construct from containers that: + +template< class Container, class ElementType + gsl_REQUIRES_T(( + ! detail::is_span< Container >::value + && ! detail::is_array< Container >::value + && ! detail::is_std_array< Container >::value + && std::is_convertible().data())>::type(*)[], ElementType(*)[] >::value + )) +#if gsl_HAVE( STD_DATA ) + // data(cont) and size(cont) well-formed: + , class = decltype( std::data( std::declval() ) ) + , class = decltype( std::size( std::declval() ) ) +#endif +> +struct can_construct_span_from : std11::true_type{}; + +} // namespace detail + +#endif + +// +// span<> - A 1D view of contiguous T's, replace (*,len). +// +template< class T > +class span +{ + template< class U > friend class span; + +public: + typedef index index_type; + + typedef T element_type; + typedef typename std11::remove_cv< T >::type value_type; + + typedef T & reference; + typedef T * pointer; + typedef T const * const_pointer; + typedef T const & const_reference; + + typedef pointer iterator; + typedef const_pointer const_iterator; + + typedef std::reverse_iterator< iterator > reverse_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + + typedef typename std::iterator_traits< iterator >::difference_type difference_type; + + // 26.7.3.2 Constructors, copy, and assignment [span.cons] + + gsl_api gsl_constexpr14 span() gsl_noexcept + : first_( gsl_nullptr ) + , last_ ( gsl_nullptr ) + { + Expects( size() == 0 ); + } + +#if ! gsl_DEPRECATE_TO_LEVEL( 5 ) + +#if gsl_HAVE( NULLPTR ) + gsl_api gsl_constexpr14 span( std::nullptr_t, index_type gsl_EXPECTS_UNUSED_PARAM( size_in ) ) + : first_( nullptr ) + , last_ ( nullptr ) + { + Expects( size_in == 0 ); + } +#endif + +#if gsl_HAVE( IS_DELETE ) + gsl_api gsl_constexpr span( reference data_in ) + : span( &data_in, 1 ) + {} + + gsl_api gsl_constexpr span( element_type && ) = delete; +#endif + +#endif // deprecate + + gsl_api gsl_constexpr14 span( pointer data_in, index_type size_in ) + : first_( data_in ) + , last_ ( data_in + size_in ) + { + Expects( size_in == 0 || ( size_in > 0 && data_in != gsl_nullptr ) ); + } + + gsl_api gsl_constexpr14 span( pointer first_in, pointer last_in ) + : first_( first_in ) + , last_ ( last_in ) + { + Expects( first_in <= last_in ); + } + +#if ! gsl_DEPRECATE_TO_LEVEL( 5 ) + + template< class U > + gsl_api gsl_constexpr14 span( U * & data_in, index_type size_in ) + : first_( data_in ) + , last_ ( data_in + size_in ) + { + Expects( size_in == 0 || ( size_in > 0 && data_in != gsl_nullptr ) ); + } + + template< class U > + gsl_api gsl_constexpr14 span( U * const & data_in, index_type size_in ) + : first_( data_in ) + , last_ ( data_in + size_in ) + { + Expects( size_in == 0 || ( size_in > 0 && data_in != gsl_nullptr ) ); + } + +#endif // deprecate + +#if ! gsl_DEPRECATE_TO_LEVEL( 5 ) + template< class U, size_t N > + gsl_api gsl_constexpr span( U (&arr)[N] ) gsl_noexcept + : first_( gsl_ADDRESSOF( arr[0] ) ) + , last_ ( gsl_ADDRESSOF( arr[0] ) + N ) + {} +#else + template< size_t N +# if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + gsl_REQUIRES_T(( std::is_convertible::value )) +# endif + > + gsl_api gsl_constexpr span( element_type (&arr)[N] ) gsl_noexcept + : first_( gsl_ADDRESSOF( arr[0] ) ) + , last_ ( gsl_ADDRESSOF( arr[0] ) + N ) + {} +#endif // deprecate + +#if gsl_HAVE( ARRAY ) +#if ! gsl_DEPRECATE_TO_LEVEL( 5 ) + + template< class U, size_t N > + gsl_api gsl_constexpr span( std::array< U, N > & arr ) + : first_( arr.data() ) + , last_ ( arr.data() + N ) + {} + + template< class U, size_t N > + gsl_api gsl_constexpr span( std::array< U, N > const & arr ) + : first_( arr.data() ) + , last_ ( arr.data() + N ) + {} + +#else + + template< size_t N +# if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + gsl_REQUIRES_T(( std::is_convertible::value )) +# endif + > + gsl_api gsl_constexpr span( std::array< value_type, N > & arr ) + : first_( arr.data() ) + , last_ ( arr.data() + N ) + {} + + template< size_t N +# if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + gsl_REQUIRES_T(( std::is_convertible::value )) +# endif + > + gsl_api gsl_constexpr span( std::array< value_type, N > const & arr ) + : first_( arr.data() ) + , last_ ( arr.data() + N ) + {} + +#endif // deprecate +#endif // gsl_HAVE( ARRAY ) + +#if gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + template< class Container + gsl_REQUIRES_T(( detail::can_construct_span_from< Container, element_type >::value )) + > + gsl_api gsl_constexpr span( Container & cont ) + : first_( cont.data() ) + , last_ ( cont.data() + cont.size() ) + {} + + template< class Container + gsl_REQUIRES_T(( + std::is_const< element_type >::value + && detail::can_construct_span_from< Container, element_type >::value + )) + > + gsl_api gsl_constexpr span( Container const & cont ) + : first_( cont.data() ) + , last_ ( cont.data() + cont.size() ) + {} + +#elif gsl_HAVE( UNCONSTRAINED_SPAN_CONTAINER_CTOR ) + + template< class Container > + gsl_api gsl_constexpr span( Container & cont ) + : first_( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) ) + , last_ ( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) + cont.size() ) + {} + + template< class Container > + gsl_api gsl_constexpr span( Container const & cont ) + : first_( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) ) + , last_ ( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) + cont.size() ) + {} + +#endif + +#if gsl_FEATURE_TO_STD( WITH_CONTAINER ) + + template< class Container > + gsl_api gsl_constexpr span( with_container_t, Container & cont ) + : first_( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) ) + , last_ ( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) + cont.size() ) + {} + + template< class Container > + gsl_api gsl_constexpr span( with_container_t, Container const & cont ) + : first_( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) ) + , last_ ( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) + cont.size() ) + {} + +#endif + +#if ! gsl_DEPRECATE_TO_LEVEL( 4 ) + // constructor taking shared_ptr deprecated since 0.29.0 + +#if gsl_HAVE( SHARED_PTR ) + gsl_api gsl_constexpr span( shared_ptr const & ptr ) + : first_( ptr.get() ) + , last_ ( ptr.get() ? ptr.get() + 1 : gsl_nullptr ) + {} +#endif + + // constructors taking unique_ptr deprecated since 0.29.0 + +#if gsl_HAVE( UNIQUE_PTR ) +# if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + template< class ArrayElementType = typename std::add_pointer::type > +# else + template< class ArrayElementType > +# endif + gsl_api gsl_constexpr span( unique_ptr const & ptr, index_type count ) + : first_( ptr.get() ) + , last_ ( ptr.get() + count ) + {} + + gsl_api gsl_constexpr span( unique_ptr const & ptr ) + : first_( ptr.get() ) + , last_ ( ptr.get() ? ptr.get() + 1 : gsl_nullptr ) + {} +#endif + +#endif // deprecate shared_ptr, unique_ptr + +#if gsl_HAVE( IS_DEFAULT ) && ! gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 430, 600) + gsl_api gsl_constexpr span( span && ) gsl_noexcept = default; + gsl_api gsl_constexpr span( span const & ) = default; +#else + gsl_api gsl_constexpr span( span const & other ) + : first_( other.begin() ) + , last_ ( other.end() ) + {} +#endif + +#if gsl_HAVE( IS_DEFAULT ) + ~span() = default; +#else + ~span() {} +#endif + +#if gsl_HAVE( IS_DEFAULT ) + gsl_api gsl_constexpr14 span & operator=( span && ) gsl_noexcept = default; + gsl_api gsl_constexpr14 span & operator=( span const & ) gsl_noexcept = default; +#else + gsl_api span & operator=( span other ) gsl_noexcept + { + other.swap( *this ); + return *this; + } +#endif + + template< class U +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + gsl_REQUIRES_T(( std::is_convertible::value )) +#endif + > + gsl_api gsl_constexpr span( span const & other ) + : first_( other.begin() ) + , last_ ( other.end() ) + {} + +#if 0 + // Converting from other span ? + template< class U > operator=(); +#endif + + // 26.7.3.3 Subviews [span.sub] + + gsl_api gsl_constexpr14 span first( index_type count ) const gsl_noexcept + { + Expects( 0 <= count && count <= this->size() ); + return span( this->data(), count ); + } + + gsl_api gsl_constexpr14 span last( index_type count ) const gsl_noexcept + { + Expects( 0 <= count && count <= this->size() ); + return span( this->data() + this->size() - count, count ); + } + + gsl_api gsl_constexpr14 span subspan( index_type offset ) const gsl_noexcept + { + Expects( 0 <= offset && offset <= this->size() ); + return span( this->data() + offset, this->size() - offset ); + } + + gsl_api gsl_constexpr14 span subspan( index_type offset, index_type count ) const gsl_noexcept + { + Expects( + 0 <= offset && offset <= this->size() && + 0 <= count && count <= this->size() - offset ); + return span( this->data() + offset, count ); + } + + // 26.7.3.4 Observers [span.obs] + + gsl_api gsl_constexpr index_type size() const gsl_noexcept + { + return narrow_cast( last_ - first_ ); + } + + gsl_api gsl_constexpr std::ptrdiff_t ssize() const gsl_noexcept + { + return narrow_cast( last_ - first_ ); + } + + gsl_api gsl_constexpr index_type size_bytes() const gsl_noexcept + { + return size() * narrow_cast( sizeof( element_type ) ); + } + + gsl_api gsl_constexpr bool empty() const gsl_noexcept + { + return size() == 0; + } + + // 26.7.3.5 Element access [span.elem] + + gsl_api gsl_constexpr reference operator[]( index_type index ) const + { + return at( index ); + } + + gsl_api gsl_constexpr reference operator()( index_type index ) const + { + return at( index ); + } + + gsl_api gsl_constexpr14 reference at( index_type index ) const + { + Expects( index < size() ); + return first_[ index ]; + } + + gsl_api gsl_constexpr pointer data() const gsl_noexcept + { + return first_; + } + + // 26.7.3.6 Iterator support [span.iterators] + + gsl_api gsl_constexpr iterator begin() const gsl_noexcept + { + return iterator( first_ ); + } + + gsl_api gsl_constexpr iterator end() const gsl_noexcept + { + return iterator( last_ ); + } + + gsl_api gsl_constexpr const_iterator cbegin() const gsl_noexcept + { +#if gsl_CPP11_OR_GREATER + return { begin() }; +#else + return const_iterator( begin() ); +#endif + } + + gsl_api gsl_constexpr const_iterator cend() const gsl_noexcept + { +#if gsl_CPP11_OR_GREATER + return { end() }; +#else + return const_iterator( end() ); +#endif + } + + gsl_api gsl_constexpr reverse_iterator rbegin() const gsl_noexcept + { + return reverse_iterator( end() ); + } + + gsl_api gsl_constexpr reverse_iterator rend() const gsl_noexcept + { + return reverse_iterator( begin() ); + } + + gsl_api gsl_constexpr const_reverse_iterator crbegin() const gsl_noexcept + { + return const_reverse_iterator( cend() ); + } + + gsl_api gsl_constexpr const_reverse_iterator crend() const gsl_noexcept + { + return const_reverse_iterator( cbegin() ); + } + + gsl_api void swap( span & other ) gsl_noexcept + { + using std::swap; + swap( first_, other.first_ ); + swap( last_ , other.last_ ); + } + +#if ! gsl_DEPRECATE_TO_LEVEL( 3 ) + // member length() deprecated since 0.29.0 + + gsl_api gsl_constexpr index_type length() const gsl_noexcept + { + return size(); + } + + // member length_bytes() deprecated since 0.29.0 + + gsl_api gsl_constexpr index_type length_bytes() const gsl_noexcept + { + return size_bytes(); + } +#endif + +#if ! gsl_DEPRECATE_TO_LEVEL( 2 ) + // member as_bytes(), as_writeable_bytes deprecated since 0.17.0 + + gsl_api span< const byte > as_bytes() const gsl_noexcept + { + return span< const byte >( reinterpret_cast( data() ), size_bytes() ); // NOLINT + } + + gsl_api span< byte > as_writeable_bytes() const gsl_noexcept + { + return span< byte >( reinterpret_cast( data() ), size_bytes() ); // NOLINT + } + +#endif + + template< class U > + gsl_api span< U > as_span() const gsl_noexcept + { + Expects( ( this->size_bytes() % sizeof(U) ) == 0 ); + return span< U >( reinterpret_cast( this->data() ), this->size_bytes() / sizeof( U ) ); // NOLINT + } + +private: + pointer first_; + pointer last_; +}; + +// class template argument deduction guides: + +#if gsl_HAVE( DEDUCTION_GUIDES ) // gsl_CPP17_OR_GREATER + +template< class T, size_t N > +span( T (&)[N] ) -> span; + +template< class T, size_t N > +span( std::array & ) -> span; + +template< class T, size_t N > +span( std::array const & ) -> span; + +template< class Container > +span( Container& ) -> span; + +template< class Container > +span( Container const & ) -> span; + +#endif // gsl_HAVE( DEDUCTION_GUIDES ) + +// 26.7.3.7 Comparison operators [span.comparison] + +#if gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator==( span const & l, span const & r ) +{ + return l.size() == r.size() + && (l.begin() == r.begin() || std::equal( l.begin(), l.end(), r.begin() ) ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator< ( span const & l, span const & r ) +{ + return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); +} + +#else + +template< class T > +gsl_api inline gsl_constexpr bool operator==( span const & l, span const & r ) +{ + return l.size() == r.size() + && (l.begin() == r.begin() || std::equal( l.begin(), l.end(), r.begin() ) ); +} + +template< class T > +gsl_api inline gsl_constexpr bool operator< ( span const & l, span const & r ) +{ + return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); +} +#endif + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator!=( span const & l, span const & r ) +{ + return !( l == r ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator<=( span const & l, span const & r ) +{ + return !( r < l ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator> ( span const & l, span const & r ) +{ + return ( r < l ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr bool operator>=( span const & l, span const & r ) +{ + return !( l < r ); +} + +// span algorithms + +template< class T > +gsl_api inline gsl_constexpr std::size_t size( span const & spn ) +{ + return static_cast( spn.size() ); +} + +template< class T > +gsl_api inline gsl_constexpr std::ptrdiff_t ssize( span const & spn ) +{ + return spn.ssize(); +} + +namespace detail { + +template< class II, class N, class OI > +gsl_api inline OI copy_n( II first, N count, OI result ) +{ + if ( count > 0 ) + { + *result++ = *first; + for ( N i = 1; i < count; ++i ) + { + *result++ = *++first; + } + } + return result; +} +} + +template< class T, class U > +gsl_api inline void copy( span src, span dest ) +{ +#if gsl_CPP14_OR_GREATER // gsl_HAVE( TYPE_TRAITS ) (circumvent Travis clang 3.4) + static_assert( std::is_assignable::value, "Cannot assign elements of source span to elements of destination span" ); +#endif + Expects( dest.size() >= src.size() ); + detail::copy_n( src.data(), src.size(), dest.data() ); +} + +// span creator functions (see ctors) + +template< class T > +gsl_api inline span< const byte > as_bytes( span spn ) gsl_noexcept +{ + return span< const byte >( reinterpret_cast( spn.data() ), spn.size_bytes() ); // NOLINT +} + +template< class T> +gsl_api inline span< byte > as_writeable_bytes( span spn ) gsl_noexcept +{ + return span< byte >( reinterpret_cast( spn.data() ), spn.size_bytes() ); // NOLINT +} + +#if gsl_FEATURE_TO_STD( MAKE_SPAN ) + +template< class T > +gsl_api inline gsl_constexpr span +make_span( T * ptr, typename span::index_type count ) +{ + return span( ptr, count ); +} + +template< class T > +gsl_api inline gsl_constexpr span +make_span( T * first, T * last ) +{ + return span( first, last ); +} + +template< class T, size_t N > +gsl_api inline gsl_constexpr span +make_span( T (&arr)[N] ) +{ + return span( gsl_ADDRESSOF( arr[0] ), N ); +} + +#if gsl_HAVE( ARRAY ) + +template< class T, size_t N > +gsl_api inline gsl_constexpr span +make_span( std::array & arr ) +{ + return span( arr ); +} + +template< class T, size_t N > +gsl_api inline gsl_constexpr span +make_span( std::array const & arr ) +{ + return span( arr ); +} +#endif + +#if gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) && gsl_HAVE( AUTO ) + +template< class Container, class = decltype(std::declval().data()) > +gsl_api inline gsl_constexpr auto +make_span( Container & cont ) -> span< typename Container::value_type > +{ + return span< typename Container::value_type >( cont ); +} + +template< class Container, class = decltype(std::declval().data()) > +gsl_api inline gsl_constexpr auto +make_span( Container const & cont ) -> span< const typename Container::value_type > +{ + return span< const typename Container::value_type >( cont ); +} + +#else + +template< class T > +gsl_api inline span +make_span( std::vector & cont ) +{ + return span( with_container, cont ); +} + +template< class T > +gsl_api inline span +make_span( std::vector const & cont ) +{ + return span( with_container, cont ); +} +#endif + +#if gsl_FEATURE_TO_STD( WITH_CONTAINER ) + +template< class Container > +gsl_api inline gsl_constexpr span +make_span( with_container_t, Container & cont ) gsl_noexcept +{ + return span< typename Container::value_type >( with_container, cont ); +} + +template< class Container > +gsl_api inline gsl_constexpr span +make_span( with_container_t, Container const & cont ) gsl_noexcept +{ + return span< const typename Container::value_type >( with_container, cont ); +} + +#endif // gsl_FEATURE_TO_STD( WITH_CONTAINER ) + +template< class Ptr > +gsl_api inline span +make_span( Ptr & ptr ) +{ + return span( ptr ); +} + +template< class Ptr > +gsl_api inline span +make_span( Ptr & ptr, typename span::index_type count ) +{ + return span( ptr, count); +} + +#endif // gsl_FEATURE_TO_STD( MAKE_SPAN ) + +#if gsl_FEATURE_TO_STD( BYTE_SPAN ) + +template< class T > +gsl_api inline gsl_constexpr span +byte_span( T & t ) gsl_noexcept +{ + return span( reinterpret_cast( &t ), sizeof(T) ); +} + +template< class T > +gsl_api inline gsl_constexpr span +byte_span( T const & t ) gsl_noexcept +{ + return span( reinterpret_cast( &t ), sizeof(T) ); +} + +#endif // gsl_FEATURE_TO_STD( BYTE_SPAN ) + +// +// basic_string_span: +// + +template< class T > +class basic_string_span; + +namespace detail { + +template< class T > +struct is_basic_string_span_oracle : std11::false_type {}; + +template< class T > +struct is_basic_string_span_oracle< basic_string_span > : std11::true_type {}; + +template< class T > +struct is_basic_string_span : is_basic_string_span_oracle< typename std11::remove_cv::type > {}; + +template< class T > +gsl_api inline gsl_constexpr14 std::size_t string_length( T * ptr, std::size_t max ) +{ + if ( ptr == gsl_nullptr || max <= 0 ) + return 0; + + std::size_t len = 0; + while ( len < max && ptr[len] ) // NOLINT + ++len; + + return len; +} + +} // namespace detail + +// +// basic_string_span<> - A view of contiguous characters, replace (*,len). +// +template< class T > +class basic_string_span +{ +public: + typedef T element_type; + typedef span span_type; + + typedef typename span_type::index_type index_type; + typedef typename span_type::difference_type difference_type; + + typedef typename span_type::pointer pointer ; + typedef typename span_type::reference reference ; + + typedef typename span_type::iterator iterator ; + typedef typename span_type::const_iterator const_iterator ; + typedef typename span_type::reverse_iterator reverse_iterator; + typedef typename span_type::const_reverse_iterator const_reverse_iterator; + + // construction: + +#if gsl_HAVE( IS_DEFAULT ) + gsl_api gsl_constexpr basic_string_span() gsl_noexcept = default; +#else + gsl_api gsl_constexpr basic_string_span() gsl_noexcept {} +#endif + +#if gsl_HAVE( NULLPTR ) + gsl_api gsl_constexpr basic_string_span( std::nullptr_t ptr ) gsl_noexcept + : span_( ptr, index_type( 0 ) ) + {} +#endif + + gsl_api gsl_constexpr basic_string_span( pointer ptr ) + : span_( remove_z( ptr, (std::numeric_limits::max)() ) ) + {} + + gsl_api gsl_constexpr basic_string_span( pointer ptr, index_type count ) + : span_( ptr, count ) + {} + + gsl_api gsl_constexpr basic_string_span( pointer firstElem, pointer lastElem ) + : span_( firstElem, lastElem ) + {} + + template< std::size_t N > + gsl_api gsl_constexpr basic_string_span( element_type (&arr)[N] ) + : span_( remove_z( gsl_ADDRESSOF( arr[0] ), N ) ) + {} + +#if gsl_HAVE( ARRAY ) + + template< std::size_t N > + gsl_api gsl_constexpr basic_string_span( std::array< typename std11::remove_const::type, N> & arr ) + : span_( remove_z( arr ) ) + {} + + template< std::size_t N > + gsl_api gsl_constexpr basic_string_span( std::array< typename std11::remove_const::type, N> const & arr ) + : span_( remove_z( arr ) ) + {} + +#endif + +#if gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + + // Exclude: array, [basic_string,] basic_string_span + + template< class Container + gsl_REQUIRES_T(( + ! detail::is_std_array< Container >::value + && ! detail::is_basic_string_span< Container >::value + && std::is_convertible< typename Container::pointer, pointer >::value + && std::is_convertible< typename Container::pointer, decltype(std::declval().data()) >::value + )) + > + gsl_api gsl_constexpr basic_string_span( Container & cont ) + : span_( ( cont ) ) + {} + + // Exclude: array, [basic_string,] basic_string_span + + template< class Container + gsl_REQUIRES_T(( + ! detail::is_std_array< Container >::value + && ! detail::is_basic_string_span< Container >::value + && std::is_convertible< typename Container::pointer, pointer >::value + && std::is_convertible< typename Container::pointer, decltype(std::declval().data()) >::value + )) + > + gsl_api gsl_constexpr basic_string_span( Container const & cont ) + : span_( ( cont ) ) + {} + +#elif gsl_HAVE( UNCONSTRAINED_SPAN_CONTAINER_CTOR ) + + template< class Container > + gsl_api gsl_constexpr basic_string_span( Container & cont ) + : span_( cont ) + {} + + template< class Container > + gsl_api gsl_constexpr basic_string_span( Container const & cont ) + : span_( cont ) + {} + +#else + + template< class U > + gsl_api gsl_constexpr basic_string_span( span const & rhs ) + : span_( rhs ) + {} + +#endif + +#if gsl_FEATURE_TO_STD( WITH_CONTAINER ) + + template< class Container > + gsl_api gsl_constexpr basic_string_span( with_container_t, Container & cont ) + : span_( with_container, cont ) + {} +#endif + +#if gsl_HAVE( IS_DEFAULT ) +# if gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 440, 600 ) + gsl_api gsl_constexpr basic_string_span( basic_string_span const & rhs ) = default; + + gsl_api gsl_constexpr basic_string_span( basic_string_span && rhs ) = default; +# else + gsl_api gsl_constexpr basic_string_span( basic_string_span const & rhs ) gsl_noexcept = default; + + gsl_api gsl_constexpr basic_string_span( basic_string_span && rhs ) gsl_noexcept = default; +# endif +#endif + + template< class U +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + gsl_REQUIRES_T(( std::is_convertible::pointer, pointer>::value )) +#endif + > + gsl_api gsl_constexpr basic_string_span( basic_string_span const & rhs ) + : span_( reinterpret_cast( rhs.data() ), rhs.length() ) // NOLINT + {} + +#if gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 120 + template< class U + gsl_REQUIRES_T(( std::is_convertible::pointer, pointer>::value )) + > + gsl_api gsl_constexpr basic_string_span( basic_string_span && rhs ) + : span_( reinterpret_cast( rhs.data() ), rhs.length() ) // NOLINT + {} +#endif + + template< class CharTraits, class Allocator > + gsl_api gsl_constexpr basic_string_span( + std::basic_string< typename std11::remove_const::type, CharTraits, Allocator > & str ) + : span_( gsl_ADDRESSOF( str[0] ), str.length() ) + {} + + template< class CharTraits, class Allocator > + gsl_api gsl_constexpr basic_string_span( + std::basic_string< typename std11::remove_const::type, CharTraits, Allocator > const & str ) + : span_( gsl_ADDRESSOF( str[0] ), str.length() ) + {} + + // destruction, assignment: + +#if gsl_HAVE( IS_DEFAULT ) + gsl_api ~basic_string_span() gsl_noexcept = default; + + gsl_api basic_string_span & operator=( basic_string_span const & rhs ) gsl_noexcept = default; + + gsl_api basic_string_span & operator=( basic_string_span && rhs ) gsl_noexcept = default; +#endif + + // sub span: + + gsl_api gsl_constexpr basic_string_span first( index_type count ) const + { + return span_.first( count ); + } + + gsl_api gsl_constexpr basic_string_span last( index_type count ) const + { + return span_.last( count ); + } + + gsl_api gsl_constexpr basic_string_span subspan( index_type offset ) const + { + return span_.subspan( offset ); + } + + gsl_api gsl_constexpr basic_string_span subspan( index_type offset, index_type count ) const + { + return span_.subspan( offset, count ); + } + + // observers: + + gsl_api gsl_constexpr index_type length() const gsl_noexcept + { + return span_.size(); + } + + gsl_api gsl_constexpr index_type size() const gsl_noexcept + { + return span_.size(); + } + + gsl_api gsl_constexpr index_type length_bytes() const gsl_noexcept + { + return span_.size_bytes(); + } + + gsl_api gsl_constexpr index_type size_bytes() const gsl_noexcept + { + return span_.size_bytes(); + } + + gsl_api gsl_constexpr bool empty() const gsl_noexcept + { + return size() == 0; + } + + gsl_api gsl_constexpr reference operator[]( index_type idx ) const + { + return span_[idx]; + } + + gsl_api gsl_constexpr reference operator()( index_type idx ) const + { + return span_[idx]; + } + + gsl_api gsl_constexpr pointer data() const gsl_noexcept + { + return span_.data(); + } + + gsl_api iterator begin() const gsl_noexcept + { + return span_.begin(); + } + + gsl_api iterator end() const gsl_noexcept + { + return span_.end(); + } + + gsl_api reverse_iterator rbegin() const gsl_noexcept + { + return span_.rbegin(); + } + + gsl_api reverse_iterator rend() const gsl_noexcept + { + return span_.rend(); + } + + // const version not in p0123r2: + + gsl_api const_iterator cbegin() const gsl_noexcept + { + return span_.cbegin(); + } + + gsl_api const_iterator cend() const gsl_noexcept + { + return span_.cend(); + } + + gsl_api const_reverse_iterator crbegin() const gsl_noexcept + { + return span_.crbegin(); + } + + gsl_api const_reverse_iterator crend() const gsl_noexcept + { + return span_.crend(); + } + +private: + gsl_api static gsl_constexpr14 span_type remove_z( pointer const & sz, std::size_t max ) + { + return span_type( sz, detail::string_length( sz, max ) ); + } + +#if gsl_HAVE( ARRAY ) + template< size_t N > + gsl_api static gsl_constexpr14 span_type remove_z( std::array::type, N> & arr ) + { + return remove_z( gsl_ADDRESSOF( arr[0] ), narrow_cast< std::size_t >( N ) ); + } + + template< size_t N > + gsl_api static gsl_constexpr14 span_type remove_z( std::array::type, N> const & arr ) + { + return remove_z( gsl_ADDRESSOF( arr[0] ), narrow_cast< std::size_t >( N ) ); + } +#endif + +private: + span_type span_; +}; + +// basic_string_span comparison functions: + +#if gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) + +template< class T, class U > +gsl_api inline gsl_constexpr14 bool operator==( basic_string_span const & l, U const & u ) gsl_noexcept +{ + const basic_string_span< typename std11::add_const::type > r( u ); + + return l.size() == r.size() + && std::equal( l.begin(), l.end(), r.begin() ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr14 bool operator<( basic_string_span const & l, U const & u ) gsl_noexcept +{ + const basic_string_span< typename std11::add_const::type > r( u ); + + return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); +} + +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + +template< class T, class U + gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) +> +gsl_api inline gsl_constexpr14 bool operator==( U const & u, basic_string_span const & r ) gsl_noexcept +{ + const basic_string_span< typename std11::add_const::type > l( u ); + + return l.size() == r.size() + && std::equal( l.begin(), l.end(), r.begin() ); +} + +template< class T, class U + gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) +> +gsl_api inline gsl_constexpr14 bool operator<( U const & u, basic_string_span const & r ) gsl_noexcept +{ + const basic_string_span< typename std11::add_const::type > l( u ); + + return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); +} +#endif + +#else //gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) + +template< class T > +gsl_api inline gsl_constexpr14 bool operator==( basic_string_span const & l, basic_string_span const & r ) gsl_noexcept +{ + return l.size() == r.size() + && std::equal( l.begin(), l.end(), r.begin() ); +} + +template< class T > +gsl_api inline gsl_constexpr14 bool operator<( basic_string_span const & l, basic_string_span const & r ) gsl_noexcept +{ + return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); +} + +#endif // gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) + +template< class T, class U > +gsl_api inline gsl_constexpr14 bool operator!=( basic_string_span const & l, U const & r ) gsl_noexcept +{ + return !( l == r ); +} + +template< class T, class U > +gsl_api inline gsl_constexpr14 bool operator<=( basic_string_span const & l, U const & r ) gsl_noexcept +{ +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) || ! gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) + return !( r < l ); +#else + basic_string_span< typename std11::add_const::type > rr( r ); + return !( rr < l ); +#endif +} + +template< class T, class U > +gsl_api inline gsl_constexpr14 bool operator>( basic_string_span const & l, U const & r ) gsl_noexcept +{ +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) || ! gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) + return ( r < l ); +#else + basic_string_span< typename std11::add_const::type > rr( r ); + return ( rr < l ); +#endif +} + +template< class T, class U > +gsl_api inline gsl_constexpr14 bool operator>=( basic_string_span const & l, U const & r ) gsl_noexcept +{ + return !( l < r ); +} + +#if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + +template< class T, class U + gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) +> +gsl_api inline gsl_constexpr14 bool operator!=( U const & l, basic_string_span const & r ) gsl_noexcept +{ + return !( l == r ); +} + +template< class T, class U + gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) +> +gsl_api inline gsl_constexpr14 bool operator<=( U const & l, basic_string_span const & r ) gsl_noexcept +{ + return !( r < l ); +} + +template< class T, class U + gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) +> +gsl_api inline gsl_constexpr14 bool operator>( U const & l, basic_string_span const & r ) gsl_noexcept +{ + return ( r < l ); +} + +template< class T, class U + gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) +> +gsl_api inline gsl_constexpr14 bool operator>=( U const & l, basic_string_span const & r ) gsl_noexcept +{ + return !( l < r ); +} + +#endif // gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + +// convert basic_string_span to byte span: + +template< class T > +gsl_api inline span< const byte > as_bytes( basic_string_span spn ) gsl_noexcept +{ + return span< const byte >( reinterpret_cast( spn.data() ), spn.size_bytes() ); // NOLINT +} + +// +// String types: +// + +typedef char * zstring; +typedef const char * czstring; + +#if gsl_HAVE( WCHAR ) +typedef wchar_t * zwstring; +typedef const wchar_t * cwzstring; +#endif + +typedef basic_string_span< char > string_span; +typedef basic_string_span< char const > cstring_span; + +#if gsl_HAVE( WCHAR ) +typedef basic_string_span< wchar_t > wstring_span; +typedef basic_string_span< wchar_t const > cwstring_span; +#endif + +// to_string() allow (explicit) conversions from string_span to string + +#if 0 + +template< class T > +gsl_api inline std::basic_string< typename std::remove_const::type > to_string( basic_string_span spn ) +{ + std::string( spn.data(), spn.length() ); +} + +#else + +gsl_api inline std::string to_string( string_span const & spn ) +{ + return std::string( spn.data(), spn.length() ); +} + +gsl_api inline std::string to_string( cstring_span const & spn ) +{ + return std::string( spn.data(), spn.length() ); +} + +#if gsl_HAVE( WCHAR ) + +gsl_api inline std::wstring to_string( wstring_span const & spn ) +{ + return std::wstring( spn.data(), spn.length() ); +} + +gsl_api inline std::wstring to_string( cwstring_span const & spn ) +{ + return std::wstring( spn.data(), spn.length() ); +} + +#endif // gsl_HAVE( WCHAR ) +#endif // to_string() + +// +// Stream output for string_span types +// + +namespace detail { + +template< class Stream > +gsl_api void write_padding( Stream & os, std::streamsize n ) +{ + for ( std::streamsize i = 0; i < n; ++i ) + os.rdbuf()->sputc( os.fill() ); +} + +template< class Stream, class Span > +gsl_api Stream & write_to_stream( Stream & os, Span const & spn ) +{ + typename Stream::sentry sentry( os ); + + if ( !os ) + return os; + + const std::streamsize length = narrow( spn.length() ); + + // Whether, and how, to pad + const bool pad = ( length < os.width() ); + const bool left_pad = pad && ( os.flags() & std::ios_base::adjustfield ) == std::ios_base::right; + + if ( left_pad ) + write_padding( os, os.width() - length ); + + // Write span characters + os.rdbuf()->sputn( spn.begin(), length ); + + if ( pad && !left_pad ) + write_padding( os, os.width() - length ); + + // Reset output stream width + os.width(0); + + return os; +} + +} // namespace detail + +template< typename Traits > +gsl_api std::basic_ostream< char, Traits > & operator<<( std::basic_ostream< char, Traits > & os, string_span const & spn ) +{ + return detail::write_to_stream( os, spn ); +} + +template< typename Traits > +gsl_api std::basic_ostream< char, Traits > & operator<<( std::basic_ostream< char, Traits > & os, cstring_span const & spn ) +{ + return detail::write_to_stream( os, spn ); +} + +#if gsl_HAVE( WCHAR ) + +template< typename Traits > +gsl_api std::basic_ostream< wchar_t, Traits > & operator<<( std::basic_ostream< wchar_t, Traits > & os, wstring_span const & spn ) +{ + return detail::write_to_stream( os, spn ); +} + +template< typename Traits > +gsl_api std::basic_ostream< wchar_t, Traits > & operator<<( std::basic_ostream< wchar_t, Traits > & os, cwstring_span const & spn ) +{ + return detail::write_to_stream( os, spn ); +} + +#endif // gsl_HAVE( WCHAR ) + +// +// ensure_sentinel() +// +// Provides a way to obtain a span from a contiguous sequence +// that ends with a (non-inclusive) sentinel value. +// +// Will fail-fast if sentinel cannot be found before max elements are examined. +// +namespace detail { + +template< class T, class SizeType, const T Sentinel > +gsl_api static span ensure_sentinel( T * seq, SizeType max = (std::numeric_limits::max)() ) +{ + typedef T * pointer; + + gsl_SUPPRESS_MSVC_WARNING( 26429, "f.23: symbol 'cur' is never tested for nullness, it can be marked as not_null" ) + + pointer cur = seq; + + while ( static_cast( cur - seq ) < max && *cur != Sentinel ) + ++cur; + + Expects( *cur == Sentinel ); + + return span( seq, narrow_cast< typename span::index_type >( cur - seq ) ); +} +} // namespace detail + +// +// ensure_z - creates a string_span for a czstring or cwzstring. +// Will fail fast if a null-terminator cannot be found before +// the limit of size_type. +// + +template< class T > +gsl_api inline span ensure_z( T * const & sz, size_t max = (std::numeric_limits::max)() ) +{ + return detail::ensure_sentinel( sz, max ); +} + +template< class T, size_t N > +gsl_api inline span ensure_z( T (&sz)[N] ) +{ + return ensure_z( gsl_ADDRESSOF( sz[0] ), N ); +} + +# if gsl_HAVE( TYPE_TRAITS ) + +template< class Container > +gsl_api inline span< typename std::remove_pointer::type > +ensure_z( Container & cont ) +{ + return ensure_z( cont.data(), cont.length() ); +} +# endif + +} // namespace gsl + +#if gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 120 + +namespace std { + +template<> +struct hash< gsl::byte > +{ +public: + std::size_t operator()( gsl::byte v ) const gsl_noexcept + { + return gsl::to_integer( v ); + } +}; + +} // namespace std + +#endif + +gsl_RESTORE_MSVC_WARNINGS() + +#endif // GSL_GSL_LITE_HPP_INCLUDED + +// end of file From 433e6a4dfd5dc77a9b1e9aca3d8e0c211bec2282 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 12 Jun 2019 22:59:55 -0500 Subject: [PATCH 108/151] Fix bug in openmc_material_add_nuclide --- src/material.cpp | 2 +- tests/unit_tests/test_capi.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/material.cpp b/src/material.cpp index ec76eb9884..5667821fb7 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -1169,7 +1169,7 @@ openmc_material_add_nuclide(int32_t index, const char* name, double density) // Create copy of atom_density_ array with one extra entry xt::xtensor atom_density = xt::zeros({n}); xt::view(atom_density, xt::range(0, n-1)) = m->atom_density_; - atom_density(n) = density; + atom_density(n-1) = density; m->atom_density_ = atom_density; m->density_ += density; diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 17219c82e4..53ad09e906 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -124,6 +124,13 @@ def test_material(capi_init): assert m.density == pytest.approx(0.1) +def test_material_add_nuclide(capi_init): + m = openmc.capi.materials[3] + m.add_nuclide('Xe135', 1e-12) + assert m.nuclides[-1] == 'Xe135' + assert m.densities[-1] == 1e-12 + + def test_new_material(capi_init): with pytest.raises(exc.AllocationError): openmc.capi.Material(1) @@ -135,7 +142,7 @@ def test_new_material(capi_init): def test_nuclide_mapping(capi_init): nucs = openmc.capi.nuclides assert isinstance(nucs, Mapping) - assert len(nucs) == 12 + assert len(nucs) == 13 for name, nuc in nucs.items(): assert isinstance(nuc, openmc.capi.Nuclide) assert name == nuc.name From f1e930b8bab460fa879b63df6a198f3db54fc2bb Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 13 Jun 2019 19:06:07 -0400 Subject: [PATCH 109/151] Remove mention of OPENMC_DEPLETE_CHAIN in model.py --- openmc/deplete/abc.py | 2 +- openmc/model/model.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 226877c149..2e598fb74a 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -68,7 +68,7 @@ class TransportOperator(metaclass=ABCMeta): if chain_file is None: data = DataLibrary.from_xml() # search for depletion_chain path from end of list - for lib in data.libraries[::-1]: + for lib in reversed(data.libraries): if lib['type'] == 'depletion_chain': break else: diff --git a/openmc/model/model.py b/openmc/model/model.py index cf6603638e..bdd35079d4 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -133,8 +133,9 @@ class Model(object): Array of timesteps in units of [s]. Note that values are not cumulative. chain_file : str, optional - Path to the depletion chain XML file. Defaults to the - :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. + Path to the depletion chain XML file. Defaults to the chain + found under the ``depletion_chain`` in the + :envvar:`OPENMC_CROSS_SECITON` environment variable if it exists. method : str Integration method used for depletion (e.g., 'cecm', 'predictor') **kwargs From 52ff408651813f4c82b0ce76c2f32031028cc41e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 13 Jun 2019 19:32:35 -0400 Subject: [PATCH 110/151] Better exporting/importing of depletion_chain in DataLibrary Use str(path) in the path attribute when registering. This preserves the full path, not just the basename. Give special depletion_chain tag when exporting a library of type depletion_chain, rather than using previous library tag --- openmc/data/library.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openmc/data/library.py b/openmc/data/library.py index d1a0d778d9..d5f18aa170 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -72,7 +72,7 @@ class DataLibrary(EqualityMixin): "File type {} not supported by {}" .format(path.name, self.__class__.__name__)) - library = {'path': path.name, 'type': filetype, 'materials': materials} + library = {'path': str(path), 'type': filetype, 'materials': materials} self.libraries.append(library) def export_to_xml(self, path='cross_sections.xml'): @@ -97,8 +97,10 @@ class DataLibrary(EqualityMixin): dir_element.text = os.path.realpath(common_dir) for library in self.libraries: - lib_element = ET.SubElement(root, "library") - if library['materials']: + if library['type'] == "depletion_chain": + lib_element = ET.SubElement(root, "depletion_chain") + else: + lib_element = ET.SubElement(root, "library") lib_element.set('materials', ' '.join(library['materials'])) lib_element.set('path', os.path.relpath(library['path'], common_dir)) lib_element.set('type', library['type']) From e702b5186c0aa0303f7422bb3ce56d3faad8f2ab Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 13 Jun 2019 19:35:15 -0400 Subject: [PATCH 111/151] Add test for working with depletion_chains in DataLibrary Load the system default cross section file, register a depletion chain, and inspect the results. Should have one additional library, with an empty list of materials. Export and create a new library from this exported xml. Examine the contents of the new depletion chain library --- tests/unit_tests/test_data_misc.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index ea20ba53c8..2585e9315f 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -2,6 +2,7 @@ from collections.abc import Mapping import os +from pathlib import Path import numpy as np import pytest @@ -33,6 +34,30 @@ def test_data_library(tmpdir): assert new_lib.libraries[-1]['type'] == 'thermal' +def test_depletion_chain_data_library(run_in_tmpdir): + dep_lib = openmc.data.DataLibrary.from_xml() + prev_len = len(dep_lib.libraries) + chain_path = Path(__file__).parents[1] / "chain_simple.xml" + dep_lib.register_file(chain_path) + assert len(dep_lib.libraries) == prev_len + 1 + # Inspect + dep_dict = dep_lib.libraries[-1] + assert dep_dict['materials'] == [] + assert dep_dict['type'] == 'depletion_chain' + assert dep_dict['path'] == str(chain_path) + + out_path = "cross_section_chain.xml" + dep_lib.export_to_xml(out_path) + + dep_import = openmc.data.DataLibrary.from_xml(out_path) + for lib in reversed(dep_import.libraries): + if lib['type'] == 'depletion_chain': + break + else: + raise IndexError("depletion_chain not found in exported DataLibrary") + assert os.path.exists(lib['path']) + + def test_linearize(): """Test linearization of a continuous function.""" x, y = openmc.data.linearize([-1., 1.], lambda x: 1 - x*x) From e86317ecd468c8327496e5f749d5a13aeaf5aebf Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 23:57:25 -0500 Subject: [PATCH 112/151] Putting history and last_dir in the simulation namespace. --- include/openmc/dagmc.h | 8 ++++++-- src/cell.cpp | 8 ++++---- src/dagmc.cpp | 6 ++++++ src/particle.cpp | 4 +--- src/surface.cpp | 6 +++--- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/include/openmc/dagmc.h b/include/openmc/dagmc.h index adc4f47f1f..d3012e3a0a 100644 --- a/include/openmc/dagmc.h +++ b/include/openmc/dagmc.h @@ -14,10 +14,14 @@ extern "C" const bool dagmc_enabled; namespace openmc { -extern moab::DagMC::RayHistory history; -extern Direction last_dir; +namespace simulation { + +extern moab::DagMC::RayHistory history; //!< facet history for DagMC particles +extern Direction last_dir; //!< last direction passed to DagMC's ray_fire #pragma omp threadprivate(history, last_dir) +} + namespace model { extern moab::DagMC* DAG; } diff --git a/src/cell.cpp b/src/cell.cpp index 4ad114b7ed..a65d07f3c4 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -617,9 +617,9 @@ DAGCell::distance(Position r, Direction u, int32_t on_surface) const { // if we've changed direction or we're not on a surface, // reset the history and update last direction - if (u != last_dir || on_surface == 0) { - history.reset(); - last_dir = u; + if (u != simulation::last_dir || on_surface == 0) { + simulation::history.reset(); + simulation::last_dir = u; } moab::ErrorCode rval; @@ -628,7 +628,7 @@ DAGCell::distance(Position r, Direction u, int32_t on_surface) const double dist; double pnt[3] = {r.x, r.y, r.z}; double dir[3] = {u.x, u.y, u.z}; - rval = dagmc_ptr_->ray_fire(vol, pnt, dir, hit_surf, dist, &history); + rval = dagmc_ptr_->ray_fire(vol, pnt, dir, hit_surf, dist, &simulation::history); MB_CHK_ERR_CONT(rval); int surf_idx; if (hit_surf != 0) { diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 05e0659415..78d8bc4fb6 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -38,9 +38,15 @@ const bool dagmc_enabled = false; const std::string DAGMC_FILENAME = "dagmc.h5m"; namespace openmc { + + +namespace simulation { + moab::DagMC::RayHistory history; Direction last_dir; +} + namespace model { moab::DagMC* DAG; diff --git a/src/particle.cpp b/src/particle.cpp index eef58a490d..afcc56317f 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -373,9 +373,7 @@ Particle::transport() } #ifdef DAGMC - if(settings::dagmc) { - history.reset(); - } + if (settings::dagmc) simulation::history.reset(); #endif // Finish particle track output. diff --git a/src/surface.cpp b/src/surface.cpp index a643d52730..a33607b775 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -266,9 +266,9 @@ Direction DAGSurface::normal(Position r) const Direction DAGSurface::reflect(Position r, Direction u) const { - history.reset_to_last_intersection(); - last_dir = Surface::reflect(r, u); - return last_dir; + simulation::history.reset_to_last_intersection(); + simulation::last_dir = Surface::reflect(r, u); + return simulation::last_dir; } BoundingBox DAGSurface::bounding_box() const From 04a87e3cf16ea1d37d1d4d74d290df066ec80c34 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 14 Jun 2019 00:00:46 -0500 Subject: [PATCH 113/151] Removing materials import from refl/uwuw DagMC tests. --- tests/regression_tests/dagmc/refl/test.py | 1 - tests/regression_tests/dagmc/uwuw/test.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/regression_tests/dagmc/refl/test.py b/tests/regression_tests/dagmc/refl/test.py index f6713c2e4f..885b83766f 100644 --- a/tests/regression_tests/dagmc/refl/test.py +++ b/tests/regression_tests/dagmc/refl/test.py @@ -1,7 +1,6 @@ import openmc import openmc.capi from openmc.stats import Box -from openmc.material import Materials import pytest from tests.testing_harness import PyAPITestHarness diff --git a/tests/regression_tests/dagmc/uwuw/test.py b/tests/regression_tests/dagmc/uwuw/test.py index f6713c2e4f..885b83766f 100644 --- a/tests/regression_tests/dagmc/uwuw/test.py +++ b/tests/regression_tests/dagmc/uwuw/test.py @@ -1,7 +1,6 @@ import openmc import openmc.capi from openmc.stats import Box -from openmc.material import Materials import pytest from tests.testing_harness import PyAPITestHarness From dc4d98a1cb2ba0cd45db19dfdbf258fff7b54203 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 23 May 2019 11:58:10 -0500 Subject: [PATCH 114/151] Update version to 0.11.0-dev --- docs/source/conf.py | 12 ++++++------ include/openmc/constants.h | 3 ++- src/output.cpp | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 22446213af..a33b8af759 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -71,17 +71,17 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'OpenMC' -copyright = u'2011-2018, Massachusetts Institute of Technology and OpenMC contributors' +project = 'OpenMC' +copyright = '2011-2019, Massachusetts Institute of Technology and OpenMC contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = "0.10" +version = "0.11" # The full version, including alpha/beta/rc tags. -release = "0.10.0" +release = "0.11.0-dev" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -208,8 +208,8 @@ htmlhelp_basename = 'openmcdoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'openmc.tex', u'OpenMC Documentation', - u'OpenMC contributors', 'manual'), + ('index', 'openmc.tex', 'OpenMC Documentation', + 'OpenMC contributors', 'manual'), ] latex_elements = { diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 49f980cc29..6b2e6f63b7 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -20,8 +20,9 @@ using double_4dvec = std::vector>>>; // OpenMC major, minor, and release numbers constexpr int VERSION_MAJOR {0}; -constexpr int VERSION_MINOR {10}; +constexpr int VERSION_MINOR {11}; constexpr int VERSION_RELEASE {0}; +constexpr bool VERSION_DEV {true}; constexpr std::array VERSION {VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE}; // HDF5 data format diff --git a/src/output.cpp b/src/output.cpp index bbc2588ac4..dff39c15fe 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -75,7 +75,7 @@ void title() " Copyright | 2011-2019 MIT and OpenMC contributors\n" << " License | http://openmc.readthedocs.io/en/latest/license.html\n" << " Version | " << VERSION_MAJOR << '.' << VERSION_MINOR << '.' - << VERSION_RELEASE << '\n'; + << VERSION_RELEASE << (VERSION_DEV ? "-dev" : "") << '\n'; #ifdef GIT_SHA1 std::cout << " Git SHA1 | " << GIT_SHA1 << '\n'; #endif From 02b07a52b697b59354d4b37c90a384321d9f1769 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 10 Jun 2019 18:16:11 -0500 Subject: [PATCH 115/151] Adding overlap coloring. --- include/openmc/geometry.h | 2 +- include/openmc/plot.h | 7 +++++ openmc/capi/plot.py | 65 +++++++++++++++++++++++---------------- src/geometry.cpp | 4 +-- src/plot.cpp | 12 ++++++++ 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/include/openmc/geometry.h b/include/openmc/geometry.h index 8e6e8a82ef..0ceaab1046 100644 --- a/include/openmc/geometry.h +++ b/include/openmc/geometry.h @@ -47,7 +47,7 @@ inline bool coincident(double d1, double d2) { //! Check for overlapping cells at a particle's position. //============================================================================== -bool check_cell_overlap(Particle* p); + bool check_cell_overlap(Particle* p, bool error=true); //============================================================================== //! Locate a particle in the geometry tree and set its geometry data fields. diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 8e5aa02a6d..1d8ac0ea1f 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -61,6 +61,7 @@ struct IdData { // Methods void set_value(size_t y, size_t x, const Particle& p, int level); + void set_overlap(size_t y, size_t x); // Members xt::xtensor data_; //!< 2D array of cell & material ids @@ -72,6 +73,7 @@ struct PropertyData { // Methods void set_value(size_t y, size_t x, const Particle& p, int level); + void set_overlap(size_t y, size_t x); // Members xt::xtensor data_; //!< 2D array of temperature & density data @@ -106,6 +108,7 @@ public: Position width_; //!< Plot width in geometry PlotBasis basis_; //!< Plot basis (XY/XZ/YZ) std::array pixels_; //!< Plot size in pixels + bool check_overlaps_ = false; int level_; //!< Plot universe level }; @@ -173,6 +176,9 @@ T PlotBase::get_map() const { if (found_cell) { data.set_value(y, x, p, j); } + if (check_overlaps_ && check_cell_overlap(&p, false)) { + data.set_overlap(y, x); + } } // inner for } // outer for } // omp parallel @@ -210,6 +216,7 @@ public: int index_meshlines_mesh_; //!< Index of the mesh to draw on the plot RGBColor meshlines_color_; //!< Color of meshlines on the plot RGBColor not_found_; //!< Plot background color + RGBColor overlap_color_; //!< Plot overlap color std::vector colors_; //!< Plot colors std::string path_plot_; //!< Plot output filename }; diff --git a/openmc/capi/plot.py b/openmc/capi/plot.py index 9ff398f5c1..375660b38a 100644 --- a/openmc/capi/plot.py +++ b/openmc/capi/plot.py @@ -1,4 +1,5 @@ -from ctypes import c_int, c_size_t, c_int32, c_double, Structure, POINTER +from ctypes import (c_bool, c_int, c_size_t, c_int32, + c_double, Structure, POINTER) from . import _dll from .error import _error_handler @@ -84,6 +85,7 @@ class _PlotBase(Structure): ('width_', _Position), ('basis_', c_int), ('pixels_', 3*c_size_t), + ('color_overlaps_', c_bool), ('level_', c_int)] def __init__(self): @@ -112,32 +114,6 @@ class _PlotBase(Structure): raise ValueError("Plot basis {} is invalid".format(self.basis_)) - @property - def h_res(self): - return self.pixels_[0] - - @property - def v_res(self): - return self.pixels_[1] - - @property - def level(self): - return int(self.level_) - - @origin.setter - def origin(self, origin): - self.origin_.x = origin[0] - self.origin_.y = origin[1] - self.origin_.z = origin[2] - - @width.setter - def width(self, width): - self.width_.x = width - - @height.setter - def height(self, height): - self.width_.y = height - @basis.setter def basis(self, basis): if isinstance(basis, str): @@ -164,6 +140,32 @@ class _PlotBase(Structure): raise ValueError("{} of type {} is an" " invalid plot basis".format(basis, type(basis))) + @property + def h_res(self): + return self.pixels_[0] + + @property + def v_res(self): + return self.pixels_[1] + + @property + def level(self): + return int(self.level_) + + @origin.setter + def origin(self, origin): + self.origin_.x = origin[0] + self.origin_.y = origin[1] + self.origin_.z = origin[2] + + @width.setter + def width(self, width): + self.width_.x = width + + @height.setter + def height(self, height): + self.width_.y = height + @h_res.setter def h_res(self, h_res): self.pixels_[0] = h_res @@ -176,6 +178,14 @@ class _PlotBase(Structure): def level(self, level): self.level_ = level + @property + def color_overlaps(self): + return self.color_overlaps_ + + @color_overlaps.setter + def color_overlaps(self, val): + self.color_overlaps_ = val + def __repr__(self): out_str = ["-----", "Plot:", @@ -186,6 +196,7 @@ class _PlotBase(Structure): "Basis: {}".format(self.basis), "HRes: {}".format(self.h_res), "VRes: {}".format(self.v_res), + "Color Ovrelaps: {}".format(self.color_overlaps), "Level: {}".format(self.level)] return '\n'.join(out_str) diff --git a/src/geometry.cpp b/src/geometry.cpp index aec272ae17..4169a54201 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -32,7 +32,7 @@ std::vector overlap_check_count; // Non-member functions //============================================================================== -bool check_cell_overlap(Particle* p) +bool check_cell_overlap(Particle* p, bool error) { int n_coord = p->n_coord_; @@ -44,7 +44,7 @@ bool check_cell_overlap(Particle* p) for (auto index_cell : univ.cells_) { Cell& c = *model::cells[index_cell]; if (c.contains(p->coord_[j].r, p->coord_[j].u, p->surface_)) { - if (index_cell != p->coord_[j].cell) { + if (error && index_cell != p->coord_[j].cell) { std::stringstream err_msg; err_msg << "Overlapping cells detected: " << c.id_ << ", " << model::cells[p->coord_[j].cell]->id_ << " on universe " diff --git a/src/plot.cpp b/src/plot.cpp index 6bc82e1641..0ec26473e8 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -29,8 +29,11 @@ namespace openmc { //============================================================================== const RGBColor WHITE {255, 255, 255}; +const RGBColor RED {255, 0, 0}; + constexpr int PLOT_LEVEL_LOWEST {-1}; //!< lower bound on plot universe level constexpr int32_t NOT_FOUND {-2}; +constexpr int32_t OVERLAP{-3}; IdData::IdData(size_t h_res, size_t v_res) : data_({v_res, h_res, 2}, NOT_FOUND) @@ -49,6 +52,10 @@ IdData::set_value(size_t y, size_t x, const Particle& p, int level) { } } +void IdData::set_overlap(size_t y, size_t x) { + data_(y, x) = OVERLAP; +} + PropertyData::PropertyData(size_t h_res, size_t v_res) : data_({v_res, h_res, 2}, NOT_FOUND) { } @@ -63,6 +70,10 @@ PropertyData::set_value(size_t y, size_t x, const Particle& p, int level) { } } +void PropertyData::set_overlap(size_t y, size_t x) { + data_(y, x) = OVERLAP; +} + //============================================================================== // Global variables //============================================================================== @@ -143,6 +154,7 @@ void create_ppm(Plot pl) auto id = ids.data_(y, x, pl.color_by_); // no setting needed if not found if (id == NOT_FOUND) { continue; } + if (id == OVERLAP) data(x,y) = RED; if (PlotColorBy::cells == pl.color_by_) { data(x,y) = pl.colors_[model::cell_map[id]]; } else if (PlotColorBy::mats == pl.color_by_) { From 5c78cd62594c96181dc149d2c99b1ec75098d737 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 10 Jun 2019 19:46:11 -0500 Subject: [PATCH 116/151] Correcting a couple of issues in setting the OVERLAP value and color. --- include/openmc/plot.h | 4 ++-- src/geometry.cpp | 5 ++++- src/plot.cpp | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 1d8ac0ea1f..d762cad701 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -108,7 +108,7 @@ public: Position width_; //!< Plot width in geometry PlotBasis basis_; //!< Plot basis (XY/XZ/YZ) std::array pixels_; //!< Plot size in pixels - bool check_overlaps_ = false; + bool color_overlaps_ = true; int level_; //!< Plot universe level }; @@ -176,7 +176,7 @@ T PlotBase::get_map() const { if (found_cell) { data.set_value(y, x, p, j); } - if (check_overlaps_ && check_cell_overlap(&p, false)) { + if (color_overlaps_ && check_cell_overlap(&p, false)) { data.set_overlap(y, x); } } // inner for diff --git a/src/geometry.cpp b/src/geometry.cpp index 4169a54201..ce118c0e0c 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -44,12 +44,15 @@ bool check_cell_overlap(Particle* p, bool error) for (auto index_cell : univ.cells_) { Cell& c = *model::cells[index_cell]; if (c.contains(p->coord_[j].r, p->coord_[j].u, p->surface_)) { - if (error && index_cell != p->coord_[j].cell) { + if (index_cell != p->coord_[j].cell) { + if (error) { std::stringstream err_msg; err_msg << "Overlapping cells detected: " << c.id_ << ", " << model::cells[p->coord_[j].cell]->id_ << " on universe " << univ.id_; fatal_error(err_msg); + } + return true; } ++model::overlap_check_count[index_cell]; } diff --git a/src/plot.cpp b/src/plot.cpp index 0ec26473e8..d53a128053 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -53,7 +53,7 @@ IdData::set_value(size_t y, size_t x, const Particle& p, int level) { } void IdData::set_overlap(size_t y, size_t x) { - data_(y, x) = OVERLAP; + xt::view(data_, y, x, xt::all()) = OVERLAP; } PropertyData::PropertyData(size_t h_res, size_t v_res) @@ -154,7 +154,10 @@ void create_ppm(Plot pl) auto id = ids.data_(y, x, pl.color_by_); // no setting needed if not found if (id == NOT_FOUND) { continue; } - if (id == OVERLAP) data(x,y) = RED; + if (id == OVERLAP) { + data(x,y) = RED; + continue; + } if (PlotColorBy::cells == pl.color_by_) { data(x,y) = pl.colors_[model::cell_map[id]]; } else if (PlotColorBy::mats == pl.color_by_) { From 552ab71a5007b2098b1a05a052c8bd3f831b2951 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 10 Jun 2019 19:55:56 -0500 Subject: [PATCH 117/151] Making sure default colors aren't being used. --- include/openmc/plot.h | 4 ++++ src/plot.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/include/openmc/plot.h b/include/openmc/plot.h index d762cad701..48920afad2 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -49,6 +49,10 @@ struct RGBColor { blue = v[2]; } + bool operator ==(const RGBColor& other) { + return red == other.red && green == other.green && blue == other.blue; + } + // Members uint8_t red, green, blue; }; diff --git a/src/plot.cpp b/src/plot.cpp index d53a128053..78b1c6a71a 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -403,6 +403,10 @@ Plot::set_default_colors(pugi::xml_node plot_node) for (auto& c : colors_) { c = random_color(); + // make sure we don't interfere with some default colors + while (c == RED || c == WHITE) { + c = random_color(); + } } } From e837f2eb1999e8560a4ed2f3486e4524b92c5fc8 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 10 Jun 2019 20:19:57 -0500 Subject: [PATCH 118/151] Adding an xml setting for the showing the overlaps. --- include/openmc/plot.h | 3 ++- src/plot.cpp | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 48920afad2..1621def937 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -112,7 +112,7 @@ public: Position width_; //!< Plot width in geometry PlotBasis basis_; //!< Plot basis (XY/XZ/YZ) std::array pixels_; //!< Plot size in pixels - bool color_overlaps_ = true; + bool color_overlaps_; int level_; //!< Plot universe level }; @@ -210,6 +210,7 @@ private: void set_user_colors(pugi::xml_node plot_node); void set_meshlines(pugi::xml_node plot_node); void set_mask(pugi::xml_node plot_node); + void set_color_overlaps(pugi::xml_node plot_node); // Members public: diff --git a/src/plot.cpp b/src/plot.cpp index 78b1c6a71a..91effefb35 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -657,6 +657,19 @@ Plot::set_mask(pugi::xml_node plot_node) } } +void Plot::set_color_overlaps(pugi::xml_node plot_node) { + color_overlaps_ = false; + if (check_for_node(plot_node, "show_overlaps")) { + color_overlaps_ = get_node_value_bool(plot_node, "show_overlaps"); + } + + // make sure we allocate the vector for counting overlap checks if + // they're going to be plotted + if (color_overlaps_ && settings::run_mode == RUN_MODE_PLOTTING) { + settings::check_overlaps = true; + } +} + Plot::Plot(pugi::xml_node plot_node) : index_meshlines_mesh_{-1} { @@ -672,6 +685,7 @@ Plot::Plot(pugi::xml_node plot_node) set_user_colors(plot_node); set_meshlines(plot_node); set_mask(plot_node); + set_color_overlaps(plot_node); } // End Plot constructor //============================================================================== @@ -868,6 +882,7 @@ void create_voxel(Plot pl) pltbase.basis_ = PlotBasis::xy; pltbase.pixels_ = pl.pixels_; pltbase.level_ = -1; // all universes for voxel files + pltbase.color_overlaps_ = pl.color_overlaps_; ProgressBar pb; for (int z = 0; z < pl.pixels_[2]; z++) { From 2b7057667c69eeb438c2ba8373cfb6cdef4fe454 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 11 Jun 2019 09:56:49 -0500 Subject: [PATCH 119/151] Making sure overlap check counter is initialized if a plot requests that overlaps be drawn. --- src/plot.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plot.cpp b/src/plot.cpp index 91effefb35..da880a5b12 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -667,6 +667,7 @@ void Plot::set_color_overlaps(pugi::xml_node plot_node) { // they're going to be plotted if (color_overlaps_ && settings::run_mode == RUN_MODE_PLOTTING) { settings::check_overlaps = true; + model::overlap_check_count.resize(model::cells.size(), 0); } } From 42ff5a4c85b5ac2aeadb80a03ffb990fb3d2e80b Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 11 Jun 2019 15:51:25 -0500 Subject: [PATCH 120/151] Adding show_overlaps and overlap_color to Python API. --- openmc/plots.py | 48 ++++++++++++++++++++++++++++++++++ tests/unit_tests/test_plots.py | 4 +++ 2 files changed, 52 insertions(+) diff --git a/openmc/plots.py b/openmc/plots.py index 5a52822989..3c810fcf6f 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -208,6 +208,10 @@ class Plot(IDManagerMixin): The cells or materials to plot mask_background : Iterable of int or str Color to apply to all cells/materials not listed in mask_components + show_overlaps : bool + Inidicate whether or not overlapping regions are shown + overlap_color : Iterable of int or str + Color to apply to overlapping regions colors : dict Dictionary indicating that certain cells/materials (keys) should be displayed with a particular color. @@ -236,6 +240,8 @@ class Plot(IDManagerMixin): self._background = None self._mask_components = None self._mask_background = None + self._show_overlaps = False + self._overlap_color = None self._colors = {} self._level = None self._meshlines = None @@ -284,6 +290,14 @@ class Plot(IDManagerMixin): def mask_background(self): return self._mask_background + @property + def show_overlaps(self): + return self._show_overlaps + + @property + def overlap_color(self): + return self._overlap_color + @property def colors(self): return self._colors @@ -391,6 +405,25 @@ class Plot(IDManagerMixin): cv.check_less_than('plot mask background', rgb, 256) self._mask_background = mask_background + @show_overlaps.setter + def show_overlaps(self, show_overlaps): + cv.check_type('Show overlaps flag for Plot ID="{}"'.format(self.id), + show_overlaps, bool) + self._show_overlaps = show_overlaps + + @overlap_color.setter + def overlap_color(self, overlap_color): + cv.check_type('plot overlap color', overlap_color, Iterable) + if isinstance(overlap_color, str): + if overlap_color.lower() not in _SVG_COLORS: + raise ValueError("'{}' is not a valid color.".format(overlap_color)) + else: + cv.check_length('plot overlap color', overlap_color, 3) + for rgb in overlap_color: + cv.check_greater_than('plot overlap color', rgb, 0, True) + cv.check_less_than('plot overlap color', rgb, 256) + self._overlap_color = overlap_color + @level.setter def level(self, plot_level): cv.check_type('plot level', plot_level, Integral) @@ -446,6 +479,8 @@ class Plot(IDManagerMixin): self._mask_components) string += '{: <16}=\t{}\n'.format('\tMask background', self._mask_background) + string += '{: <16}=\t{}\n'.format('\Overlap Color', + self._overlap_color) string += '{: <16}=\t{}\n'.format('\tColors', self._colors) string += '{: <16}=\t{}\n'.format('\tLevel', self._level) string += '{: <16}=\t{}\n'.format('\tMeshlines', self._meshlines) @@ -635,6 +670,19 @@ class Plot(IDManagerMixin): subelement.set("background", ' '.join( str(x) for x in color)) + if self._show_overlaps: + subelement = ET.SubElement(element, "show_overlaps") + subelement.text = "true" + + if self._overlap_color is not None: + color = self._overlap_color + if isinstance(color, str): + color = _SVG_COLORS[color.lower()] + + subelement = ET.SubElement(element, "overlap_color") + subelement.text = ' '.join(str(x) for x in color) + + if self._level is not None: subelement = ET.SubElement(element, "level") subelement.text = str(self._level) diff --git a/tests/unit_tests/test_plots.py b/tests/unit_tests/test_plots.py index 840e7e635f..f2d3e10148 100644 --- a/tests/unit_tests/test_plots.py +++ b/tests/unit_tests/test_plots.py @@ -24,6 +24,10 @@ def myplot(): plot.mask_background = (255, 255, 255) plot.mask_background = 'white' + plot.overlap_color = (255, 211, 0) + plot.overlap_color = 'yellow' + plot.show_overlaps = True + plot.level = 1 plot.meshlines = { 'type': 'tally', From 43959714eecb191316638f99365181ada4dbb61d Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 11 Jun 2019 16:13:50 -0500 Subject: [PATCH 121/151] Allowing overlap color setting via plots.xml --- include/openmc/plot.h | 2 +- src/plot.cpp | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 1621def937..e08e62d5fa 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -210,7 +210,7 @@ private: void set_user_colors(pugi::xml_node plot_node); void set_meshlines(pugi::xml_node plot_node); void set_mask(pugi::xml_node plot_node); - void set_color_overlaps(pugi::xml_node plot_node); + void set_overlap_color(pugi::xml_node plot_node); // Members public: diff --git a/src/plot.cpp b/src/plot.cpp index da880a5b12..60c39389f1 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -155,7 +155,7 @@ void create_ppm(Plot pl) // no setting needed if not found if (id == NOT_FOUND) { continue; } if (id == OVERLAP) { - data(x,y) = RED; + data(x,y) = pl.overlap_color_; continue; } if (PlotColorBy::cells == pl.color_by_) { @@ -657,10 +657,28 @@ Plot::set_mask(pugi::xml_node plot_node) } } -void Plot::set_color_overlaps(pugi::xml_node plot_node) { +void Plot::set_overlap_color(pugi::xml_node plot_node) { color_overlaps_ = false; if (check_for_node(plot_node, "show_overlaps")) { color_overlaps_ = get_node_value_bool(plot_node, "show_overlaps"); + overlap_color_ = RED; + // check for custom overlap color + if (check_for_node(plot_node, "overlap_color")) { + if (!color_overlaps_) { + std::stringstream wrn_msg; + wrn_msg << "Overlap color specified in plot " << id_ + << " but overlaps won't be shown."; + warning(wrn_msg); + } + std::vector olap_clr = get_node_array(plot_node, "overlap_color"); + if (olap_clr.size() == 3) { + overlap_color_ = olap_clr; + } else { + std::stringstream err_msg; + err_msg << "Bad overlap RGB in plot " << id_; + fatal_error(err_msg); + } + } } // make sure we allocate the vector for counting overlap checks if @@ -686,7 +704,7 @@ Plot::Plot(pugi::xml_node plot_node) set_user_colors(plot_node); set_meshlines(plot_node); set_mask(plot_node); - set_color_overlaps(plot_node); + set_overlap_color(plot_node); } // End Plot constructor //============================================================================== From 8946bbd238500a3b044839c73fc5d47eb38204b1 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 11 Jun 2019 16:14:08 -0500 Subject: [PATCH 122/151] Adding a test plotting overlap regions. --- .../plot_overlaps/__init__.py | 0 .../plot_overlaps/geometry.xml | 14 +++++ .../plot_overlaps/materials.xml | 19 ++++++ .../regression_tests/plot_overlaps/plots.xml | 35 +++++++++++ .../plot_overlaps/results_true.dat | 1 + .../plot_overlaps/settings.xml | 13 ++++ tests/regression_tests/plot_overlaps/test.py | 63 +++++++++++++++++++ 7 files changed, 145 insertions(+) create mode 100644 tests/regression_tests/plot_overlaps/__init__.py create mode 100644 tests/regression_tests/plot_overlaps/geometry.xml create mode 100644 tests/regression_tests/plot_overlaps/materials.xml create mode 100644 tests/regression_tests/plot_overlaps/plots.xml create mode 100644 tests/regression_tests/plot_overlaps/results_true.dat create mode 100644 tests/regression_tests/plot_overlaps/settings.xml create mode 100644 tests/regression_tests/plot_overlaps/test.py diff --git a/tests/regression_tests/plot_overlaps/__init__.py b/tests/regression_tests/plot_overlaps/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/plot_overlaps/geometry.xml b/tests/regression_tests/plot_overlaps/geometry.xml new file mode 100644 index 0000000000..7a9f1fb41f --- /dev/null +++ b/tests/regression_tests/plot_overlaps/geometry.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/tests/regression_tests/plot_overlaps/materials.xml b/tests/regression_tests/plot_overlaps/materials.xml new file mode 100644 index 0000000000..90b3542675 --- /dev/null +++ b/tests/regression_tests/plot_overlaps/materials.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/regression_tests/plot_overlaps/plots.xml b/tests/regression_tests/plot_overlaps/plots.xml new file mode 100644 index 0000000000..28064f58fb --- /dev/null +++ b/tests/regression_tests/plot_overlaps/plots.xml @@ -0,0 +1,35 @@ + + + + + 0. 0. 0. + 25 25 + 200 200 + + + true + + + + 0. 0. 0. + 25 25 + 200 200 + + true + 255 211 0 + + + + 0. 0. 0. + 25 25 + 200 200 + 0 0 0 + + + + 100 100 10 + 0. 0. 0. + 20 20 10 + + + diff --git a/tests/regression_tests/plot_overlaps/results_true.dat b/tests/regression_tests/plot_overlaps/results_true.dat new file mode 100644 index 0000000000..1e34f0ecbc --- /dev/null +++ b/tests/regression_tests/plot_overlaps/results_true.dat @@ -0,0 +1 @@ +1f962e0dfd63fc540c39faaf16eeb4a725b16c1a82e89972524716ff763d2d9e12f5ae9f50fafd86caeeb0bab54f8501e104ced085378e98cb9d014a357c7544 \ No newline at end of file diff --git a/tests/regression_tests/plot_overlaps/settings.xml b/tests/regression_tests/plot_overlaps/settings.xml new file mode 100644 index 0000000000..adf256d2d4 --- /dev/null +++ b/tests/regression_tests/plot_overlaps/settings.xml @@ -0,0 +1,13 @@ + + + + plot + + + 5 4 3 + -10 -10 -10 + 10 10 10 + + 1 + + diff --git a/tests/regression_tests/plot_overlaps/test.py b/tests/regression_tests/plot_overlaps/test.py new file mode 100644 index 0000000000..4e5b6caa42 --- /dev/null +++ b/tests/regression_tests/plot_overlaps/test.py @@ -0,0 +1,63 @@ +import glob +import hashlib +import os + +import h5py +import openmc + +from tests.testing_harness import TestHarness +from tests.regression_tests import config + + +class PlotTestHarness(TestHarness): + """Specialized TestHarness for running OpenMC plotting tests.""" + def __init__(self, plot_names): + super().__init__(None) + self._plot_names = plot_names + + def _run_openmc(self): + openmc.plot_geometry(openmc_exec=config['exe']) + + + + def _test_output_created(self): + """Make sure *.ppm has been created.""" + for fname in self._plot_names: + assert os.path.exists(fname), 'Plot output file does not exist.' + + def _cleanup(self): + super()._cleanup() + for fname in self._plot_names: + if os.path.exists(fname): + os.remove(fname) + + def _get_results(self): + """Return a string hash of the plot files.""" + outstr = bytes() + + for fname in self._plot_names: + if fname.endswith('.ppm'): + # Add PPM output to results + with open(fname, 'rb') as fh: + outstr += fh.read() + elif fname.endswith('.h5'): + # Add voxel data to results + with h5py.File(fname, 'r') as fh: + outstr += fh.attrs['filetype'] + outstr += fh.attrs['num_voxels'].tostring() + outstr += fh.attrs['lower_left'].tostring() + outstr += fh.attrs['voxel_width'].tostring() + outstr += fh['data'].value.tostring() + + # Hash the information and return. + sha512 = hashlib.sha512() + sha512.update(outstr) + outstr = sha512.hexdigest() + + return outstr + + +def test_plot(): + harness = PlotTestHarness(('plot_1.ppm', 'plot_2.ppm', 'plot_3.ppm', + 'plot_4.h5')) + harness.main() From 2d4c9934b7c1f43b5430f4f9b361f2fb94060b4f Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 12 Jun 2019 16:50:44 -0500 Subject: [PATCH 123/151] Small change in spacing. --- openmc/plots.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openmc/plots.py b/openmc/plots.py index 3c810fcf6f..f88d35153e 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -678,7 +678,6 @@ class Plot(IDManagerMixin): color = self._overlap_color if isinstance(color, str): color = _SVG_COLORS[color.lower()] - subelement = ET.SubElement(element, "overlap_color") subelement.text = ' '.join(str(x) for x in color) From 489b35c90a07c495c3fcbfa6b98b66dafb8216fa Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 22:35:41 -0500 Subject: [PATCH 124/151] Update src/plot.cpp Co-Authored-By: Paul Romano --- src/plot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plot.cpp b/src/plot.cpp index 60c39389f1..e9069a0d04 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -33,7 +33,7 @@ const RGBColor RED {255, 0, 0}; constexpr int PLOT_LEVEL_LOWEST {-1}; //!< lower bound on plot universe level constexpr int32_t NOT_FOUND {-2}; -constexpr int32_t OVERLAP{-3}; +constexpr int32_t OVERLAP {-3}; IdData::IdData(size_t h_res, size_t v_res) : data_({v_res, h_res, 2}, NOT_FOUND) From 317781949508175ca78da62a09d9df1f04a4435b Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 22:35:52 -0500 Subject: [PATCH 125/151] Update openmc/capi/plot.py Co-Authored-By: Paul Romano --- openmc/capi/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/capi/plot.py b/openmc/capi/plot.py index 375660b38a..4e62a181dd 100644 --- a/openmc/capi/plot.py +++ b/openmc/capi/plot.py @@ -196,7 +196,7 @@ class _PlotBase(Structure): "Basis: {}".format(self.basis), "HRes: {}".format(self.h_res), "VRes: {}".format(self.v_res), - "Color Ovrelaps: {}".format(self.color_overlaps), + "Color Overlaps: {}".format(self.color_overlaps), "Level: {}".format(self.level)] return '\n'.join(out_str) From 5e323539ff8e95c48384230325e296e5642e68a5 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 22:36:00 -0500 Subject: [PATCH 126/151] Update include/openmc/plot.h Co-Authored-By: Paul Romano --- include/openmc/plot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/openmc/plot.h b/include/openmc/plot.h index e08e62d5fa..061c366f78 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -112,7 +112,7 @@ public: Position width_; //!< Plot width in geometry PlotBasis basis_; //!< Plot basis (XY/XZ/YZ) std::array pixels_; //!< Plot size in pixels - bool color_overlaps_; + bool color_overlaps_; //!< Show overlapping cells? int level_; //!< Plot universe level }; From 235b662775e4ff5483160d33e4a9cee6443da2ae Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 22:36:14 -0500 Subject: [PATCH 127/151] Update include/openmc/geometry.h Co-Authored-By: Paul Romano --- include/openmc/geometry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/openmc/geometry.h b/include/openmc/geometry.h index 0ceaab1046..e386348c44 100644 --- a/include/openmc/geometry.h +++ b/include/openmc/geometry.h @@ -47,7 +47,7 @@ inline bool coincident(double d1, double d2) { //! Check for overlapping cells at a particle's position. //============================================================================== - bool check_cell_overlap(Particle* p, bool error=true); +bool check_cell_overlap(Particle* p, bool error=true); //============================================================================== //! Locate a particle in the geometry tree and set its geometry data fields. From 2c6aa62977eda0cc80848514901908d21db3e50d Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 22:38:45 -0500 Subject: [PATCH 128/151] Correcting indentation. --- src/geometry.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/geometry.cpp b/src/geometry.cpp index ce118c0e0c..a92a4d34e1 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -46,11 +46,11 @@ bool check_cell_overlap(Particle* p, bool error) if (c.contains(p->coord_[j].r, p->coord_[j].u, p->surface_)) { if (index_cell != p->coord_[j].cell) { if (error) { - std::stringstream err_msg; - err_msg << "Overlapping cells detected: " << c.id_ << ", " - << model::cells[p->coord_[j].cell]->id_ << " on universe " - << univ.id_; - fatal_error(err_msg); + std::stringstream err_msg; + err_msg << "Overlapping cells detected: " << c.id_ << ", " + << model::cells[p->coord_[j].cell]->id_ << " on universe " + << univ.id_; + fatal_error(err_msg); } return true; } From 8d1098400cfd966f3dfd26faa3e59b76d8262904 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 22:39:58 -0500 Subject: [PATCH 129/151] PEP8 corrections to plot overlap test. --- tests/regression_tests/plot_overlaps/test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/regression_tests/plot_overlaps/test.py b/tests/regression_tests/plot_overlaps/test.py index 4e5b6caa42..bfaef019c1 100644 --- a/tests/regression_tests/plot_overlaps/test.py +++ b/tests/regression_tests/plot_overlaps/test.py @@ -18,8 +18,6 @@ class PlotTestHarness(TestHarness): def _run_openmc(self): openmc.plot_geometry(openmc_exec=config['exe']) - - def _test_output_created(self): """Make sure *.ppm has been created.""" for fname in self._plot_names: From daf129f1ec4affea4c1ee38da3e85fa41561d188 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 22:44:58 -0500 Subject: [PATCH 130/151] Initializing overlap color in the class constructor. --- src/plot.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plot.cpp b/src/plot.cpp index e9069a0d04..93d0abaf2d 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -661,7 +661,6 @@ void Plot::set_overlap_color(pugi::xml_node plot_node) { color_overlaps_ = false; if (check_for_node(plot_node, "show_overlaps")) { color_overlaps_ = get_node_value_bool(plot_node, "show_overlaps"); - overlap_color_ = RED; // check for custom overlap color if (check_for_node(plot_node, "overlap_color")) { if (!color_overlaps_) { @@ -690,7 +689,7 @@ void Plot::set_overlap_color(pugi::xml_node plot_node) { } Plot::Plot(pugi::xml_node plot_node) - : index_meshlines_mesh_{-1} + : index_meshlines_mesh_{-1}, overlap_color_{RED} { set_id(plot_node); set_type(plot_node); From 89c0d06b3059217c0c30e2f903aea6827132c355 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 23:09:26 -0500 Subject: [PATCH 131/151] Moving a few defaults into the class def. --- include/openmc/plot.h | 11 ++++++++--- src/plot.cpp | 5 ----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 061c366f78..da83f42aa1 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -57,6 +57,11 @@ struct RGBColor { uint8_t red, green, blue; }; +// some default colors +const RGBColor WHITE {255, 255, 255}; +const RGBColor RED {255, 0, 0}; + + typedef xt::xtensor ImageData; struct IdData { @@ -218,10 +223,10 @@ public: PlotType type_; //!< Plot type (Slice/Voxel) PlotColorBy color_by_; //!< Plot coloring (cell/material) int meshlines_width_; //!< Width of lines added to the plot - int index_meshlines_mesh_; //!< Index of the mesh to draw on the plot + int index_meshlines_mesh_ {-1}; //!< Index of the mesh to draw on the plot RGBColor meshlines_color_; //!< Color of meshlines on the plot - RGBColor not_found_; //!< Plot background color - RGBColor overlap_color_; //!< Plot overlap color + RGBColor not_found_ {WHITE}; //!< Plot background color + RGBColor overlap_color_ {RED}; //!< Plot overlap color std::vector colors_; //!< Plot colors std::string path_plot_; //!< Plot output filename }; diff --git a/src/plot.cpp b/src/plot.cpp index 93d0abaf2d..4dd62fbedc 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -28,8 +28,6 @@ namespace openmc { // Constants //============================================================================== -const RGBColor WHITE {255, 255, 255}; -const RGBColor RED {255, 0, 0}; constexpr int PLOT_LEVEL_LOWEST {-1}; //!< lower bound on plot universe level constexpr int32_t NOT_FOUND {-2}; @@ -291,9 +289,6 @@ Plot::set_bg_color(pugi::xml_node plot_node) << id_; fatal_error(err_msg); } - } else { - // default to a white background - not_found_ = WHITE; } } From 36b4fbad1262f538fb8d30b18b8f4b0db3b67d54 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 13 Jun 2019 23:25:33 -0500 Subject: [PATCH 132/151] Adding check color function to replace duplicate code. --- openmc/plots.py | 55 +++++++++++++++---------------------------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/openmc/plots.py b/openmc/plots.py index f88d35153e..70afb7fd77 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -357,15 +357,7 @@ class Plot(IDManagerMixin): @background.setter def background(self, background): - cv.check_type('plot background', background, Iterable) - if isinstance(background, str): - if background.lower() not in _SVG_COLORS: - raise ValueError("'{}' is not a valid color.".format(background)) - else: - 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.check_color('plot background', background) self._background = background @colors.setter @@ -373,17 +365,7 @@ class Plot(IDManagerMixin): 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, str): - 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.check_color('plot color value', value) self._colors = colors @mask_components.setter @@ -394,15 +376,7 @@ class Plot(IDManagerMixin): @mask_background.setter def mask_background(self, mask_background): - cv.check_type('plot mask background', mask_background, Iterable) - if isinstance(mask_background, str): - 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.check_color('plot mask background', mask_background) self._mask_background = mask_background @show_overlaps.setter @@ -413,15 +387,7 @@ class Plot(IDManagerMixin): @overlap_color.setter def overlap_color(self, overlap_color): - cv.check_type('plot overlap color', overlap_color, Iterable) - if isinstance(overlap_color, str): - if overlap_color.lower() not in _SVG_COLORS: - raise ValueError("'{}' is not a valid color.".format(overlap_color)) - else: - cv.check_length('plot overlap color', overlap_color, 3) - for rgb in overlap_color: - cv.check_greater_than('plot overlap color', rgb, 0, True) - cv.check_less_than('plot overlap color', rgb, 256) + self.check_color('plot overlap color', overlap_color) self._overlap_color = overlap_color @level.setter @@ -463,6 +429,19 @@ class Plot(IDManagerMixin): self._meshlines = meshlines + @staticmethod + 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) + def __repr__(self): string = 'Plot\n' string += '{: <16}=\t{}\n'.format('\tID', self._id) From 48959ab84c502eeb63173d1ca6ffdc3ed2a1eccd Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 14 Jun 2019 09:48:21 -0500 Subject: [PATCH 133/151] Updating test name --- tests/regression_tests/plot_overlaps/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regression_tests/plot_overlaps/test.py b/tests/regression_tests/plot_overlaps/test.py index bfaef019c1..d8c0943cf5 100644 --- a/tests/regression_tests/plot_overlaps/test.py +++ b/tests/regression_tests/plot_overlaps/test.py @@ -55,7 +55,7 @@ class PlotTestHarness(TestHarness): return outstr -def test_plot(): +def test_plot_overlap(): harness = PlotTestHarness(('plot_1.ppm', 'plot_2.ppm', 'plot_3.ppm', 'plot_4.h5')) harness.main() From 4256e031c3912d46cf7540ca1ef822b3b130866a Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 14 Jun 2019 09:57:08 -0500 Subject: [PATCH 134/151] Applying internal color check to meshlines. --- openmc/plots.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/openmc/plots.py b/openmc/plots.py index 70afb7fd77..b4712c9026 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -357,7 +357,7 @@ class Plot(IDManagerMixin): @background.setter def background(self, background): - self.check_color('plot background', background) + self._check_color('plot background', background) self._background = background @colors.setter @@ -365,7 +365,7 @@ class Plot(IDManagerMixin): cv.check_type('plot colors', colors, Mapping) for key, value in colors.items(): cv.check_type('plot color key', key, (openmc.Cell, openmc.Material)) - self.check_color('plot color value', value) + self._check_color('plot color value', value) self._colors = colors @mask_components.setter @@ -376,7 +376,7 @@ class Plot(IDManagerMixin): @mask_background.setter def mask_background(self, mask_background): - self.check_color('plot mask background', mask_background) + self._check_color('plot mask background', mask_background) self._mask_background = mask_background @show_overlaps.setter @@ -387,7 +387,7 @@ class Plot(IDManagerMixin): @overlap_color.setter def overlap_color(self, overlap_color): - self.check_color('plot overlap color', overlap_color) + self._check_color('plot overlap color', overlap_color) self._overlap_color = overlap_color @level.setter @@ -420,17 +420,12 @@ class Plot(IDManagerMixin): 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._check_color('plot meshlines color', meshlines['color']) self._meshlines = meshlines @staticmethod - def check_color(err_string, color): + def _check_color(err_string, color): cv.check_type(err_string, color, Iterable) if isinstance(color, str): if color.lower() not in _SVG_COLORS: From ea92cc1800ed4eb802fafd780f69c4fa1f2e65cf Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 14 Jun 2019 11:07:11 -0500 Subject: [PATCH 135/151] Fix name of capi.RegularMesh in documentation --- docs/source/capi/index.rst | 2 +- docs/source/pythonapi/capi.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 354ee28784..90bef1909c 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -341,7 +341,7 @@ Functions :param int32_t index: Index in the materials array :param double* denity: Pointer to a density - :return Return status (negative if an error occurs) + :return: Return status (negative if an error occurs) :rtype: int .. c:function:: int openmc_material_get_id(int32_t index, int32_t* id) diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 38ce61fb35..cacc5472e1 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -44,8 +44,8 @@ Classes EnergyFilter MaterialFilter Material - Mesh MeshFilter MeshSurfaceFilter Nuclide + RegularMesh Tally From 93e043e630d640616efeff9dbac97cd7a41762bc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 May 2019 07:06:10 -0500 Subject: [PATCH 136/151] Add old release notes and current in new directory in docs/ --- docs/source/index.rst | 2 +- .../0.10.0.rst} | 2 - docs/source/releasenotes/0.4.0.rst | 36 +++++ docs/source/releasenotes/0.4.1.rst | 55 +++++++ docs/source/releasenotes/0.4.2.rst | 56 +++++++ docs/source/releasenotes/0.4.3.rst | 53 +++++++ docs/source/releasenotes/0.4.4.rst | 45 ++++++ docs/source/releasenotes/0.5.0.rst | 52 ++++++ docs/source/releasenotes/0.5.1.rst | 45 ++++++ docs/source/releasenotes/0.5.2.rst | 57 +++++++ docs/source/releasenotes/0.5.3.rst | 49 ++++++ docs/source/releasenotes/0.5.4.rst | 64 ++++++++ docs/source/releasenotes/0.6.0.rst | 59 +++++++ docs/source/releasenotes/0.6.1.rst | 65 ++++++++ docs/source/releasenotes/0.6.2.rst | 58 +++++++ docs/source/releasenotes/0.7.0.rst | 65 ++++++++ docs/source/releasenotes/0.7.1.rst | 89 +++++++++++ docs/source/releasenotes/0.8.0.rst | 94 +++++++++++ docs/source/releasenotes/0.9.0.rst | 150 ++++++++++++++++++ docs/source/releasenotes/index.rst | 27 ++++ 20 files changed, 1120 insertions(+), 3 deletions(-) rename docs/source/{releasenotes.rst => releasenotes/0.10.0.rst} (99%) create mode 100644 docs/source/releasenotes/0.4.0.rst create mode 100644 docs/source/releasenotes/0.4.1.rst create mode 100644 docs/source/releasenotes/0.4.2.rst create mode 100644 docs/source/releasenotes/0.4.3.rst create mode 100644 docs/source/releasenotes/0.4.4.rst create mode 100644 docs/source/releasenotes/0.5.0.rst create mode 100644 docs/source/releasenotes/0.5.1.rst create mode 100644 docs/source/releasenotes/0.5.2.rst create mode 100644 docs/source/releasenotes/0.5.3.rst create mode 100644 docs/source/releasenotes/0.5.4.rst create mode 100644 docs/source/releasenotes/0.6.0.rst create mode 100644 docs/source/releasenotes/0.6.1.rst create mode 100644 docs/source/releasenotes/0.6.2.rst create mode 100644 docs/source/releasenotes/0.7.0.rst create mode 100644 docs/source/releasenotes/0.7.1.rst create mode 100644 docs/source/releasenotes/0.8.0.rst create mode 100644 docs/source/releasenotes/0.9.0.rst create mode 100644 docs/source/releasenotes/index.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 4071b9794c..5500890e2b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -35,7 +35,7 @@ list `_. quickinstall examples/index - releasenotes + releasenotes/index methods/index usersguide/index devguide/index diff --git a/docs/source/releasenotes.rst b/docs/source/releasenotes/0.10.0.rst similarity index 99% rename from docs/source/releasenotes.rst rename to docs/source/releasenotes/0.10.0.rst index d5ea6c58cd..86eb8f1f64 100644 --- a/docs/source/releasenotes.rst +++ b/docs/source/releasenotes/0.10.0.rst @@ -1,5 +1,3 @@ -.. _releasenotes: - =============================== Release Notes for OpenMC 0.10.0 =============================== diff --git a/docs/source/releasenotes/0.4.0.rst b/docs/source/releasenotes/0.4.0.rst new file mode 100644 index 0000000000..3e0a444e35 --- /dev/null +++ b/docs/source/releasenotes/0.4.0.rst @@ -0,0 +1,36 @@ +.. _notes_0.4.0: + +============================== +Release Notes for OpenMC 0.4.0 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions as well as +Mac OS X. However, it has not been tested yet on any releases of Microsoft +Windows. Memory requirements will vary depending on the size of the problem at +hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- The probability table method for treatment of energy self-shielding in the + unresolved resonance range has been implemented and is now turned on by + default. +- Calculation of Shannon entropy for assessing convergence of the fission source + distribution. +- Ability to compile with the PGI Fortran compiler. +- Ability to run on IBM BlueGene/P machines. +- Completely rewrote how nested universes are handled. Geometry is now much more + robust. + +--------- +Bug Fixes +--------- + +- Many geometry errors have been fixed. The Monte Carlo performance benchmark + can now be successfully run in OpenMC. diff --git a/docs/source/releasenotes/0.4.1.rst b/docs/source/releasenotes/0.4.1.rst new file mode 100644 index 0000000000..2492f23a98 --- /dev/null +++ b/docs/source/releasenotes/0.4.1.rst @@ -0,0 +1,55 @@ +.. _notes_0.4.1: + +============================== +Release Notes for OpenMC 0.4.1 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions as well as +Mac OS X. However, it has not been tested yet on any releases of Microsoft +Windows. Memory requirements will vary depending on the size of the problem at +hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- A batching method has been implemented so that statistics can be calculated + based on multiple generations instead of a single generation. This can help to + overcome problems with underpredicted variance in problems where there is + correlation between successive fission source iterations. +- Users now have the option to select a non-unionized energy grid for problems + with many nuclides where the use of a unionized grid is not feasible. +- Improved plotting capability (Nick Horelik). The plotting input is now in + ``plots.xml`` instead of ``plot.xml``. +- Added multiple estimators for k-effective and added a global tally for + leakage. +- Moved cross section-related output into cross_sections.out. +- Improved timing capabilities. +- Can now use more than 2**31 - 1 particles per generation. +- Improved fission bank synchronization method. This also necessitated changing + the source bank to be of type Bank rather than of type Particle. +- Added HDF5 output (not complete yet). +- Major changes to tally implementation. + +--------- +Bug Fixes +--------- + +- `b206a8`_: Fixed subtle error in the sampling of energy distributions. +- `800742`_: Fixed error in sampling of angle and rotating angles. +- `a07c08`_: Fixed bug in linear-linear interpolation during sampling energy. +- `a75283`_: Fixed energy and energyout tally filters to support many bins. +- `95cfac`_: Fixed error in cell neighbor searches. +- `83a803`_: Fixed bug related to probability tables. + +.. _b206a8: https://github.com/mit-crpg/openmc/commit/b206a8 +.. _800742: https://github.com/mit-crpg/openmc/commit/800742 +.. _a07c08: https://github.com/mit-crpg/openmc/commit/a07c08 +.. _a75283: https://github.com/mit-crpg/openmc/commit/a75283 +.. _95cfac: https://github.com/mit-crpg/openmc/commit/95cfac +.. _83a803: https://github.com/mit-crpg/openmc/commit/83a803 diff --git a/docs/source/releasenotes/0.4.2.rst b/docs/source/releasenotes/0.4.2.rst new file mode 100644 index 0000000000..0ca5287a1c --- /dev/null +++ b/docs/source/releasenotes/0.4.2.rst @@ -0,0 +1,56 @@ +.. _notes_0.4.2: + +============================== +Release Notes for OpenMC 0.4.2 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Ability to specify void materials. +- Option to not reduce tallies across processors at end of each batch. +- Uniform fission site method for reducing variance on local tallies. +- Reading/writing binary source files. +- Added more messages for or high verbosity. +- Estimator for diffusion coefficient. +- Ability to specify 'point' source type. +- Ability to change random number seed. +- Users can now specify units='sum' on a tag. This tells the code that + the total material density is the sum of the atom fractions listed for each + nuclide on the material. + +--------- +Bug Fixes +--------- + +- a27f8f_: Fixed runtime error bug when using Intel compiler with DEBUG on. +- afe121_: Fixed minor bug in fission bank algorithms. +- e0968e_: Force re-evaluation of cross-sections when each particle is born. +- 298db8_: Fixed bug in surface currents when using energy-in filter. +- 2f3bbe_: Fixed subtle bug in S(a,b) cross section calculation. +- 671f30_: Fixed surface currents on mesh not encompassing geometry. +- b2c40e_: Fixed bug in incoming energy filter for track-length tallies. +- 5524fd_: Mesh filter now works with track-length tallies. +- d050c7_: Added Bessel's correction to make estimate of variance unbiased. +- 2a5b9c_: Fixed regression in plotting. + +.. _a27f8f: https://github.com/mit-crpg/openmc/commit/a27f8f +.. _afe121: https://github.com/mit-crpg/openmc/commit/afe121 +.. _e0968e: https://github.com/mit-crpg/openmc/commit/e0968e +.. _298db8: https://github.com/mit-crpg/openmc/commit/298db8 +.. _2f3bbe: https://github.com/mit-crpg/openmc/commit/2f3bbe +.. _671f30: https://github.com/mit-crpg/openmc/commit/671f30 +.. _b2c40e: https://github.com/mit-crpg/openmc/commit/b2c40e +.. _5524fd: https://github.com/mit-crpg/openmc/commit/5524fd +.. _d050c7: https://github.com/mit-crpg/openmc/commit/d050c7 +.. _2a5b9c: https://github.com/mit-crpg/openmc/commit/2a5b9c diff --git a/docs/source/releasenotes/0.4.3.rst b/docs/source/releasenotes/0.4.3.rst new file mode 100644 index 0000000000..fce7a7da37 --- /dev/null +++ b/docs/source/releasenotes/0.4.3.rst @@ -0,0 +1,53 @@ +.. _notes_0.4.3: + +============================== +Release Notes for OpenMC 0.4.3 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Option to report confidence intervals for tally results. +- Rotation and translation for filled cells. +- Ability to explicitly specify for tallies. +- Ability to store state points and use them to restart runs. +- Fixed source calculations (no subcritical multiplication however). +- Expanded options for external source distribution. +- Ability to tally reaction rates for individual nuclides within a material. +- Reduced memory usage by removing redundant storage or some cross-sections. +- 3bd35b_: Log-log interpolation for URR probability tables. +- Support to specify labels on tallies (nelsonag_). + +--------- +Bug Fixes +--------- + +- 33f29a_: Handle negative values in probability table. +- 1c472d_: Fixed survival biasing with probability tables. +- 3c6e80_: Fixed writing tallies with no filters. +- 460ef1_: Invalid results for duplicate tallies. +- 0069d5_: Fixed bug with 0 inactive batches. +- 7af2cf_: Fixed bug in score_analog_tallies. +- 85a60e_: Pick closest angular distribution for law 61. +- 3212f5_: Fixed issue with blank line at beginning of XML files. + +.. _nelsonag: https://github.com/nelsonag +.. _33f29a: https://github.com/mit-crpg/openmc/commit/33f29a +.. _1c472d: https://github.com/mit-crpg/openmc/commit/1c472d +.. _3c6e80: https://github.com/mit-crpg/openmc/commit/3c6e80 +.. _3bd35b: https://github.com/mit-crpg/openmc/commit/3bd35b +.. _0069d5: https://github.com/mit-crpg/openmc/commit/0069d5 +.. _7af2cf: https://github.com/mit-crpg/openmc/commit/7af2cf +.. _460ef1: https://github.com/mit-crpg/openmc/commit/460ef1 +.. _85a60e: https://github.com/mit-crpg/openmc/commit/85a60e +.. _3212f5: https://github.com/mit-crpg/openmc/commit/3212f5 diff --git a/docs/source/releasenotes/0.4.4.rst b/docs/source/releasenotes/0.4.4.rst new file mode 100644 index 0000000000..55a0c3f6f8 --- /dev/null +++ b/docs/source/releasenotes/0.4.4.rst @@ -0,0 +1,45 @@ +.. _notes_0.4.4: + +============================== +Release Notes for OpenMC 0.4.4 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Ability to write state points when using . +- Real-time XML validation in GNU Emacs with RELAX NG schemata. +- Writing state points every n batches with +- Suppress creation of summary.out and cross_sections.out by default with option + to turn them on with tag in settings.xml file. +- Ability to create HDF5 state points. +- Binary source file is now part of state point file by default. +- Enhanced state point usage and added state point Python scripts. +- Turning confidence intervals on affects k-effective. +- Option to specify for tally meshes. + +--------- +Bug Fixes +--------- + +- 4654ee_: Fixed plotting with void cells. +- 7ee461_: Fixed bug with multi-line input using type='word'. +- 792eb3_: Fixed degrees of freedom for confidence intervals. +- 7fd617_: Fixed bug with restart runs in parallel. +- dc4a8f_: Fixed bug with fixed source restart runs. + +.. _4654ee: https://github.com/mit-crpg/openmc/commit/4654ee +.. _7ee461: https://github.com/mit-crpg/openmc/commit/7ee461 +.. _792eb3: https://github.com/mit-crpg/openmc/commit/792eb3 +.. _7fd617: https://github.com/mit-crpg/openmc/commit/7fd617 +.. _dc4a8f: https://github.com/mit-crpg/openmc/commit/dc4a8f diff --git a/docs/source/releasenotes/0.5.0.rst b/docs/source/releasenotes/0.5.0.rst new file mode 100644 index 0000000000..16be1b063b --- /dev/null +++ b/docs/source/releasenotes/0.5.0.rst @@ -0,0 +1,52 @@ +.. _notes_0.5.0: + +============================== +Release Notes for OpenMC 0.5.0 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- All user input options that formerly accepted "off" or "on" should now be + "false" or "true" (the proper XML schema datatype). +- The element is deprecated and was replaced with . +- Added 'events' score that returns number of events that scored to a tally. +- Restructured tally filter implementation and user input. +- Source convergence acceleration via CMFD (implemented with PETSc). +- Ability to read source files in parallel when number of particles is greater + than that number of source sites. +- Cone surface types. + +--------- +Bug Fixes +--------- + +- 737b90_: Coincident surfaces from separate universes / particle traveling + tangent to a surface. +- a819b4_: Output of surface neighbors in summary.out file. +- b11696_: Reading long attribute lists in XML input. +- 2bd46a_: Search for tallying nuclides when no default_xs specified. +- 7a1f08_: Fix word wrapping when writing messages. +- c0e3ec_: Prevent underflow when compiling with MPI=yes and DEBUG=yes. +- 6f8d9d_: Set default tally labels. +- 6a3a5e_: Fix problem with corner-crossing in lattices. + +.. _737b90: https://github.com/mit-crpg/openmc/commit/737b90 +.. _a819b4: https://github.com/mit-crpg/openmc/commit/a819b4 +.. _b11696: https://github.com/mit-crpg/openmc/commit/b11696 +.. _2bd46a: https://github.com/mit-crpg/openmc/commit/2bd46a +.. _7a1f08: https://github.com/mit-crpg/openmc/commit/7a1f08 +.. _c0e3ec: https://github.com/mit-crpg/openmc/commit/c0e3ec +.. _6f8d9d: https://github.com/mit-crpg/openmc/commit/6f8d9d +.. _6a3a5e: https://github.com/mit-crpg/openmc/commit/6a3a5e + diff --git a/docs/source/releasenotes/0.5.1.rst b/docs/source/releasenotes/0.5.1.rst new file mode 100644 index 0000000000..9ba85d304c --- /dev/null +++ b/docs/source/releasenotes/0.5.1.rst @@ -0,0 +1,45 @@ +.. _notes_0.5.1: + +============================== +Release Notes for OpenMC 0.5.1 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Absorption and combined estimators for k-effective. +- Natural elements can now be specified in materials using rather than + . +- Support for multiple S(a,b) tables in a single material (e.g. BeO). +- Test suite using Python nosetests. +- Proper install capability with 'make install'. +- Lattices can now be 2 or 3 dimensions. +- New scatter-PN score type. +- New kappa-fission score type. +- Ability to tally any reaction by specifying MT. + +--------- +Bug Fixes +--------- + +- 94103e_: Two checks for outgoing energy filters. +- e77059_: Fix reaction name for MT=849. +- b0fe88_: Fix distance to surface for cones. +- 63bfd2_: Fix tracklength tallies with cell filter and universes. +- 88daf7_: Fix analog tallies with survival biasing. + +.. _94103e: https://github.com/mit-crpg/openmc/commit/94103e +.. _e77059: https://github.com/mit-crpg/openmc/commit/e77059 +.. _b0fe88: https://github.com/mit-crpg/openmc/commit/b0fe88 +.. _63bfd2: https://github.com/mit-crpg/openmc/commit/63bfd2 +.. _88daf7: https://github.com/mit-crpg/openmc/commit/88daf7 diff --git a/docs/source/releasenotes/0.5.2.rst b/docs/source/releasenotes/0.5.2.rst new file mode 100644 index 0000000000..d030615db7 --- /dev/null +++ b/docs/source/releasenotes/0.5.2.rst @@ -0,0 +1,57 @@ +.. _notes_0.5.2: + +============================== +Release Notes for OpenMC 0.5.2 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Python script for mesh tally plotting +- Isotopic abundances based on IUPAC 2009 when using +- Particle restart files for debugging +- Code will abort after certain number of lost particles (defaults to 10) +- Region outside lattice can be filled with material (void by default) +- 3D voxel plots +- Full HDF5/PHDF5 support (including support in statepoint.py) +- Cell overlap checking with -g command line flag (or when plotting) + +--------- +Bug Fixes +--------- + +- 7632f3_: Fixed bug in statepoint.py for multiple generations per batch. +- f85ac4_: Fix infinite loop bug in error module. +- 49c36b_: Don't convert surface ids if surface filter is for current tallies. +- 5ccc78_: Fix bug in reassignment of bins for mesh filter. +- b1f52f_: Fixed bug in plot color specification. +- eae7e5_: Fixed many memory leaks. +- 10c1cc_: Minor CMFD fixes. +- afdb50_: Add compatibility for XML comments without whitespace. +- a3c593_: Fixed bug in use of free gas scattering for H-1. +- 3a66e3_: Fixed bug in 2D mesh tally implementation. +- ab0793_: Corrected PETSC_NULL references to their correct types. +- 182ebd_: Use analog estimator with energyout filter. + +.. _7632f3: https://github.com/mit-crpg/openmc/commit/7632f3 +.. _f85ac4: https://github.com/mit-crpg/openmc/commit/f85ac4 +.. _49c36b: https://github.com/mit-crpg/openmc/commit/49c36b +.. _5ccc78: https://github.com/mit-crpg/openmc/commit/5ccc78 +.. _b1f52f: https://github.com/mit-crpg/openmc/commit/b1f52f +.. _eae7e5: https://github.com/mit-crpg/openmc/commit/eae7e5 +.. _10c1cc: https://github.com/mit-crpg/openmc/commit/10c1cc +.. _afdb50: https://github.com/mit-crpg/openmc/commit/afdb50 +.. _a3c593: https://github.com/mit-crpg/openmc/commit/a3c593 +.. _3a66e3: https://github.com/mit-crpg/openmc/commit/3a66e3 +.. _ab0793: https://github.com/mit-crpg/openmc/commit/ab0793 +.. _182ebd: https://github.com/mit-crpg/openmc/commit/182ebd diff --git a/docs/source/releasenotes/0.5.3.rst b/docs/source/releasenotes/0.5.3.rst new file mode 100644 index 0000000000..6d93f9aece --- /dev/null +++ b/docs/source/releasenotes/0.5.3.rst @@ -0,0 +1,49 @@ +.. _notes_0.5.3: + +============================== +Release Notes for OpenMC 0.5.3 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Output interface enhanced to allow multiple files handles to be opened +- Particle restart file linked to output interface +- Particle restarts and state point restarts are both identified with the -r + command line flag. +- Particle instance no longer global, passed to all physics routines +- Physics routines refactored to rely less on global memory, more arguments + passed in +- CMFD routines refactored and now can compute dominance ratio on the fly +- PETSc 3.4.2 or higher must be used and compiled with fortran datatype support +- Memory leaks fixed except for ones from xml-fortran package +- Test suite enhanced to test output with different compiler options +- Description of OpenMC development workflow added +- OpenMP shared-memory parallelism added +- Special run mode --tallies removed. + +--------- +Bug Fixes +--------- + +- 2b1e8a_: Normalize direction vector after reflecting particle. +- 5853d2_: Set blank default for cross section listing alias. +- e178c7_: Fix infinite loop with words greater than 80 characters in write_message. +- c18a6e_: Check for valid secondary mode on S(a,b) tables. +- 82c456_: Fix bug where last process could have zero particles. + +.. _2b1e8a: https://github.com/mit-crpg/openmc/commit/2b1e8a +.. _5853d2: https://github.com/mit-crpg/openmc/commit/5853d2 +.. _e178c7: https://github.com/mit-crpg/openmc/commit/e178c7 +.. _c18a6e: https://github.com/mit-crpg/openmc/commit/c18a6e +.. _82c456: https://github.com/mit-crpg/openmc/commit/82c456 diff --git a/docs/source/releasenotes/0.5.4.rst b/docs/source/releasenotes/0.5.4.rst new file mode 100644 index 0000000000..a69354aa0a --- /dev/null +++ b/docs/source/releasenotes/0.5.4.rst @@ -0,0 +1,64 @@ +.. _notes_0.5.4: + +============================== +Release Notes for OpenMC 0.5.4 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Source sites outside geometry are resampled +- XML-Fortran backend replaced by FoX XML +- Ability to write particle track files +- Handle lost particles more gracefully (via particle track files) +- Multiple random number generator streams +- Mesh tally plotting utility converted to use Tkinter rather than PyQt +- Script added to download ACE data from NNDC +- Mixed ASCII/binary cross_sections.xml now allowed +- Expanded options for writing source bank +- Re-enabled ability to use source file as starting source +- S(a,b) recalculation avoided when same nuclide and S(a,b) table are accessed + +--------- +Bug Fixes +--------- + +- 32c03c_: Check for valid data in cross_sections.xml +- c71ef5_: Fix bug in statepoint.py +- 8884fb_: Check for all ZAIDs for S(a,b) tables +- b38af0_: Fix XML reading on multiple levels of input +- d28750_: Fix bug in convert_xsdir.py +- cf567c_: ENDF/B-VI data checked for compatibility +- 6b9461_: Fix p_valid sampling inside of sample_energy + +.. _32c03c: https://github.com/mit-crpg/openmc/commit/32c03c +.. _c71ef5: https://github.com/mit-crpg/openmc/commit/c71ef5 +.. _8884fb: https://github.com/mit-crpg/openmc/commit/8884fb +.. _b38af0: https://github.com/mit-crpg/openmc/commit/b38af0 +.. _d28750: https://github.com/mit-crpg/openmc/commit/d28750 +.. _cf567c: https://github.com/mit-crpg/openmc/commit/cf567c +.. _6b9461: https://github.com/mit-crpg/openmc/commit/6b9461 + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Sterling Harper `_ +- `Bryan Herman `_ +- `Nick Horelik `_ +- `Adam Nelson `_ +- `Paul Romano `_ +- `Tuomas Viitanen `_ +- `Jon Walsh `_ diff --git a/docs/source/releasenotes/0.6.0.rst b/docs/source/releasenotes/0.6.0.rst new file mode 100644 index 0000000000..3f98c461dd --- /dev/null +++ b/docs/source/releasenotes/0.6.0.rst @@ -0,0 +1,59 @@ +.. _notes_0.6.0: + +============================== +Release Notes for OpenMC 0.6.0 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Legendre and spherical harmonic expansion tally scores +- CMake is now default build system +- Regression test suite based on CTests and NNDC cross sections +- FoX is now a git submodule +- Support for older cross sections (e.g. MCNP 66c) +- Progress bar for plots +- Expanded support for natural elements via in settings.xml + +--------- +Bug Fixes +--------- + +- 41f7ca_: Fixed erroneous results from survival biasing +- 038736_: Fix tallies over void materials +- 46f9e8_: Check for negative values in probability tables +- d1ca35_: Fixed sampling of angular distribution +- 0291c0_: Fixed indexing error in plotting +- d7a7d0_: Fix bug with specifying xs attribute +- 85b3cb_: Fix out-of-bounds error with OpenMP threading + +.. _41f7ca: https://github.com/mit-crpg/openmc/commit/41f7ca +.. _038736: https://github.com/mit-crpg/openmc/commit/038736 +.. _46f9e8: https://github.com/mit-crpg/openmc/commit/46f9e8 +.. _d1ca35: https://github.com/mit-crpg/openmc/commit/d1ca35 +.. _0291c0: https://github.com/mit-crpg/openmc/commit/0291c0 +.. _d7a7d0: https://github.com/mit-crpg/openmc/commit/d7a7d0 +.. _85b3cb: https://github.com/mit-crpg/openmc/commit/85b3cb + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Sterling Harper `_ +- `Bryan Herman `_ +- `Nick Horelik `_ +- `Adam Nelson `_ +- `Paul Romano `_ +- `Jon Walsh `_ diff --git a/docs/source/releasenotes/0.6.1.rst b/docs/source/releasenotes/0.6.1.rst new file mode 100644 index 0000000000..71852bd174 --- /dev/null +++ b/docs/source/releasenotes/0.6.1.rst @@ -0,0 +1,65 @@ +.. _notes_0.6.1: + +============================== +Release Notes for OpenMC 0.6.1 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Coarse mesh finite difference (CMFD) acceleration no longer requires PETSc +- Statepoint file numbering is now zero-padded +- Python scripts now compatible with Python 2 or 3 +- Ability to run particle restarts in fixed source calculations +- Capability to filter box source by fissionable materials +- Nuclide/element names are now case insensitive in input files +- Improved treatment of resonance scattering for heavy nuclides + +--------- +Bug Fixes +--------- + +- 03e890_: Check for energy-dependent multiplicities in ACE files +- 4439de_: Fix distance-to-surface calculation for general plane surface +- 5808ed_: Account for differences in URR band probabilities at different energies +- 2e60c0_: Allow zero atom/weight percents in materials +- 3e0870_: Don't use PWD environment variable when setting path to input files +- dc4776_: Handle probability table resampling correctly +- 01178b_: Fix metastables nuclides in NNDC cross_sections.xml file +- 62ec43_: Don't read tallies.xml when OpenMC is run in plotting mode +- 2a95ef_: Prevent segmentation fault on "current" score without mesh filter +- 93e482_: Check for negative values in probability tables + +.. _03e890: https://github.com/mit-crpg/openmc/commit/03e890 +.. _4439de: https://github.com/mit-crpg/openmc/commit/4439de +.. _5808ed: https://github.com/mit-crpg/openmc/commit/5808ed +.. _2e60c0: https://github.com/mit-crpg/openmc/commit/2e60c0 +.. _3e0870: https://github.com/mit-crpg/openmc/commit/3e0870 +.. _dc4776: https://github.com/mit-crpg/openmc/commit/dc4776 +.. _01178b: https://github.com/mit-crpg/openmc/commit/01178b +.. _62ec43: https://github.com/mit-crpg/openmc/commit/62ec43 +.. _2a95ef: https://github.com/mit-crpg/openmc/commit/2a95ef +.. _93e482: https://github.com/mit-crpg/openmc/commit/93e482 + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Sterling Harper `_ +- `Bryan Herman `_ +- `Adam Nelson `_ +- `Paul Romano `_ +- `Jon Walsh `_ +- `Will Boyd `_ diff --git a/docs/source/releasenotes/0.6.2.rst b/docs/source/releasenotes/0.6.2.rst new file mode 100644 index 0000000000..eefc493fcf --- /dev/null +++ b/docs/source/releasenotes/0.6.2.rst @@ -0,0 +1,58 @@ +.. _notes_0.6.2: + +============================== +Release Notes for OpenMC 0.6.2 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Meshline plotting capability +- Support for plotting cells/materials on middle universe levels +- Ability to model cells with no surfaces +- Compatibility with PETSc 3.5 +- Compatability with OpenMPI 1.7/1.8 +- Improved overall performance via logarithmic-mapped energy grid search +- Improved multi-threaded performance with atomic operations +- Support for fixed source problems with fissionable materials + +--------- +Bug Fixes +--------- + +- 26fb93_: Fix problem with -t, --track command-line flag +- 2f07c0_: Improved evaporation spectrum algorithm +- e6abb9_: Fix segfault when tallying in a void material +- 291b45_: Handle metastable nuclides in NNDC data and multiplicities in MT=5 data + +.. _26fb93: https://github.com/mit-crpg/openmc/commit/26fb93 +.. _2f07c0: https://github.com/mit-crpg/openmc/commit/2f07c0 +.. _e6abb9: https://github.com/mit-crpg/openmc/commit/e6abb9 +.. _291b45: https://github.com/mit-crpg/openmc/commit/291b45 + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Will Boyd `_ +- `Matt Ellis `_ +- `Sterling Harper `_ +- `Bryan Herman `_ +- `Nicholas Horelik `_ +- `Anton Leontiev `_ +- `Adam Nelson `_ +- `Paul Romano `_ +- `Jon Walsh `_ +- `John Xia `_ diff --git a/docs/source/releasenotes/0.7.0.rst b/docs/source/releasenotes/0.7.0.rst new file mode 100644 index 0000000000..e7b72686af --- /dev/null +++ b/docs/source/releasenotes/0.7.0.rst @@ -0,0 +1,65 @@ +============================== +Release Notes for OpenMC 0.7.0 +============================== + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Complete Python API +- Python 3 compatability for all scripts +- All scripts consistently named openmc-* and installed together +- New 'distribcell' tally filter for repeated cells +- Ability to specify outer lattice universe +- XML input validation utility (openmc-validate-xml) +- Support for hexagonal lattices +- Material union energy grid method +- Tally triggers +- Remove dependence on PETSc +- Significant OpenMP performance improvements +- Support for Fortran 2008 MPI interface +- Use of Travis CI for continuous integration +- Simplifications and improvements to test suite + +--------- +Bug Fixes +--------- + +- b5f712_: Fix bug in spherical harmonics tallies +- e6675b_: Ensure all constants are double precision +- 04e2c1_: Fix potential bug in sample_nuclide routine +- 6121d9_: Fix bugs related to particle track files +- 2f0e89_: Fixes for nuclide specification in tallies + +.. _b5f712: https://github.com/mit-crpg/openmc/commit/b5f712 +.. _e6675b: https://github.com/mit-crpg/openmc/commit/e6675b +.. _04e2c1: https://github.com/mit-crpg/openmc/commit/04e2c1 +.. _6121d9: https://github.com/mit-crpg/openmc/commit/6121d9 +.. _2f0e89: https://github.com/mit-crpg/openmc/commit/2f0e89 + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Will Boyd `_ +- `Matt Ellis `_ +- `Sterling Harper `_ +- `Bryan Herman `_ +- `Nicholas Horelik `_ +- `Colin Josey `_ +- `William Lyu `_ +- `Adam Nelson `_ +- `Paul Romano `_ +- `Anthony Scopatz `_ +- `Jon Walsh `_ diff --git a/docs/source/releasenotes/0.7.1.rst b/docs/source/releasenotes/0.7.1.rst new file mode 100644 index 0000000000..f1ab0d1aa3 --- /dev/null +++ b/docs/source/releasenotes/0.7.1.rst @@ -0,0 +1,89 @@ +============================== +Release Notes for OpenMC 0.7.1 +============================== + +This release of OpenMC provides some substantial improvements over version +0.7.0. Non-simple cell regions can now be defined through the ``|`` (union) and +``~`` (complement) operators. Similar changes in the Python API also allow +complex cell regions to be defined. A true secondary particle bank now exists; +this is crucial for photon transport (to be added in the next minor release). A +rich API for multi-group cross section generation has been added via the +``openmc.mgxs`` Python module. + +Various improvements to tallies have also been made. It is now possible to +explicitly specify that a collision estimator be used in a tally. A new +``delayedgroup`` filter and ``delayed-nu-fission`` score allow a user to obtain +delayed fission neutron production rates filtered by delayed group. Finally, the +new ``inverse-velocity`` score may be useful for calculating kinetics +parameters. + +.. caution:: In previous versions, depending on how OpenMC was compiled binary + output was either given in HDF5 or a flat binary format. With this + version, all binary output is now HDF5 which means you **must** + have HDF5 in order to install OpenMC. Please consult the user's + guide for instructions on how to compile with HDF5. + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, +and Microsoft Windows 7. Memory requirements will vary depending on the size of +the problem at hand (mostly on the number of nuclides in the problem). + +------------ +New Features +------------ + +- Support for complex cell regions (union and complement operators) +- Generic quadric surface type +- Improved handling of secondary particles +- Binary output is now solely HDF5 +- ``openmc.mgxs`` Python module enabling multi-group cross section generation +- Collision estimator for tallies +- Delayed fission neutron production tallies with ability to filter by delayed + group +- Inverse velocity tally score +- Performance improvements for binary search +- Performance improvements for reaction rate tallies + +--------- +Bug Fixes +--------- + +- 299322_: Bug with material filter when void material present +- d74840_: Fix triggers on tallies with multiple filters +- c29a81_: Correctly handle maximum transport energy +- 3edc23_: Fixes in the nu-scatter score +- 629e3b_: Assume unspecified surface coefficients are zero in Python API +- 5dbe8b_: Fix energy filters for openmc-plot-mesh-tally +- ff66f4_: Fixes in the openmc-plot-mesh-tally script +- 441fd4_: Fix bug in kappa-fission score +- 7e5974_: Allow fixed source simulations from Python API + +.. _299322: https://github.com/mit-crpg/openmc/commit/299322 +.. _d74840: https://github.com/mit-crpg/openmc/commit/d74840 +.. _c29a81: https://github.com/mit-crpg/openmc/commit/c29a81 +.. _3edc23: https://github.com/mit-crpg/openmc/commit/3edc23 +.. _629e3b: https://github.com/mit-crpg/openmc/commit/629e3b +.. _5dbe8b: https://github.com/mit-crpg/openmc/commit/5dbe8b +.. _ff66f4: https://github.com/mit-crpg/openmc/commit/ff66f4 +.. _441fd4: https://github.com/mit-crpg/openmc/commit/441fd4 +.. _7e5974: https://github.com/mit-crpg/openmc/commit/7e5974 + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Will Boyd `_ +- `Sterling Harper `_ +- `Bryan Herman `_ +- `Colin Josey `_ +- `Adam Nelson `_ +- `Paul Romano `_ +- `Kelly Rowland `_ +- `Sam Shaner `_ +- `Jon Walsh `_ diff --git a/docs/source/releasenotes/0.8.0.rst b/docs/source/releasenotes/0.8.0.rst new file mode 100644 index 0000000000..52a03d614e --- /dev/null +++ b/docs/source/releasenotes/0.8.0.rst @@ -0,0 +1,94 @@ +============================== +Release Notes for OpenMC 0.8.0 +============================== + +This release of OpenMC includes a few new major features including the +capability to perform neutron transport with multi-group cross section data as +well as experimental support for the windowed multipole method being developed +at MIT. Source sampling options have also been expanded significantly, with the +option to supply arbitrary tabular and discrete distributions for energy, angle, +and spatial coordinates. + +The Python API has been significantly restructured in this release compared to +version 0.7.1. Any scripts written based on the version 0.7.1 API will likely +need to be rewritten. Some of the most visible changes include the following: + +- ``SettingsFile`` is now ``Settings``, ``MaterialsFile`` is now ``Materials``, + and ``TalliesFile`` is now ``Tallies``. +- The ``GeometryFile`` class no longer exists and is replaced by the + ``Geometry`` class which now has an ``export_to_xml()`` method. +- Source distributions are defined using the ``Source`` class and assigned to + the ``Settings.source`` property. +- The ``Executor`` class no longer exists and is replaced by ``openmc.run()`` + and ``openmc.plot_geometry()`` functions. + +The Python API documentation has also been significantly expanded. + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions and Mac +OS X. Numerous users have reported working builds on Microsoft Windows, but your +mileage may vary. Memory requirements will vary depending on the size of the +problem at hand (mostly on the number of nuclides and tallies in the problem). + +------------ +New Features +------------ + +- Multi-group mode +- Vast improvements to the Python API +- Experimental windowed multipole capability +- Periodic boundary conditions +- Expanded source sampling options +- Distributed materials +- Subcritical multiplication support +- Improved method for reproducible URR table sampling +- Refactor of continuous-energy reaction data +- Improved documentation and new Jupyter notebooks + +--------- +Bug Fixes +--------- + +- 70daa7_: Make sure MT=3 cross section is not used +- 40b05f_: Ensure source bank is resampled for fixed source runs +- 9586ed_: Fix two hexagonal lattice bugs +- a855e8_: Make sure graphite models don't error out on max events +- 7294a1_: Fix incorrect check on cmfd.xml +- 12f246_: Ensure number of realizations is written to statepoint +- 0227f4_: Fix bug when sampling multiple energy distributions +- 51deaa_: Prevent segfault when user specifies '18' on tally scores +- fed74b_: Prevent duplicate tally scores +- 8467ae_: Better threshold for allowable lost particles +- 493c6f_: Fix type of return argument for h5pget_driver_f + +.. _70daa7: https://github.com/mit-crpg/openmc/commit/70daa7 +.. _40b05f: https://github.com/mit-crpg/openmc/commit/40b05f +.. _9586ed: https://github.com/mit-crpg/openmc/commit/9586ed +.. _a855e8: https://github.com/mit-crpg/openmc/commit/a855e8 +.. _7294a1: https://github.com/mit-crpg/openmc/commit/7294a1 +.. _12f246: https://github.com/mit-crpg/openmc/commit/12f246 +.. _0227f4: https://github.com/mit-crpg/openmc/commit/0227f4 +.. _51deaa: https://github.com/mit-crpg/openmc/commit/51deaa +.. _fed74b: https://github.com/mit-crpg/openmc/commit/fed74b +.. _8467ae: https://github.com/mit-crpg/openmc/commit/8467ae +.. _493c6f: https://github.com/mit-crpg/openmc/commit/493c6f + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Will Boyd `_ +- `Derek Gaston `_ +- `Sterling Harper `_ +- `Colin Josey `_ +- `Jingang Liang `_ +- `Adam Nelson `_ +- `Paul Romano `_ +- `Kelly Rowland `_ +- `Sam Shaner `_ diff --git a/docs/source/releasenotes/0.9.0.rst b/docs/source/releasenotes/0.9.0.rst new file mode 100644 index 0000000000..6a9961e69e --- /dev/null +++ b/docs/source/releasenotes/0.9.0.rst @@ -0,0 +1,150 @@ +============================== +Release Notes for OpenMC 0.9.0 +============================== + +.. currentmodule:: openmc + +This release of OpenMC is the first release to use a new native HDF5 cross +section format rather than ACE format cross sections. Other significant new +features include a nuclear data interface in the Python API (:mod:`openmc.data`) +a stochastic volume calculation capability, a random sphere packing algorithm +that can handle packing fractions up to 60%, and a new XML parser with +significantly better performance than the parser used previously. + +.. caution:: With the new cross section format, the default energy units are now + **electronvolts (eV)** rather than megaelectronvolts (MeV)! If you + are specifying an energy filter for a tally, make sure you use + units of eV now. + +The Python API continues to improve over time; several backwards incompatible +changes were made in the API which users of previous versions should take note +of: + +- Each type of tally filter is now specified with a separate class. For example:: + + energy_filter = openmc.EnergyFilter([0.0, 0.625, 4.0, 1.0e6, 20.0e6]) + +- Several attributes of the :class:`Plot` class have changed (``color`` -> + ``color_by`` and ``col_spec`` > ``colors``). :attr:`Plot.colors` now accepts a + dictionary mapping :class:`Cell` or :class:`Material` instances to RGB + 3-tuples or string colors names, e.g.:: + + plot.colors = { + fuel: 'yellow', + water: 'blue' + } + +- ``make_hexagon_region`` is now :func:`get_hexagonal_prism` +- Several changes in :class:`Settings` attributes: + + - ``weight`` is now set as ``Settings.cutoff['weight']`` + - Shannon entropy is now specified by passing a :class:`openmc.Mesh` to + :attr:`Settings.entropy_mesh` + - Uniform fission site method is now specified by passing a + :class:`openmc.Mesh` to :attr:`Settings.ufs_mesh` + - All ``sourcepoint_*`` options are now specified in a + :attr:`Settings.sourcepoint` dictionary + - Resonance scattering method is now specified as a dictionary in + :attr:`Settings.resonance_scattering` + - Multipole is now turned on by setting ``Settings.temperature['multipole'] = + True`` + - The ``output_path`` attribute is now ``Settings.output['path']`` + +- All the ``openmc.mgxs.Nu*`` classes are gone. Instead, a ``nu`` argument was + added to the constructor of the corresponding classes. + +------------------- +System Requirements +------------------- + +There are no special requirements for running the OpenMC code. As of this +release, OpenMC has been tested on a variety of Linux distributions and Mac +OS X. Numerous users have reported working builds on Microsoft Windows, but your +mileage may vary. Memory requirements will vary depending on the size of the +problem at hand (mostly on the number of nuclides and tallies in the problem). + +------------ +New Features +------------ + +- Stochastic volume calculations +- Multi-delayed group cross section generation +- Ability to calculate multi-group cross sections over meshes +- Temperature interpolation on cross section data +- Nuclear data interface in Python API, :mod:`openmc.data` +- Allow cutoff energy via :attr:`Settings.cutoff` +- Ability to define fuel by enrichment (see :meth:`Material.add_element`) +- Random sphere packing for TRISO particle generation, + :func:`openmc.model.pack_trisos` +- Critical eigenvalue search, :func:`openmc.search_for_keff` +- Model container, :class:`openmc.model.Model` +- In-line plotting in Jupyter, :func:`openmc.plot_inline` +- Energy function tally filters, :class:`openmc.EnergyFunctionFilter` +- Replaced FoX XML parser with `pugixml `_ +- Cell/material instance counting, :meth:`Geometry.determine_paths` +- Differential tallies (see :class:`openmc.TallyDerivative`) +- Consistent multi-group scattering matrices +- Improved documentation and new Jupyter notebooks +- OpenMOC compatibility module, :mod:`openmc.openmoc_compatible` + +--------- +Bug Fixes +--------- + +- c5df6c_: Fix mesh filter max iterator check +- 1cfa39_: Reject external source only if 95% of sites are rejected +- 335359_: Fix bug in plotting meshlines +- 17c678_: Make sure system_clock uses high-resolution timer +- 23ec0b_: Fix use of S(a,b) with multipole data +- 7eefb7_: Fix several bugs in tally module +- 7880d4_: Allow plotting calculation with no boundary conditions +- ad2d9f_: Fix filter weight missing when scoring all nuclides +- 59fdca_: Fix use of source files for fixed source calculations +- 9eff5b_: Fix thermal scattering bugs +- 7848a9_: Fix combined k-eff estimator producing NaN +- f139ce_: Fix printing bug for tallies with AggregateNuclide +- b8ddfa_: Bugfix for short tracks near tally mesh edges +- ec3cfb_: Fix inconsistency in filter weights +- 5e9b06_: Fix XML representation for verbosity +- c39990_: Fix bug tallying reaction rates with multipole on +- c6b67e_: Fix fissionable source sampling bug +- 489540_: Check for void materials in tracklength tallies +- f0214f_: Fixes/improvements to the ARES algorithm + +.. _c5df6c: https://github.com/mit-crpg/openmc/commit/c5df6c +.. _1cfa39: https://github.com/mit-crpg/openmc/commit/1cfa39 +.. _335359: https://github.com/mit-crpg/openmc/commit/335359 +.. _17c678: https://github.com/mit-crpg/openmc/commit/17c678 +.. _23ec0b: https://github.com/mit-crpg/openmc/commit/23ec0b +.. _7eefb7: https://github.com/mit-crpg/openmc/commit/7eefb7 +.. _7880d4: https://github.com/mit-crpg/openmc/commit/7880d4 +.. _ad2d9f: https://github.com/mit-crpg/openmc/commit/ad2d9f +.. _59fdca: https://github.com/mit-crpg/openmc/commit/59fdca +.. _9eff5b: https://github.com/mit-crpg/openmc/commit/9eff5b +.. _7848a9: https://github.com/mit-crpg/openmc/commit/7848a9 +.. _f139ce: https://github.com/mit-crpg/openmc/commit/f139ce +.. _b8ddfa: https://github.com/mit-crpg/openmc/commit/b8ddfa +.. _ec3cfb: https://github.com/mit-crpg/openmc/commit/ec3cfb +.. _5e9b06: https://github.com/mit-crpg/openmc/commit/5e9b06 +.. _c39990: https://github.com/mit-crpg/openmc/commit/c39990 +.. _c6b67e: https://github.com/mit-crpg/openmc/commit/c6b67e +.. _489540: https://github.com/mit-crpg/openmc/commit/489540 +.. _f0214f: https://github.com/mit-crpg/openmc/commit/f0214f + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Will Boyd `_ +- `Sterling Harper `_ +- `Qingming He <906459647@qq.com>`_ +- `Colin Josey `_ +- `Travis Labossiere-Hickman `_ +- `Jingang Liang `_ +- `Amanda Lund `_ +- `Adam Nelson `_ +- `Paul Romano `_ +- `Sam Shaner `_ +- `Jon Walsh `_ diff --git a/docs/source/releasenotes/index.rst b/docs/source/releasenotes/index.rst new file mode 100644 index 0000000000..3c66b6a60b --- /dev/null +++ b/docs/source/releasenotes/index.rst @@ -0,0 +1,27 @@ +.. _releasenotes: + +============= +Release Notes +============= + +.. toctree:: + :maxdepth: 1 + + 0.10.0 + 0.9.0 + 0.8.0 + 0.7.1 + 0.7.0 + 0.6.2 + 0.6.1 + 0.6.0 + 0.5.4 + 0.5.3 + 0.5.2 + 0.5.1 + 0.5.0 + 0.4.4 + 0.4.3 + 0.4.2 + 0.4.1 + 0.4.0 From 68faf095fbf09a1ae474da8ab359431effd9b232 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 14 Jun 2019 11:48:41 -0500 Subject: [PATCH 137/151] Use sphinxcontrib-katex for faster math rendering --- docs/requirements-rtd.txt | 1 + docs/source/conf.py | 2 +- docs/source/methods/neutron_physics.rst | 4 ++-- docs/source/methods/photon_physics.rst | 24 +++++++++++++++++++++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/requirements-rtd.txt b/docs/requirements-rtd.txt index 652f880ff8..15345247eb 100644 --- a/docs/requirements-rtd.txt +++ b/docs/requirements-rtd.txt @@ -1,2 +1,3 @@ sphinx-numfig jupyter +sphinxcontrib-katex diff --git a/docs/source/conf.py b/docs/source/conf.py index a33b8af759..a24f9b398a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -49,10 +49,10 @@ sys.path.insert(0, os.path.abspath('../..')) # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon', - 'sphinx.ext.mathjax', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', + 'sphinxcontrib.katex', 'sphinx_numfig', 'notebook_sphinxext'] if not on_rtd: diff --git a/docs/source/methods/neutron_physics.rst b/docs/source/methods/neutron_physics.rst index 465929ba0c..60d3c1468a 100644 --- a/docs/source/methods/neutron_physics.rst +++ b/docs/source/methods/neutron_physics.rst @@ -1177,12 +1177,12 @@ parts as such: .. math:: :label: divide-pdf + \begin{aligned} p(v_T, \mu) &= f_1(v_T, \mu) f_2(v_T) \\ - f_1(v_T, \mu) &= \frac{4\sigma_s}{\sqrt{\pi} C'} \frac{ \sqrt{v_n^2 + v_T^2 - 2v_n v_T \mu}}{v_n + v_T} \\ - f_2(v_T) &= (v_n + v_T) \beta^3 v_T^2 \exp \left ( -\beta^2 v_T^2 \right ). + \end{aligned} In general, any probability distribution function of the form :math:`p(x) = f_1(x) f_2(x)` with :math:`f_1(x)` bounded can be sampled by sampling diff --git a/docs/source/methods/photon_physics.rst b/docs/source/methods/photon_physics.rst index 2d9a62ef37..4494fc6431 100644 --- a/docs/source/methods/photon_physics.rst +++ b/docs/source/methods/photon_physics.rst @@ -50,10 +50,12 @@ The differential cross section for Rayleigh scattering is given by .. math:: :label: coherent-xs + \begin{aligned} \frac{d\sigma(E,E',\mu)}{d\mu} &= \pi r_e^2 ( 1 + \mu^2 )~\left| F(x,Z) + F' + iF'' \right|^2 \\ &= \pi r_e^2 ( 1 + \mu^2 ) \left [ ( F(x,Z) + F'(E) )^2 + F''(E)^2 \right ] + \end{aligned} where :math:`F(x,Z)` is a form factor as a function of the momentum transfer :math:`x` and the atomic number :math:`Z` and the term :math:`F' + iF''` @@ -93,7 +95,7 @@ mass, and the coefficient :math:`a` can be shown to be .. math:: :label: omega - a = \frac{m_e c^2}{\sqrt{2}hc} \approx 29.14329~\unicode{x212B}, + a = \frac{m_e c^2}{\sqrt{2}hc} \approx 2.914329\times10^{-9}~\text{m} where :math:`m_e` is the mass of the electron, :math:`c` is the speed of light in a vacuum, and :math:`h` is Planck's constant. Using :eq:`momentum-transfer`, @@ -371,9 +373,11 @@ where .. math:: :label: mu-pdf-factors + \begin{aligned} \psi(\mu_{-}) &= \frac{(1 - \beta_{-}^2)(1 - \mu_{-}^2)}{(1 - \beta_{-}\mu_{-})^2}, \\ g(\mu_{-}) &= \frac{1 - \beta_{-}^2}{2 (1 - \beta_{-}\mu_{-})^2}. + \end{aligned} In the interval :math:`[-1, 1]`, :math:`g(\mu_{-})` is a normalized PDF and :math:`\psi(\mu_{-})` satisfies the condition :math:`0 < \psi(\mu_{-}) < 1`. @@ -450,10 +454,12 @@ The Coulomb correction, given by .. math:: :label: coulomb-correction + \begin{aligned} f_C = \alpha^{2}Z^{2} \big[&(1 + \alpha^{2}Z^{2})^{-1} + 0.202059 - 0.03693\alpha^{2}Z^{2} + 0.00835\alpha^{4}Z^{4} \\ &- 0.00201\alpha^{6}Z^{6} + 0.00049\alpha^{8}Z^{8} - 0.00012\alpha^{10}Z^{10} + 0.00003\alpha^{12}Z^{12}\big] + \end{aligned} is introduced to correct for the fact that the Bethe-Heitler differential cross section was derived using the Born approximation, which treats the Coulomb @@ -469,9 +475,11 @@ approximations of the screening functions can be derived: .. math:: :label: screening-functions + \begin{aligned} \Phi_1 &= 2 - 2\ln(1 + b^2) - 4b\arctan(b^{-1}) + 4\ln(Rm_{e}c/\hbar) \\ \Phi_2 &= \frac{4}{3} - 2\ln(1 + b^2) + 2b^2 \left[ 4 - 4b\arctan(b^{-1}) - 3\ln(1 + b^{-2}) \right] + 4\ln(Rm_{e}c/\hbar) + \end{aligned} where @@ -492,10 +500,12 @@ upper boundary of :math:`\epsilon` will be shifted below .. math:: :label: correcting-factor + \begin{aligned} F_0(k, Z) =~& (0.1774 + 12.10\alpha Z - 11.18\alpha^{2}Z^{2})(2/k)^{1/2} \\ &+ (8.523 + 73.26\alpha Z - 44.41\alpha^{2}Z^{2})(2/k) \\ &- (13.52 + 121.1\alpha Z - 96.41\alpha^{2}Z^{2})(2/k)^{3/2} \\ &+ (8.946 + 62.05\alpha Z - 63.41\alpha^{2}Z^{2})(2/k)^{2}. + \end{aligned} To aid sampling, the differential cross section used to sample :math:`\epsilon` (minus the normalization constant) can now be expressed in the form @@ -512,23 +522,29 @@ where .. math:: :label: u + \begin{aligned} u_1 &= \frac{2}{3} \left(\frac{1}{2} - \frac{1}{k}\right)^2 \phi_1(1/2), \\ u_2 &= \phi_2(1/2), + \end{aligned} .. math:: :label: phi + \begin{aligned} \phi_1(\epsilon) &= \frac{1}{2}(3\Phi_1 - \Phi_2) - 4f_{C}(Z) + F_0(k, Z), \\ \phi_2(\epsilon) &= \frac{1}{4}(3\Phi_1 + \Phi_2) - 4f_{C}(Z) + F_0(k, Z), + \end{aligned} and .. math:: :label: pi + \begin{aligned} \pi_1(\epsilon) &= \frac{3}{2} \left(\frac{1}{2} - \frac{1}{k}\right)^{-3} \left(\frac{1}{2} - \epsilon\right)^2, \\ \pi_2(\epsilon) &= \frac{1}{2} \left(\frac{1}{2} - \frac{1}{k}\right)^{-1}. + \end{aligned} The functions in :eq:`phi` are non-negative and maximum at :math:`\epsilon = 1/2`. In the interval :math:`(\epsilon_{\text{min}}, \epsilon_{\text{max}})`, @@ -545,10 +561,12 @@ sample the reduced electron energy :math:`\epsilon`: .. math:: + \begin{aligned} \epsilon &= \frac{1}{2} + \left(\frac{1}{2} - \frac{1}{k}\right) (2\xi_1 - 1)^{1/3} ~~~~&\text{if}~~ i = 1 \\ \epsilon &= \frac{1}{k} + \left(\frac{1}{2} - \frac{1}{k}\right) 2\xi_1 ~~~~&\text{if}~~ i = 2. + \end{aligned} 3. If :math:`\xi_2 \le \phi_i(\epsilon)/\phi_i(1/2)`, accept :math:`\epsilon`. Otherwise, repeat the sampling from step 1. @@ -701,10 +719,12 @@ in Salvat_, .. math:: :label: positron-factor + \begin{aligned} F_{\text{p}}(Z,T) = & 1 - \text{exp}(-1.2359\times 10^{-1}t + 6.1274\times 10^{-2}t^2 - 3.1516\times 10^{-2}t^3 \\ & + 7.7446\times 10^{-3}t^4 - 1.0595\times 10^{-3}t^5 + 7.0568\times 10^{-5}t^6 \\ & - 1.8080\times 10^{-6}t^7), + \end{aligned} where @@ -840,8 +860,10 @@ defined as .. math:: :label: density-effect-li + \begin{aligned} l_i &= (\bar{\nu}_i^2 + 2/3f_i)^{1/2} ~~~~&\text{for}~~ \bar{\nu}_i > 0 \\ l_n &= f_n^{1/2} ~~~~&\text{for}~~ \bar{\nu}_n = 0, + \end{aligned} where the second case applies to conduction electrons. For a conductor, :math:`f_n` is given by :math:`n_c/Z`, where :math:`n_c` is the effective From 7096b4463367684a54438f2cefad87b83d615572 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 14 Jun 2019 12:36:45 -0500 Subject: [PATCH 138/151] Updating test results after rebase to account for changes in develop. --- tests/regression_tests/plot_overlaps/results_true.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regression_tests/plot_overlaps/results_true.dat b/tests/regression_tests/plot_overlaps/results_true.dat index 1e34f0ecbc..a2afd4c35a 100644 --- a/tests/regression_tests/plot_overlaps/results_true.dat +++ b/tests/regression_tests/plot_overlaps/results_true.dat @@ -1 +1 @@ -1f962e0dfd63fc540c39faaf16eeb4a725b16c1a82e89972524716ff763d2d9e12f5ae9f50fafd86caeeb0bab54f8501e104ced085378e98cb9d014a357c7544 \ No newline at end of file +e67f7737b49af347a617bfdeebebc3288bd85050f33c9208403f3dfb4625a42f63f3396ecb2517ed9214c3e4e68c9038a8f9a7b3e27a414565c5580827dbb6e6 \ No newline at end of file From ed6c78a0e7e0b0b1d7bd8d399bae367f2b85b4b6 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 14 Jun 2019 13:43:22 -0400 Subject: [PATCH 139/151] Fix documentation in openmc/model/model.py --- openmc/model/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index bdd35079d4..f66bd8f16d 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -135,7 +135,7 @@ class Model(object): chain_file : str, optional Path to the depletion chain XML file. Defaults to the chain found under the ``depletion_chain`` in the - :envvar:`OPENMC_CROSS_SECITON` environment variable if it exists. + :envvar:`OPENMC_CROSS_SECTIONS` environment variable if it exists. method : str Integration method used for depletion (e.g., 'cecm', 'predictor') **kwargs From f21de21ccc1feb8f1186dd9c50cae185ba1580fd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 14 Jun 2019 14:33:19 -0500 Subject: [PATCH 140/151] Start writing release notes for the next release --- docs/source/releasenotes/0.10.0.rst | 6 +- docs/source/releasenotes/0.11.0.rst | 100 ++++++++++++++++++++++++++++ docs/source/releasenotes/0.4.0.rst | 8 +-- docs/source/releasenotes/0.4.1.rst | 8 +-- docs/source/releasenotes/0.4.2.rst | 8 +-- docs/source/releasenotes/0.4.3.rst | 8 +-- docs/source/releasenotes/0.4.4.rst | 8 +-- docs/source/releasenotes/0.5.0.rst | 9 +-- docs/source/releasenotes/0.5.1.rst | 8 +-- docs/source/releasenotes/0.5.2.rst | 8 +-- docs/source/releasenotes/0.5.3.rst | 8 +-- docs/source/releasenotes/0.5.4.rst | 8 +-- docs/source/releasenotes/0.6.0.rst | 8 +-- docs/source/releasenotes/0.6.1.rst | 8 +-- docs/source/releasenotes/0.6.2.rst | 8 +-- docs/source/releasenotes/0.7.0.rst | 6 +- docs/source/releasenotes/0.7.1.rst | 6 +- docs/source/releasenotes/0.8.0.rst | 6 +- docs/source/releasenotes/0.9.0.rst | 6 +- docs/source/releasenotes/index.rst | 1 + docs/source/usersguide/basics.rst | 4 +- 21 files changed, 157 insertions(+), 83 deletions(-) create mode 100644 docs/source/releasenotes/0.11.0.rst diff --git a/docs/source/releasenotes/0.10.0.rst b/docs/source/releasenotes/0.10.0.rst index 86eb8f1f64..e847fd0be3 100644 --- a/docs/source/releasenotes/0.10.0.rst +++ b/docs/source/releasenotes/0.10.0.rst @@ -1,6 +1,6 @@ -=============================== -Release Notes for OpenMC 0.10.0 -=============================== +==================== +What's New in 0.10.0 +==================== .. currentmodule:: openmc diff --git a/docs/source/releasenotes/0.11.0.rst b/docs/source/releasenotes/0.11.0.rst new file mode 100644 index 0000000000..fa008a21cc --- /dev/null +++ b/docs/source/releasenotes/0.11.0.rst @@ -0,0 +1,100 @@ +==================== +What's New in 0.11.0 +==================== + +.. note:: + These release notes are for a future release of OpenMC and are still subject + to change. The new features and bug fixes documented here reflect the + current status of the ``develop`` branch of OpenMC. + +.. currentmodule:: openmc + +------- +Summary +------- + +This release of OpenMC adds several major new features: depletion, photon +transport, and support for CAD geometries through DAGMC. In addition, the core +codebase has been rewritten in C++14 (it was previously written in Fortran +2008). This makes compiling the code cosiderably simpler as no Fortran compiler +is needed. + +Functional expansion tallies are now supported through several new tally filters +that can be arbitrarily combined: + +- :class:`openmc.LegendreFilter` +- :class:`openmc.SpatialLegendreFilter` +- :class:`openmc.SphericalHarmonicsFilter` +- :class:`openmc.ZernikeFilter` +- :class:`openmc.ZernikeRadialFilter` + +Note that these filters replace the use expansion scores like ``scatter-P1``. +Instead, a normal ``scatter`` score should be used along with a +:class:`openmc.LegendreFilter`. + +The interface for random sphere packing has been significantly improved. A new +:func:`openmc.model.pack_spheres` function takes a region and generates a +random, non-overlapping configuration of spheres within the region. + +------------ +New Features +------------ + +- The :class:`Geometry`, :class:`Materials`, and :class:`Settings` classes now + have a ``from_xml`` method that will build an instance from an existing XML + file. +- Predefined energy group structures can be found in + :data:`openmc.mgxs.GROUP_STRUCTURES`. +- New tally scores: ``H1-production``, ``H2-production``, ``H3-production``, + ``He3-production``, ``He4-production``, ``heating``, and ``damage-energy`` +- Switched to cell-based neighor lists (`PR 1140 + `_) +- Two new probability distributions that can be used for source distributions: + :class:`openmc.stats.Normal` and :class:`openmc.stats.Muir` +- The :mod:`openmc.data` module now supports reading and sampling from ENDF File + 32 resonance covariance data (`PR 1024 + `_). + +--------- +Bug Fixes +--------- + +- `Fix reading ASCII ACE tables in Python 3 `_ +- `Fix bug for distributed temperatures `_ +- `Fix bug for distance to boundary in complex cells `_ +- `Bug fixes for precursor decay rate tallies `_ +- `Check for invalid surface IDs in region expression `_ +- `Support for 32-bit operating systems `_ +- `Avoid segfault from unused nuclides `_ +- `Avoid overflow when broadcasting tally results `_ + +------------ +Contributors +------------ + +This release contains new contributions from the following people: + +- `Brody Bassett `_ +- `Will Boyd `_ +- `Guillaume Giudicelli `_ +- `Brittany Grayson `_ +- `Zhuoran Han `_ +- `Sterling Harper `_ +- `Colin Josey `_ +- `Shikhar Kumar `_ +- `Travis Labossiere-Hickman `_ +- `Matias Lavista `_ +- `Jingang Liang `_ +- `Alex Lindsay `_ +- `Johnny Liu `_ +- `Amanda Lund `_ +- `Jan Malec `_ +- `Isaac Meyer `_ +- `April Novak `_ +- `Adam Nelson `_ +- `Jose Salcedo Perez `_ +- `Paul Romano `_ +- `Sam Shaner `_ +- `Jonathan Shimwell `_ +- `Patrick Shriwise `_ +- `John Tramm `_ diff --git a/docs/source/releasenotes/0.4.0.rst b/docs/source/releasenotes/0.4.0.rst index 3e0a444e35..b243656694 100644 --- a/docs/source/releasenotes/0.4.0.rst +++ b/docs/source/releasenotes/0.4.0.rst @@ -1,8 +1,6 @@ -.. _notes_0.4.0: - -============================== -Release Notes for OpenMC 0.4.0 -============================== +=================== +What's New in 0.4.0 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.4.1.rst b/docs/source/releasenotes/0.4.1.rst index 2492f23a98..8ec96897ef 100644 --- a/docs/source/releasenotes/0.4.1.rst +++ b/docs/source/releasenotes/0.4.1.rst @@ -1,8 +1,6 @@ -.. _notes_0.4.1: - -============================== -Release Notes for OpenMC 0.4.1 -============================== +=================== +What's New in 0.4.1 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.4.2.rst b/docs/source/releasenotes/0.4.2.rst index 0ca5287a1c..c0d399301e 100644 --- a/docs/source/releasenotes/0.4.2.rst +++ b/docs/source/releasenotes/0.4.2.rst @@ -1,8 +1,6 @@ -.. _notes_0.4.2: - -============================== -Release Notes for OpenMC 0.4.2 -============================== +=================== +What's New in 0.4.2 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.4.3.rst b/docs/source/releasenotes/0.4.3.rst index fce7a7da37..7ef12198d7 100644 --- a/docs/source/releasenotes/0.4.3.rst +++ b/docs/source/releasenotes/0.4.3.rst @@ -1,8 +1,6 @@ -.. _notes_0.4.3: - -============================== -Release Notes for OpenMC 0.4.3 -============================== +=================== +What's New in 0.4.3 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.4.4.rst b/docs/source/releasenotes/0.4.4.rst index 55a0c3f6f8..d700e610d2 100644 --- a/docs/source/releasenotes/0.4.4.rst +++ b/docs/source/releasenotes/0.4.4.rst @@ -1,8 +1,6 @@ -.. _notes_0.4.4: - -============================== -Release Notes for OpenMC 0.4.4 -============================== +=================== +What's New in 0.4.4 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.5.0.rst b/docs/source/releasenotes/0.5.0.rst index 16be1b063b..443216e335 100644 --- a/docs/source/releasenotes/0.5.0.rst +++ b/docs/source/releasenotes/0.5.0.rst @@ -1,8 +1,6 @@ -.. _notes_0.5.0: - -============================== -Release Notes for OpenMC 0.5.0 -============================== +=================== +What's New in 0.5.0 +=================== ------------------- System Requirements @@ -49,4 +47,3 @@ Bug Fixes .. _c0e3ec: https://github.com/mit-crpg/openmc/commit/c0e3ec .. _6f8d9d: https://github.com/mit-crpg/openmc/commit/6f8d9d .. _6a3a5e: https://github.com/mit-crpg/openmc/commit/6a3a5e - diff --git a/docs/source/releasenotes/0.5.1.rst b/docs/source/releasenotes/0.5.1.rst index 9ba85d304c..6fa584fcf0 100644 --- a/docs/source/releasenotes/0.5.1.rst +++ b/docs/source/releasenotes/0.5.1.rst @@ -1,8 +1,6 @@ -.. _notes_0.5.1: - -============================== -Release Notes for OpenMC 0.5.1 -============================== +=================== +What's New in 0.5.1 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.5.2.rst b/docs/source/releasenotes/0.5.2.rst index d030615db7..ba6374ea7c 100644 --- a/docs/source/releasenotes/0.5.2.rst +++ b/docs/source/releasenotes/0.5.2.rst @@ -1,8 +1,6 @@ -.. _notes_0.5.2: - -============================== -Release Notes for OpenMC 0.5.2 -============================== +=================== +What's New in 0.5.2 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.5.3.rst b/docs/source/releasenotes/0.5.3.rst index 6d93f9aece..a7305da716 100644 --- a/docs/source/releasenotes/0.5.3.rst +++ b/docs/source/releasenotes/0.5.3.rst @@ -1,8 +1,6 @@ -.. _notes_0.5.3: - -============================== -Release Notes for OpenMC 0.5.3 -============================== +=================== +What's New in 0.5.3 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.5.4.rst b/docs/source/releasenotes/0.5.4.rst index a69354aa0a..36d9ec4d57 100644 --- a/docs/source/releasenotes/0.5.4.rst +++ b/docs/source/releasenotes/0.5.4.rst @@ -1,8 +1,6 @@ -.. _notes_0.5.4: - -============================== -Release Notes for OpenMC 0.5.4 -============================== +=================== +What's New in 0.5.4 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.6.0.rst b/docs/source/releasenotes/0.6.0.rst index 3f98c461dd..63e234583c 100644 --- a/docs/source/releasenotes/0.6.0.rst +++ b/docs/source/releasenotes/0.6.0.rst @@ -1,8 +1,6 @@ -.. _notes_0.6.0: - -============================== -Release Notes for OpenMC 0.6.0 -============================== +=================== +What's New in 0.6.0 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.6.1.rst b/docs/source/releasenotes/0.6.1.rst index 71852bd174..a8c59aa519 100644 --- a/docs/source/releasenotes/0.6.1.rst +++ b/docs/source/releasenotes/0.6.1.rst @@ -1,8 +1,6 @@ -.. _notes_0.6.1: - -============================== -Release Notes for OpenMC 0.6.1 -============================== +=================== +What's New in 0.6.1 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.6.2.rst b/docs/source/releasenotes/0.6.2.rst index eefc493fcf..6fe6c28dac 100644 --- a/docs/source/releasenotes/0.6.2.rst +++ b/docs/source/releasenotes/0.6.2.rst @@ -1,8 +1,6 @@ -.. _notes_0.6.2: - -============================== -Release Notes for OpenMC 0.6.2 -============================== +=================== +What's New in 0.6.2 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.7.0.rst b/docs/source/releasenotes/0.7.0.rst index e7b72686af..8f86631ea5 100644 --- a/docs/source/releasenotes/0.7.0.rst +++ b/docs/source/releasenotes/0.7.0.rst @@ -1,6 +1,6 @@ -============================== -Release Notes for OpenMC 0.7.0 -============================== +=================== +What's New in 0.7.0 +=================== ------------------- System Requirements diff --git a/docs/source/releasenotes/0.7.1.rst b/docs/source/releasenotes/0.7.1.rst index f1ab0d1aa3..5ee2c9f69f 100644 --- a/docs/source/releasenotes/0.7.1.rst +++ b/docs/source/releasenotes/0.7.1.rst @@ -1,6 +1,6 @@ -============================== -Release Notes for OpenMC 0.7.1 -============================== +=================== +What's New in 0.7.1 +=================== This release of OpenMC provides some substantial improvements over version 0.7.0. Non-simple cell regions can now be defined through the ``|`` (union) and diff --git a/docs/source/releasenotes/0.8.0.rst b/docs/source/releasenotes/0.8.0.rst index 52a03d614e..bd8a89a64a 100644 --- a/docs/source/releasenotes/0.8.0.rst +++ b/docs/source/releasenotes/0.8.0.rst @@ -1,6 +1,6 @@ -============================== -Release Notes for OpenMC 0.8.0 -============================== +=================== +What's New in 0.8.0 +=================== This release of OpenMC includes a few new major features including the capability to perform neutron transport with multi-group cross section data as diff --git a/docs/source/releasenotes/0.9.0.rst b/docs/source/releasenotes/0.9.0.rst index 6a9961e69e..478c570962 100644 --- a/docs/source/releasenotes/0.9.0.rst +++ b/docs/source/releasenotes/0.9.0.rst @@ -1,6 +1,6 @@ -============================== -Release Notes for OpenMC 0.9.0 -============================== +=================== +What's New in 0.9.0 +=================== .. currentmodule:: openmc diff --git a/docs/source/releasenotes/index.rst b/docs/source/releasenotes/index.rst index 3c66b6a60b..320ea9ca05 100644 --- a/docs/source/releasenotes/index.rst +++ b/docs/source/releasenotes/index.rst @@ -7,6 +7,7 @@ Release Notes .. toctree:: :maxdepth: 1 + 0.11.0 0.10.0 0.9.0 0.8.0 diff --git a/docs/source/usersguide/basics.rst b/docs/source/usersguide/basics.rst index 2dd91ee0fa..dbf63136cd 100644 --- a/docs/source/usersguide/basics.rst +++ b/docs/source/usersguide/basics.rst @@ -100,7 +100,7 @@ that roughly correspond to elements in the XML files. For example, the :class:`openmc.Geometry` for ``geometry.xml``, :class:`openmc.Materials` for ``materials.xml``, :class:`openmc.Settings` for ``settings.xml``, and so on. To create a model then, one creates instances of these classes and then uses the -``export_to_xml()`` method, e.g. :meth:`Geometry.export_to_xml`. Most scripts +``export_to_xml()`` method, e.g., :meth:`Geometry.export_to_xml`. Most scripts that generate a full model will look something like the following: .. code-block:: Python @@ -120,7 +120,7 @@ that generate a full model will look something like the following: ... settings.export_to_xml() -One a model has been created and exported to XML, a simulation can be run either +Once a model has been created and exported to XML, a simulation can be run either by calling :ref:`scripts_openmc` directly from a shell or by using the :func:`openmc.run()` function from Python. From 52293dae83d0af5cc37a8b5fb9fe590823ffc760 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 14 Jun 2019 14:35:39 -0500 Subject: [PATCH 141/151] Improve display of Python API in generated HTML documentation --- docs/source/pythonapi/index.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/source/pythonapi/index.rst b/docs/source/pythonapi/index.rst index 8abb52528b..87d0d2a245 100644 --- a/docs/source/pythonapi/index.rst +++ b/docs/source/pythonapi/index.rst @@ -36,9 +36,7 @@ or class. .. tip:: Users are strongly encouraged to use the Python API to generate input files and analyze results. -------- -Modules -------- +.. rubric:: Modules .. toctree:: :maxdepth: 1 From e5b2c6b477ae517419c6f7be813a43c095838ee5 Mon Sep 17 00:00:00 2001 From: jingang Date: Fri, 14 Jun 2019 19:59:27 -0400 Subject: [PATCH 142/151] &name[0] -> name.data() --- src/initialize.cpp | 2 +- src/lattice.cpp | 6 +++--- src/mesh.cpp | 28 ++++++++++++++-------------- src/tallies/filter_legendre.cpp | 2 +- src/tallies/filter_mesh.cpp | 2 +- src/tallies/filter_sph_harm.cpp | 4 ++-- src/tallies/filter_sptl_legendre.cpp | 2 +- src/tallies/filter_zernike.cpp | 4 ++-- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index fd524a2de9..8331c30b5d 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -102,7 +102,7 @@ void initialize_mpi(MPI_Comm intracomm) // Create bank datatype Particle::Bank b; MPI_Aint disp[6]; - MPI_Get_address(&b.r, &disp[0]); + MPI_Get_address(c&b.r, &disp[0]); MPI_Get_address(&b.u, &disp[1]); MPI_Get_address(&b.E, &disp[2]); MPI_Get_address(&b.wgt, &disp[3]); diff --git a/src/lattice.cpp b/src/lattice.cpp index 5e679042ad..42e15013d4 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -401,7 +401,7 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const } hsize_t dims[3] {nz, ny, nx}; - write_int(lat_group, 3, dims, "universes", &out[0], false); + write_int(lat_group, 3, dims, "universes", out.data(), false); } else { hsize_t nx {static_cast(n_cells_[0])}; @@ -417,7 +417,7 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const } hsize_t dims[3] {1, ny, nx}; - write_int(lat_group, 3, dims, "universes", &out[0], false); + write_int(lat_group, 3, dims, "universes", out.data(), false); } } @@ -897,7 +897,7 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const } hsize_t dims[3] {nz, ny, nx}; - write_int(lat_group, 3, dims, "universes", &out[0], false); + write_int(lat_group, 3, dims, "universes", out.data(), false); } //============================================================================== diff --git a/src/mesh.cpp b/src/mesh.cpp index 075e58de24..01ad329b7a 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -183,11 +183,11 @@ int RegularMesh::get_bin(Position r) const // Determine indices std::vector ijk(n_dimension_); bool in_mesh; - get_indices(r, &ijk[0], &in_mesh); + get_indices(r, ijk.data(), &in_mesh); if (!in_mesh) return -1; // Convert indices to bin - return get_bin_from_indices(&ijk[0]); + return get_bin_from_indices(ijk.data()); } int RegularMesh::get_bin_from_indices(const int* ijk) const @@ -497,9 +497,9 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, int n = n_dimension_; std::vector ijk0(n), ijk1(n); bool start_in_mesh; - get_indices(r0, &ijk0[0], &start_in_mesh); + get_indices(r0, ijk0.data(), &start_in_mesh); bool end_in_mesh; - get_indices(r1, &ijk1[0], &end_in_mesh); + get_indices(r1, ijk1.data(), &end_in_mesh); // Reset coordinates and check for a mesh intersection if necessary. if (start_in_mesh) { @@ -509,7 +509,7 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, // The initial coords do not lie in the mesh. Check to see if the particle // eventually intersects the mesh and compute the relevant coords and // indices. - if (!intersects(r0, r1, &ijk0[0])) return; + if (!intersects(r0, r1, ijk0.data())) return; } r1 = r; @@ -521,7 +521,7 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, // The track ends in this cell. Use the particle end location rather // than the mesh surface and stop iterating. double distance = (r1 - r0).norm(); - bins.push_back(get_bin_from_indices(&ijk0[0])); + bins.push_back(get_bin_from_indices(ijk0.data())); lengths.push_back(distance / total_distance); break; } @@ -543,7 +543,7 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, // Pick the closest mesh surface and append this traversal to the output. auto j = std::min_element(d.begin(), d.end()) - d.begin(); double distance = d[j]; - bins.push_back(get_bin_from_indices(&ijk0[0])); + bins.push_back(get_bin_from_indices(ijk0.data())); lengths.push_back(distance / total_distance); // Translate to the oncoming mesh surface. @@ -584,15 +584,15 @@ void RegularMesh::surface_bins_crossed(const Particle* p, int n = n_dimension_; std::vector ijk0(n), ijk1(n); bool start_in_mesh; - get_indices(r0, &ijk0[0], &start_in_mesh); + get_indices(r0, ijk0.data(), &start_in_mesh); bool end_in_mesh; - get_indices(r1, &ijk1[0], &end_in_mesh); + get_indices(r1, ijk1.data(), &end_in_mesh); // Check if the track intersects any part of the mesh. if (!start_in_mesh) { Position r0_copy = r0; std::vector ijk0_copy(ijk0); - if (!intersects(r0_copy, r1, &ijk0_copy[0])) return; + if (!intersects(r0_copy, r1, ijk0_copy.data())) return; } // ======================================================================== @@ -650,7 +650,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // Outward current on i max surface if (in_mesh) { int i_surf = 4*i + 3; - int i_mesh = get_bin_from_indices(&ijk0[0]); + int i_mesh = get_bin_from_indices(ijk0.data()); int i_bin = 4*n*i_mesh + i_surf - 1; bins.push_back(i_bin); @@ -671,7 +671,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // i min surface if (in_mesh) { int i_surf = 4*i + 2; - int i_mesh = get_bin_from_indices(&ijk0[0]); + int i_mesh = get_bin_from_indices(ijk0.data()); int i_bin = 4*n*i_mesh + i_surf - 1; bins.push_back(i_bin); @@ -683,7 +683,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // Outward current on i min surface if (in_mesh) { int i_surf = 4*i + 1; - int i_mesh = get_bin_from_indices(&ijk0[0]); + int i_mesh = get_bin_from_indices(ijk0.data()); int i_bin = 4*n*i_mesh + i_surf - 1; bins.push_back(i_bin); @@ -704,7 +704,7 @@ void RegularMesh::surface_bins_crossed(const Particle* p, // i max surface if (in_mesh) { int i_surf = 4*i + 4; - int i_mesh = get_bin_from_indices(&ijk0[0]); + int i_mesh = get_bin_from_indices(ijk0.data()); int i_bin = 4*n*i_mesh + i_surf - 1; bins.push_back(i_bin); diff --git a/src/tallies/filter_legendre.cpp b/src/tallies/filter_legendre.cpp index 0446cb2955..6fa041c76b 100644 --- a/src/tallies/filter_legendre.cpp +++ b/src/tallies/filter_legendre.cpp @@ -19,7 +19,7 @@ LegendreFilter::get_all_bins(const Particle* p, int estimator, FilterMatch& match) const { std::vector wgt(n_bins_); - calc_pn_c(order_, p->mu_, &wgt[0]); + calc_pn_c(order_, p->mu_, wgt.data()); for (int i = 0; i < n_bins_; i++) { match.bins_.push_back(i); match.weights_.push_back(wgt[i]); diff --git a/src/tallies/filter_mesh.cpp b/src/tallies/filter_mesh.cpp index c62578b47e..babc4b6e61 100644 --- a/src/tallies/filter_mesh.cpp +++ b/src/tallies/filter_mesh.cpp @@ -59,7 +59,7 @@ MeshFilter::text_label(int bin) const int n_dim = mesh.n_dimension_; std::vector ijk(n_dim); - mesh.get_indices_from_bin(bin, &ijk[0]); + mesh.get_indices_from_bin(bin, ijk.data()); std::stringstream out; out << "Mesh Index (" << ijk[0]; diff --git a/src/tallies/filter_sph_harm.cpp b/src/tallies/filter_sph_harm.cpp index bbb34b16cc..0b3a371187 100644 --- a/src/tallies/filter_sph_harm.cpp +++ b/src/tallies/filter_sph_harm.cpp @@ -37,14 +37,14 @@ SphericalHarmonicsFilter::get_all_bins(const Particle* p, int estimator, // Determine cosine term for scatter expansion if necessary std::vector wgt(order_ + 1); if (cosine_ == SphericalHarmonicsCosine::scatter) { - calc_pn_c(order_, p->mu_, &wgt[0]); + calc_pn_c(order_, p->mu_, wgt.data()); } else { for (int i = 0; i < order_ + 1; i++) wgt[i] = 1; } // Find the Rn,m values std::vector rn(n_bins_); - calc_rn(order_, p->u_last_, &rn[0]); + calc_rn(order_, p->u_last_, rn.data()); int j = 0; for (int n = 0; n < order_ + 1; n++) { diff --git a/src/tallies/filter_sptl_legendre.cpp b/src/tallies/filter_sptl_legendre.cpp index 6727bfed10..6562ff01c7 100644 --- a/src/tallies/filter_sptl_legendre.cpp +++ b/src/tallies/filter_sptl_legendre.cpp @@ -51,7 +51,7 @@ SpatialLegendreFilter::get_all_bins(const Particle* p, int estimator, // Compute and return the Legendre weights. std::vector wgt(order_ + 1); - calc_pn_c(order_, x_norm, &wgt[0]); + calc_pn_c(order_, x_norm, wgt.data()); for (int i = 0; i < order_ + 1; i++) { match.bins_.push_back(i); match.weights_.push_back(wgt[i]); diff --git a/src/tallies/filter_zernike.cpp b/src/tallies/filter_zernike.cpp index 9ab7b4bcda..125bfada89 100644 --- a/src/tallies/filter_zernike.cpp +++ b/src/tallies/filter_zernike.cpp @@ -37,7 +37,7 @@ ZernikeFilter::get_all_bins(const Particle* p, int estimator, if (r <= 1.0) { // Compute and return the Zernike weights. std::vector zn(n_bins_); - calc_zn(order_, r, theta, &zn[0]); + calc_zn(order_, r, theta, zn.data()); for (int i = 0; i < n_bins_; i++) { match.bins_.push_back(i); match.weights_.push_back(zn[i]); @@ -94,7 +94,7 @@ ZernikeRadialFilter::get_all_bins(const Particle* p, int estimator, if (r <= 1.0) { // Compute and return the Zernike weights. std::vector zn(n_bins_); - calc_zn_rad(order_, r, &zn[0]); + calc_zn_rad(order_, r, zn.data()); for (int i = 0; i < n_bins_; i++) { match.bins_.push_back(i); match.weights_.push_back(zn[i]); From 23d7e41592609c6d782d044ea436ff17e4d26bd6 Mon Sep 17 00:00:00 2001 From: jingang Date: Fri, 14 Jun 2019 20:07:42 -0400 Subject: [PATCH 143/151] and -> &&, or -> || --- src/surface.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/surface.cpp b/src/surface.cpp index 9361fa4241..f329fda6d2 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include "openmc/error.h" #include "openmc/hdf5_interface.h" @@ -298,7 +297,7 @@ template double axis_aligned_plane_distance(Position r, Direction u, bool coincident, double offset) { const double f = offset - r[i]; - if (coincident or std::abs(f) < FP_COINCIDENT or u[i] == 0.0) return INFTY; + if (coincident || std::abs(f) < FP_COINCIDENT || u[i] == 0.0) return INFTY; const double d = f / u[i]; if (d < 0.0) return INFTY; return d; @@ -494,7 +493,7 @@ SurfacePlane::distance(Position r, Direction u, bool coincident) const { const double f = A_*r.x + B_*r.y + C_*r.z - D_; const double projection = A_*u.x + B_*u.y + C_*u.z; - if (coincident or std::abs(f) < FP_COINCIDENT or projection == 0.0) { + if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) { return INFTY; } else { const double d = -f / projection; @@ -574,7 +573,7 @@ axis_aligned_cylinder_distance(Position r, Direction u, // No intersection with cylinder. return INFTY; - } else if (coincident or std::abs(c) < FP_COINCIDENT) { + } else if (coincident || std::abs(c) < FP_COINCIDENT) { // Particle is on the cylinder, thus one distance is positive/negative // and the other is zero. The sign of k determines if we are facing in or // out. @@ -744,7 +743,7 @@ double SurfaceSphere::distance(Position r, Direction u, bool coincident) const // No intersection with sphere. return INFTY; - } else if (coincident or std::abs(c) < FP_COINCIDENT) { + } else if (coincident || std::abs(c) < FP_COINCIDENT) { // Particle is on the sphere, thus one distance is positive/negative and // the other is zero. The sign of k determines if we are facing in or out. if (k >= 0.0) { @@ -821,7 +820,7 @@ axis_aligned_cone_distance(Position r, Direction u, // No intersection with cone. return INFTY; - } else if (coincident or std::abs(c) < FP_COINCIDENT) { + } else if (coincident || std::abs(c) < FP_COINCIDENT) { // Particle is on the cone, thus one distance is positive/negative // and the other is zero. The sign of k determines if we are facing in or // out. @@ -1009,7 +1008,7 @@ SurfaceQuadric::distance(Position r, Direction ang, bool coincident) const // No intersection with surface. return INFTY; - } else if (coincident or std::abs(c) < FP_COINCIDENT) { + } else if (coincident || std::abs(c) < FP_COINCIDENT) { // Particle is on the surface, thus one distance is positive/negative and // the other is zero. The sign of k determines which distance is zero and // which is not. @@ -1156,27 +1155,27 @@ void read_surfaces(pugi::xml_node node) // See if this surface makes part of the global bounding box. BoundingBox bb = surf->bounding_box(); - if (bb.xmin > -INFTY and bb.xmin < xmin) { + if (bb.xmin > -INFTY && bb.xmin < xmin) { xmin = bb.xmin; i_xmin = i_surf; } - if (bb.xmax < INFTY and bb.xmax > xmax) { + if (bb.xmax < INFTY && bb.xmax > xmax) { xmax = bb.xmax; i_xmax = i_surf; } - if (bb.ymin > -INFTY and bb.ymin < ymin) { + if (bb.ymin > -INFTY && bb.ymin < ymin) { ymin = bb.ymin; i_ymin = i_surf; } - if (bb.ymax < INFTY and bb.ymax > ymax) { + if (bb.ymax < INFTY && bb.ymax > ymax) { ymax = bb.ymax; i_ymax = i_surf; } - if (bb.zmin > -INFTY and bb.zmin < zmin) { + if (bb.zmin > -INFTY && bb.zmin < zmin) { zmin = bb.zmin; i_zmin = i_surf; } - if (bb.zmax < INFTY and bb.zmax > zmax) { + if (bb.zmax < INFTY && bb.zmax > zmax) { zmax = bb.zmax; i_zmax = i_surf; } From 7cf9bca90845ca8f94108553fca79915f5175b72 Mon Sep 17 00:00:00 2001 From: jingang Date: Fri, 14 Jun 2019 20:39:49 -0400 Subject: [PATCH 144/151] added include in cell.cpp --- src/cell.cpp | 1 + src/initialize.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cell.cpp b/src/cell.cpp index 157d0d1a2c..a60f20fbea 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "openmc/capi.h" #include "openmc/constants.h" diff --git a/src/initialize.cpp b/src/initialize.cpp index 8331c30b5d..fd524a2de9 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -102,7 +102,7 @@ void initialize_mpi(MPI_Comm intracomm) // Create bank datatype Particle::Bank b; MPI_Aint disp[6]; - MPI_Get_address(c&b.r, &disp[0]); + MPI_Get_address(&b.r, &disp[0]); MPI_Get_address(&b.u, &disp[1]); MPI_Get_address(&b.E, &disp[2]); MPI_Get_address(&b.wgt, &disp[3]); From 5f001ee6b55a7c11682c7626223500e00b89dcfc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 14 Jun 2019 22:22:38 -0500 Subject: [PATCH 145/151] Add a short user's guide section on depletion --- docs/source/usersguide/depletion.rst | 53 ++++++++++++++++++++++++++ docs/source/usersguide/index.rst | 1 + openmc/data/photon.py | 11 +++--- openmc/deplete/integrator/cecm.py | 14 +++---- openmc/deplete/integrator/celi.py | 17 +++++---- openmc/deplete/integrator/cf4.py | 27 +++++++------ openmc/deplete/integrator/epc_rk4.py | 23 +++++------ openmc/deplete/integrator/leqi.py | 33 ++++++++-------- openmc/deplete/integrator/predictor.py | 8 ++-- openmc/mgxs/mdgxs.py | 8 ++++ openmc/mgxs/mgxs.py | 14 ++++++- 11 files changed, 136 insertions(+), 73 deletions(-) create mode 100644 docs/source/usersguide/depletion.rst diff --git a/docs/source/usersguide/depletion.rst b/docs/source/usersguide/depletion.rst new file mode 100644 index 0000000000..5dd98406e6 --- /dev/null +++ b/docs/source/usersguide/depletion.rst @@ -0,0 +1,53 @@ +.. _usersguide_depletion: + +========= +Depletion +========= + +OpenMC supports coupled depletion, or burnup, calculations through the +:mod:`openmc.deplete` Python module. OpenMC solves the transport equation to +obtain transmutation reaction rates, and then the reaction rates are used to +solve a set of transmutation equations that determine the evolution of nuclide +densities within a material. The nuclide densities predicted as some future time +are then used to determine updated reaction rates, and the process is repeated +for as many timesteps as are requested. + +The depletion module is designed such that the flux/reaction rate solution (the +transport "operator") is completely isolated from the solution of the +transmutation equations and the method used for advancing time. At present, the +:mod:`openmc.deplete` module offers a single transport operator, +:class:`openmc.deplete.Operator` (which uses the OpenMC transport solver), but +in principle additional operator classes based on other transport codes could be +implemented and no changes to the depletion solver itself would be needed. The +operator class requires a :class:`openmc.Geometry` instance and a +:class:`openmc.Settings` instance:: + + geom = openmc.Geometry() + settings = openmc.Settings() + ... + + op = openmc.deplete.Operator(geom, settings) + +:mod:`openmc.deplete` supports multiple time-integration methods for determining +material compositions over time. Each method appears as a different function. +For example, :func:`openmc.deplete.integrator.cecm` runs a depletion calculation +using the CE/CM algorithm (deplete over a timestep using the middle-of-step +reaction rates). An instance of :class:`openmc.deplete.Operator` is passed to +one of these functions along with the power level and timesteps:: + + power = 1200.0e6 + days = 24*60*60 + timesteps = [10.0*days, 10.0*days, 10.0*days] + openmc.deplete.cecm(op, power, timesteps) + +The coupled transport-depletion problem is executed, and once it is done a +``depletion_results.h5`` file is written. The results can be analyzed using the +:class:`openmc.deplete.ResultsList` class. This class has methods that allow for +easy retrieval of k-effective, nuclide concentrations, and reaction rates over +time:: + + results = openmc.deplete.ResultsList("depletion_results.h5") + time, keff = results.get_eigenvalue() + +Note that the coupling between the transport solver and the transmutation solver +happens in-memory rather than by reading/writing files on disk. diff --git a/docs/source/usersguide/index.rst b/docs/source/usersguide/index.rst index d22acba505..fab353c773 100644 --- a/docs/source/usersguide/index.rst +++ b/docs/source/usersguide/index.rst @@ -20,6 +20,7 @@ essential aspects of using OpenMC to perform simulations. settings tallies plots + depletion scripts processing parallel diff --git a/openmc/data/photon.py b/openmc/data/photon.py index da576b5c8f..0d0e527533 100644 --- a/openmc/data/photon.py +++ b/openmc/data/photon.py @@ -929,13 +929,12 @@ class IncidentPhoton(EqualityMixin): The point-wise heating cross section is calculated as: .. math:: - \sigma_{Hx}(E) &= (E - \overline{E}_x(E)) \cdot \sigma_x(E), x \in \left\{I, PP, PE \right\} - - \overline{E}_I(E) &= \frac {\int E' \sigma_I (E,E',\mu) d\mu} {\int \sigma_I (E,E',\mu) d\mu} - - \overline{E}_{PP} &= 2 m_e c^2 = 1.022 \times 10^6 eV - + \begin{aligned} + \sigma_{Hx}(E) &= (E - \overline{E}_x(E)) \cdot \sigma_x(E), x \in \left\{I, PP, PE \right\} \\ + \overline{E}_I(E) &= \frac {\int E' \sigma_I (E,E',\mu) d\mu} {\int \sigma_I (E,E',\mu) d\mu} \\ + \overline{E}_{PP} &= 2 m_e c^2 = 1.022 \times 10^6 eV \\ \overline{E}_{PE} &= E(\text{fluorescent photons}) + \end{aligned} The differential cross section representation for incoherent scattering can be found in the theory manual. diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 42024863a1..b1bcec448d 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -17,15 +17,13 @@ def cecm(operator, timesteps, power=None, power_density=None, print_out=True): midpoint on corrector. This algorithm is mathematically defined as: .. math:: - y' &= A(y, t) y(t) - - A_p &= A(y_n, t_n) - - y_m &= \text{expm}(A_p h/2) y_n - - A_c &= A(y_m, t_n + h/2) - + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_p &= A(y_n, t_n) \\ + y_m &= \text{expm}(A_p h/2) y_n \\ + A_c &= A(y_m, t_n + h/2) \\ y_{n+1} &= \text{expm}(A_c h) y_n + \end{aligned} Parameters ---------- diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 0ba86d5850..e7b8014990 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -12,10 +12,12 @@ def _celi_f1(chain, rates): return 5/12 * chain.form_matrix(rates[0]) + \ 1/12 * chain.form_matrix(rates[1]) + def _celi_f2(chain, rates): return 1/12 * chain.form_matrix(rates[0]) + \ 5/12 * chain.form_matrix(rates[1]) + def celi(operator, timesteps, power=None, power_density=None, print_out=True): r"""Deplete using the CE/LI CFQ4 algorithm. @@ -27,16 +29,14 @@ def celi(operator, timesteps, power=None, power_density=None, interpolation on corrector. This algorithm is mathematically defined as: .. math:: - y' &= A(y, t) y(t) - - A_0 &= A(y_n, t_n) - - y_p &= \text{expm}(h A_0) y_n - - A_1 &= A(y_p, t_n + h) - + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_0 &= A(y_n, t_n) \\ + y_p &= \text{expm}(h A_0) y_n \\ + A_1 &= A(y_p, t_n + h) \\ y_{n+1} &= \text{expm}(\frac{h}{12} A_0 + \frac{5h}{12} A1) \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n + \end{aligned} Parameters ---------- @@ -90,6 +90,7 @@ def celi(operator, timesteps, power=None, power_density=None, # Create results, write to disk Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) + def celi_inner(operator, vec, p, i, i_res, t, dt, print_out): """ The inner loop of CE/LI CFQ4. diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index e93b22d5cf..fecb917517 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -11,22 +11,26 @@ from ..results import Results def _cf4_f1(chain, rates): return 1/2 * chain.form_matrix(rates) + def _cf4_f2(chain, rates): return -1/2 * chain.form_matrix(rates[0]) + \ chain.form_matrix(rates[1]) + def _cf4_f3(chain, rates): return 1/4 * chain.form_matrix(rates[0]) + \ 1/6 * chain.form_matrix(rates[1]) + \ 1/6 * chain.form_matrix(rates[2]) + \ -1/12 * chain.form_matrix(rates[3]) + def _cf4_f4(chain, rates): return -1/12 * chain.form_matrix(rates[0]) + \ 1/6 * chain.form_matrix(rates[1]) + \ 1/6 * chain.form_matrix(rates[2]) + \ 1/4 * chain.form_matrix(rates[3]) + def cf4(operator, timesteps, power=None, power_density=None, print_out=True): r"""Deplete using the CF4 algorithm. @@ -35,22 +39,17 @@ def cf4(operator, timesteps, power=None, power_density=None, print_out=True): This algorithm is mathematically defined as: .. math:: - F_1 &= h A(y_0) - - y_1 &= \text{expm}(1/2 F_1) y_0 - - F_2 &= h A(y_1) - - y_2 &= \text{expm}(1/2 F_2) y_0 - - F_3 &= h A(y_2) - - y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1 - - F_4 &= h A(y_3) - + \begin{aligned} + F_1 &= h A(y_0) \\ + y_1 &= \text{expm}(1/2 F_1) y_0 \\ + F_2 &= h A(y_1) \\ + y_2 &= \text{expm}(1/2 F_2) y_0 \\ + F_3 &= h A(y_2) \\ + y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1 \\ + F_4 &= h A(y_3) \\ y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4) \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 + \end{aligned} Parameters ---------- diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index 3789f36985..58c4d08f7e 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -25,21 +25,16 @@ def epc_rk4(operator, timesteps, power=None, power_density=None, print_out=True) This algorithm is mathematically defined as: .. math:: - F_1 &= h A(y_0) - - y_1 &= \text{expm}(1/2 F_1) y_0 - - F_2 &= h A(y_1) - - y_2 &= \text{expm}(1/2 F_2) y_0 - - F_3 &= h A(y_2) - - y_3 &= \text{expm}(F_3) y_0 - - F_4 &= h A(y_3) - + \begin{aligned} + F_1 &= h A(y_0) \\ + y_1 &= \text{expm}(1/2 F_1) y_0 \\ + F_2 &= h A(y_1) \\ + y_2 &= \text{expm}(1/2 F_2) y_0 \\ + F_3 &= h A(y_2) \\ + y_3 &= \text{expm}(F_3) y_0 \\ + F_4 &= h A(y_3) \\ y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 + \end{aligned} Parameters ---------- diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 9d221e877c..66b0c253ad 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -16,12 +16,14 @@ def _leqi_f1(chain, inputs): dt_l, dt = inputs[2], inputs[3] return -dt / (12 * dt_l) * f1 + (dt + 6 * dt_l) / (12 * dt_l) * f2 + def _leqi_f2(chain, inputs): f1 = chain.form_matrix(inputs[0]) f2 = chain.form_matrix(inputs[1]) dt_l, dt = inputs[2], inputs[3] return -5 * dt / (12 * dt_l) * f1 + (5 * dt + 6 * dt_l) / (12 * dt_l) * f2 + def _leqi_f3(chain, inputs): f1 = chain.form_matrix(inputs[0]) f2 = chain.form_matrix(inputs[1]) @@ -31,6 +33,7 @@ def _leqi_f3(chain, inputs): (dt**2 + 6*dt*dt_l + 5*dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \ dt_l / (12 * (dt + dt_l)) * f3 + def _leqi_f4(chain, inputs): f1 = chain.form_matrix(inputs[0]) f2 = chain.form_matrix(inputs[1]) @@ -40,6 +43,7 @@ def _leqi_f4(chain, inputs): (dt**2 + 2*dt*dt_l + dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \ (4 * dt * dt_l + 5 * dt_l**2) / (12 * dt_l * (dt + dt_l)) * f3 + def leqi(operator, timesteps, power=None, power_density=None, print_out=True): r"""Deplete using the LE/QI CFQ4 algorithm. @@ -50,29 +54,22 @@ def leqi(operator, timesteps, power=None, power_density=None, print_out=True): interpolation on corrector. This algorithm is mathematically defined as: .. math:: - y' &= A(y, t) y(t) - - A_{last} &= A(y_{n-1}, t_n - h_1) - - A_0 &= A(y_n, t_n) - - F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0 - - F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0 - - y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n - - A_1 &= A(y_p, t_n + h_2) - + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_{last} &= A(y_{n-1}, t_n - h_1) \\ + A_0 &= A(y_n, t_n) \\ + F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0 \\ + F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0 \\ + y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n \\ + A_1 &= A(y_p, t_n + h_2) \\ F_3 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + \frac{h_2 (5 h_1^2 + 6 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + - \frac{h_2 h_1)}{12 (h_1 + h_2)} A_1 - + \frac{h_2 h_1)}{12 (h_1 + h_2)} A_1 \\ F_4 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + \frac{h_2 (h_1^2 + 2 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + - \frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1 - + \frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1 \\ y_{n+1} &= \text{expm}(F_4) \text{expm}(F_3) y_n + \end{aligned} It is initialized using the CE/LI algorithm. diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index f670a125ee..6872ae6ef6 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -15,11 +15,11 @@ def predictor(operator, timesteps, power=None, power_density=None, mathematically defined as: .. math:: - y' &= A(y, t) y(t) - - A_p &= A(y_n, t_n) - + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_p &= A(y_n, t_n) \\ y_{n+1} &= \text{expm}(A_p h) y_n + \end{aligned} Parameters ---------- diff --git a/openmc/mgxs/mdgxs.py b/openmc/mgxs/mdgxs.py index 14bc524ee9..2d1e75e81c 100644 --- a/openmc/mgxs/mdgxs.py +++ b/openmc/mgxs/mdgxs.py @@ -906,6 +906,7 @@ class ChiDelayed(MDGXS): .. math:: + \begin{aligned} \langle \nu^d \sigma_{f,g' \rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_{E_g}^{E_{g-1}} dE \; \chi(E) \nu^d \sigma_f (r, E') \psi(r, E', \Omega')\\ @@ -914,6 +915,7 @@ class ChiDelayed(MDGXS): E') \psi(r, E', \Omega') \\ \chi_g^d &= \frac{\langle \nu^d \sigma_{f,g' \rightarrow g} \phi \rangle} {\langle \nu^d \sigma_f \phi \rangle} + \end{aligned} Parameters ---------- @@ -1547,6 +1549,7 @@ class Beta(MDGXS): .. math:: + \begin{aligned} \langle \nu^d \sigma_f \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_0^\infty dE \; \chi(E) \nu^d \sigma_f (r, E') \psi(r, E', \Omega') \\ @@ -1555,6 +1558,7 @@ class Beta(MDGXS): \sigma_f (r, E') \psi(r, E', \Omega') \\ \beta_{d,g} &= \frac{\langle \nu^d \sigma_f \phi \rangle} {\langle \nu \sigma_f \phi \rangle} + \end{aligned} NOTE: The Beta MGXS is the delayed neutron fraction computed directly from the nuclear data. Often the delayed neutron fraction is @@ -1735,6 +1739,7 @@ class DecayRate(MDGXS): .. math:: + \begin{aligned} \langle \lambda_d \nu^d \sigma_f \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_0^\infty dE \; \lambda_d \nu^d \sigma_f (r, E') \psi(r, E', \Omega') \\ @@ -1743,6 +1748,7 @@ class DecayRate(MDGXS): \sigma_f (r, E') \psi(r, E', \Omega') \\ \lambda_d &= \frac{\langle \lambda_d \nu^d \sigma_f \phi \rangle} {\langle \nu^d \sigma_f \phi \rangle} + \end{aligned} Parameters ---------- @@ -2501,6 +2507,7 @@ class DelayedNuFissionMatrixXS(MatrixMDGXS): .. math:: + \begin{aligned} \langle \nu\sigma_{f,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{E_g}^{E_{g-1}} dE \; \chi(E) \nu\sigma_f^d (r, E') \psi(r, E', \Omega')\\ @@ -2508,6 +2515,7 @@ class DelayedNuFissionMatrixXS(MatrixMDGXS): \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega) \\ \nu\sigma_{f,g'\rightarrow g} &= \frac{\langle \nu\sigma_{f,g'\rightarrow g}^d \phi \rangle}{\langle \phi \rangle} + \end{aligned} Parameters diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index d3d39e0f67..1c8f00f6e4 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -2591,6 +2591,7 @@ class TransportXS(MGXS): .. math:: + \begin{aligned} \langle \sigma_t \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \sigma_t (r, E) \psi (r, E, \Omega) \\ @@ -2603,6 +2604,7 @@ class TransportXS(MGXS): \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega) \\ \sigma_{tr} &= \frac{\langle \sigma_t \phi \rangle - \langle \sigma_{s1} \phi \rangle}{\langle \phi \rangle} + \end{aligned} To incorporate the effect of scattering multiplication in the above relation, the `nu` parameter can be set to `True`. @@ -3549,7 +3551,8 @@ class ScatterMatrixXS(MatrixMGXS): .. math:: - \langle \sigma_{s,\ell,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr + \begin{aligned} + \langle \sigma}_{s,\ell,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; P_\ell (\Omega \cdot \Omega') \sigma_s (r, E' \rightarrow E, \Omega' \cdot \Omega) \psi(r, E', \Omega')\\ @@ -3557,6 +3560,7 @@ class ScatterMatrixXS(MatrixMGXS): \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega) \\ \sigma_{s,\ell,g'\rightarrow g} &= \frac{\langle \sigma_{s,\ell,g'\rightarrow g} \phi \rangle}{\langle \phi \rangle} + \end{aligned} If the order is zero and a :math:`P_0` transport-correction is applied (default), the scattering matrix elements are: @@ -4699,6 +4703,7 @@ class MultiplicityMatrixXS(MatrixMGXS): .. math:: + \begin{aligned} \langle \upsilon \sigma_{s,g'\rightarrow g} \phi \rangle &= \int_{r \in D} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \sum_i \upsilon_i \sigma_i (r, E' \rightarrow @@ -4710,6 +4715,7 @@ class MultiplicityMatrixXS(MatrixMGXS): \upsilon_{g'\rightarrow g} &= \frac{\langle \upsilon \sigma_{s,g'\rightarrow g} \rangle}{\langle \sigma_{s,g'\rightarrow g} \rangle} + \end{aligned} where :math:`\upsilon_i` is the multiplicity for the :math:`i`-th reaction. @@ -4866,6 +4872,7 @@ class ScatterProbabilityMatrix(MatrixMGXS): .. math:: + \begin{aligned} \langle \sigma_{s,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; \sigma_{s} (r, E' \rightarrow E, \Omega' @@ -4877,6 +4884,7 @@ class ScatterProbabilityMatrix(MatrixMGXS): P_{s,g'\rightarrow g} &= \frac{\langle \sigma_{s,g'\rightarrow g} \phi \rangle}{\langle \sigma_{s,g'} \phi \rangle} + \end{aligned} Parameters ---------- @@ -5032,6 +5040,7 @@ class NuFissionMatrixXS(MatrixMGXS): .. math:: + \begin{aligned} \langle \nu\sigma_{f,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{E_g}^{E_{g-1}} dE \; \chi(E) \nu\sigma_f (r, E') \psi(r, E', \Omega')\\ @@ -5039,6 +5048,7 @@ class NuFissionMatrixXS(MatrixMGXS): \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega) \\ \nu\sigma_{f,g'\rightarrow g} &= \frac{\langle \nu\sigma_{f,g'\rightarrow g} \phi \rangle}{\langle \phi \rangle} + \end{aligned} Parameters ---------- @@ -5183,6 +5193,7 @@ class Chi(MGXS): .. math:: + \begin{aligned} \langle \nu\sigma_{f,g' \rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_{E_g}^{E_{g-1}} dE \; \chi(E) \nu\sigma_f (r, E') \psi(r, E', \Omega')\\ @@ -5191,6 +5202,7 @@ class Chi(MGXS): E') \psi(r, E', \Omega') \\ \chi_g &= \frac{\langle \nu\sigma_{f,g' \rightarrow g} \phi \rangle} {\langle \nu\sigma_f \phi \rangle} + \end{aligned} This class can also be used to gather a prompt-chi (which only includes the outgoing energy spectrum of prompt neutrons). This is accomplished by From df2b03c704f7a726173bf3239a167d94e9a42ea9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 14 Jun 2019 22:45:18 -0500 Subject: [PATCH 146/151] Update 0.11.0 release notes --- docs/source/releasenotes/0.11.0.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/source/releasenotes/0.11.0.rst b/docs/source/releasenotes/0.11.0.rst index fa008a21cc..0bf44be3d4 100644 --- a/docs/source/releasenotes/0.11.0.rst +++ b/docs/source/releasenotes/0.11.0.rst @@ -13,11 +13,11 @@ What's New in 0.11.0 Summary ------- -This release of OpenMC adds several major new features: depletion, photon -transport, and support for CAD geometries through DAGMC. In addition, the core -codebase has been rewritten in C++14 (it was previously written in Fortran -2008). This makes compiling the code cosiderably simpler as no Fortran compiler -is needed. +This release of OpenMC adds several major new features: :ref:`depletion +`, photon transport, and support for CAD geometries +through DAGMC. In addition, the core codebase has been rewritten in C++14 (it +was previously written in Fortran 2008). This makes compiling the code +cosiderably simpler as no Fortran compiler is needed. Functional expansion tallies are now supported through several new tally filters that can be arbitrarily combined: @@ -76,10 +76,12 @@ This release contains new contributions from the following people: - `Brody Bassett `_ - `Will Boyd `_ +- `Andrew Davis `_ - `Guillaume Giudicelli `_ - `Brittany Grayson `_ - `Zhuoran Han `_ - `Sterling Harper `_ +- `Andrew Johnson `_ - `Colin Josey `_ - `Shikhar Kumar `_ - `Travis Labossiere-Hickman `_ From c7950cba00915ec13b0f929cf523a429a3761fb9 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 15 Jun 2019 12:14:49 -0500 Subject: [PATCH 147/151] Adding overlaps property to capi PlotBase. --- openmc/capi/plot.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openmc/capi/plot.py b/openmc/capi/plot.py index 4e62a181dd..c4bff4cb3f 100644 --- a/openmc/capi/plot.py +++ b/openmc/capi/plot.py @@ -90,6 +90,7 @@ class _PlotBase(Structure): def __init__(self): self.level_ = -1 + self.color_overlaps_ = False @property def origin(self): @@ -152,6 +153,14 @@ class _PlotBase(Structure): def level(self): return int(self.level_) + @property + def color_overlaps(self): + return self.color_overlaps_ + + @color_overlaps.setter + def color_overlaps(self, color_overlaps): + self.color_overlaps_ = color_overlaps + @origin.setter def origin(self, origin): self.origin_.x = origin[0] From 42627cfce4ca98f63bde06287636de23db1ab085 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 16 Jun 2019 16:10:29 -0500 Subject: [PATCH 148/151] Resizing overlap plot counters if plot is asking for overlap plotting. --- src/plot.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/plot.cpp b/src/plot.cpp index 4dd62fbedc..106f178679 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -969,11 +969,17 @@ extern "C" int openmc_id_map(const void* plot, int32_t* data_out) return OPENMC_E_INVALID_ARGUMENT; } + if (plt->color_overlaps_ && model::overlap_check_count.size() == 0) { + model::overlap_check_count.resize(model::cells.size()); + } + + std::cout << "About to get ids" << std::endl; auto ids = plt->get_map(); + std::cout << "About to copy data" << std::endl; // write id data to array std::copy(ids.data_.begin(), ids.data_.end(), data_out); - + std::cout << "About to return" << std::endl; return 0; } @@ -985,6 +991,10 @@ extern "C" int openmc_property_map(const void* plot, double* data_out) { return OPENMC_E_INVALID_ARGUMENT; } + if (plt->color_overlaps_ && model::overlap_check_count.size() == 0) { + model::overlap_check_count.resize(model::cells.size()); + } + auto props = plt->get_map(); // write id data to array From 71957b24b8aa71a9d70d858f6e763ef4b163df75 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 16 Jun 2019 20:59:37 -0500 Subject: [PATCH 149/151] Removing some hasty comments. --- src/plot.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/plot.cpp b/src/plot.cpp index 106f178679..98a3645c76 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -973,13 +973,11 @@ extern "C" int openmc_id_map(const void* plot, int32_t* data_out) model::overlap_check_count.resize(model::cells.size()); } - std::cout << "About to get ids" << std::endl; auto ids = plt->get_map(); - std::cout << "About to copy data" << std::endl; // write id data to array std::copy(ids.data_.begin(), ids.data_.end(), data_out); - std::cout << "About to return" << std::endl; + return 0; } From cb3f878d1184ed45c90e5b7c5c0d18e4e4dee421 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 16 Jun 2019 22:02:03 -0500 Subject: [PATCH 150/151] Renaming test. Altering includes in surface/cell.h to isolate external package includes. --- include/openmc/cell.h | 2 -- include/openmc/surface.h | 5 +---- tests/regression_tests/dagmc/refl/test.py | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 080dac345c..ee12305951 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -10,9 +10,7 @@ #include "hdf5.h" #include "pugixml.hpp" -#ifdef DAGMC #include "dagmc.h" -#endif #include "openmc/constants.h" #include "openmc/neighbor_list.h" diff --git a/include/openmc/surface.h b/include/openmc/surface.h index 6fd0a73ae1..7b0ccefd1d 100644 --- a/include/openmc/surface.h +++ b/include/openmc/surface.h @@ -12,10 +12,7 @@ #include "openmc/constants.h" #include "openmc/position.h" - -#ifdef DAGMC -#include "DagMC.hpp" -#endif +#include "dagmc.h" namespace openmc { diff --git a/tests/regression_tests/dagmc/refl/test.py b/tests/regression_tests/dagmc/refl/test.py index 885b83766f..93104f1d0d 100644 --- a/tests/regression_tests/dagmc/refl/test.py +++ b/tests/regression_tests/dagmc/refl/test.py @@ -35,6 +35,6 @@ class UWUWTest(PyAPITestHarness): model.tallies.export_to_xml() -def test_uwuw(): +def test_refl(): harness = UWUWTest('statepoint.5.h5') harness.main() From a6c099e29ae5e9c3b1795aeea3dd5030706dcb5d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 17 Jun 2019 06:24:45 -0500 Subject: [PATCH 151/151] A few more fixes pointed out by @smharper --- openmc/lattice.py | 16 ++++++++-------- src/relaxng/geometry.rnc | 2 +- src/relaxng/geometry.rng | 32 ++++++++++++++++---------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 663d051e3c..4776222da2 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -1085,7 +1085,7 @@ class HexLattice(Lattice): # Check the center ring. if len(axial_slice[-1]) != 1: msg = 'HexLattice ID={0:d} has the wrong number of ' \ - 'elements in the innermost ring.Only 1 element is ' \ + 'elements in the innermost ring. Only 1 element is ' \ 'allowed in the innermost ring.'.format(self._id) raise ValueError(msg) @@ -1093,7 +1093,7 @@ class HexLattice(Lattice): for r in range(self._num_rings-1): if len(axial_slice[r]) != 6*(self._num_rings - 1 - r): msg = 'HexLattice ID={0:d} has the wrong number of ' \ - 'elements in ring number{1:d}(counting from the '\ + 'elements in ring number {1:d} (counting from the '\ 'outermost ring). This ring should have {2:d} ' \ 'elements.'.format(self._id, r, 6*(self._num_rings - 1 - r)) @@ -1788,15 +1788,15 @@ class HexLattice(Lattice): setting the :attr:`HexLattice.universes` property. For example,running this method with num_rings=3 will return the similar diagram:: - (0, 4) (0, 3) (0, 2) - - (0, 5) (1, 2) (1, 1) (0, 1) - - (0, 6) (1, 3) (2, 0) (1, 0) (0, 0) + (0, 8) (0, 9) (0,10) (0, 7) (1, 4) (1, 5) (0,11) - (0, 8) (0, 9) (0,10) + (0, 6) (1, 3) (2, 0) (1, 0) (0, 0) + + (0, 5) (1, 2) (1, 1) (0, 1) + + (0, 4) (0, 3) (0, 2) Parameters ---------- diff --git a/src/relaxng/geometry.rnc b/src/relaxng/geometry.rnc index c349340717..e3c88c445c 100644 --- a/src/relaxng/geometry.rnc +++ b/src/relaxng/geometry.rnc @@ -36,7 +36,6 @@ element geometry { attribute dimension { list { xsd:positiveInteger+ } }) & (element lower_left { list { xsd:double+ } } | attribute lower_left { list { xsd:double+ } }) & (element pitch { list { xsd:double+ } } | attribute pitch { list { xsd:double+ } }) & - (element orientation { ( "x" | "y" ) } | attribute orientation { ( "x" | "y" ) })? & (element universes { list { xsd:int+ } } | attribute universes { list { xsd:int+ } }) & (element outer { xsd:int } | attribute outer { xsd:int })? }* @@ -49,6 +48,7 @@ element geometry { (element n_axial { xsd:int } | attribute n_axial { xsd:int })? & (element center { list { xsd:double+ } } | attribute center { list { xsd:double+ } }) & (element pitch { list { xsd:double+ } } | attribute pitch { list { xsd:double+ } }) & + (element orientation { ( "x" | "y" ) } | attribute orientation { ( "x" | "y" ) })? & (element universes { list { xsd:int+ } } | attribute universes { list { xsd:int+ } }) & (element outer { xsd:int } | attribute outer { xsd:int })? }* diff --git a/src/relaxng/geometry.rng b/src/relaxng/geometry.rng index 52b9825be8..56bf385807 100644 --- a/src/relaxng/geometry.rng +++ b/src/relaxng/geometry.rng @@ -294,22 +294,6 @@ - - - - - x - y - - - - - x - y - - - - @@ -414,6 +398,22 @@ + + + + + x + y + + + + + x + y + + + +