diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f0d4807aa..3cf72db20d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -455,9 +455,10 @@ target_compile_options(openmc PRIVATE ${cxxflags}) target_include_directories(openmc PRIVATE ${CMAKE_BINARY_DIR}/include) target_link_libraries(openmc libopenmc) -# Ensure C++14 standard is used +# Ensure C++14 standard is used and turn off GNU extensions target_compile_features(openmc PUBLIC cxx_std_14) target_compile_features(libopenmc PUBLIC cxx_std_14) +set_target_properties(openmc libopenmc PROPERTIES CXX_EXTENSIONS OFF) #=============================================================================== # Python package diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index d2c15c56fd..1a8f63716f 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -490,9 +490,10 @@ attributes/sub-elements: independent distributions of r-, phi-, and z-coordinates where phi is the azimuthal angle and the origin for the cylindrical coordinate system is specified by origin. A "spherical" spatial distribution specifies - independent distributions of r-, theta-, and phi-coordinates where theta - is the angle with respect to the z-axis, phi is the azimuthal angle, and - the sphere is centered on the coordinate (x0,y0,z0). + independent distributions of r-, cos_theta-, and phi-coordinates where + cos_theta is the cosine of the angle with respect to the z-axis, phi is + the azimuthal angle, and the sphere is centered on the coordinate + (x0,y0,z0). *Default*: None diff --git a/docs/source/io_formats/track.rst b/docs/source/io_formats/track.rst index c617cd73e3..a97d75e58e 100644 --- a/docs/source/io_formats/track.rst +++ b/docs/source/io_formats/track.rst @@ -4,18 +4,34 @@ Track File Format ================= -The current revision of the particle track file format is 2.0. +The current revision of the particle track file format is 3.0. **/** :Attributes: - **filetype** (*char[]*) -- String indicating the type of file. - **version** (*int[2]*) -- Major and minor version of the track file format. - - **n_particles** (*int*) -- Number of particles for which tracks - are recorded. - - **n_coords** (*int[]*) -- Number of coordinates for each - particle. :Datasets: - - **coordinates_** (*double[][3]*) -- (x,y,z) coordinates for the - *i*-th particle. + - **track___

** (Compound type) -- Particle track information + for source particle in batch *b*, generation *g*, and particle + number *p*. particle. The compound type has fields ``r``, ``u``, + ``E``, ``time``, ``wgt``, ``cell_id``, ``cell_instance``, and + ``material_id``, which represent the position (each coordinate in + [cm]), direction, energy in [eV], time in [s], weight, cell ID, + cell instance, and material ID, respectively. When the particle is + present in a cell with no material assigned, the material ID is + given as -1. Note that this array contains information for one or + more primary/secondary particles originating. The starting index + for each primary/secondary particle is given by the ``offsets`` + attribute. + + :Attributes: - **n_particles** (*int*) -- Number of + primary/secondary particles for the source history. + - **offsets** (*int[]*) Offset (starting index) into + the array for each primary/secondary particle. The + last offset should match the total size of the + array. + - **particles** (*int[]*) -- Particle type for each + primary/secondary particle (0=neutron, 1=photon, + 2=electron, 3=positron). diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index b471d12a86..076b02722b 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -185,8 +185,11 @@ Post-processing :template: myclass.rst openmc.Particle + openmc.ParticleTrack openmc.StatePoint openmc.Summary + openmc.Track + openmc.Tracks The following classes and functions are used for functional expansion reconstruction. diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index e3138a53b0..480db5be68 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -8,39 +8,56 @@ This quick install guide outlines the basic steps needed to install OpenMC on your computer. For more detailed instructions on configuring and installing OpenMC, see :ref:`usersguide_install` in the User's Manual. ----------------------------------------- -Installing on Linux/Mac with conda-forge ----------------------------------------- +-------------------------------------------------- +Installing on Linux/Mac with Mamba and conda-forge +-------------------------------------------------- `Conda `_ is an open source package management -system and environment management system for installing multiple versions of -software packages and their dependencies and switching easily between them. If -you have `conda` installed on your system, OpenMC can be installed via the -`conda-forge` channel. First, add the `conda-forge` channel with: +system and environments management system for installing multiple versions of +software packages and their dependencies and switching easily between them. +`Mamba `_ is a cross-platform package +manager and is compatible with `conda` packages. +OpenMC can be installed in a `conda` environment with `mamba`. +First, `conda` should be installed with one of the following installers: +`Miniconda `_, +`Anaconda `_, or `Miniforge `_. +Once you have `conda` installed on your system, OpenMC can be installed via the +`conda-forge` channel with `mamba`. + +First, add the `conda-forge` channel with: .. code-block:: sh conda config --add channels conda-forge +Then create and activate a new conda enviroment called `openmc-env` in +which to install OpenMC. + +.. code-block:: sh + + conda create -n openmc-env + conda activate openmc-env + +Then install `mamba`, which will be used to install OpenMC. + +.. code-block:: sh + + conda install mamba + To list the versions of OpenMC that are available on the `conda-forge` channel, in your terminal window or an Anaconda Prompt run: .. code-block:: sh - conda search openmc + mamba search openmc OpenMC can then be installed with: .. code-block:: sh - conda create -n openmc-env openmc + mamba install openmc -This will install OpenMC in a conda environment called `openmc-env`. To activate -the environment, run: - -.. code-block:: sh - - conda activate openmc-env +You are now in a conda environment called `openmc-env` that has OpenMC installed. ------------------------------------------- Installing on Linux/Mac/Windows with Docker diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 3e3f7754a6..d05c00bb3d 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -8,39 +8,56 @@ Installation and Configuration .. _install_conda: ----------------------------------------- -Installing on Linux/Mac with conda-forge ----------------------------------------- +-------------------------------------------------- +Installing on Linux/Mac with Mamba and conda-forge +-------------------------------------------------- -Conda_ is an open source package management system and environment management -system for installing multiple versions of software packages and their -dependencies and switching easily between them. If you have `conda` installed on -your system, OpenMC can be installed via the `conda-forge` channel. First, add -the `conda-forge` channel with: +`Conda `_ is an open source package management +systems and environments management system for installing multiple versions of +software packages and their dependencies and switching easily between them. +`Mamba `_ is a cross-platform package +manager and is compatible with `conda` packages. +OpenMC can be installed in a `conda` environment with `mamba`. +First, `conda` should be installed with one of the following installers: +`Miniconda `_, +`Anaconda `_, or `Miniforge `_. +Once you have `conda` installed on your system, OpenMC can be installed via the +`conda-forge` channel with `mamba`. + +First, add the `conda-forge` channel with: .. code-block:: sh conda config --add channels conda-forge +Then create and activate a new conda enviroment called `openmc-env` in +which to install OpenMC. + +.. code-block:: sh + + conda create -n openmc-env + conda activate openmc-env + +Then install `mamba`, which will be used to install OpenMC. + +.. code-block:: sh + + conda install mamba + To list the versions of OpenMC that are available on the `conda-forge` channel, in your terminal window or an Anaconda Prompt run: .. code-block:: sh - conda search openmc + mamba search openmc OpenMC can then be installed with: .. code-block:: sh - conda create -n openmc-env openmc + mamba install openmc -This will install OpenMC in a conda environment called `openmc-env`. To activate -the environment, run: - -.. code-block:: sh - - conda activate openmc-env +You are now in a conda environment called `openmc-env` that has OpenMC installed. ------------------------------------------- Installing on Linux/Mac/Windows with Docker diff --git a/docs/source/usersguide/scripts.rst b/docs/source/usersguide/scripts.rst index 963e91cf2d..f2ba816052 100644 --- a/docs/source/usersguide/scripts.rst +++ b/docs/source/usersguide/scripts.rst @@ -47,7 +47,7 @@ flags: -r, --restart file Restart a previous run from a state point or a particle restart file -s, --threads N Run with *N* OpenMP threads --t, --track Write tracks for all particles +-t, --track Write tracks for all particles (up to max_tracks) -v, --version Show version information -h, --help Show help message @@ -112,6 +112,19 @@ otherwise. tallies. The path to the statepoint file can be provided as an optional arugment (if omitted, a file dialog will be presented). +.. _scripts_track_combine: + +------------------------ +``openmc-track-combine`` +------------------------ + +This script combines multiple HDF5 :ref:`particle track files +` into a single HDF5 particle track file. The filenames of the +particle track files should be given as posititional arguments. The output +filename can also be changed with the ``-o`` flag: + +-o OUT, --out OUT Output HDF5 particle track file + .. _scripts_track: ----------------------- diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index dabad32bd5..336982ac6e 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -514,4 +514,108 @@ As an example, to write a statepoint file every five batches:: settings.batches = n settings.statepoint = {'batches': range(5, n + 5, 5)} -.. _NIST ESTAR database: https://physics.nist.gov/PhysRefData/Star/Text/ESTAR.html +Particle Track Files +-------------------- + +OpenMC can generate a particle track file that contains track information +(position, direction, energy, time, weight, cell ID, and material ID) for each +state along a particle's history. There are two ways to indicate which particles +and/or how many particles should have their tracks written. First, you can +identify specific source particles by their batch, generation, and particle ID +numbers:: + + settings.tracks = [ + (1, 1, 50), + (2, 1, 30), + (5, 1, 75) + ] + +In this example, track information would be written for the 50th particle in the +1st generation of batch 1, the 30th particle in the first generation of batch 2, +and the 75th particle in the 1st generation of batch 5. Unless you are using +more than one generation per batch (see :ref:`usersguide_particles`), the +generation number should be 1. Alternatively, you can run OpenMC in a mode where +track information is written for *all* particles, up to a user-specified limit:: + + openmc.run(tracks=True) + +In this case, you can control the maximum number of source particles for which +tracks will be written as follows:: + + settings.max_tracks = 1000 + +Particle track information is written to the ``tracks.h5`` file, which can be +analyzed using the :class:`~openmc.Tracks` class:: + + >>> tracks = openmc.Tracks('tracks.h5') + >>> tracks + [, + , + ] + +Each :class:`~openmc.Track` object stores a list of track information for every +primary/secondary particle. In the above example, the first source particle +produced 150 secondary particles for a total of 151 particles. Information for +each primary/secondary particle can be accessed using the +:attr:`~openmc.Track.particle_tracks` attribute:: + + >>> first_track = tracks[0] + >>> first_track.particle_tracks + [, + , + , + , + , + ... + , + ] + >>> photon = first_track.particle_tracks[1] + +The :class:`~openmc.ParticleTrack` class is a named tuple indicating the +particle type and then a NumPy array of the "states". The states array is a +compound type with a field for each physical quantity (position, direction, +energy, time, weight, cell ID, and material ID). For example, to get the +position for the above particle track:: + + >>> photon.states['r'] + array([(-11.92987939, -12.28467295, 0.67837495), + (-11.95213726, -12.2682 , 0.68783964), + (-12.2682 , -12.03428339, 0.82223855), + (-12.5913778 , -11.79510096, 0.95966298), + (-12.6622572 , -11.74264344, 0.98980293), + (-12.6907775 , -11.7215357 , 1.00193058)], + dtype=[('x', '>> tracks.filter(particle='photon') + [, + , + ] + +The :meth:`openmc.Tracks.filter` method returns a new :class:`~openmc.Tracks` +instance, whereas the :meth:`openmc.Track.filter` method returns a new +:class:`~openmc.Track` instance. + +.. note:: If you are using an MPI-enabled install of OpenMC and run a simulation + with more than one process, a separate track file will be written for + each MPI process with the filename ``tracks_p#.h5`` where # is the + rank of the corresponding process. Multiple track files can be + combined with the :ref:`scripts_track_combine` script: + + .. code-block:: sh + + openmc-track-combine tracks_p*.h5 --out tracks.h5 diff --git a/include/openmc/cell.h b/include/openmc/cell.h index d975750a16..01f0375865 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -300,5 +300,8 @@ struct CellInstanceHash { void read_cells(pugi::xml_node node); +//! Add cells to universes +void populate_universes(); + } // namespace openmc #endif // OPENMC_CELL_H diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 8427c2d558..73f96ff956 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -26,7 +26,7 @@ constexpr int HDF5_VERSION[] {3, 0}; // Version numbers for binary files constexpr array VERSION_STATEPOINT {17, 0}; constexpr array VERSION_PARTICLE_RESTART {2, 0}; -constexpr array VERSION_TRACK {2, 0}; +constexpr array VERSION_TRACK {3, 0}; constexpr array VERSION_SUMMARY {6, 0}; constexpr array VERSION_VOLUME {1, 0}; constexpr array VERSION_VOXEL {2, 0}; diff --git a/include/openmc/dagmc.h b/include/openmc/dagmc.h index 64bf4d2091..9930763474 100644 --- a/include/openmc/dagmc.h +++ b/include/openmc/dagmc.h @@ -22,6 +22,7 @@ void check_dagmc_root_univ(); #ifdef DAGMC #include "DagMC.hpp" +#include "dagmcmetadata.hpp" #include "openmc/cell.h" #include "openmc/particle.h" @@ -87,6 +88,11 @@ public: explicit DAGUniverse(const std::string& filename, bool auto_geom_ids = false, bool auto_mat_ids = false); + //! Alternative DAGMC universe constructor for external DAGMC instance + explicit DAGUniverse(std::shared_ptr external_dagmc_ptr, + const std::string& filename = "", bool auto_geom_ids = false, + bool auto_mat_ids = false); + //! Initialize the DAGMC accel. data structures, indices, material //! assignments, etc. void initialize(); @@ -139,10 +145,16 @@ public: bool has_graveyard() const { return has_graveyard_; } private: + void set_id(); //!< Deduce the universe id from model::universes + void init_dagmc(); //!< Create and initialise DAGMC pointer + void init_metadata(); //!< Create and initialise dagmcMetaData pointer + void init_geometry(); //!< Create cells and surfaces from DAGMC entities + std::string filename_; //!< Name of the DAGMC file used to create this universe std::shared_ptr - uwuw_; //!< Pointer to the UWUW instance for this universe + uwuw_; //!< Pointer to the UWUW instance for this universe + std::unique_ptr dmd_ptr; //! Pointer to DAGMC metadata object bool adjust_geometry_ids_; //!< Indicates whether or not to automatically //!< generate new cell and surface IDs for the //!< universe diff --git a/include/openmc/distribution_spatial.h b/include/openmc/distribution_spatial.h index 9cceee184f..5e426b8781 100644 --- a/include/openmc/distribution_spatial.h +++ b/include/openmc/distribution_spatial.h @@ -70,7 +70,7 @@ private: }; //============================================================================== -//! Distribution of points specified by spherical coordinates r,theta,phi +//! Distribution of points specified by spherical coordinates r,cos_theta,phi //============================================================================== class SphericalIndependent : public SpatialDistribution { @@ -83,15 +83,15 @@ public: Position sample(uint64_t* seed) const; Distribution* r() const { return r_.get(); } - Distribution* theta() const { return theta_.get(); } + Distribution* cos_theta() const { return cos_theta_.get(); } Distribution* phi() const { return phi_.get(); } Position origin() const { return origin_; } private: - UPtrDist r_; //!< Distribution of r coordinates - UPtrDist theta_; //!< Distribution of theta coordinates - UPtrDist phi_; //!< Distribution of phi coordinates - Position origin_; //!< Cartesian coordinates of the sphere center + UPtrDist r_; //!< Distribution of r coordinates + UPtrDist cos_theta_; //!< Distribution of cos_theta coordinates + UPtrDist phi_; //!< Distribution of phi coordinates + Position origin_; //!< Cartesian coordinates of the sphere center }; //============================================================================== diff --git a/include/openmc/material.h b/include/openmc/material.h index 709d205738..b251a3ca85 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -113,6 +113,9 @@ public: //! \param[in] units Units of density void set_density(double density, gsl::cstring_span units); + //! Set temperature of the material + void set_temperature(double temperature) { temperature_ = temperature; }; + //! Get nuclides in material //! \return Indices into the global nuclides vector gsl::span nuclides() const diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index ddf072517c..d3d00571a8 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -56,6 +56,24 @@ struct SourceSite { int64_t progeny_id; }; +//! State of a particle used for particle track files +struct TrackState { + Position r; //!< Position in [cm] + Direction u; //!< Direction + double E; //!< Energy in [eV] + double time {0.0}; //!< Time in [s] + double wgt {1.0}; //!< Weight + int cell_id; //!< Cell ID + int cell_instance; //!< Cell instance + int material_id {-1}; //!< Material ID (default value indicates void) +}; + +//! Full history of a single particle's track states +struct TrackStateHistory { + ParticleType particle; + std::vector states; +}; + //! Saved ("banked") state of a particle, for nu-fission tallying struct NuBank { double E; //!< particle energy @@ -290,7 +308,7 @@ private: vector filter_matches_; // tally filter matches - vector> tracks_; // tracks for outputting to file + vector tracks_; // tracks for outputting to file vector nu_bank_; // bank of most recently fissioned particles @@ -342,6 +360,9 @@ public: const LocalCoord& coord(int i) const { return coord_[i]; } const vector& coord() const { return coord_; } + LocalCoord& lowest_coord() { return coord_[n_coord_ - 1]; } + const LocalCoord& lowest_coord() const { return coord_[n_coord_ - 1]; } + int& n_coord_last() { return n_coord_last_; } const int& n_coord_last() const { return n_coord_last_; } int& cell_last(int i) { return cell_last_[i]; } @@ -357,6 +378,7 @@ public: const int& g_last() const { return g_last_; } double& wgt() { return wgt_; } + double wgt() const { return wgt_; } double& mu() { return mu_; } const double& mu() const { return mu_; } double& time() { return time_; } @@ -479,6 +501,9 @@ public: n_coord_ = 1; } + //! Get track information based on particle's current state + TrackState get_track_state() const; + void zero_delayed_bank() { for (int& n : n_delayed_bank_) { diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 50be80a506..1e061b235b 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -88,6 +88,7 @@ extern int max_order; //!< Maximum Legendre order for multigroup data extern int n_log_bins; //!< number of bins for logarithmic energy grid extern int n_batches; //!< number of (inactive+active) batches extern int n_max_batches; //!< Maximum number of batches +extern int max_tracks; //!< Maximum number of particle tracks written to file extern ResScatMethod res_scat_method; //!< resonance upscattering method extern double res_scat_energy_min; //!< Min energy in [eV] for res. upscattering extern double res_scat_energy_max; //!< Max energy in [eV] for res. upscattering diff --git a/include/openmc/track_output.h b/include/openmc/track_output.h index fc65a2eaab..2380fe4405 100644 --- a/include/openmc/track_output.h +++ b/include/openmc/track_output.h @@ -9,8 +9,31 @@ namespace openmc { // Non-member functions //============================================================================== +//! Open HDF5 track file for writing and create track datatype +void open_track_file(); + +//! Close HDF5 resources for track file +void close_track_file(); + +//! Determine whether a given particle should collect/write track information +// +//! \param[in] p Current particle +//! \return Whether to collect/write track information +bool check_track_criteria(const Particle& p); + +//! Create a new track state history for a primary/secondary particle +// +//! \param[in] p Current particle void add_particle_track(Particle& p); + +//! Store particle's current state +// +//! \param[in] p Current particle void write_particle_track(Particle& p); + +//! Write full particle state history to HDF5 track file +// +//! \param[in] p Current particle void finalize_particle_track(Particle& p); } // namespace openmc diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index 460dbd3588..6310750a25 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -34,7 +34,7 @@ Restart a previous run from a state point or a particle restart file named Use \fIN\fP OpenMP threads. .TP .B "\-t\fR, \fP\-\-track" -Write tracks for all particles. +Write tracks for all particles (up to max_tracks). .TP .B "\-v\fR, \fP\-\-version" Show version information. diff --git a/openmc/__init__.py b/openmc/__init__.py index 0a11fbf53b..e63e9e4d16 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -30,6 +30,7 @@ from openmc.mixin import * from openmc.plotter import * from openmc.search import * from openmc.polynomial import * +from openmc.tracks import * from . import examples # Import a few names from the model module diff --git a/openmc/cell.py b/openmc/cell.py index feba18da54..103a34fe63 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -690,7 +690,10 @@ class Cell(IDManagerMixin): for key in ('temperature', 'rotation', 'translation'): value = get_text(elem, key) if value is not None: - setattr(c, key, [float(x) for x in value.split()]) + values = [float(x) for x in value.split()] + if key == 'rotation' and len(values) == 9: + values = np.array(values).reshape(3, 3) + setattr(c, key, values) # Add this cell to appropriate universe univ_id = int(get_text(elem, 'universe', 0)) diff --git a/openmc/material.py b/openmc/material.py index e5c3f8e4b4..de1b2187be 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -29,9 +29,9 @@ class Material(IDManagerMixin): To create a material, one should create an instance of this class, add nuclides or elements with :meth:`Material.add_nuclide` or - `Material.add_element`, respectively, and set the total material density - with `Material.set_density()`. The material can then be assigned to a cell - using the :attr:`Cell.fill` attribute. + :meth:`Material.add_element`, respectively, and set the total material + density with :meth:`Material.set_density()`. The material can then be + assigned to a cell using the :attr:`Cell.fill` attribute. Parameters ---------- diff --git a/openmc/mesh.py b/openmc/mesh.py index 584ea81ce8..7f7b078795 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -1,4 +1,4 @@ -from abc import ABC +from abc import ABC, abstractmethod from collections.abc import Iterable from math import pi from numbers import Real, Integral @@ -60,7 +60,7 @@ class MeshBase(IDManagerMixin, ABC): return string def _volume_dim_check(self): - if len(self.dimension) != 3 or \ + if self.n_dimension != 3 or \ any([d == 0 for d in self.dimension]): raise RuntimeError(f'Mesh {self.id} is not 3D. ' 'Volumes cannot be provided.') @@ -126,7 +126,75 @@ class MeshBase(IDManagerMixin, ABC): raise ValueError(f'Unrecognized mesh type "{mesh_type}" found.') -class RegularMesh(MeshBase): +class StructuredMesh(MeshBase): + """A base class for structured mesh functionality + + 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 + + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @property + @abstractmethod + def dimension(self): + pass + + @property + @abstractmethod + def n_dimension(self): + pass + + @property + @abstractmethod + def _grids(self): + pass + + @property + def vertices(self): + """Return coordinates of mesh vertices. + + Returns + ------- + vertices : numpy.ndarray + Returns a numpy.ndarray representing the coordinates of the mesh + vertices with a shape equal to (dim1 + 1, ..., dimn + 1, ndim). + + """ + return np.stack(np.meshgrid(*self._grids, indexing='ij'), axis=-1) + + @property + def centroids(self): + """Return coordinates of mesh element centroids. + + Returns + ------- + centroids : numpy.ndarray + Returns a numpy.ndarray representing the mesh element centroid + coordinates with a shape equal to (dim1, ..., dimn, ndim). + + """ + ndim = self.n_dimension + vertices = self.vertices + s0 = (slice(0, -1),)*ndim + (slice(None),) + s1 = (slice(1, None),)*ndim + (slice(None),) + return (vertices[s0] + vertices[s1]) / 2 + + +class RegularMesh(StructuredMesh): """A regular Cartesian mesh in one, two, or three dimensions Parameters @@ -244,6 +312,30 @@ class RegularMesh(MeshBase): nx, = self.dimension return ((x,) for x in range(1, nx + 1)) + @property + def _grids(self): + ndim = len(self._dimension) + if ndim == 3: + x0, y0, z0 = self.lower_left + x1, y1, z1 = self.upper_right + nx, ny, nz = self.dimension + xarr = np.linspace(x0, x1, nx + 1) + yarr = np.linspace(y0, y1, ny + 1) + zarr = np.linspace(z0, z1, nz + 1) + return (xarr, yarr, zarr) + elif ndim == 2: + x0, y0 = self.lower_left + x1, y1 = self.upper_right + nx, ny = self.dimension + xarr = np.linspace(x0, x1, nx + 1) + yarr = np.linspace(y0, y1, ny + 1) + return (xarr, yarr) + else: + nx, = self.dimension + x0, = self.lower_left + x1, = self.upper_right + return (np.linspace(x0, x1, nx + 1),) + @dimension.setter def dimension(self, dimension): cv.check_type('mesh dimension', dimension, Iterable, Integral) @@ -440,7 +532,7 @@ class RegularMesh(MeshBase): for entry in bc: cv.check_value('bc', entry, _BOUNDARY_TYPES) - n_dim = len(self.dimension) + n_dim = self.n_dimension # Build the cell which will contain the lattice xplanes = [openmc.XPlane(self.lower_left[0], boundary_type=bc[0]), @@ -537,7 +629,7 @@ def Mesh(*args, **kwargs): return RegularMesh(*args, **kwargs) -class RectilinearMesh(MeshBase): +class RectilinearMesh(StructuredMesh): """A 3D rectilinear Cartesian mesh Parameters @@ -598,6 +690,10 @@ class RectilinearMesh(MeshBase): def z_grid(self): return self._z_grid + @property + def _grids(self): + return (self.x_grid, self.y_grid, self.z_grid) + @property def volumes(self): """Return Volumes for every mesh cell @@ -726,7 +822,7 @@ class RectilinearMesh(MeshBase): return element -class CylindricalMesh(MeshBase): +class CylindricalMesh(StructuredMesh): """A 3D cylindrical mesh Parameters @@ -789,6 +885,10 @@ class CylindricalMesh(MeshBase): def z_grid(self): return self._z_grid + @property + def _grids(self): + return (self.r_grid, self.phi_grid, self.z_grid) + @property def indices(self): nr, np, nz = self.dimension @@ -913,7 +1013,7 @@ class CylindricalMesh(MeshBase): return np.multiply.outer(np.outer(V_r, V_p), V_z) -class SphericalMesh(MeshBase): +class SphericalMesh(StructuredMesh): """A 3D spherical mesh Parameters @@ -977,6 +1077,10 @@ class SphericalMesh(MeshBase): def phi_grid(self): return self._phi_grid + @property + def _grids(self): + return (self.r_grid, self.theta_grid, self.phi_grid) + @property def indices(self): nr, nt, np = self.dimension @@ -1146,7 +1250,7 @@ class UnstructuredMesh(MeshBase): (1.0, 1.0, 1.0), ...] """ def __init__(self, filename, library, mesh_id=None, name='', - length_multiplier=1.0): + length_multiplier=1.0): super().__init__(mesh_id, name) self.filename = filename self._volumes = None @@ -1241,6 +1345,19 @@ class UnstructuredMesh(MeshBase): Real) self._length_multiplier = length_multiplier + @property + def dimension(self): + return self.n_elements + + @property + def n_dimension(self): + return 3 + + @property + def vertices(self): + raise NotImplementedError("Vertices for UnstructuredMesh objects are " + "not yet available") + def __repr__(self): string = super().__repr__() string += '{: <16}=\t{}\n'.format('\tFilename', self.filename) @@ -1371,7 +1488,7 @@ class UnstructuredMesh(MeshBase): subelement.text = self.filename if self._length_multiplier != 1.0: - element.set("length_multiplier", str(self.length_multiplier)) + element.set("length_multiplier", str(self.length_multiplier)) return element diff --git a/openmc/model/surface_composite.py b/openmc/model/surface_composite.py index 74f64c54ac..6271154fb8 100644 --- a/openmc/model/surface_composite.py +++ b/openmc/model/surface_composite.py @@ -212,7 +212,7 @@ class CylinderSector(CompositeSurface): class IsogonalOctagon(CompositeSurface): - """Infinite isogonal octagon composite surface + r"""Infinite isogonal octagon composite surface An isogonal octagon is composed of eight planar surfaces. The prism is parallel to the x, y, or z axis. The remaining two axes (y and z, z and x, diff --git a/openmc/plots.py b/openmc/plots.py index 5e9c48413d..b22ee91254 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -213,8 +213,8 @@ class Plot(IDManagerMixin): The basis directions for the plot background : Iterable of int or str Color of the background - mask_components : Iterable of openmc.Cell or openmc.Material - The cells or materials to plot + mask_components : Iterable of openmc.Cell or openmc.Material or int + The cells or materials (or corresponding IDs) to mask mask_background : Iterable of int or str Color to apply to all cells/materials not listed in mask_components show_overlaps : bool @@ -222,8 +222,10 @@ class Plot(IDManagerMixin): 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. + Dictionary indicating that certain cells/materials should be + displayed with a particular color. The keys can be of type + :class:`~openmc.Cell`, :class:`~openmc.Material`, or int (ID for a + cell/material). level : int Universe depth to plot at meshlines : dict @@ -373,14 +375,15 @@ class Plot(IDManagerMixin): def colors(self, colors): cv.check_type('plot colors', colors, Mapping) for key, value in colors.items(): - cv.check_type('plot color key', key, (openmc.Cell, openmc.Material)) + cv.check_type('plot color key', key, + (openmc.Cell, openmc.Material, Integral)) self._check_color('plot color value', value) self._colors = colors @mask_components.setter def mask_components(self, mask_components): cv.check_type('plot mask components', mask_components, Iterable, - (openmc.Cell, openmc.Material)) + (openmc.Cell, openmc.Material, Integral)) self._mask_components = mask_components @mask_background.setter @@ -634,11 +637,16 @@ class Plot(IDManagerMixin): color = _SVG_COLORS[color.lower()] subelement.text = ' '.join(str(x) for x in color) + # Helper function that returns the domain ID given either a + # Cell/Material object or the domain ID itself + def get_id(domain): + return domain if isinstance(domain, Integral) else domain.id + if self._colors: for domain, color in sorted(self._colors.items(), - key=lambda x: x[0].id): + key=lambda x: get_id(x[0])): subelement = ET.SubElement(element, "color") - subelement.set("id", str(domain.id)) + subelement.set("id", str(get_id(domain))) if isinstance(color, str): color = _SVG_COLORS[color.lower()] subelement.set("rgb", ' '.join(str(x) for x in color)) @@ -646,7 +654,7 @@ class Plot(IDManagerMixin): if self._mask_components is not None: subelement = ET.SubElement(element, "mask") subelement.set("components", ' '.join( - str(d.id) for d in self._mask_components)) + str(get_id(d)) for d in self._mask_components)) color = self._mask_background if color is not None: if isinstance(color, str): @@ -720,15 +728,14 @@ class Plot(IDManagerMixin): # Set plot colors colors = {} for color_elem in elem.findall("color"): - uid = color_elem.get("id") + uid = int(color_elem.get("id")) colors[uid] = tuple([int(x) for x in color_elem.get("rgb").split()]) - # TODO: set colors (needs geometry information) + plot.colors = colors # Set masking information mask_elem = elem.find("mask") if mask_elem is not None: - mask_components = [int(x) for x in mask_elem.get("components").split()] - # TODO: set mask components (needs geometry information) + plot.mask_components = [int(x) for x in mask_elem.get("components").split()] background = mask_elem.get("background") if background is not None: plot.mask_background = tuple([int(x) for x in background.split()]) diff --git a/openmc/settings.py b/openmc/settings.py index d11a577d16..2d81216599 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -2,6 +2,7 @@ import os import typing # imported separately as py3.8 requires typing.Iterable from collections.abc import Iterable, Mapping, MutableSequence from enum import Enum +import itertools from math import ceil from numbers import Integral, Real from pathlib import Path @@ -104,6 +105,10 @@ class Settings: Maximum number of times a particle can split during a history .. versionadded:: 0.13 + max_tracks : int + Maximum number of tracks written to a track file (per MPI process). + + .. versionadded:: 0.13.1 no_reduce : bool Indicate that all user-defined and global tallies should not be reduced across processes in a parallel calculation. @@ -187,7 +192,7 @@ class Settings: integers: the batch number, generation number, and particle number track : tuple or list Specify particles for which track files should be written. Each particle - is identified by a triplet with the batch number, generation number, and + is identified by a tuple with the batch number, generation number, and particle number. trigger_active : bool Indicate whether tally triggers are used @@ -290,6 +295,7 @@ class Settings: self._weight_windows = cv.CheckedList(WeightWindows, 'weight windows') self._weight_windows_on = None self._max_splits = None + self._max_tracks = None @property def run_mode(self): @@ -475,6 +481,10 @@ class Settings: def max_splits(self): return self._max_splits + @property + def max_tracks(self): + return self._max_tracks + @run_mode.setter def run_mode(self, run_mode: str): cv.check_value('run mode', run_mode, {x.value for x in RunMode}) @@ -776,16 +786,18 @@ class Settings: self._trace = trace @track.setter - def track(self, track: typing.Iterable[int]): - cv.check_type('track', track, Iterable, Integral) - if len(track) % 3 != 0: - msg = f'Unable to set the track to "{track}" since its length is ' \ - 'not a multiple of 3' - raise ValueError(msg) - for t in zip(track[::3], track[1::3], track[2::3]): + def track(self, track: typing.Iterable[typing.Iterable[int]]): + cv.check_type('track', track, Iterable) + for t in track: + if len(t) != 3: + msg = f'Unable to set the track to "{t}" since its length is not 3' + raise ValueError(msg) cv.check_greater_than('track batch', t[0], 0) - cv.check_greater_than('track generation', t[0], 0) - cv.check_greater_than('track particle', t[0], 0) + cv.check_greater_than('track generation', t[1], 0) + cv.check_greater_than('track particle', t[2], 0) + cv.check_type('track batch', t[0], Integral) + cv.check_type('track generation', t[1], Integral) + cv.check_type('track particle', t[2], Integral) self._track = track @ufs_mesh.setter @@ -881,9 +893,15 @@ class Settings: @max_splits.setter def max_splits(self, value: int): cv.check_type('maximum particle splits', value, Integral) - cv.check_greater_than('max particles in flight', value, 0) + cv.check_greater_than('max particle splits', value, 0) self._max_splits = value + @max_tracks.setter + def max_tracks(self, value: int): + cv.check_type('maximum particle tracks', value, Integral) + cv.check_greater_than('maximum particle tracks', value, 0, True) + self._max_tracks = value + def _create_run_mode_subelement(self, root): elem = ET.SubElement(root, "run_mode") elem.text = self._run_mode.value @@ -1110,7 +1128,7 @@ class Settings: def _create_track_subelement(self, root): if self._track is not None: element = ET.SubElement(root, "track") - element.text = ' '.join(map(str, self._track)) + element.text = ' '.join(map(str, itertools.chain(*self._track))) def _create_ufs_mesh_subelement(self, root): if self.ufs_mesh is not None: @@ -1196,6 +1214,11 @@ class Settings: elem = ET.SubElement(root, "max_splits") elem.text = str(self._max_splits) + def _create_max_tracks_subelement(self, root): + if self._max_tracks is not None: + elem = ET.SubElement(root, "max_tracks") + elem.text = str(self._max_tracks) + def _eigenvalue_from_xml_element(self, root): elem = root.find('eigenvalue') if elem is not None: @@ -1424,7 +1447,8 @@ class Settings: 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()] + values = [int(x) for x in text.split()] + self.track = list(zip(values[::3], values[1::3], values[2::3])) def _ufs_mesh_from_xml_element(self, root): text = get_text(root, 'ufs_mesh') @@ -1498,6 +1522,11 @@ class Settings: if text is not None: self.max_splits = int(text) + def _max_tracks_from_xml_element(self, root): + text = get_text(root, 'max_tracks') + if text is not None: + self.max_tracks = int(text) + def export_to_xml(self, path: Union[str, os.PathLike] = 'settings.xml'): """Export simulation settings to an XML file. @@ -1554,6 +1583,7 @@ class Settings: self._create_write_initial_source_subelement(root_element) self._create_weight_windows_subelement(root_element) self._create_max_splits_subelement(root_element) + self._create_max_tracks_subelement(root_element) # Clean the indentation in the file to be user-readable clean_indentation(root_element) @@ -1633,6 +1663,7 @@ class Settings: settings._write_initial_source_from_xml_element(root) settings._weight_windows_from_xml_element(root) settings._max_splits_from_xml_element(root) + settings._max_tracks_from_xml_element(root) # TODO: Get volume calculations diff --git a/openmc/source.py b/openmc/source.py index 1abe01f657..bd4c28ba46 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -298,6 +298,10 @@ class SourceParticle: self.surf_id = surf_id self.particle = particle + def __repr__(self): + name = self.particle.name.lower() + return f'' + def to_tuple(self): """Return source particle attributes as a tuple diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index 8635bcd71c..98c76ea674 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from collections.abc import Iterable -from math import pi +from math import pi, cos from numbers import Real from xml.etree import ElementTree as ET @@ -8,7 +8,7 @@ import numpy as np import openmc.checkvalue as cv from .._xml import get_text -from .univariate import Univariate, Uniform +from .univariate import Univariate, Uniform, PowerLaw class UnitSphere(ABC): @@ -208,7 +208,6 @@ class Monodirectional(UnitSphere): """ - def __init__(self, reference_uvw=[1., 0., 0.]): super().__init__(reference_uvw) @@ -375,8 +374,8 @@ class SphericalIndependent(Spatial): r"""Spatial distribution represented in spherical coordinates. This distribution allows one to specify coordinates whose :math:`r`, - :math:`\theta`, and :math:`\phi` components are sampled independently from - one another and centered on the coordinates (x0, y0, z0). + :math:`\theta`, and :math:`\phi` components are sampled independently + from one another and centered on the coordinates (x0, y0, z0). .. versionadded: 0.12 @@ -385,9 +384,9 @@ class SphericalIndependent(Spatial): r : openmc.stats.Univariate Distribution of r-coordinates in a reference frame specified by the origin parameter - theta : openmc.stats.Univariate - Distribution of theta-coordinates (angle relative to the z-axis) in a - reference frame specified by the origin parameter + cos_theta : openmc.stats.Univariate + Distribution of the cosine of the theta-coordinates (angle relative to + the z-axis) in a reference frame specified by the origin parameter phi : openmc.stats.Univariate Distribution of phi-coordinates (azimuthal angle) in a reference frame specified by the origin parameter @@ -399,9 +398,9 @@ class SphericalIndependent(Spatial): ---------- r : openmc.stats.Univariate Distribution of r-coordinates in the local reference frame - theta : openmc.stats.Univariate - Distribution of theta-coordinates (angle relative to the z-axis) in the - local reference frame + cos_theta : openmc.stats.Univariate + Distribution of the cosine of the theta-coordinates (angle relative to + the z-axis) in the local reference frame phi : openmc.stats.Univariate Distribution of phi-coordinates (azimuthal angle) in the local reference frame @@ -411,9 +410,9 @@ class SphericalIndependent(Spatial): """ - def __init__(self, r, theta, phi, origin=(0.0, 0.0, 0.0)): + def __init__(self, r, cos_theta, phi, origin=(0.0, 0.0, 0.0)): self.r = r - self.theta = theta + self.cos_theta = cos_theta self.phi = phi self.origin = origin @@ -422,8 +421,8 @@ class SphericalIndependent(Spatial): return self._r @property - def theta(self): - return self._theta + def cos_theta(self): + return self._cos_theta @property def phi(self): @@ -438,10 +437,10 @@ class SphericalIndependent(Spatial): cv.check_type('r coordinate', r, Univariate) self._r = r - @theta.setter - def theta(self, theta): - cv.check_type('theta coordinate', theta, Univariate) - self._theta = theta + @cos_theta.setter + def cos_theta(self, cos_theta): + cv.check_type('cos_theta coordinate', cos_theta, Univariate) + self._cos_theta = cos_theta @phi.setter def phi(self, phi): @@ -466,7 +465,7 @@ class SphericalIndependent(Spatial): element = ET.Element('space') element.set('type', 'spherical') element.append(self.r.to_xml_element('r')) - element.append(self.theta.to_xml_element('theta')) + element.append(self.cos_theta.to_xml_element('cos_theta')) element.append(self.phi.to_xml_element('phi')) element.set("origin", ' '.join(map(str, self.origin))) return element @@ -487,10 +486,10 @@ class SphericalIndependent(Spatial): """ r = Univariate.from_xml_element(elem.find('r')) - theta = Univariate.from_xml_element(elem.find('theta')) + cos_theta = Univariate.from_xml_element(elem.find('cos_theta')) phi = Univariate.from_xml_element(elem.find('phi')) origin = [float(x) for x in elem.get('origin').split()] - return cls(r, theta, phi, origin=origin) + return cls(r, cos_theta, phi, origin=origin) class CylindricalIndependent(Spatial): @@ -640,7 +639,6 @@ class Box(Spatial): """ - def __init__(self, lower_left, upper_right, only_fissionable=False): self.lower_left = lower_left self.upper_right = upper_right @@ -779,3 +777,42 @@ class Point(Spatial): """ xyz = [float(x) for x in get_text(elem, 'parameters').split()] return cls(xyz) + + +def spherical_uniform(r_outer, r_inner=0.0, thetas=(0., pi), phis=(0., 2*pi), + origin=(0., 0., 0.)): + """Return a uniform spatial distribution over a spherical shell. + + This function provides a uniform spatial distribution over a spherical + shell between `r_inner` and `r_outer`. Optionally, the range of angles + can be restricted by the `thetas` and `phis` arguments. + + .. versionadded: 0.13.1 + + Parameters + ---------- + r_outer : float + Outer radius of the spherical shell in [cm] + r_inner : float, optional + Inner radius of the spherical shell in [cm] + thetas : iterable of float, optional + Starting and ending theta coordinates (angle relative to + the z-axis) in radius in a reference frame centered at `origin` + phis : iterable of float, optional + Starting and ending phi coordinates (azimuthal angle) in + radians in a reference frame centered at `origin` + origin: iterable of float, optional + Coordinates (x0, y0, z0) of the center of the spherical + reference frame for the distribution. + + Returns + ------- + openmc.stats.SphericalIndependent + Uniform distribution over the spherical shell + """ + + r_dist = PowerLaw(r_inner, r_outer, 2) + cos_thetas_dist = Uniform(cos(thetas[1]), cos(thetas[0])) + phis_dist = Uniform(phis[0], phis[1]) + + return SphericalIndependent(r_dist, cos_thetas_dist, phis_dist, origin) diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index f8e50044fc..e4a9b8f924 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from collections import defaultdict from collections.abc import Iterable from numbers import Real from xml.etree import ElementTree as ET @@ -156,6 +157,38 @@ class Discrete(Univariate): p = params[len(params)//2:] return cls(x, p) + @classmethod + def merge(cls, dists, probs): + """Merge multiple discrete distributions into a single distribution + + Parameters + ---------- + dists : iterable of openmc.stats.Discrete + Discrete distributions to combine + probs : iterable of float + Probability of each distribution + + Returns + ------- + openmc.stats.Discrete + Combined discrete distribution + + """ + if len(dists) != len(probs): + raise ValueError("Number of distributions and probabilities must match.") + + # Combine distributions accounting for duplicate x values + x_merged = set() + p_merged = defaultdict(float) + for dist, p_dist in zip(dists, probs): + for x, p in zip(dist.x, dist.p): + x_merged.add(x) + p_merged[x] += p*p_dist + + # Create values and probabilities as arrays + x_arr = np.array(sorted(x_merged)) + p_arr = np.array([p_merged[x] for x in x_arr]) + return cls(x_arr, p_arr) class Uniform(Univariate): """Distribution with constant probability over a finite interval [a,b] diff --git a/openmc/surface.py b/openmc/surface.py index 926b2fedcd..b3bcebb11b 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -760,7 +760,7 @@ class XPlane(PlaneMixin, Surface): Parameters ---------- x0 : float, optional - Location of the plane. Defaults to 0. + Location of the plane in [cm]. Defaults to 0. boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles @@ -775,7 +775,7 @@ class XPlane(PlaneMixin, Surface): Attributes ---------- x0 : float - Location of the plane + Location of the plane in [cm] boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -819,7 +819,7 @@ class YPlane(PlaneMixin, Surface): Parameters ---------- y0 : float, optional - Location of the plane + Location of the plane in [cm] boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles @@ -834,7 +834,7 @@ class YPlane(PlaneMixin, Surface): Attributes ---------- y0 : float - Location of the plane + Location of the plane in [cm] boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -878,7 +878,7 @@ class ZPlane(PlaneMixin, Surface): Parameters ---------- z0 : float, optional - Location of the plane. Defaults to 0. + Location of the plane in [cm]. Defaults to 0. boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles @@ -893,7 +893,7 @@ class ZPlane(PlaneMixin, Surface): Attributes ---------- z0 : float - Location of the plane + Location of the plane in [cm] boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -991,8 +991,8 @@ class QuadricMixin: Parameters ---------- point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. + The Cartesian coordinates, :math:`(x',y',z')`, in [cm] at which the + surface equation should be evaluated. Returns ------- @@ -1106,13 +1106,13 @@ class Cylinder(QuadricMixin, Surface): Parameters ---------- x0 : float, optional - x-coordinate for the origin of the Cylinder. Defaults to 0 + x-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 y0 : float, optional - y-coordinate for the origin of the Cylinder. Defaults to 0 + y-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 z0 : float, optional - z-coordinate for the origin of the Cylinder. Defaults to 0 + z-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 r : float, optional - Radius of the cylinder. Defaults to 1. + Radius of the cylinder in [cm]. Defaults to 1. dx : float, optional x-component of the vector representing the axis of the cylinder. Defaults to 0. @@ -1136,13 +1136,13 @@ class Cylinder(QuadricMixin, Surface): Attributes ---------- x0 : float - x-coordinate for the origin of the Cylinder + x-coordinate for the origin of the Cylinder in [cm] y0 : float - y-coordinate for the origin of the Cylinder + y-coordinate for the origin of the Cylinder in [cm] z0 : float - z-coordinate for the origin of the Cylinder + z-coordinate for the origin of the Cylinder in [cm] r : float - Radius of the cylinder + Radius of the cylinder in [cm] dx : float x-component of the vector representing the axis of the cylinder dy : float @@ -1243,7 +1243,7 @@ class Cylinder(QuadricMixin, Surface): p1, p2 : 3-tuples Points that pass through the plane, p1 will be used as (x0, y0, z0) r : float, optional - Radius of the cylinder. Defaults to 1. + Radius of the cylinder in [cm]. Defaults to 1. kwargs : dict Keyword arguments passed to the :class:`Cylinder` constructor @@ -1288,11 +1288,11 @@ class XCylinder(QuadricMixin, Surface): Parameters ---------- y0 : float, optional - y-coordinate for the origin of the Cylinder. Defaults to 0 + y-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 z0 : float, optional - z-coordinate for the origin of the Cylinder. Defaults to 0 + z-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 r : float, optional - Radius of the cylinder. Defaults to 1. + Radius of the cylinder in [cm]. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles @@ -1307,11 +1307,11 @@ class XCylinder(QuadricMixin, Surface): Attributes ---------- y0 : float - y-coordinate for the origin of the Cylinder + y-coordinate for the origin of the Cylinder in [cm] z0 : float - z-coordinate for the origin of the Cylinder + z-coordinate for the origin of the Cylinder in [cm] r : float - Radius of the cylinder + Radius of the cylinder in [cm] boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -1379,11 +1379,11 @@ class YCylinder(QuadricMixin, Surface): Parameters ---------- x0 : float, optional - x-coordinate for the origin of the Cylinder. Defaults to 0 + x-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 z0 : float, optional - z-coordinate for the origin of the Cylinder. Defaults to 0 + z-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 r : float, optional - Radius of the cylinder. Defaults to 1. + Radius of the cylinder in [cm]. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles @@ -1398,11 +1398,11 @@ class YCylinder(QuadricMixin, Surface): Attributes ---------- x0 : float - x-coordinate for the origin of the Cylinder + x-coordinate for the origin of the Cylinder in [cm] z0 : float - z-coordinate for the origin of the Cylinder + z-coordinate for the origin of the Cylinder in [cm] r : float - Radius of the cylinder + Radius of the cylinder in [cm] boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -1470,11 +1470,11 @@ class ZCylinder(QuadricMixin, Surface): Parameters ---------- x0 : float, optional - x-coordinate for the origin of the Cylinder. Defaults to 0 + x-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 y0 : float, optional - y-coordinate for the origin of the Cylinder. Defaults to 0 + y-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 r : float, optional - Radius of the cylinder. Defaults to 1. + Radius of the cylinder in [cm]. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles @@ -1489,11 +1489,11 @@ class ZCylinder(QuadricMixin, Surface): Attributes ---------- x0 : float - x-coordinate for the origin of the Cylinder + x-coordinate for the origin of the Cylinder in [cm] y0 : float - y-coordinate for the origin of the Cylinder + y-coordinate for the origin of the Cylinder in [cm] r : float - Radius of the cylinder + Radius of the cylinder in [cm] boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -1560,13 +1560,13 @@ class Sphere(QuadricMixin, Surface): Parameters ---------- x0 : float, optional - x-coordinate of the center of the sphere. Defaults to 0. + x-coordinate of the center of the sphere in [cm]. Defaults to 0. y0 : float, optional - y-coordinate of the center of the sphere. Defaults to 0. + y-coordinate of the center of the sphere in [cm]. Defaults to 0. z0 : float, optional - z-coordinate of the center of the sphere. Defaults to 0. + z-coordinate of the center of the sphere in [cm]. Defaults to 0. r : float, optional - Radius of the sphere. Defaults to 1. + Radius of the sphere in [cm]. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles @@ -1580,13 +1580,13 @@ class Sphere(QuadricMixin, Surface): Attributes ---------- x0 : float - x-coordinate of the center of the sphere + x-coordinate of the center of the sphere in [cm] y0 : float - y-coordinate of the center of the sphere + y-coordinate of the center of the sphere in [cm] z0 : float - z-coordinate of the center of the sphere + z-coordinate of the center of the sphere in [cm] r : float - Radius of the sphere + Radius of the sphere in [cm] boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -1653,11 +1653,11 @@ class Cone(QuadricMixin, Surface): Parameters ---------- x0 : float, optional - x-coordinate of the apex. Defaults to 0. + x-coordinate of the apex in [cm]. Defaults to 0. y0 : float, optional - y-coordinate of the apex. Defaults to 0. + y-coordinate of the apex in [cm]. Defaults to 0. z0 : float, optional - z-coordinate of the apex. Defaults to 0. + z-coordinate of the apex in [cm]. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. dx : float, optional @@ -1682,11 +1682,11 @@ class Cone(QuadricMixin, Surface): Attributes ---------- x0 : float - x-coordinate of the apex + x-coordinate of the apex in [cm] y0 : float - y-coordinate of the apex + y-coordinate of the apex in [cm] z0 : float - z-coordinate of the apex + z-coordinate of the apex in [cm] r2 : float Parameter related to the aperature dx : float @@ -1796,11 +1796,11 @@ class XCone(QuadricMixin, Surface): Parameters ---------- x0 : float, optional - x-coordinate of the apex. Defaults to 0. + x-coordinate of the apex in [cm]. Defaults to 0. y0 : float, optional - y-coordinate of the apex. Defaults to 0. + y-coordinate of the apex in [cm]. Defaults to 0. z0 : float, optional - z-coordinate of the apex. Defaults to 0. + z-coordinate of the apex in [cm]. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional @@ -1816,11 +1816,11 @@ class XCone(QuadricMixin, Surface): Attributes ---------- x0 : float - x-coordinate of the apex + x-coordinate of the apex in [cm] y0 : float - y-coordinate of the apex + y-coordinate of the apex in [cm] z0 : float - z-coordinate of the apex + z-coordinate of the apex in [cm] r2 : float Parameter related to the aperature boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} @@ -1885,11 +1885,11 @@ class YCone(QuadricMixin, Surface): Parameters ---------- x0 : float, optional - x-coordinate of the apex. Defaults to 0. + x-coordinate of the apex in [cm]. Defaults to 0. y0 : float, optional - y-coordinate of the apex. Defaults to 0. + y-coordinate of the apex in [cm]. Defaults to 0. z0 : float, optional - z-coordinate of the apex. Defaults to 0. + z-coordinate of the apex in [cm]. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional @@ -1905,11 +1905,11 @@ class YCone(QuadricMixin, Surface): Attributes ---------- x0 : float - x-coordinate of the apex + x-coordinate of the apex in [cm] y0 : float - y-coordinate of the apex + y-coordinate of the apex in [cm] z0 : float - z-coordinate of the apex + z-coordinate of the apex in [cm] r2 : float Parameter related to the aperature boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} @@ -1974,11 +1974,11 @@ class ZCone(QuadricMixin, Surface): Parameters ---------- x0 : float, optional - x-coordinate of the apex. Defaults to 0. + x-coordinate of the apex in [cm]. Defaults to 0. y0 : float, optional - y-coordinate of the apex. Defaults to 0. + y-coordinate of the apex in [cm]. Defaults to 0. z0 : float, optional - z-coordinate of the apex. Defaults to 0. + z-coordinate of the apex in [cm]. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional @@ -1994,11 +1994,11 @@ class ZCone(QuadricMixin, Surface): Attributes ---------- x0 : float - x-coordinate of the apex + x-coordinate of the apex in [cm] y0 : float - y-coordinate of the apex + y-coordinate of the apex in [cm] z0 : float - z-coordinate of the apex + z-coordinate of the apex in [cm] r2 : float Parameter related to the aperature boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} @@ -2198,34 +2198,34 @@ class XTorus(TorusMixin, Surface): Parameters ---------- x0 : float - x-coordinate of the center of the axis of revolution + x-coordinate of the center of the axis of revolution in [cm] y0 : float - y-coordinate of the center of the axis of revolution + y-coordinate of the center of the axis of revolution in [cm] z0 : float - z-coordinate of the center of the axis of revolution + z-coordinate of the center of the axis of revolution in [cm] a : float - Major radius of the torus + Major radius of the torus in [cm] b : float - Minor radius of the torus (parallel to axis of revolution) + Minor radius of the torus in [cm] (parallel to axis of revolution) c : float - Minor radius of the torus (perpendicular to axis of revolution) + Minor radius of the torus in [cm] (perpendicular to axis of revolution) kwargs : dict Keyword arguments passed to the :class:`Surface` constructor Attributes ---------- x0 : float - x-coordinate of the center of the axis of revolution + x-coordinate of the center of the axis of revolution in [cm] y0 : float - y-coordinate of the center of the axis of revolution + y-coordinate of the center of the axis of revolution in [cm] z0 : float - z-coordinate of the center of the axis of revolution + z-coordinate of the center of the axis of revolution in [cm] a : float - Major radius of the torus + Major radius of the torus in [cm] b : float - Minor radius of the torus (parallel to axis of revolution) + Minor radius of the torus in [cm] (parallel to axis of revolution) c : float - Minor radius of the torus (perpendicular to axis of revolution) + Minor radius of the torus in [cm] (perpendicular to axis of revolution) boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -2269,32 +2269,32 @@ class YTorus(TorusMixin, Surface): Parameters ---------- x0 : float - x-coordinate of the center of the axis of revolution + x-coordinate of the center of the axis of revolution in [cm] y0 : float - y-coordinate of the center of the axis of revolution + y-coordinate of the center of the axis of revolution in [cm] z0 : float - z-coordinate of the center of the axis of revolution + z-coordinate of the center of the axis of revolution in [cm] a : float - Major radius of the torus + Major radius of the torus in [cm] b : float - Minor radius of the torus (parallel to axis of revolution) + Minor radius of the torus in [cm] (parallel to axis of revolution) c : float - Minor radius of the torus (perpendicular to axis of revolution) + Minor radius of the torus in [cm] (perpendicular to axis of revolution) kwargs : dict Keyword arguments passed to the :class:`Surface` constructor Attributes ---------- x0 : float - x-coordinate of the center of the axis of revolution + x-coordinate of the center of the axis of revolution in [cm] y0 : float - y-coordinate of the center of the axis of revolution + y-coordinate of the center of the axis of revolution in [cm] z0 : float - z-coordinate of the center of the axis of revolution + z-coordinate of the center of the axis of revolution in [cm] a : float - Major radius of the torus + Major radius of the torus in [cm] b : float - Minor radius of the torus (parallel to axis of revolution) + Minor radius of the torus in [cm] (parallel to axis of revolution) c : float Minor radius of the torus (perpendicular to axis of revolution) boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} @@ -2340,34 +2340,34 @@ class ZTorus(TorusMixin, Surface): Parameters ---------- x0 : float - x-coordinate of the center of the axis of revolution + x-coordinate of the center of the axis of revolution in [cm] y0 : float - y-coordinate of the center of the axis of revolution + y-coordinate of the center of the axis of revolution in [cm] z0 : float - z-coordinate of the center of the axis of revolution + z-coordinate of the center of the axis of revolution in [cm] a : float - Major radius of the torus + Major radius of the torus in [cm] b : float - Minor radius of the torus (parallel to axis of revolution) + Minor radius of the torus in [cm] (parallel to axis of revolution) c : float - Minor radius of the torus (perpendicular to axis of revolution) + Minor radius of the torus in [cm] (perpendicular to axis of revolution) kwargs : dict Keyword arguments passed to the :class:`Surface` constructor Attributes ---------- x0 : float - x-coordinate of the center of the axis of revolution + x-coordinate of the center of the axis of revolution in [cm] y0 : float - y-coordinate of the center of the axis of revolution + y-coordinate of the center of the axis of revolution in [cm] z0 : float - z-coordinate of the center of the axis of revolution + z-coordinate of the center of the axis of revolution in [cm] a : float - Major radius of the torus + Major radius of the torus in [cm] b : float - Minor radius of the torus (parallel to axis of revolution) + Minor radius of the torus in [cm] (parallel to axis of revolution) c : float - Minor radius of the torus (perpendicular to axis of revolution) + Minor radius of the torus in [cm] (perpendicular to axis of revolution) boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. diff --git a/openmc/tracks.py b/openmc/tracks.py new file mode 100644 index 0000000000..e9097e8eeb --- /dev/null +++ b/openmc/tracks.py @@ -0,0 +1,291 @@ +from collections import namedtuple +from collections.abc import Sequence + +import h5py + +from .checkvalue import check_filetype_version +from .source import SourceParticle, ParticleType + + +ParticleTrack = namedtuple('ParticleTrack', ['particle', 'states']) +ParticleTrack.__doc__ = """\ +Particle track information + +Parameters +---------- +particle : openmc.ParticleType + Type of the particle +states : numpy.ndarray + Structured array containing each state of the particle. The structured array + contains the following fields: ``r`` (position; each direction in [cm]), + ``u`` (direction), ``E`` (energy in [eV]), ``time`` (time in [s]), ``wgt`` + (weight), ``cell_id`` (cell ID) , ``cell_instance`` (cell instance), and + ``material_id`` (material ID). + +""" +def _particle_track_repr(self): + name = self.particle.name.lower() + return f"" +ParticleTrack.__repr__ = _particle_track_repr + + +_VERSION_TRACK = 3 + + +def _identifier(dset_name): + """Return (batch, gen, particle) tuple given dataset name""" + _, batch, gen, particle = dset_name.split('_') + return (int(batch), int(gen), int(particle)) + + +class Track(Sequence): + """Tracks resulting from a single source particle + + This class stores information for all tracks resulting from a primary source + particle and any secondary particles that it created. The track for each + primary/secondary particle is stored in the :attr:`particle_tracks` + attribute. + + Parameters + ---------- + dset : h5py.Dataset + Dataset to read track data from + + Attributes + ---------- + identifier : tuple + Tuple of (batch, generation, particle number) + particle_tracks : list + List of tuples containing (particle type, array of track states) + sources : list + List of :class:`SourceParticle` representing each primary/secondary + particle + + """ + + def __init__(self, dset): + tracks = dset[()] + offsets = dset.attrs['offsets'] + particles = dset.attrs['particles'] + self.identifier = _identifier(dset.name) + + # Construct list of track histories + tracks_list = [] + for particle, start, end in zip(particles, offsets[:-1], offsets[1:]): + ptype = ParticleType(particle) + tracks_list.append(ParticleTrack(ptype, tracks[start:end])) + self.particle_tracks = tracks_list + + def __repr__(self): + return f'' + + def __getitem__(self, index): + return self.particle_tracks[index] + + def __len__(self): + return len(self.particle_tracks) + + def filter(self, particle=None, state_filter=None): + """Filter particle tracks by given criteria + + Parameters + ---------- + particle : {'neutron', 'photon', 'electron', 'positron'} + Matching particle type + state_filter : function + Function that takes a state (structured datatype) and returns a bool + depending on some criteria. + + Returns + ------- + Track + New instance with only matching :class:`openmc.ParticleTrack` objects + + Examples + -------- + Get all particle tracks for photons: + + >>> track.filter(particle='photon') + + Get all particle tracks that entered cell with ID=15: + + >>> track.filter(state_filter=lambda s: s['cell_id'] == 15) + + Get all particle tracks in entered material with ID=2: + + >>> track.filter(state_filter=lambda s: s['material_id'] == 2) + + See Also + -------- + openmc.ParticleTrack + + """ + matching = [] + for t in self: + # Check for matching particle + if particle is not None: + if t.particle.name.lower() != particle: + continue + + # Apply arbitrary state filter + match = True + if state_filter is not None: + for state in t.states: + if state_filter(state): + break + else: + match = False + + if match: + matching.append(t) + + # Return new Track instance with only matching particle tracks + track = type(self).__new__(type(self)) + track.identifier = self.identifier + track.particle_tracks = matching + return track + + def plot(self, axes=None): + """Produce a 3D plot of particle tracks + + Parameters + ---------- + axes : matplotlib.axes.Axes, optional + Axes for plot + + Returns + ------- + axes : matplotlib.axes.Axes + Axes for plot + + """ + import matplotlib.pyplot as plt + + # Setup axes is one wasn't passed + if axes is None: + fig = plt.figure() + ax = plt.axes(projection='3d') + ax.set_xlabel('x [cm]') + ax.set_ylabel('y [cm]') + ax.set_zlabel('z [cm]') + else: + ax = axes + + # Plot each particle track + for _, states in self: + r = states['r'] + ax.plot3D(r['x'], r['y'], r['z']) + + return ax + + @property + def sources(self): + sources = [] + for particle_track in self: + particle_type = ParticleType(particle_track.particle) + state = particle_track.states[0] + sources.append( + SourceParticle( + r=state['r'], u=state['u'], E=state['E'], + time=state['time'], wgt=state['wgt'], + particle=particle_type + ) + ) + return sources + + +class Tracks(list): + """Collection of particle tracks + + This class behaves like a list and can be indexed using the normal subscript + notation. Each element in the list is a :class:`openmc.Track` object. + + Parameters + ---------- + filepath : str or pathlib.Path + Path of file to load + + """ + + def __init__(self, filepath='tracks.h5'): + # Read data from track file + with h5py.File(filepath, 'r') as fh: + # Check filetype and version + check_filetype_version(fh, 'track', _VERSION_TRACK) + + for dset_name in sorted(fh, key=_identifier): + dset = fh[dset_name] + self.append(Track(dset)) + + def filter(self, particle=None, state_filter=None): + """Filter tracks by given criteria + + Parameters + ---------- + particle : {'neutron', 'photon', 'electron', 'positron'} + Matching particle type + state_filter : function + Function that takes a state (structured datatype) and returns a bool + depending on some criteria. + + Returns + ------- + Tracks + List of :class:`openmc.Track` objects + + See Also + -------- + openmc.Track.filter + + """ + # Create a new Tracks instance but avoid call to __init__ + matching = type(self).__new__(type(self)) + + # Append matching Track objects + for track in self: + if track.filter(particle, state_filter): + matching.append(track) + return matching + + def plot(self): + """Produce a 3D plot of particle tracks + + Returns + ------- + matplotlib.axes.Axes + Axes for plot + + """ + import matplotlib.pyplot as plt + fig = plt.figure() + ax = plt.axes(projection='3d') + ax.set_xlabel('x [cm]') + ax.set_ylabel('y [cm]') + ax.set_zlabel('z [cm]') + for track in self: + track.plot(ax) + return ax + + @staticmethod + def combine(track_files, path='tracks.h5'): + """Combine multiple track files into a single track file + + Parameters + ---------- + track_files : list of path-like + Paths to track files to combine + path : path-like + Path of combined track file to create + + """ + with h5py.File(path, 'w') as h5_out: + for i, fname in enumerate(track_files): + with h5py.File(fname, 'r') as h5_in: + # Copy file attributes for first file + if i == 0: + h5_out.attrs['filetype'] = h5_in.attrs['filetype'] + h5_out.attrs['version'] = h5_in.attrs['version'] + + # Copy each 'track_*' dataset from input file + for dset in h5_in: + h5_in.copy(dset, h5_out) diff --git a/scripts/openmc-track-combine b/scripts/openmc-track-combine new file mode 100755 index 0000000000..f69f2151c6 --- /dev/null +++ b/scripts/openmc-track-combine @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +"""Combine multiple HDF5 particle track files.""" + +import argparse + +import openmc + + +def main(): + # Parse command-line arguments + parser = argparse.ArgumentParser( + description='Combine particle track files into a single .h5 file.') + parser.add_argument('input', metavar='IN', nargs='+', + help='Input HDF5 particle track filename(s).') + parser.add_argument('-o', '--out', metavar='OUT', default='tracks.h5', + help='Output HDF5 particle track file.') + + args = parser.parse_args() + openmc.Tracks.combine(args.input, args.out) + + +if __name__ == '__main__': + main() diff --git a/scripts/openmc-track-to-vtk b/scripts/openmc-track-to-vtk index 3c781d19ec..3b3507974c 100755 --- a/scripts/openmc-track-to-vtk +++ b/scripts/openmc-track-to-vtk @@ -4,18 +4,16 @@ """ -import os import argparse -import struct -import h5py +import openmc import vtk def _parse_args(): # Create argument parser. parser = argparse.ArgumentParser( - description='Convert particle track file to a .pvtp file.') + description='Convert particle track file(s) to a .pvtp file.') parser.add_argument('input', metavar='IN', type=str, nargs='+', help='Input particle track data filename(s).') parser.add_argument('-o', '--out', metavar='OUT', type=str, dest='out', @@ -41,25 +39,22 @@ def main(): point_offset = 0 for fname in args.input: # Write coordinate values to points array. - track = h5py.File(fname) - n_particles = track.attrs['n_particles'] - n_coords = track.attrs['n_coords'] - coords = [] - for i in range(n_particles): - coords.append(track['coordinates_' + str(i + 1)][()]) - for j in range(n_coords[i]): - points.InsertNextPoint(coords[i][j,:]) + track_file = openmc.Tracks(fname) + for track in track_file: + for particle in track: + for state in particle.states: + points.InsertNextPoint(state['r']) - for i in range(n_particles): - # Create VTK line and assign points to line. - line = vtk.vtkPolyLine() - line.GetPointIds().SetNumberOfIds(n_coords[i]) - for j in range(n_coords[i]): - line.GetPointIds().SetId(j, point_offset + j) + # Create VTK line and assign points to line. + n = particle.states.size + line = vtk.vtkPolyLine() + line.GetPointIds().SetNumberOfIds(n) + for i in range(n): + line.GetPointIds().SetId(i, point_offset + i) + point_offset += n - # Add line to cell array - cells.InsertNextCell(line) - point_offset += n_coords[i] + # Add line to cell array + cells.InsertNextCell(line) data = vtk.vtkPolyData() data.SetPoints(points) diff --git a/src/cell.cpp b/src/cell.cpp index 28be2c2cff..f9786ce3eb 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -846,6 +846,20 @@ void read_cells(pugi::xml_node node) read_dagmc_universes(node); + populate_universes(); + + // Allocate the cell overlap count if necessary. + if (settings::check_overlaps) { + model::overlap_check_count.resize(model::cells.size(), 0); + } + + if (model::cells.size() == 0) { + fatal_error("No cells were found in the geometry.xml file"); + } +} + +void populate_universes() +{ // Populate the Universe vector and map. for (int i = 0; i < model::cells.size(); i++) { int32_t uid = model::cells[i]->universe_; @@ -860,15 +874,6 @@ void read_cells(pugi::xml_node node) } } model::universes.shrink_to_fit(); - - // Allocate the cell overlap count if necessary. - if (settings::check_overlaps) { - model::overlap_check_count.resize(model::cells.size(), 0); - } - - if (model::cells.size() == 0) { - fatal_error("No cells were found in the geometry.xml file"); - } } //============================================================================== diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 9b894d3bb9..e780d2e77f 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -12,7 +12,6 @@ #include "openmc/string_utils.h" #ifdef DAGMC -#include "dagmcmetadata.hpp" #include "uwuw.hpp" #endif #include @@ -50,6 +49,9 @@ DAGUniverse::DAGUniverse(pugi::xml_node node) if (check_for_node(node, "filename")) { filename_ = get_node_value(node, "filename"); + if (!file_exists(filename_)) { + fatal_error(fmt::format("DAGMC file '{}' could not be found", filename_)); + } } else { fatal_error("Must specify a file for the DAGMC universe"); } @@ -71,6 +73,23 @@ DAGUniverse::DAGUniverse( const std::string& filename, bool auto_geom_ids, bool auto_mat_ids) : filename_(filename), adjust_geometry_ids_(auto_geom_ids), adjust_material_ids_(auto_mat_ids) +{ + set_id(); + initialize(); +} + +DAGUniverse::DAGUniverse(std::shared_ptr dagmc_ptr, + const std::string& filename, bool auto_geom_ids, bool auto_mat_ids) + : dagmc_instance_(dagmc_ptr), filename_(filename), + adjust_geometry_ids_(auto_geom_ids), adjust_material_ids_(auto_mat_ids) +{ + set_id(); + init_metadata(); + read_uwuw_materials(); + init_geometry(); +} + +void DAGUniverse::set_id() { // determine the next universe id int32_t next_univ_id = 0; @@ -82,48 +101,27 @@ DAGUniverse::DAGUniverse( // set the universe id id_ = next_univ_id; - - initialize(); } void DAGUniverse::initialize() { geom_type() = GeometryType::DAG; - // determine the next cell id - int32_t next_cell_id = 0; - for (const auto& c : model::cells) { - if (c->id_ > next_cell_id) - next_cell_id = c->id_; - } - cell_idx_offset_ = model::cells.size(); - next_cell_id++; + init_dagmc(); - // determine the next surface id - int32_t next_surf_id = 0; - for (const auto& s : model::surfaces) { - if (s->id_ > next_surf_id) - next_surf_id = s->id_; - } - surf_idx_offset_ = model::surfaces.size(); - next_surf_id++; + init_metadata(); + + read_uwuw_materials(); + + init_geometry(); +} + +void DAGUniverse::init_dagmc() +{ // create a new DAGMC instance dagmc_instance_ = std::make_shared(); - // --- Materials --- - - // read any UWUW materials from the file - read_uwuw_materials(); - - // check for uwuw material definitions - bool using_uwuw = uses_uwuw(); - - // notify user if UWUW materials are going to be used - if (using_uwuw) { - write_message("Found UWUW Materials in the DAGMC geometry file.", 6); - } - // load the DAGMC geometry filename_ = settings::path_input + filename_; if (!file_exists(filename_)) { @@ -135,18 +133,35 @@ void DAGUniverse::initialize() // initialize acceleration data structures rval = dagmc_instance_->init_OBBTree(); MB_CHK_ERR_CONT(rval); +} +void DAGUniverse::init_metadata() +{ // parse model metadata - dagmcMetaData DMD(dagmc_instance_.get(), false, false); - DMD.load_property_data(); + dmd_ptr = + std::make_unique(dagmc_instance_.get(), false, false); + dmd_ptr->load_property_data(); std::vector keywords {"temp"}; std::map dum; std::string delimiters = ":/"; + moab::ErrorCode rval; rval = dagmc_instance_->parse_properties(keywords, dum, delimiters.c_str()); MB_CHK_ERR_CONT(rval); +} - // --- Cells (Volumes) --- +void DAGUniverse::init_geometry() +{ + moab::ErrorCode rval; + + // determine the next cell id + int32_t next_cell_id = 0; + for (const auto& c : model::cells) { + if (c->id_ > next_cell_id) + next_cell_id = c->id_; + } + cell_idx_offset_ = model::cells.size(); + next_cell_id++; // initialize cell objects int n_cells = dagmc_instance_->num_entities(3); @@ -175,7 +190,7 @@ void DAGUniverse::initialize() // --- Materials --- // determine volume material assignment - std::string mat_str = DMD.get_volume_property("material", vol_handle); + std::string mat_str = dmd_ptr->get_volume_property("material", vol_handle); if (mat_str.empty()) { fatal_error(fmt::format("Volume {} has no material assignment.", c->id_)); @@ -191,9 +206,10 @@ void DAGUniverse::initialize() if (mat_str == "void" || mat_str == "vacuum" || mat_str == "graveyard") { c->material_.push_back(MATERIAL_VOID); } else { - if (using_uwuw) { + if (uses_uwuw()) { // lookup material in uwuw if present - std::string uwuw_mat = DMD.volume_material_property_data_eh[vol_handle]; + std::string uwuw_mat = + dmd_ptr->volume_material_property_data_eh[vol_handle]; if (uwuw_->material_library.count(uwuw_mat) != 0) { // Note: material numbers are set by UWUW int mat_number = uwuw_->material_library.get_material(uwuw_mat) @@ -243,7 +259,14 @@ void DAGUniverse::initialize() has_graveyard_ = graveyard; - // --- Surfaces --- + // determine the next surface id + int32_t next_surf_id = 0; + for (const auto& s : model::surfaces) { + if (s->id_ > next_surf_id) + next_surf_id = s->id_; + } + surf_idx_offset_ = model::surfaces.size(); + next_surf_id++; // initialize surface objects int n_surfaces = dagmc_instance_->num_entities(2); @@ -256,7 +279,8 @@ void DAGUniverse::initialize() : dagmc_instance_->id_by_index(2, i + 1); // set BCs - std::string bc_value = DMD.get_surface_property("boundary", surf_handle); + std::string bc_value = + dmd_ptr->get_surface_property("boundary", surf_handle); to_lower(bc_value); if (bc_value.empty() || bc_value == "transmit" || bc_value == "transmission") { @@ -395,7 +419,7 @@ void DAGUniverse::to_hdf5(hid_t universes_group) const bool DAGUniverse::uses_uwuw() const { - return !uwuw_->material_library.empty(); + return uwuw_ && !uwuw_->material_library.empty(); } std::string DAGUniverse::get_uwuw_materials_xml() const @@ -484,33 +508,32 @@ void DAGUniverse::legacy_assign_material( void DAGUniverse::read_uwuw_materials() { - - int32_t next_material_id = 0; - for (const auto& m : model::materials) { - next_material_id = std::max(m->id_, next_material_id); - } - next_material_id++; + // If no filename was provided, don't read UWUW materials + if (filename_ == "") + return; uwuw_ = std::make_shared(filename_.c_str()); - const auto& mat_lib = uwuw_->material_library; - if (mat_lib.size() == 0) + + if (!uses_uwuw()) return; + // Notify user if UWUW materials are going to be used + write_message("Found UWUW Materials in the DAGMC geometry file.", 6); + // if we're using automatic IDs, update the UWUW material metadata if (adjust_material_ids_) { + int32_t next_material_id = 0; + for (const auto& m : model::materials) { + next_material_id = std::max(m->id_, next_material_id); + } + next_material_id++; + for (auto& mat : uwuw_->material_library) { mat.second->metadata["mat_number"] = next_material_id++; } } - std::stringstream ss; - ss << "\n"; - ss << "\n"; - for (auto mat : mat_lib) { - ss << mat.second->openmc("atom"); - } - ss << ""; - std::string mat_xml_string = ss.str(); + std::string mat_xml_string = get_uwuw_materials_xml(); // create a pugi XML document from this string pugi::xml_document doc; diff --git a/src/distribution_spatial.cpp b/src/distribution_spatial.cpp index 66430ddffe..a06f84d808 100644 --- a/src/distribution_spatial.cpp +++ b/src/distribution_spatial.cpp @@ -132,15 +132,15 @@ SphericalIndependent::SphericalIndependent(pugi::xml_node node) r_ = make_unique(x, p, 1); } - // Read distribution for theta-coordinate - if (check_for_node(node, "theta")) { - pugi::xml_node node_dist = node.child("theta"); - theta_ = distribution_from_xml(node_dist); + // Read distribution for cos_theta-coordinate + if (check_for_node(node, "cos_theta")) { + pugi::xml_node node_dist = node.child("cos_theta"); + cos_theta_ = distribution_from_xml(node_dist); } else { - // If no distribution was specified, default to a single point at theta=0 + // If no distribution was specified, default to a single point at cos_theta=0 double x[] {0.0}; double p[] {1.0}; - theta_ = make_unique(x, p, 1); + cos_theta_ = make_unique(x, p, 1); } // Read distribution for phi-coordinate @@ -171,11 +171,12 @@ SphericalIndependent::SphericalIndependent(pugi::xml_node node) Position SphericalIndependent::sample(uint64_t* seed) const { double r = r_->sample(seed); - double theta = theta_->sample(seed); + double cos_theta = cos_theta_->sample(seed); double phi = phi_->sample(seed); - double x = r * sin(theta) * cos(phi) + origin_.x; - double y = r * sin(theta) * sin(phi) + origin_.y; - double z = r * cos(theta) + origin_.z; + // sin(theta) by sin**2 + cos**2 = 1 + double x = r * std::sqrt(1 - cos_theta * cos_theta) * cos(phi) + origin_.x; + double y = r * std::sqrt(1 - cos_theta * cos_theta) * sin(phi) + origin_.y; + double z = r * cos_theta + origin_.z; return {x, y, z}; } diff --git a/src/finalize.cpp b/src/finalize.cpp index 5bfe8907c9..0c2c62310e 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -85,6 +85,7 @@ int openmc_finalize() settings::material_cell_offsets = true; settings::max_particles_in_flight = 100000; settings::max_splits = 1000; + settings::max_tracks = 1000; settings::n_inactive = 0; settings::n_particles = -1; settings::output_summary = true; diff --git a/src/output.cpp b/src/output.cpp index 6565a9e67a..ece475de0f 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -318,7 +318,8 @@ void print_usage() " -r, --restart Restart a previous run from a state point\n" " or a particle restart file\n" " -s, --threads Number of OpenMP threads\n" - " -t, --track Write tracks for all particles\n" + " -t, --track Write tracks for all particles (up to " + "max_tracks)\n" " -e, --event Run using event-based parallelism\n" " -v, --version Show version information\n" " -h, --help Show this message\n"); diff --git a/src/particle.cpp b/src/particle.cpp index b622a4d691..cb959e14d7 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -337,6 +337,11 @@ void Particle::event_revive_from_secondary() // Check for secondary particles if this particle is dead if (!alive()) { + // Write final position for this particle + if (write_track()) { + write_particle_track(*this); + } + // If no secondary particles, break out of event loop if (secondary_bank().empty()) return; @@ -359,7 +364,6 @@ void Particle::event_death() // Finish particle track output. if (write_track()) { - write_particle_track(*this); finalize_particle_track(*this); } @@ -454,8 +458,7 @@ void Particle::cross_surface() // ========================================================================== // COULDN'T FIND PARTICLE IN NEIGHBORING CELLS, SEARCH ALL CELLS - // Remove lower coordinate levels and assignment of surface - surface() = 0; + // Remove lower coordinate levels n_coord() = 1; bool found = exhaustive_find_cell(*this); @@ -465,6 +468,7 @@ void Particle::cross_surface() // the particle is really traveling tangent to a surface, if we move it // forward a tiny bit it should fix the problem. + surface() = 0; n_coord() = 1; r() += TINY_BIT * u(); diff --git a/src/particle_data.cpp b/src/particle_data.cpp index 701e6cbc0e..a2be720840 100644 --- a/src/particle_data.cpp +++ b/src/particle_data.cpp @@ -1,6 +1,8 @@ #include "openmc/particle_data.h" +#include "openmc/cell.h" #include "openmc/geometry.h" +#include "openmc/material.h" #include "openmc/nuclide.h" #include "openmc/photon.h" #include "openmc/settings.h" @@ -53,4 +55,20 @@ ParticleData::ParticleData() photon_xs_.resize(data::elements.size()); } +TrackState ParticleData::get_track_state() const +{ + TrackState state; + state.r = this->r(); + state.u = this->u(); + state.E = this->E(); + state.time = this->time(); + state.wgt = this->wgt(); + state.cell_id = model::cells[this->lowest_coord().cell]->id_; + state.cell_instance = this->cell_instance(); + if (this->material() != MATERIAL_VOID) { + state.material_id = model::materials[material()]->id_; + } + return state; +} + } // namespace openmc diff --git a/src/settings.cpp b/src/settings.cpp index 795b164ec4..eb0d4936c7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -95,6 +95,7 @@ int n_log_bins {8000}; int n_batches; int n_max_batches; int max_splits {1000}; +int max_tracks {1000}; ResScatMethod res_scat_method {ResScatMethod::rvs}; double res_scat_energy_min {0.01}; double res_scat_energy_max {1000.0}; @@ -802,6 +803,12 @@ void read_settings_xml() } if (check_for_node(root, "temperature_multipole")) { temperature_multipole = get_node_value_bool(root, "temperature_multipole"); + + // Multipole currently doesn't work with photon transport + if (temperature_multipole && photon_transport) { + fatal_error("Multipole data cannot currently be used in conjunction with " + "photon transport."); + } } if (check_for_node(root, "temperature_range")) { auto range = get_node_array(root, "temperature_range"); @@ -871,6 +878,10 @@ void read_settings_xml() if (check_for_node(root, "max_splits")) { settings::max_splits = std::stoi(get_node_value(root, "max_splits")); } + + if (check_for_node(root, "max_tracks")) { + settings::max_tracks = std::stoi(get_node_value(root, "max_tracks")); + } } void free_memory_settings() diff --git a/src/simulation.cpp b/src/simulation.cpp index b7f5b58f16..b70535c330 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -82,6 +82,11 @@ int openmc_simulation_init() // Allocate source, fission and surface source banks. allocate_banks(); + // Create track file if needed + if (!settings::track_identifiers.empty() || settings::write_all_tracks) { + open_track_file(); + } + // If doing an event-based simulation, intialize the particle buffer // and event queues if (settings::event_based) { @@ -152,6 +157,11 @@ int openmc_simulation_finalize() mat->mat_nuclide_index_.clear(); } + // Close track file if open + if (!settings::track_identifiers.empty() || settings::write_all_tracks) { + close_track_file(); + } + // Increment total number of generations simulation::total_gen += simulation::current_batch * settings::gen_per_batch; @@ -507,18 +517,7 @@ void initialize_history(Particle& p, int64_t index_source) p.trace() = true; // Set particle track. - p.write_track() = false; - if (settings::write_all_tracks) { - p.write_track() = true; - } else if (settings::track_identifiers.size() > 0) { - for (const auto& t : settings::track_identifiers) { - if (simulation::current_batch == t[0] && - simulation::current_gen == t[1] && p.id() == t[2]) { - p.write_track() = true; - break; - } - } - } + p.write_track() = check_track_criteria(p); // Display message if high verbosity or trace is on if (settings::verbosity >= 9 || p.trace()) { diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index c9930ee39c..68eb344af4 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -963,18 +963,23 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, score = score_neutron_heating( p, tally, flux, HEATING, i_nuclide, atom_density); } else { - // The energy deposited is the difference between the pre-collision and - // post-collision energy... - score = E - p.E(); + if (i_nuclide == -1 || i_nuclide == p.event_nuclide()) { + // The energy deposited is the difference between the pre-collision + // and post-collision energy... + score = E - p.E(); - // ...less the energy of any secondary particles since they will be - // transported individually later - const auto& bank = p.secondary_bank(); - for (auto it = bank.end() - p.n_bank_second(); it < bank.end(); ++it) { - score -= it->E; + // ...less the energy of any secondary particles since they will be + // transported individually later + const auto& bank = p.secondary_bank(); + for (auto it = bank.end() - p.n_bank_second(); it < bank.end(); + ++it) { + score -= it->E; + } + + score *= p.wgt_last(); + } else { + score = 0.0; } - - score *= p.wgt_last(); } break; diff --git a/src/track_output.cpp b/src/track_output.cpp index 065c2d465c..5c1436de71 100644 --- a/src/track_output.cpp +++ b/src/track_output.cpp @@ -2,6 +2,7 @@ #include "openmc/constants.h" #include "openmc/hdf5_interface.h" +#include "openmc/message_passing.h" #include "openmc/position.h" #include "openmc/settings.h" #include "openmc/simulation.h" @@ -9,6 +10,7 @@ #include "xtensor/xtensor.hpp" #include +#include #include // for size_t #include @@ -19,6 +21,10 @@ namespace openmc { // Global variables //============================================================================== +hid_t track_file; //! HDF5 identifier for track file +hid_t track_dtype; //! HDF5 identifier for track datatype +int n_tracks_written; //! Number of tracks written + //============================================================================== // Non-member functions //============================================================================== @@ -26,45 +32,123 @@ namespace openmc { void add_particle_track(Particle& p) { p.tracks().emplace_back(); + p.tracks().back().particle = p.type(); } void write_particle_track(Particle& p) { - p.tracks().back().push_back(p.r()); + p.tracks().back().states.push_back(p.get_track_state()); +} + +void open_track_file() +{ + // Open file and write filetype/version -- when MPI is enabled and there is + // more than one rank, each rank writes its own file +#ifdef OPENMC_MPI + std::string filename; + if (mpi::n_procs > 1) { + filename = fmt::format("{}tracks_p{}.h5", settings::path_output, mpi::rank); + } else { + filename = fmt::format("{}tracks.h5", settings::path_output); + } +#else + std::string filename = fmt::format("{}tracks.h5", settings::path_output); +#endif + track_file = file_open(filename, 'w'); + write_attribute(track_file, "filetype", "track"); + write_attribute(track_file, "version", VERSION_TRACK); + + // Create compound type for Position + hid_t postype = H5Tcreate(H5T_COMPOUND, sizeof(struct Position)); + H5Tinsert(postype, "x", HOFFSET(Position, x), H5T_NATIVE_DOUBLE); + H5Tinsert(postype, "y", HOFFSET(Position, y), H5T_NATIVE_DOUBLE); + H5Tinsert(postype, "z", HOFFSET(Position, z), H5T_NATIVE_DOUBLE); + + // Create compound type for TrackState + track_dtype = H5Tcreate(H5T_COMPOUND, sizeof(struct TrackState)); + H5Tinsert(track_dtype, "r", HOFFSET(TrackState, r), postype); + H5Tinsert(track_dtype, "u", HOFFSET(TrackState, u), postype); + H5Tinsert(track_dtype, "E", HOFFSET(TrackState, E), H5T_NATIVE_DOUBLE); + H5Tinsert(track_dtype, "time", HOFFSET(TrackState, time), H5T_NATIVE_DOUBLE); + H5Tinsert(track_dtype, "wgt", HOFFSET(TrackState, wgt), H5T_NATIVE_DOUBLE); + H5Tinsert( + track_dtype, "cell_id", HOFFSET(TrackState, cell_id), H5T_NATIVE_INT); + H5Tinsert(track_dtype, "cell_instance", HOFFSET(TrackState, cell_instance), + H5T_NATIVE_INT); + H5Tinsert(track_dtype, "material_id", HOFFSET(TrackState, material_id), + H5T_NATIVE_INT); + H5Tclose(postype); +} + +void close_track_file() +{ + H5Tclose(track_dtype); + file_close(track_file); + + // Reset number of tracks written + n_tracks_written = 0; +} + +bool check_track_criteria(const Particle& p) +{ + if (settings::write_all_tracks) { + // Increment number of tracks written and get previous value + int n; +#pragma omp atomic capture + n = n_tracks_written++; + + // Indicate that track should be written for this particle + return n < settings::max_tracks; + } + + // Check for match from explicit track identifiers + if (settings::track_identifiers.size() > 0) { + for (const auto& t : settings::track_identifiers) { + if (simulation::current_batch == t[0] && + simulation::current_gen == t[1] && p.id() == t[2]) { + return true; + } + } + } + return false; } void finalize_particle_track(Particle& p) { - std::string filename = - fmt::format("{}track_{}_{}_{}.h5", settings::path_output, - simulation::current_batch, simulation::current_gen, p.id()); - // Determine number of coordinates for each particle - vector n_coords; - for (auto& coords : p.tracks()) { - n_coords.push_back(coords.size()); + vector offsets; + vector particles; + vector tracks; + int offset = 0; + for (auto& track_i : p.tracks()) { + offsets.push_back(offset); + particles.push_back(static_cast(track_i.particle)); + offset += track_i.states.size(); + tracks.insert(tracks.end(), track_i.states.begin(), track_i.states.end()); } + offsets.push_back(offset); #pragma omp critical(FinalizeParticleTrack) { - hid_t file_id = file_open(filename, 'w'); - write_attribute(file_id, "filetype", "track"); - write_attribute(file_id, "version", VERSION_TRACK); - write_attribute(file_id, "n_particles", p.tracks().size()); - write_attribute(file_id, "n_coords", n_coords); - for (auto i = 1; i <= p.tracks().size(); ++i) { - const auto& t {p.tracks()[i - 1]}; - size_t n = t.size(); - xt::xtensor data({n, 3}); - for (int j = 0; j < n; ++j) { - data(j, 0) = t[j].x; - data(j, 1) = t[j].y; - data(j, 2) = t[j].z; - } - std::string name = fmt::format("coordinates_{}", i); - write_dataset(file_id, name.c_str(), data); - } - file_close(file_id); + // Create name for dataset + std::string dset_name = fmt::format("track_{}_{}_{}", + simulation::current_batch, simulation::current_gen, p.id()); + + // Write array of TrackState to file + hsize_t dims[] {static_cast(tracks.size())}; + hid_t dspace = H5Screate_simple(1, dims, nullptr); + hid_t dset = H5Dcreate(track_file, dset_name.c_str(), track_dtype, dspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dset, track_dtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, tracks.data()); + + // Write attributes + write_attribute(dset, "n_particles", p.tracks().size()); + write_attribute(dset, "offsets", offsets); + write_attribute(dset, "particles", particles); + + // Free resources + H5Dclose(dset); + H5Sclose(dspace); } // Clear particle tracks diff --git a/tests/regression_tests/dagmc/external/__init__.py b/tests/regression_tests/dagmc/external/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/dagmc/external/dagmc.h5m b/tests/regression_tests/dagmc/external/dagmc.h5m new file mode 120000 index 0000000000..92c41719c5 --- /dev/null +++ b/tests/regression_tests/dagmc/external/dagmc.h5m @@ -0,0 +1 @@ +../legacy/dagmc.h5m \ No newline at end of file diff --git a/tests/regression_tests/dagmc/external/inputs_true.dat b/tests/regression_tests/dagmc/external/inputs_true.dat new file mode 100644 index 0000000000..dd75bbdbd9 --- /dev/null +++ b/tests/regression_tests/dagmc/external/inputs_true.dat @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 0 + + + -4 -4 -4 4 4 4 + + + 293 + + + + + 1 + + + 1 + total + + diff --git a/tests/regression_tests/dagmc/external/main.cpp b/tests/regression_tests/dagmc/external/main.cpp new file mode 100644 index 0000000000..42f7d97c90 --- /dev/null +++ b/tests/regression_tests/dagmc/external/main.cpp @@ -0,0 +1,94 @@ +#include "openmc/capi.h" +#include "openmc/cross_sections.h" +#include "openmc/dagmc.h" +#include "openmc/error.h" +#include "openmc/geometry.h" +#include "openmc/geometry_aux.h" +#include "openmc/material.h" +#include "openmc/nuclide.h" +#include + +int main(int argc, char* argv[]) +{ + using namespace openmc; + int openmc_err; + + // Initialise OpenMC + openmc_err = openmc_init(argc, argv, nullptr); + if (openmc_err == -1) { + // This happens for the -h and -v flags + return EXIT_SUCCESS; + } else if (openmc_err) { + fatal_error(openmc_err_msg); + } + + // Create DAGMC ptr + std::string filename = "dagmc.h5m"; + std::shared_ptr dag_ptr = std::make_shared(); + moab::ErrorCode rval = dag_ptr->load_file(filename.c_str()); + if (rval != moab::MB_SUCCESS) { + fatal_error("Failed to load file"); + } + + // Initialize acceleration data structures + rval = dag_ptr->init_OBBTree(); + if (rval != moab::MB_SUCCESS) { + fatal_error("Failed to initialise OBB tree"); + } + + // Get rid of existing geometry + std::unordered_map nuclide_map_copy = + openmc::data::nuclide_map; + openmc::data::nuclides.clear(); + openmc::data::nuclide_map = nuclide_map_copy; + openmc::model::surfaces.clear(); + openmc::model::surface_map.clear(); + openmc::model::cells.clear(); + openmc::model::cell_map.clear(); + openmc::model::universes.clear(); + openmc::model::universe_map.clear(); + + // Update materials (emulate an external program) + for (auto& mat_ptr : openmc::model::materials) { + mat_ptr->set_temperature(300); + } + + // Create new DAGMC universe + openmc::model::universes.push_back( + std::make_unique(dag_ptr)); + model::universe_map[model::universes.back()->id_] = + model::universes.size() - 1; + + // Add cells to universes + openmc::populate_universes(); + + // Set root universe + openmc::model::root_universe = openmc::find_root_universe(); + openmc::check_dagmc_root_univ(); + + // Final geometry setup and assign temperatures + openmc::finalize_geometry(); + + // Finalize cross sections having assigned temperatures + openmc::finalize_cross_sections(); + + // Check that we correctly assigned cell temperatures with non-void fill + for (auto& cell_ptr : openmc::model::cells) { + if (cell_ptr->material_.front() != openmc::C_NONE && + cell_ptr->temperature() != 300) { + fatal_error("Failed to set cell temperature"); + } + } + + // Run OpenMC + openmc_err = openmc_run(); + if (openmc_err) + fatal_error(openmc_err_msg); + + // Deallocate memory + openmc_err = openmc_finalize(); + if (openmc_err) + fatal_error(openmc_err_msg); + + return EXIT_SUCCESS; +} diff --git a/tests/regression_tests/dagmc/external/results_true.dat b/tests/regression_tests/dagmc/external/results_true.dat new file mode 100644 index 0000000000..e5127c8272 --- /dev/null +++ b/tests/regression_tests/dagmc/external/results_true.dat @@ -0,0 +1,5 @@ +k-combined: +8.426936E-01 5.715847E-02 +tally 1: +8.093843E+00 +1.328829E+01 diff --git a/tests/regression_tests/dagmc/external/test.py b/tests/regression_tests/dagmc/external/test.py new file mode 100644 index 0000000000..93123ae1ed --- /dev/null +++ b/tests/regression_tests/dagmc/external/test.py @@ -0,0 +1,128 @@ +from pathlib import Path +import os +import shutil +import subprocess +import textwrap + +import openmc +import openmc.lib +import pytest + +from tests.regression_tests import config +from tests.testing_harness import PyAPITestHarness + +pytestmark = pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), + reason="DAGMC is not enabled.") + +# Test that an external DAGMC instance can be passed in through the C API + +@pytest.fixture +def cpp_driver(request): + """Compile the external source""" + + # Get build directory and write CMakeLists.txt file + openmc_dir = Path(str(request.config.rootdir)) / 'build' + with open('CMakeLists.txt', 'w') as f: + f.write(textwrap.dedent(""" + cmake_minimum_required(VERSION 3.3 FATAL_ERROR) + project(openmc_cpp_driver CXX) + add_executable(main main.cpp) + find_package(OpenMC REQUIRED HINTS {}) + target_link_libraries(main OpenMC::libopenmc) + target_compile_features(main PUBLIC cxx_std_14) + set(CMAKE_CXX_FLAGS "-pedantic-errors") + add_compile_definitions(DAGMC=1) + """.format(openmc_dir))) + + # Create temporary build directory and change to there + local_builddir = Path('build') + local_builddir.mkdir(exist_ok=True) + os.chdir(local_builddir) + + mpi_arg = "On" if config['mpi'] else "Off" + + try: + # Run cmake/make to build the shared libary + subprocess.run(['cmake', os.path.pardir, f'-DOPENMC_USE_MPI={mpi_arg}'], check=True) + subprocess.run(['make'], check=True) + os.chdir(os.path.pardir) + + yield "./build/main" + + finally: + # Remove local build directory when test is complete + shutil.rmtree('build') + os.remove('CMakeLists.txt') + +@pytest.fixture +def model(): + model = openmc.model.Model() + + # Settings + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 100 + source_box = openmc.stats.Box([-4, -4, -4], + [ 4, 4, 4]) + source = openmc.Source(space=source_box) + model.settings.source = source + model.settings.temperature['default'] = 293 + + # Geometry + dag_univ = openmc.DAGMCUniverse("dagmc.h5m") + model.geometry = openmc.Geometry(dag_univ) + + # Tallies + tally = openmc.Tally() + tally.scores = ['total'] + tally.filters = [openmc.CellFilter(1)] + model.tallies = [tally] + + # Materials + u235 = openmc.Material(name="no-void fuel") + u235.add_nuclide('U235', 1.0, 'ao') + u235.set_density('g/cc', 11) + u235.id = 40 + water = openmc.Material(name="water") + water.add_nuclide('H1', 2.0, 'ao') + water.add_nuclide('O16', 1.0, 'ao') + water.set_density('g/cc', 1.0) + water.add_s_alpha_beta('c_H_in_H2O') + water.id = 41 + mats = openmc.Materials([u235, water]) + model.materials = mats + + return model + +class ExternalDAGMCTest(PyAPITestHarness): + def __init__(self, executable, statepoint_name, model): + super().__init__(statepoint_name, model) + self.executable = executable + + def _run_openmc(self): + """ + Just test if results generated with the external C++ API are + self-consistent with the internal python API. + We generate the "truth" results with the python API but + the main test produces the results file by running the + executable compiled from main.cpp. This future-proofs + the test - we only care that the two routes are equivalent. + """ + if config['update']: + # Generate the results file with internal python API + openmc.run(openmc_exec=config['exe'], event_based=config['event']) + elif config['mpi']: + mpi_args = [config['mpiexec'], '-n', config['mpi_np']] + # Run main cpp executable with MPI + openmc.run(openmc_exec=self.executable, + mpi_args=mpi_args, + event_based=config['event']) + else: + # Run main cpp executable + openmc.run(openmc_exec=self.executable, + event_based=config['event']) + +def test_external_dagmc(cpp_driver, model): + harness = ExternalDAGMCTest(cpp_driver, 'statepoint.5.h5', model) + harness.main() diff --git a/tests/regression_tests/photon_production/results_true.dat b/tests/regression_tests/photon_production/results_true.dat index 03fb80b81e..398deeed8b 100644 --- a/tests/regression_tests/photon_production/results_true.dat +++ b/tests/regression_tests/photon_production/results_true.dat @@ -67,8 +67,8 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -1.764573E+05 -3.113718E+10 +0.000000E+00 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 @@ -79,8 +79,8 @@ tally 3: 0.000000E+00 0.000000E+00 0.000000E+00 -7.691658E+03 -5.916160E+07 +0.000000E+00 +0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 diff --git a/tests/regression_tests/source/inputs_true.dat b/tests/regression_tests/source/inputs_true.dat index 131d20ac12..c9c9c4277a 100644 --- a/tests/regression_tests/source/inputs_true.dat +++ b/tests/regression_tests/source/inputs_true.dat @@ -53,9 +53,9 @@ - - 0.7853981633974483 1.5707963267948966 2.356194490192345 0.3 0.4 0.3 - + + 0.7071067811865476 0.0 -0.7071067811865475 0.3 0.4 0.3 + @@ -102,9 +102,9 @@ - - 0.7853981633974483 1.5707963267948966 2.356194490192345 0.3 0.4 0.3 - + + 0.7071067811865476 0.0 -0.7071067811865475 0.3 0.4 0.3 + diff --git a/tests/regression_tests/source/test.py b/tests/regression_tests/source/test.py index 31debde474..e6a5bcf702 100644 --- a/tests/regression_tests/source/test.py +++ b/tests/regression_tests/source/test.py @@ -1,4 +1,4 @@ -from math import pi +from math import pi, cos import numpy as np import openmc @@ -32,19 +32,19 @@ class SourceTestHarness(PyAPITestHarness): r_dist = openmc.stats.Uniform(2., 3.) r_dist1 = openmc.stats.PowerLaw(2., 3., 1.) r_dist2 = openmc.stats.PowerLaw(2., 3., 2.) - theta_dist = openmc.stats.Discrete([pi/4, pi/2, 3*pi/4], - [0.3, 0.4, 0.3]) + cos_theta_dist = openmc.stats.Discrete([cos(pi/4), 0.0, cos(3*pi/4)], + [0.3, 0.4, 0.3]) phi_dist = openmc.stats.Uniform(0.0, 2*pi) spatial1 = openmc.stats.CartesianIndependent(x_dist, y_dist, z_dist) spatial2 = openmc.stats.Box([-4., -4., -4.], [4., 4., 4.]) spatial3 = openmc.stats.Point([1.2, -2.3, 0.781]) - spatial4 = openmc.stats.SphericalIndependent(r_dist, theta_dist, + spatial4 = openmc.stats.SphericalIndependent(r_dist, cos_theta_dist, phi_dist, origin=(1., 1., 0.)) spatial5 = openmc.stats.CylindricalIndependent(r_dist, phi_dist, z_dist, origin=(1., 1., 0.)) - spatial6 = openmc.stats.SphericalIndependent(r_dist2, theta_dist, + spatial6 = openmc.stats.SphericalIndependent(r_dist2, cos_theta_dist, phi_dist, origin=(1., 1., 0.)) spatial7 = openmc.stats.CylindricalIndependent(r_dist1, phi_dist, diff --git a/tests/regression_tests/track_output/results_true.dat b/tests/regression_tests/track_output/results_true.dat index 1d0ca80399..756226d101 100644 --- a/tests/regression_tests/track_output/results_true.dat +++ b/tests/regression_tests/track_output/results_true.dat @@ -1,9 +1,266 @@ - - - - - - - - - +ParticleType.NEUTRON [((-9.663085e-01, -6.616522e-01, -9.863690e-01), (-7.513921e-01, 4.140970e-01, 5.137447e-01), 5.293873e+06, 0.000000e+00, 1.000000e+00, 23, 2403, 1) + ((-1.281491e+00, -4.879529e-01, -7.708709e-01), (-7.513921e-01, 4.140970e-01, 5.137447e-01), 5.293873e+06, 1.323629e-10, 1.000000e+00, 22, 2403, 3) + ((-1.368452e+00, -4.400285e-01, -7.114141e-01), (-7.513921e-01, 4.140970e-01, 5.137447e-01), 5.293873e+06, 1.688824e-10, 1.000000e+00, 21, 2403, 2) + ((-2.150539e+00, -9.015151e-03, -1.766823e-01), (-7.513921e-01, 4.140970e-01, 5.137447e-01), 5.293873e+06, 4.973246e-10, 1.000000e+00, 22, 2403, 3) + ((-2.237499e+00, 3.890922e-02, -1.172255e-01), (-7.513921e-01, 4.140970e-01, 5.137447e-01), 5.293873e+06, 5.338441e-10, 1.000000e+00, 23, 2403, 1) + ((-2.453640e+00, 1.580257e-01, 3.055504e-02), (-7.513921e-01, 4.140970e-01, 5.137447e-01), 5.293873e+06, 6.246136e-10, 1.000000e+00, 23, 2402, 1) + ((-2.767485e+00, 3.309877e-01, 2.451383e-01), (-7.513921e-01, 4.140970e-01, 5.137447e-01), 5.293873e+06, 7.564146e-10, 1.000000e+00, 22, 2402, 3) + ((-2.825099e+00, 3.627392e-01, 2.845305e-01), (-6.842434e-01, 3.658561e-01, 6.308409e-01), 5.292729e+06, 7.806100e-10, 1.000000e+00, 22, 2402, 3) + ((-3.274427e+00, 6.029890e-01, 6.987901e-01), (-6.842434e-01, 3.658561e-01, 6.308409e-01), 5.292729e+06, 9.878481e-10, 1.000000e+00, 23, 2402, 1) + ((-3.676327e+00, 8.178800e-01, 1.069324e+00), (-6.842434e-01, 3.658561e-01, 6.308409e-01), 5.292729e+06, 1.173212e-09, 1.000000e+00, 23, 2416, 1) + ((-4.089400e+00, 1.038745e+00, 1.450158e+00), (-6.842434e-01, 3.658561e-01, 6.308409e-01), 5.292729e+06, 1.363728e-09, 1.000000e+00, 23, 2415, 1) + ((-4.456639e+00, 1.235102e+00, 1.788735e+00), (-6.842434e-01, 3.658561e-01, 6.308409e-01), 5.292729e+06, 1.533105e-09, 1.000000e+00, 22, 2415, 3) + ((-4.536974e+00, 1.278056e+00, 1.862800e+00), (-6.842434e-01, 3.658561e-01, 6.308409e-01), 5.292729e+06, 1.570157e-09, 1.000000e+00, 21, 2415, 2) + ((-5.410401e+00, 1.745067e+00, 2.668060e+00), (-6.842434e-01, 3.658561e-01, 6.308409e-01), 5.292729e+06, 1.972998e-09, 1.000000e+00, 22, 2415, 3) + ((-5.490736e+00, 1.788021e+00, 2.742125e+00), (-6.842434e-01, 3.658561e-01, 6.308409e-01), 5.292729e+06, 2.010050e-09, 1.000000e+00, 23, 2415, 1) + ((-5.576301e+00, 1.833771e+00, 2.821012e+00), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 2.049514e-09, 1.000000e+00, 23, 2415, 1) + ((-5.725160e+00, 1.896661e+00, 2.330311e+00), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 2.227030e-09, 1.000000e+00, 23, 2414, 1) + ((-6.116514e+00, 2.061999e+00, 1.040248e+00), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 2.693724e-09, 1.000000e+00, 22, 2414, 3) + ((-6.534762e+00, 2.238699e+00, -3.384686e-01), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 3.192489e-09, 1.000000e+00, 23, 2414, 1) + ((-7.043525e+00, 2.453640e+00, -2.015560e+00), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 3.799195e-09, 1.000000e+00, 23, 2428, 1) + ((-7.360920e+00, 2.587732e+00, -3.061825e+00), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 4.177692e-09, 1.000000e+00, 11, 1757, 1) + ((-8.996680e+00, 3.278803e+00, -8.453960e+00), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 6.128354e-09, 1.000000e+00, 23, 2427, 1) + ((-9.220205e+00, 3.373238e+00, -9.190791e+00), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 6.394910e-09, 1.000000e+00, 22, 2427, 3) + ((-9.320244e+00, 3.415502e+00, -9.520561e+00), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 6.514208e-09, 1.000000e+00, 21, 2427, 2) + ((-1.005591e+01, 3.726304e+00, -1.194562e+01), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 7.391498e-09, 1.000000e+00, 22, 2427, 3) + ((-1.015595e+01, 3.768569e+00, -1.227539e+01), (-2.881377e-01, 1.217316e-01, -9.498200e-01), 4.458753e+06, 7.510796e-09, 1.000000e+00, 23, 2427, 1) + ((-1.062054e+01, 3.964848e+00, -1.380687e+01), (-5.395885e-01, 2.109921e-01, -8.150623e-01), 4.068754e+06, 8.064826e-09, 1.000000e+00, 23, 2427, 1) + ((-1.063244e+01, 3.969501e+00, -1.382484e+01), (-5.395885e-01, 2.109921e-01, -8.150623e-01), 4.068754e+06, 8.072756e-09, 1.000000e+00, 23, 2426, 1) + ((-1.093907e+01, 4.089400e+00, -1.428802e+01), (-5.395885e-01, 2.109921e-01, -8.150623e-01), 4.068754e+06, 8.277097e-09, 1.000000e+00, 23, 2437, 1) + ((-1.108410e+01, 4.146112e+00, -1.450710e+01), (4.855193e-01, 3.316053e-01, -8.088937e-01), 8.856868e+05, 8.373750e-09, 1.000000e+00, 23, 2437, 1) + ((-1.080173e+01, 4.338973e+00, -1.497755e+01), (2.818553e-01, 5.419536e-02, -9.579251e-01), 7.653670e+05, 8.820862e-09, 1.000000e+00, 23, 2437, 1) + ((-1.063244e+01, 4.371524e+00, -1.555290e+01), (2.818553e-01, 5.419536e-02, -9.579251e-01), 7.653670e+05, 9.317522e-09, 1.000000e+00, 23, 2438, 1) + ((-1.041266e+01, 4.413783e+00, -1.629984e+01), (2.686183e-01, -3.269476e-01, -9.060626e-01), 6.562001e+05, 9.962308e-09, 1.000000e+00, 23, 2438, 1) + ((-1.016754e+01, 4.115428e+00, -1.712667e+01), (-6.340850e-01, -7.142137e-01, -2.963697e-01), 7.155058e+04, 1.077718e-08, 1.000000e+00, 23, 2438, 1) + ((-1.019064e+01, 4.089400e+00, -1.713747e+01), (-6.340850e-01, -7.142137e-01, -2.963697e-01), 7.155058e+04, 1.087569e-08, 1.000000e+00, 23, 2427, 1) + ((-1.024915e+01, 4.023496e+00, -1.716481e+01), (1.827568e-01, -7.452255e-01, -6.412791e-01), 2.628504e+04, 1.112511e-08, 1.000000e+00, 23, 2427, 1) + ((-1.018199e+01, 3.749638e+00, -1.740047e+01), (1.827568e-01, -7.452255e-01, -6.412791e-01), 2.628504e+04, 1.276389e-08, 1.000000e+00, 22, 2427, 3) + ((-1.015866e+01, 3.654498e+00, -1.748234e+01), (1.827568e-01, -7.452255e-01, -6.412791e-01), 2.628504e+04, 1.333321e-08, 1.000000e+00, 21, 2427, 2) + ((-1.009978e+01, 3.414404e+00, -1.768895e+01), (1.735861e-01, -9.678731e-01, 1.819053e-01), 2.620416e+04, 1.476994e-08, 1.000000e+00, 21, 2427, 2) + ((-9.994809e+00, 2.829099e+00, -1.757894e+01), (4.635724e-01, 5.558106e-01, 6.900545e-01), 2.214649e+04, 1.747088e-08, 1.000000e+00, 21, 2427, 2) + ((-9.553365e+00, 3.358379e+00, -1.692183e+01), (-5.990363e-01, 3.483710e-01, -7.209668e-01), 1.813617e+04, 2.209726e-08, 1.000000e+00, 21, 2427, 2) + ((-1.011854e+01, 3.687059e+00, -1.760205e+01), (-5.990363e-01, 3.483710e-01, -7.209668e-01), 1.813617e+04, 2.716243e-08, 1.000000e+00, 22, 2427, 3) + ((-1.020058e+01, 3.734765e+00, -1.770077e+01), (-5.990363e-01, 3.483710e-01, -7.209668e-01), 1.813617e+04, 2.789760e-08, 1.000000e+00, 23, 2427, 1) + ((-1.063244e+01, 3.985917e+00, -1.822054e+01), (-5.990363e-01, 3.483710e-01, -7.209668e-01), 1.813617e+04, 3.176800e-08, 1.000000e+00, 23, 2426, 1) + ((-1.081038e+01, 4.089400e+00, -1.843470e+01), (-5.990363e-01, 3.483710e-01, -7.209668e-01), 1.813617e+04, 3.336273e-08, 1.000000e+00, 23, 2437, 1) + ((-1.081564e+01, 4.092455e+00, -1.844103e+01), (-6.690877e-01, 6.718700e-01, -3.176671e-01), 1.355236e+04, 3.340982e-08, 1.000000e+00, 23, 2437, 1) + ((-1.096190e+01, 4.239327e+00, -1.851047e+01), (-5.904492e-01, 8.058629e-01, 4.421237e-02), 1.153343e+04, 3.476743e-08, 1.000000e+00, 23, 2437, 1) + ((-1.109457e+01, 4.420404e+00, -1.850054e+01), (-5.904492e-01, 8.058629e-01, 4.421237e-02), 1.153343e+04, 3.628014e-08, 1.000000e+00, 22, 2437, 3) + ((-1.113484e+01, 4.475368e+00, -1.849752e+01), (-3.382310e-01, -9.403895e-01, -3.560132e-02), 1.114105e+04, 3.673931e-08, 1.000000e+00, 22, 2437, 3) + ((-1.117187e+01, 4.372425e+00, -1.850142e+01), (-3.382310e-01, -9.403895e-01, -3.560132e-02), 1.114105e+04, 3.748914e-08, 1.000000e+00, 23, 2437, 1) + ((-1.127367e+01, 4.089400e+00, -1.851213e+01), (-3.382310e-01, -9.403895e-01, -3.560132e-02), 1.114105e+04, 3.955064e-08, 1.000000e+00, 23, 2426, 1) + ((-1.135375e+01, 3.866733e+00, -1.852056e+01), (-3.382310e-01, -9.403895e-01, -3.560132e-02), 1.114105e+04, 4.117251e-08, 1.000000e+00, 22, 2426, 3) + ((-1.138419e+01, 3.782113e+00, -1.852377e+01), (-3.382310e-01, -9.403895e-01, -3.560132e-02), 1.114105e+04, 4.178887e-08, 1.000000e+00, 21, 2426, 2) + ((-1.172456e+01, 2.835777e+00, -1.855959e+01), (-3.382310e-01, -9.403895e-01, -3.560132e-02), 1.114105e+04, 4.868184e-08, 1.000000e+00, 22, 2426, 3) + ((-1.175499e+01, 2.751157e+00, -1.856280e+01), (-3.382310e-01, -9.403895e-01, -3.560132e-02), 1.114105e+04, 4.929820e-08, 1.000000e+00, 23, 2426, 1) + ((-1.179525e+01, 2.639224e+00, -1.856703e+01), (-4.738733e-01, -7.934600e-01, 3.819231e-01), 8.873828e+03, 5.011351e-08, 1.000000e+00, 23, 2426, 1) + ((-1.190609e+01, 2.453640e+00, -1.847770e+01), (-4.738733e-01, -7.934600e-01, 3.819231e-01), 8.873828e+03, 5.190861e-08, 1.000000e+00, 23, 2411, 1) + ((-1.222017e+01, 1.927741e+00, -1.822457e+01), (-5.716267e-01, 2.823908e-01, 7.703884e-01), 1.028345e+03, 5.699551e-08, 1.000000e+00, 23, 2411, 1) + ((-1.226820e+01, 1.951470e+00, -1.815983e+01), (-5.716267e-01, 2.823908e-01, 7.703884e-01), 1.028345e+03, 5.888995e-08, 1.000000e+00, 23, 2100, 1) + ((-1.236181e+01, 1.997712e+00, -1.803368e+01), (1.748787e-01, 6.613493e-01, 7.294070e-01), 4.250133e+02, 6.258186e-08, 1.000000e+00, 23, 2100, 1) + ((-1.234968e+01, 2.043587e+00, -1.798308e+01), (-6.043263e-01, 1.493281e-01, 7.826180e-01), 4.022525e+02, 6.501444e-08, 1.000000e+00, 23, 2100, 1) + ((-1.276513e+01, 2.146245e+00, -1.744506e+01), (-6.043263e-01, 1.493281e-01, 7.826180e-01), 4.022525e+02, 8.979603e-08, 1.000000e+00, 22, 2100, 3) + ((-1.313233e+01, 2.236980e+00, -1.696953e+01), (-6.043263e-01, 1.493281e-01, 7.826180e-01), 4.022525e+02, 1.116994e-07, 1.000000e+00, 23, 2100, 1) + ((-1.318468e+01, 2.249916e+00, -1.690173e+01), (-8.862976e-01, -6.402442e-02, 4.586692e-01), 3.113244e+02, 1.148223e-07, 1.000000e+00, 23, 2100, 1) + ((-1.363685e+01, 2.217253e+00, -1.666773e+01), (5.149966e-01, 4.184610e-01, -7.481102e-01), 2.471907e+02, 1.357269e-07, 1.000000e+00, 23, 2100, 1) + ((-1.334593e+01, 2.453640e+00, -1.709033e+01), (5.149966e-01, 4.184610e-01, -7.481102e-01), 2.471907e+02, 1.617034e-07, 1.000000e+00, 23, 2101, 1) + ((-1.308145e+01, 2.668542e+00, -1.747452e+01), (5.149966e-01, 4.184610e-01, -7.481102e-01), 2.471907e+02, 1.853189e-07, 1.000000e+00, 22, 2101, 3) + ((-1.304150e+01, 2.701005e+00, -1.753256e+01), (-1.624676e-01, -9.847331e-01, 6.248920e-02), 2.387811e+02, 1.888862e-07, 1.000000e+00, 22, 2101, 3) + ((-1.304665e+01, 2.669815e+00, -1.753058e+01), (-1.624676e-01, -9.847331e-01, 6.248920e-02), 2.387811e+02, 1.903682e-07, 1.000000e+00, 23, 2101, 1) + ((-1.308231e+01, 2.453640e+00, -1.751686e+01), (-1.624676e-01, -9.847331e-01, 6.248920e-02), 2.387811e+02, 2.006392e-07, 1.000000e+00, 23, 2100, 1) + ((-1.311790e+01, 2.237916e+00, -1.750317e+01), (-1.624676e-01, -9.847331e-01, 6.248920e-02), 2.387811e+02, 2.108888e-07, 1.000000e+00, 22, 2100, 3) + ((-1.313266e+01, 2.148507e+00, -1.749750e+01), (-1.624676e-01, -9.847331e-01, 6.248920e-02), 2.387811e+02, 2.151369e-07, 1.000000e+00, 21, 2100, 2) + ((-1.320190e+01, 1.728791e+00, -1.747087e+01), (1.090151e-01, 4.333767e-01, -8.945950e-01), 2.357642e+02, 2.350787e-07, 1.000000e+00, 21, 2100, 2) + ((-1.309582e+01, 2.150526e+00, -1.834143e+01), (1.090151e-01, 4.333767e-01, -8.945950e-01), 2.357642e+02, 2.808996e-07, 1.000000e+00, 22, 2100, 3) + ((-1.307365e+01, 2.238628e+00, -1.852329e+01), (1.090151e-01, 4.333767e-01, -8.945950e-01), 2.357642e+02, 2.904717e-07, 1.000000e+00, 23, 2100, 1) + ((-1.301957e+01, 2.453640e+00, -1.896713e+01), (1.090151e-01, 4.333767e-01, -8.945950e-01), 2.357642e+02, 3.138324e-07, 1.000000e+00, 23, 2101, 1) + ((-1.297913e+01, 2.614390e+00, -1.929896e+01), (6.012134e-01, 7.985735e-01, -2.868317e-02), 4.041011e+01, 3.312976e-07, 1.000000e+00, 23, 2101, 1) + ((-1.295975e+01, 2.640133e+00, -1.929988e+01), (6.762805e-01, -5.875192e-01, 4.443713e-01), 3.527833e+01, 3.349640e-07, 1.000000e+00, 23, 2101, 1) + ((-1.282907e+01, 2.526604e+00, -1.921402e+01), (-1.767101e-01, -9.842348e-01, 7.448995e-03), 6.258032e+00, 3.584852e-07, 1.000000e+00, 23, 2101, 1) + ((-1.284217e+01, 2.453640e+00, -1.921346e+01), (-1.767101e-01, -9.842348e-01, 7.448995e-03), 6.258032e+00, 3.799102e-07, 1.000000e+00, 23, 2100, 1) + ((-1.288683e+01, 2.204886e+00, -1.921158e+01), (-1.767101e-01, -9.842348e-01, 7.448995e-03), 6.258032e+00, 4.529534e-07, 1.000000e+00, 22, 2100, 3) + ((-1.290264e+01, 2.116831e+00, -1.921091e+01), (-1.767101e-01, -9.842348e-01, 7.448995e-03), 6.258032e+00, 4.788097e-07, 1.000000e+00, 21, 2100, 2) + ((-1.308145e+01, 1.120923e+00, -1.920338e+01), (-1.767101e-01, -9.842348e-01, 7.448995e-03), 6.258032e+00, 7.712448e-07, 1.000000e+00, 22, 2100, 3) + ((-1.309726e+01, 1.032868e+00, -1.920271e+01), (-1.767101e-01, -9.842348e-01, 7.448995e-03), 6.258032e+00, 7.971010e-07, 1.000000e+00, 23, 2100, 1) + ((-1.312690e+01, 8.677798e-01, -1.920146e+01), (-6.009065e-01, -7.617185e-01, 2.422732e-01), 3.971935e+00, 8.455768e-07, 1.000000e+00, 23, 2100, 1) + ((-1.316626e+01, 8.178800e-01, -1.918559e+01), (-6.009065e-01, -7.617185e-01, 2.422732e-01), 3.971935e+00, 8.693415e-07, 1.000000e+00, 23, 2099, 1) + ((-1.324019e+01, 7.241633e-01, -1.915578e+01), (-9.367321e-01, -1.471894e-01, -3.175976e-01), 2.365289e+00, 9.139738e-07, 1.000000e+00, 23, 2099, 1) + ((-1.348497e+01, 6.857011e-01, -1.923877e+01), (-9.722929e-01, 2.325825e-01, -2.349230e-02), 1.799413e+00, 1.036815e-06, 1.000000e+00, 23, 2099, 1) + ((-1.390396e+01, 7.859275e-01, -1.924890e+01), (-9.722929e-01, 2.325825e-01, -2.349230e-02), 1.799413e+00, 1.269071e-06, 1.000000e+00, 23, 2114, 1) + ((-1.403754e+01, 8.178800e-01, -1.925212e+01), (-9.722929e-01, 2.325825e-01, -2.349230e-02), 1.799413e+00, 1.343115e-06, 1.000000e+00, 23, 2115, 1) + ((-1.504223e+01, 1.058212e+00, -1.927640e+01), (-4.085804e-01, -2.719942e-01, -8.712526e-01), 1.628266e+00, 1.900041e-06, 1.000000e+00, 23, 2115, 1) + ((-1.523744e+01, 9.282558e-01, -1.969268e+01), (-6.666265e-01, 3.934879e-01, -6.330690e-01), 4.946445e-01, 2.170751e-06, 1.000000e+00, 23, 2115, 1) + ((-1.553972e+01, 1.106680e+00, -1.997974e+01), (-6.666265e-01, 3.934879e-01, -6.330690e-01), 4.946445e-01, 2.636878e-06, 1.000000e+00, 23, 2129, 1) + ((-1.585973e+01, 1.295571e+00, -2.028364e+01), (-6.666265e-01, 3.934879e-01, -6.330690e-01), 4.946445e-01, 3.130348e-06, 1.000000e+00, 22, 2129, 3) + ((-1.593583e+01, 1.340489e+00, -2.035590e+01), (-6.666265e-01, 3.934879e-01, -6.330690e-01), 4.946445e-01, 3.247693e-06, 1.000000e+00, 21, 2129, 2) + ((-1.647184e+01, 1.656882e+00, -2.086494e+01), (6.457458e-01, -1.162934e-01, -7.546444e-01), 4.958073e-01, 4.074258e-06, 1.000000e+00, 21, 2129, 2) + ((-1.585080e+01, 1.545037e+00, -2.159072e+01), (6.457458e-01, -1.162934e-01, -7.546444e-01), 4.958073e-01, 5.061748e-06, 1.000000e+00, 22, 2129, 3) + ((-1.576406e+01, 1.529415e+00, -2.169209e+01), (6.457458e-01, -1.162934e-01, -7.546444e-01), 4.958073e-01, 5.199673e-06, 1.000000e+00, 23, 2129, 1) + ((-1.553972e+01, 1.489015e+00, -2.195425e+01), (6.457458e-01, -1.162934e-01, -7.546444e-01), 4.958073e-01, 5.556377e-06, 1.000000e+00, 23, 2115, 1) + ((-1.546081e+01, 1.474803e+00, -2.204648e+01), (3.257038e-01, -9.420596e-01, -8.025439e-02), 3.076319e-01, 5.681852e-06, 1.000000e+00, 23, 2115, 1) + ((-1.543563e+01, 1.401988e+00, -2.205268e+01), (-3.677195e-01, -8.784218e-01, -3.052172e-01), 8.422973e-02, 5.782605e-06, 1.000000e+00, 23, 2115, 1) + ((-1.553972e+01, 1.153338e+00, -2.213907e+01), (-3.677195e-01, -8.784218e-01, -3.052172e-01), 8.422973e-02, 6.487752e-06, 1.000000e+00, 23, 2129, 1) + ((-1.565955e+01, 8.670807e-01, -2.223854e+01), (1.611814e-01, 2.692337e-01, -9.494913e-01), 8.559448e-02, 7.299552e-06, 1.000000e+00, 23, 2129, 1) + ((-1.563263e+01, 9.120446e-01, -2.239711e+01), (-6.773989e-01, 6.985688e-01, -2.305048e-01), 3.418159e-02, 7.712256e-06, 1.000000e+00, 23, 2129, 1) + ((-1.592604e+01, 1.214620e+00, -2.249695e+01), (-6.773989e-01, 6.985688e-01, -2.305048e-01), 3.418159e-02, 9.406031e-06, 1.000000e+00, 22, 2129, 3) + ((-1.598742e+01, 1.277922e+00, -2.251784e+01), (-6.773989e-01, 6.985688e-01, -2.305048e-01), 3.418159e-02, 9.760391e-06, 1.000000e+00, 21, 2129, 2) + ((-1.670388e+01, 2.016770e+00, -2.276163e+01), (-6.773989e-01, 6.985688e-01, -2.305048e-01), 3.418159e-02, 1.389636e-05, 1.000000e+00, 22, 2129, 3) + ((-1.676526e+01, 2.080073e+00, -2.278252e+01), (-6.773989e-01, 6.985688e-01, -2.305048e-01), 3.418159e-02, 1.425072e-05, 1.000000e+00, 23, 2129, 1) + ((-1.681575e+01, 2.132139e+00, -2.279970e+01), (4.286076e-02, -9.409799e-01, -3.357375e-01), 1.813618e-02, 1.454218e-05, 1.000000e+00, 23, 2129, 1) + ((-1.681374e+01, 2.087933e+00, -2.281547e+01), (-7.741702e-02, -7.222450e-01, -6.872909e-01), 1.529603e-02, 1.479439e-05, 1.000000e+00, 23, 2129, 1) + ((-1.682083e+01, 2.021795e+00, -2.287841e+01), (-7.741702e-02, -7.222450e-01, -6.872909e-01), 1.529603e-02, 1.532970e-05, 1.000000e+00, 22, 2129, 3) + ((-1.684417e+01, 1.804084e+00, -2.308558e+01), (-7.741702e-02, -7.222450e-01, -6.872909e-01), 1.529603e-02, 1.709182e-05, 1.000000e+00, 21, 2129, 2) + ((-1.686879e+01, 1.574384e+00, -2.330417e+01), (-7.741702e-02, -7.222450e-01, -6.872909e-01), 1.529603e-02, 1.895097e-05, 1.000000e+00, 22, 2129, 3) + ((-1.689212e+01, 1.356673e+00, -2.351134e+01), (-7.741702e-02, -7.222450e-01, -6.872909e-01), 1.529603e-02, 2.071309e-05, 1.000000e+00, 23, 2129, 1) + ((-1.689233e+01, 1.354754e+00, -2.351317e+01), (6.002458e-01, 4.967948e-01, 6.268172e-01), 8.602342e-03, 2.072862e-05, 1.000000e+00, 23, 2129, 1) + ((-1.689148e+01, 1.355453e+00, -2.351229e+01), (6.002458e-01, 4.967948e-01, 6.268172e-01), 8.602342e-03, 2.073958e-05, 1.000000e+00, 22, 2129, 3) + ((-1.682182e+01, 1.413107e+00, -2.343954e+01), (6.002458e-01, 4.967948e-01, 6.268172e-01), 8.602342e-03, 2.164421e-05, 1.000000e+00, 21, 2129, 2) + ((-1.636364e+01, 1.792326e+00, -2.296107e+01), (9.641593e-01, -1.162836e-01, 2.384846e-01), 7.837617e-03, 2.759442e-05, 1.000000e+00, 21, 2129, 2) + ((-1.598564e+01, 1.746738e+00, -2.286758e+01), (9.641593e-01, -1.162836e-01, 2.384846e-01), 7.837617e-03, 3.079609e-05, 0.000000e+00, 21, 2129, 2)] +ParticleType.NEUTRON [((-9.469716e-01, -2.580266e-01, 1.414357e-01), (1.438873e-01, 2.365819e-01, 9.608983e-01), 9.724461e+05, 0.000000e+00, 1.000000e+00, 23, 2403, 1) + ((-9.064818e-01, -1.914524e-01, 4.118326e-01), (9.231638e-01, 3.100833e-01, 2.271937e-01), 1.746594e+05, 2.064696e-10, 1.000000e+00, 23, 2403, 1) + ((-8.178800e-01, -1.616918e-01, 4.336377e-01), (9.231638e-01, 3.100833e-01, 2.271937e-01), 1.746594e+05, 3.725262e-10, 1.000000e+00, 11, 1756, 1) + ((-3.834875e-01, -1.578285e-02, 5.405432e-01), (-5.547498e-01, -6.358598e-02, 8.295839e-01), 1.474011e+05, 1.186660e-09, 1.000000e+00, 11, 1756, 1) + ((-7.999329e-01, -6.351625e-02, 1.163304e+00), (-5.476898e-01, 2.985279e-03, 8.366761e-01), 1.467299e+05, 2.600465e-09, 1.000000e+00, 11, 1756, 1) + ((-8.178800e-01, -6.341842e-02, 1.190721e+00), (-5.476898e-01, 2.985279e-03, 8.366761e-01), 1.467299e+05, 2.662321e-09, 1.000000e+00, 23, 2403, 1) + ((-1.035984e+00, -6.222961e-02, 1.523906e+00), (-5.476898e-01, 2.985279e-03, 8.366761e-01), 1.467299e+05, 3.414026e-09, 1.000000e+00, 22, 2403, 3) + ((-1.124618e+00, -6.174649e-02, 1.659308e+00), (-5.476898e-01, 2.985279e-03, 8.366761e-01), 1.467299e+05, 3.719509e-09, 1.000000e+00, 21, 2403, 2) + ((-1.705404e+00, -5.858082e-02, 2.546543e+00), (-3.196694e-01, -8.974451e-01, 3.039799e-01), 1.460188e+05, 5.721217e-09, 1.000000e+00, 21, 2403, 2) + ((-1.851116e+00, -4.676544e-01, 2.685103e+00), (-3.196694e-01, -8.974451e-01, 3.039799e-01), 1.460188e+05, 6.583734e-09, 1.000000e+00, 22, 2403, 3) + ((-1.880791e+00, -5.509662e-01, 2.713322e+00), (-3.196694e-01, -8.974451e-01, 3.039799e-01), 1.460188e+05, 6.759394e-09, 1.000000e+00, 23, 2403, 1) + ((-1.948026e+00, -7.397227e-01, 2.777257e+00), (-8.068708e-01, -5.218555e-01, -2.768147e-01), 6.012975e+04, 7.157380e-09, 1.000000e+00, 23, 2403, 1) + ((-2.068870e+00, -8.178800e-01, 2.735799e+00), (-8.068708e-01, -5.218555e-01, -2.768147e-01), 6.012975e+04, 7.598974e-09, 1.000000e+00, 23, 2388, 1) + ((-2.111345e+00, -8.453511e-01, 2.721228e+00), (-4.440083e-01, -4.097242e-01, 7.968580e-01), 7.445994e+03, 7.754188e-09, 1.000000e+00, 23, 2388, 1) + ((-2.453640e+00, -1.161216e+00, 3.335542e+00), (-4.440083e-01, -4.097242e-01, 7.968580e-01), 7.445994e+03, 1.421339e-08, 1.000000e+00, 23, 2387, 1) + ((-2.715369e+00, -1.402736e+00, 3.805265e+00), (-4.440083e-01, -4.097242e-01, 7.968580e-01), 7.445994e+03, 1.915229e-08, 1.000000e+00, 22, 2387, 3) + ((-2.785083e+00, -1.467067e+00, 3.930380e+00), (-4.440083e-01, -4.097242e-01, 7.968580e-01), 7.445994e+03, 2.046780e-08, 1.000000e+00, 21, 2387, 2) + ((-2.940196e+00, -1.610203e+00, 4.208760e+00), (-7.727102e-01, 4.333905e-01, -4.637797e-01), 6.395532e+03, 2.339483e-08, 1.000000e+00, 21, 2387, 2) + ((-3.600602e+00, -1.239800e+00, 3.812386e+00), (-7.727102e-01, 4.333905e-01, -4.637797e-01), 6.395532e+03, 3.112139e-08, 1.000000e+00, 22, 2387, 3) + ((-3.682067e+00, -1.194109e+00, 3.763490e+00), (-7.727102e-01, 4.333905e-01, -4.637797e-01), 6.395532e+03, 3.207450e-08, 1.000000e+00, 23, 2387, 1) + ((-4.089400e+00, -9.656478e-01, 3.519009e+00), (-7.727102e-01, 4.333905e-01, -4.637797e-01), 6.395532e+03, 3.684019e-08, 1.000000e+00, 23, 2386, 1) + ((-4.185927e+00, -9.115084e-01, 3.461074e+00), (4.334152e-01, -1.576729e-02, -9.010564e-01), 2.532713e+01, 3.796953e-08, 1.000000e+00, 23, 2386, 1) + ((-4.089400e+00, -9.150200e-01, 3.260397e+00), (4.334152e-01, -1.576729e-02, -9.010564e-01), 2.532713e+01, 6.996444e-08, 1.000000e+00, 23, 2387, 1) + ((-3.920090e+00, -9.211794e-01, 2.908406e+00), (9.094673e-01, -3.659267e-01, -1.974002e-01), 9.547085e+00, 1.260840e-07, 1.000000e+00, 23, 2387, 1) + ((-3.729948e+00, -9.976834e-01, 2.867135e+00), (-2.916959e-01, -6.494657e-01, -7.022164e-01), 8.590591e+00, 1.750036e-07, 1.000000e+00, 23, 2387, 1) + ((-3.744933e+00, -1.031047e+00, 2.831062e+00), (-2.916959e-01, -6.494657e-01, -7.022164e-01), 8.590591e+00, 1.876753e-07, 0.000000e+00, 23, 2387, 1)] +ParticleType.NEUTRON [((6.474155e+00, -5.192870e+00, 5.413003e+00), (-3.042035e-02, -9.914979e-01, 1.265170e-01), 2.052226e+06, 4.450859e-05, 1.000000e+00, 21, 2367, 2) + ((6.467293e+00, -5.416535e+00, 5.441544e+00), (-3.042035e-02, -9.914979e-01, 1.265170e-01), 2.052226e+06, 4.450870e-05, 1.000000e+00, 22, 2367, 3) + ((6.464574e+00, -5.505149e+00, 5.452851e+00), (-3.042035e-02, -9.914979e-01, 1.265170e-01), 2.052226e+06, 4.450874e-05, 1.000000e+00, 23, 2367, 1) + ((6.461877e+00, -5.593034e+00, 5.464065e+00), (6.364108e-01, -7.713322e-01, 5.284745e-03), 1.141417e+06, 4.450879e-05, 1.000000e+00, 23, 2367, 1) + ((6.570892e+00, -5.725160e+00, 5.464970e+00), (6.364108e-01, -7.713322e-01, 5.284745e-03), 1.141417e+06, 4.450891e-05, 1.000000e+00, 32, 7, 1) + ((6.821003e+00, -6.028296e+00, 5.467047e+00), (6.364108e-01, -7.713322e-01, 5.284745e-03), 1.141417e+06, 4.450917e-05, 1.000000e+00, 31, 7, 4) + ((7.101210e+00, -6.367908e+00, 5.469374e+00), (6.364108e-01, -7.713322e-01, 5.284745e-03), 1.141417e+06, 4.450947e-05, 1.000000e+00, 32, 7, 1) + ((7.360920e+00, -6.682677e+00, 5.471531e+00), (6.364108e-01, -7.713322e-01, 5.284745e-03), 1.141417e+06, 4.450975e-05, 1.000000e+00, 23, 2353, 1) + ((7.833926e+00, -7.255962e+00, 5.475459e+00), (9.404210e-01, 3.383106e-01, -3.397550e-02), 1.289848e+05, 4.451025e-05, 1.000000e+00, 23, 2353, 1) + ((8.142528e+00, -7.144944e+00, 5.464309e+00), (9.404210e-01, 3.383106e-01, -3.397550e-02), 1.289848e+05, 4.451091e-05, 1.000000e+00, 22, 2353, 3) + ((8.590199e+00, -6.983897e+00, 5.448136e+00), (9.404210e-01, 3.383106e-01, -3.397550e-02), 1.289848e+05, 4.451187e-05, 1.000000e+00, 23, 2353, 1) + ((8.682515e+00, -6.950687e+00, 5.444801e+00), (6.997233e-01, 7.142820e-01, 1.373092e-02), 1.043159e+05, 4.451207e-05, 1.000000e+00, 23, 2353, 1) + ((8.996680e+00, -6.629985e+00, 5.450966e+00), (6.997233e-01, 7.142820e-01, 1.373092e-02), 1.043159e+05, 4.451307e-05, 1.000000e+00, 23, 2354, 1) + ((9.231138e+00, -6.390649e+00, 5.455567e+00), (6.997233e-01, 7.142820e-01, 1.373092e-02), 1.043159e+05, 4.451382e-05, 1.000000e+00, 22, 2354, 3) + ((9.650189e+00, -5.962879e+00, 5.463790e+00), (6.997233e-01, 7.142820e-01, 1.373092e-02), 1.043159e+05, 4.451516e-05, 1.000000e+00, 23, 2354, 1) + ((9.721007e+00, -5.890588e+00, 5.465180e+00), (6.565855e-01, -1.880518e-01, -7.304327e-01), 1.036285e+04, 4.451539e-05, 1.000000e+00, 23, 2354, 1) + ((9.939012e+00, -5.953026e+00, 5.222655e+00), (6.565855e-01, -1.880518e-01, -7.304327e-01), 1.036285e+04, 4.451775e-05, 1.000000e+00, 22, 2354, 3) + ((1.002133e+01, -5.976602e+00, 5.131083e+00), (6.565855e-01, -1.880518e-01, -7.304327e-01), 1.036285e+04, 4.451864e-05, 1.000000e+00, 23, 2354, 1) + ((1.020258e+01, -6.028514e+00, 4.929446e+00), (8.848761e-01, -4.005580e-02, -4.641012e-01), 8.908873e+03, 4.452060e-05, 1.000000e+00, 23, 2354, 1) + ((1.063244e+01, -6.047973e+00, 4.703991e+00), (8.848761e-01, -4.005580e-02, -4.641012e-01), 8.908873e+03, 4.452432e-05, 1.000000e+00, 23, 2355, 1) + ((1.084241e+01, -6.057477e+00, 4.593868e+00), (9.066652e-01, -6.565865e-02, 4.167099e-01), 3.324374e+03, 4.452614e-05, 1.000000e+00, 23, 2355, 1) + ((1.107130e+01, -6.074053e+00, 4.699070e+00), (9.066652e-01, -6.565865e-02, 4.167099e-01), 3.324374e+03, 4.452930e-05, 1.000000e+00, 22, 2355, 3) + ((1.121611e+01, -6.084540e+00, 4.765623e+00), (9.066652e-01, -6.565865e-02, 4.167099e-01), 3.324374e+03, 4.453130e-05, 1.000000e+00, 21, 2355, 2) + ((1.174815e+01, -6.123069e+00, 5.010155e+00), (9.066652e-01, -6.565865e-02, 4.167099e-01), 3.324374e+03, 4.453866e-05, 1.000000e+00, 22, 2355, 3) + ((1.189296e+01, -6.133556e+00, 5.076709e+00), (9.066652e-01, -6.565865e-02, 4.167099e-01), 3.324374e+03, 4.454067e-05, 1.000000e+00, 23, 2355, 1) + ((1.221698e+01, -6.157021e+00, 5.225634e+00), (5.263177e-01, -7.048209e-01, -4.756229e-01), 3.449618e+02, 4.454515e-05, 1.000000e+00, 23, 2355, 1) + ((1.226820e+01, -6.225607e+00, 5.179351e+00), (5.263177e-01, -7.048209e-01, -4.756229e-01), 3.449618e+02, 4.454893e-05, 1.000000e+00, 23, 2519, 1) + ((1.230378e+01, -6.273253e+00, 5.147199e+00), (9.203567e-01, -2.667811e-01, -2.859569e-01), 2.269067e+02, 4.455157e-05, 1.000000e+00, 23, 2519, 1) + ((1.237909e+01, -6.295083e+00, 5.123800e+00), (8.444192e-01, -5.115818e-01, 1.588717e-01), 1.686474e+02, 4.455549e-05, 1.000000e+00, 23, 2519, 1) + ((1.250758e+01, -6.372926e+00, 5.147974e+00), (8.444192e-01, -5.115818e-01, 1.588717e-01), 1.686474e+02, 4.456396e-05, 1.000000e+00, 22, 2519, 3) + ((1.258603e+01, -6.420455e+00, 5.162734e+00), (8.444192e-01, -5.115818e-01, 1.588717e-01), 1.686474e+02, 4.456914e-05, 1.000000e+00, 21, 2519, 2) + ((1.264082e+01, -6.453648e+00, 5.173042e+00), (3.837750e-01, -8.931658e-01, -2.344604e-01), 1.632815e+02, 4.457275e-05, 1.000000e+00, 21, 2519, 2) + ((1.288240e+01, -7.015898e+00, 5.025449e+00), (3.837750e-01, -8.931658e-01, -2.344604e-01), 1.632815e+02, 4.460837e-05, 1.000000e+00, 22, 2519, 3) + ((1.292943e+01, -7.125332e+00, 4.996722e+00), (3.837750e-01, -8.931658e-01, -2.344604e-01), 1.632815e+02, 4.461530e-05, 1.000000e+00, 23, 2519, 1) + ((1.303065e+01, -7.360920e+00, 4.934879e+00), (3.837750e-01, -8.931658e-01, -2.344604e-01), 1.632815e+02, 4.463022e-05, 1.000000e+00, 23, 2520, 1) + ((1.309295e+01, -7.505897e+00, 4.896822e+00), (3.189366e-01, -9.220512e-01, 2.193195e-01), 1.303968e+02, 4.463941e-05, 1.000000e+00, 23, 2520, 1) + ((1.311741e+01, -7.576618e+00, 4.913644e+00), (3.189366e-01, -9.220512e-01, 2.193195e-01), 1.303968e+02, 4.464426e-05, 1.000000e+00, 22, 2520, 3) + ((1.314895e+01, -7.667795e+00, 4.935331e+00), (3.189366e-01, -9.220512e-01, 2.193195e-01), 1.303968e+02, 4.465052e-05, 1.000000e+00, 21, 2520, 2) + ((1.345125e+01, -8.541749e+00, 5.143210e+00), (3.189366e-01, -9.220512e-01, 2.193195e-01), 1.303968e+02, 4.471053e-05, 1.000000e+00, 22, 2520, 3) + ((1.348278e+01, -8.632925e+00, 5.164897e+00), (3.189366e-01, -9.220512e-01, 2.193195e-01), 1.303968e+02, 4.471679e-05, 1.000000e+00, 23, 2520, 1) + ((1.356121e+01, -8.859646e+00, 5.218825e+00), (3.469373e-01, -8.669828e-01, 3.577365e-01), 1.276458e+02, 4.473236e-05, 1.000000e+00, 23, 2520, 1) + ((1.361604e+01, -8.996680e+00, 5.275368e+00), (3.469373e-01, -8.669828e-01, 3.577365e-01), 1.276458e+02, 4.474248e-05, 1.000000e+00, 23, 2521, 1) + ((1.390396e+01, -9.716172e+00, 5.572247e+00), (3.469373e-01, -8.669828e-01, 3.577365e-01), 1.276458e+02, 4.479558e-05, 1.000000e+00, 23, 2536, 1) + ((1.399742e+01, -9.949713e+00, 5.668611e+00), (-5.897476e-01, -6.217559e-01, 5.153807e-01), 3.643244e+01, 4.481282e-05, 1.000000e+00, 23, 2536, 1) + ((1.390396e+01, -1.004824e+01, 5.750282e+00), (-5.897476e-01, -6.217559e-01, 5.153807e-01), 3.643244e+01, 4.483180e-05, 1.000000e+00, 23, 2521, 1) + ((1.334984e+01, -1.063244e+01, 6.234531e+00), (-5.897476e-01, -6.217559e-01, 5.153807e-01), 3.643244e+01, 4.494435e-05, 1.000000e+00, 23, 2522, 1) + ((1.317711e+01, -1.081455e+01, 6.385480e+00), (-4.211158e-01, -4.515418e-01, 7.866203e-01), 3.302717e+01, 4.497943e-05, 1.000000e+00, 23, 2522, 1) + ((1.316679e+01, -1.082561e+01, 6.404749e+00), (-5.182259e-01, 3.860612e-01, 7.631505e-01), 1.437084e+01, 4.498251e-05, 1.000000e+00, 23, 2522, 1) + ((1.292206e+01, -1.064329e+01, 6.765147e+00), (-8.169098e-01, 1.482852e-01, 5.573777e-01), 1.219816e+01, 4.507257e-05, 1.000000e+00, 23, 2522, 1) + ((1.286229e+01, -1.063244e+01, 6.805923e+00), (-8.169098e-01, 1.482852e-01, 5.573777e-01), 1.219816e+01, 4.508772e-05, 1.000000e+00, 23, 2521, 1) + ((1.284917e+01, -1.063006e+01, 6.814880e+00), (-5.213758e-01, -3.360250e-01, 7.843816e-01), 8.052778e+00, 4.509105e-05, 1.000000e+00, 23, 2521, 1) + ((1.284547e+01, -1.063244e+01, 6.820442e+00), (-5.213758e-01, -3.360250e-01, 7.843816e-01), 8.052778e+00, 4.509285e-05, 1.000000e+00, 23, 2522, 1) + ((1.249929e+01, -1.085555e+01, 7.341246e+00), (-1.520079e-01, -3.513652e-01, 9.238161e-01), 7.069024e+00, 4.526201e-05, 1.000000e+00, 23, 2522, 1) + ((1.240103e+01, -1.108268e+01, 7.938428e+00), (-7.405967e-01, -3.230926e-01, 5.891754e-01), 4.460475e+00, 4.543779e-05, 1.000000e+00, 23, 2522, 1) + ((1.229354e+01, -1.112958e+01, 8.023945e+00), (-6.892797e-01, 5.508903e-01, 4.705458e-01), 2.006774e+00, 4.548748e-05, 1.000000e+00, 23, 2522, 1) + ((1.226820e+01, -1.110933e+01, 8.041241e+00), (-6.892797e-01, 5.508903e-01, 4.705458e-01), 2.006774e+00, 4.550624e-05, 1.000000e+00, 23, 2314, 1) + ((1.198501e+01, -1.088299e+01, 8.234567e+00), (-7.677533e-01, 4.569444e-01, 4.491733e-01), 1.935517e+00, 4.571593e-05, 1.000000e+00, 23, 2314, 1) + ((1.156403e+01, -1.063244e+01, 8.480859e+00), (-7.677533e-01, 4.569444e-01, 4.491733e-01), 1.935517e+00, 4.600087e-05, 1.000000e+00, 23, 2329, 1) + ((1.150264e+01, -1.059590e+01, 8.516776e+00), (-7.780064e-01, -9.937785e-02, 6.203467e-01), 1.893413e+00, 4.604243e-05, 1.000000e+00, 23, 2329, 1) + ((1.121659e+01, -1.063244e+01, 8.744860e+00), (-7.780064e-01, -9.937785e-02, 6.203467e-01), 1.893413e+00, 4.623561e-05, 1.000000e+00, 23, 2314, 1) + ((1.063244e+01, -1.070706e+01, 9.210632e+00), (-7.780064e-01, -9.937785e-02, 6.203467e-01), 1.893413e+00, 4.663011e-05, 1.000000e+00, 23, 2313, 1) + ((1.035391e+01, -1.074263e+01, 9.432717e+00), (-4.847677e-01, -8.685325e-01, -1.032060e-01), 5.647686e-01, 4.681821e-05, 1.000000e+00, 23, 2313, 1) + ((1.031409e+01, -1.081397e+01, 9.424239e+00), (-4.227592e-01, -9.022601e-01, -8.486053e-02), 5.651902e-01, 4.689723e-05, 1.000000e+00, 23, 2313, 1) + ((1.028497e+01, -1.087614e+01, 9.418392e+00), (1.549579e-01, -4.270887e-01, 8.908329e-01), 5.395517e-01, 4.696349e-05, 1.000000e+00, 23, 2313, 1) + ((1.034128e+01, -1.103135e+01, 9.742126e+00), (5.631544e-01, 4.925564e-01, 6.635098e-01), 2.637257e-01, 4.732118e-05, 1.000000e+00, 23, 2313, 1) + ((1.044415e+01, -1.094137e+01, 9.863334e+00), (-1.034377e-01, 7.912271e-01, 6.027108e-01), 1.474934e-01, 4.757836e-05, 1.000000e+00, 23, 2313, 1) + ((1.043959e+01, -1.090643e+01, 9.889950e+00), (1.163182e-01, 9.160889e-01, -3.837331e-01), 1.522434e-01, 4.766149e-05, 1.000000e+00, 23, 2313, 1) + ((1.045733e+01, -1.076664e+01, 9.831395e+00), (-8.136862e-01, 9.805444e-02, -5.729748e-01), 3.341488e-02, 4.794423e-05, 1.000000e+00, 23, 2313, 1) + ((1.044231e+01, -1.076483e+01, 9.820815e+00), (-8.437241e-01, 5.222731e-01, -1.239373e-01), 3.874838e-02, 4.801727e-05, 1.000000e+00, 23, 2313, 1) + ((1.028725e+01, -1.066884e+01, 9.798037e+00), (-8.066297e-01, 3.960430e-01, -4.387465e-01), 3.989754e-02, 4.869227e-05, 1.000000e+00, 23, 2313, 1) + ((1.021310e+01, -1.063244e+01, 9.757708e+00), (-8.066297e-01, 3.960430e-01, -4.387465e-01), 3.989754e-02, 4.902498e-05, 1.000000e+00, 23, 2328, 1) + ((1.012717e+01, -1.059025e+01, 9.710967e+00), (-4.762033e-01, 8.377852e-01, -2.671075e-01), 3.271721e-02, 4.941058e-05, 1.000000e+00, 23, 2328, 1) + ((1.001043e+01, -1.038486e+01, 9.645483e+00), (-4.762033e-01, 8.377852e-01, -2.671075e-01), 3.271721e-02, 5.039049e-05, 1.000000e+00, 22, 2328, 3) + ((9.971285e+00, -1.031600e+01, 9.623530e+00), (5.437395e-01, -6.104117e-01, -5.759730e-01), 2.765456e-02, 5.071901e-05, 1.000000e+00, 22, 2328, 3) + ((1.002723e+01, -1.037881e+01, 9.564267e+00), (5.437395e-01, -6.104117e-01, -5.759730e-01), 2.765456e-02, 5.116633e-05, 1.000000e+00, 23, 2328, 1) + ((1.025152e+01, -1.063060e+01, 9.326683e+00), (-8.159392e-01, -4.792697e-03, 5.781179e-01), 2.920905e-02, 5.295966e-05, 1.000000e+00, 23, 2328, 1) + ((1.000779e+01, -1.063203e+01, 9.499373e+00), (-6.486477e-01, -4.621598e-01, -6.047020e-01), 2.383638e-02, 5.422329e-05, 1.000000e+00, 23, 2328, 1) + ((1.000721e+01, -1.063244e+01, 9.498835e+00), (-6.486477e-01, -4.621598e-01, -6.047020e-01), 2.383638e-02, 5.422746e-05, 1.000000e+00, 23, 2313, 1) + ((9.852478e+00, -1.074269e+01, 9.354584e+00), (-6.686452e-01, -2.988396e-01, 6.808880e-01), 2.993224e-02, 5.534454e-05, 1.000000e+00, 23, 2313, 1) + ((9.765042e+00, -1.078177e+01, 9.443621e+00), (7.712484e-01, -5.215773e-01, 3.648739e-01), 3.590785e-02, 5.589099e-05, 1.000000e+00, 23, 2313, 1) + ((9.798992e+00, -1.080472e+01, 9.459682e+00), (-4.563170e-01, 8.081811e-01, -3.723144e-01), 2.674168e-02, 5.605894e-05, 1.000000e+00, 23, 2313, 1) + ((9.792857e+00, -1.079386e+01, 9.454677e+00), (-9.080336e-01, 4.172659e-01, 3.693418e-02), 9.753404e-03, 5.611838e-05, 1.000000e+00, 23, 2313, 1) + ((9.653805e+00, -1.072996e+01, 9.460332e+00), (-3.727950e-01, 2.889342e-01, -8.817828e-01), 1.781716e-02, 5.723944e-05, 1.000000e+00, 23, 2313, 1) + ((9.628677e+00, -1.071048e+01, 9.400895e+00), (-6.406523e-01, 7.657269e-01, -5.680598e-02), 1.764425e-02, 5.760453e-05, 1.000000e+00, 23, 2313, 1) + ((9.563380e+00, -1.063244e+01, 9.395105e+00), (-6.406523e-01, 7.657269e-01, -5.680598e-02), 1.764425e-02, 5.815928e-05, 1.000000e+00, 23, 2328, 1) + ((9.411305e+00, -1.045068e+01, 9.381621e+00), (3.205568e-01, -1.311309e-02, -9.471385e-01), 2.153422e-02, 5.945128e-05, 1.000000e+00, 23, 2328, 1) + ((9.860026e+00, -1.046903e+01, 8.055798e+00), (6.278124e-01, 1.023095e-01, -7.716115e-01), 2.013742e-02, 6.634788e-05, 1.000000e+00, 23, 2328, 1) + ((1.007604e+01, -1.043383e+01, 7.790303e+00), (6.790346e-01, 5.167838e-01, 5.213891e-01), 1.340865e-02, 6.810089e-05, 1.000000e+00, 23, 2328, 1) + ((1.008869e+01, -1.042421e+01, 7.800011e+00), (2.792456e-01, 1.367953e-01, 9.504257e-01), 8.576635e-03, 6.821715e-05, 1.000000e+00, 23, 2328, 1) + ((1.017046e+01, -1.038415e+01, 8.078330e+00), (-9.462370e-01, 3.185163e-01, 5.641780e-02), 1.119274e-01, 7.050324e-05, 1.000000e+00, 23, 2328, 1) + ((1.008276e+01, -1.035463e+01, 8.083559e+00), (-9.462370e-01, 3.185163e-01, 5.641780e-02), 1.119274e-01, 7.070353e-05, 1.000000e+00, 22, 2328, 3) + ((9.952201e+00, -1.031068e+01, 8.091343e+00), (-9.462370e-01, 3.185163e-01, 5.641780e-02), 1.119274e-01, 7.100170e-05, 1.000000e+00, 21, 2328, 2) + ((9.404929e+00, -1.012646e+01, 8.123973e+00), (-9.462370e-01, 3.185163e-01, 5.641780e-02), 1.119274e-01, 7.225157e-05, 1.000000e+00, 22, 2328, 3) + ((9.274369e+00, -1.008251e+01, 8.131758e+00), (-9.462370e-01, 3.185163e-01, 5.641780e-02), 1.119274e-01, 7.254974e-05, 1.000000e+00, 23, 2328, 1) + ((9.126730e+00, -1.003281e+01, 8.140560e+00), (-8.389978e-01, -2.248732e-01, 4.954945e-01), 1.164832e-01, 7.288692e-05, 1.000000e+00, 23, 2328, 1) + ((8.996680e+00, -1.006767e+01, 8.217365e+00), (-8.389978e-01, -2.248732e-01, 4.954945e-01), 1.164832e-01, 7.321528e-05, 1.000000e+00, 23, 2327, 1) + ((8.694758e+00, -1.014859e+01, 8.395674e+00), (5.156148e-01, 7.316558e-01, 4.458937e-01), 1.391972e-01, 7.397758e-05, 1.000000e+00, 23, 2327, 1) + ((8.996680e+00, -9.720167e+00, 8.656770e+00), (5.156148e-01, 7.316558e-01, 4.458937e-01), 1.391972e-01, 7.511228e-05, 1.000000e+00, 23, 2328, 1) + ((9.263031e+00, -9.342216e+00, 8.887105e+00), (5.734531e-01, -5.017666e-01, 6.475970e-01), 9.798082e-02, 7.611330e-05, 1.000000e+00, 23, 2328, 1) + ((9.334809e+00, -9.405021e+00, 8.968164e+00), (5.419546e-03, -7.721039e-01, -6.354732e-01), 1.234091e-01, 7.640240e-05, 1.000000e+00, 23, 2328, 1) + ((9.335117e+00, -9.448857e+00, 8.932084e+00), (5.419546e-03, -7.721039e-01, -6.354732e-01), 1.234091e-01, 7.651925e-05, 1.000000e+00, 22, 2328, 3) + ((9.336345e+00, -9.623801e+00, 8.788098e+00), (5.419546e-03, -7.721039e-01, -6.354732e-01), 1.234091e-01, 7.698556e-05, 1.000000e+00, 21, 2328, 2) + ((9.339070e+00, -1.001201e+01, 8.468584e+00), (5.419546e-03, -7.721039e-01, -6.354732e-01), 1.234091e-01, 7.802034e-05, 1.000000e+00, 22, 2328, 3) + ((9.340298e+00, -1.018696e+01, 8.324598e+00), (5.419546e-03, -7.721039e-01, -6.354732e-01), 1.234091e-01, 7.848665e-05, 1.000000e+00, 23, 2328, 1) + ((9.343193e+00, -1.059945e+01, 7.985097e+00), (1.798796e-01, 3.522536e-02, -9.830577e-01), 1.283272e-01, 7.958616e-05, 1.000000e+00, 23, 2328, 1) + ((9.397753e+00, -1.058877e+01, 7.686922e+00), (6.459563e-01, 7.279465e-03, -7.633397e-01), 1.319449e-01, 8.019832e-05, 1.000000e+00, 23, 2328, 1) + ((9.462343e+00, -1.058804e+01, 7.610595e+00), (-5.748716e-01, 5.760301e-01, -5.811299e-01), 8.880732e-02, 8.039733e-05, 1.000000e+00, 23, 2328, 1) + ((9.287877e+00, -1.041322e+01, 7.434230e+00), (-4.932135e-03, 1.668926e-01, 9.859627e-01), 3.503028e-02, 8.113361e-05, 1.000000e+00, 23, 2328, 1) + ((9.286930e+00, -1.038117e+01, 7.623600e+00), (-9.325544e-02, -9.933000e-01, -6.825369e-02), 3.173764e-02, 8.187553e-05, 1.000000e+00, 23, 2328, 1) + ((9.283901e+00, -1.041342e+01, 7.621384e+00), (-4.235208e-01, -5.129112e-01, 7.466942e-01), 4.258127e-02, 8.200731e-05, 1.000000e+00, 23, 2328, 1) + ((9.264517e+00, -1.043690e+01, 7.655560e+00), (-7.165846e-01, 3.926265e-01, 5.764988e-01), 4.485063e-02, 8.216767e-05, 1.000000e+00, 23, 2328, 1) + ((9.256348e+00, -1.043242e+01, 7.662132e+00), (1.703748e-01, -9.175599e-01, -3.592441e-01), 3.521226e-02, 8.220659e-05, 1.000000e+00, 23, 2328, 1) + ((9.284015e+00, -1.058143e+01, 7.603793e+00), (3.963415e-01, -8.051509e-01, 4.411865e-01), 2.970280e-02, 8.283227e-05, 1.000000e+00, 23, 2328, 1) + ((9.309126e+00, -1.063244e+01, 7.631745e+00), (3.963415e-01, -8.051509e-01, 4.411865e-01), 2.970280e-02, 8.309804e-05, 1.000000e+00, 23, 2313, 1) + ((9.317824e+00, -1.065011e+01, 7.641427e+00), (7.190682e-01, 8.976507e-02, -6.891177e-01), 2.867417e-02, 8.319011e-05, 1.000000e+00, 23, 2313, 1) + ((9.459378e+00, -1.063244e+01, 7.505770e+00), (7.190682e-01, 8.976507e-02, -6.891177e-01), 2.867417e-02, 8.403060e-05, 1.000000e+00, 23, 2328, 1) + ((9.492454e+00, -1.062831e+01, 7.474071e+00), (4.092619e-03, -9.482095e-01, -3.176193e-01), 4.656141e-02, 8.422700e-05, 1.000000e+00, 23, 2328, 1) + ((9.492472e+00, -1.063244e+01, 7.472688e+00), (4.092619e-03, -9.482095e-01, -3.176193e-01), 4.656141e-02, 8.424159e-05, 1.000000e+00, 23, 2313, 1) + ((9.492761e+00, -1.069927e+01, 7.450302e+00), (9.457151e-01, -1.928731e-01, 2.615777e-01), 4.542312e-02, 8.447773e-05, 1.000000e+00, 23, 2313, 1) + ((9.890524e+00, -1.078039e+01, 7.560320e+00), (7.849118e-01, -1.237812e-01, -6.071175e-01), 5.609444e-02, 8.590450e-05, 1.000000e+00, 23, 2313, 1) + ((1.004645e+01, -1.080498e+01, 7.439717e+00), (7.849118e-01, -1.237812e-01, -6.071175e-01), 5.609444e-02, 8.651090e-05, 0.000000e+00, 23, 2313, 1)] diff --git a/tests/regression_tests/track_output/settings.xml b/tests/regression_tests/track_output/settings.xml index 299ee72c53..dcfa118c85 100644 --- a/tests/regression_tests/track_output/settings.xml +++ b/tests/regression_tests/track_output/settings.xml @@ -16,7 +16,8 @@ 1 1 1 - 1 1 2 + 1 1 30 + 2 1 60 diff --git a/tests/regression_tests/track_output/test.py b/tests/regression_tests/track_output/test.py index a5300a4aed..62526117df 100644 --- a/tests/regression_tests/track_output/test.py +++ b/tests/regression_tests/track_output/test.py @@ -1,11 +1,13 @@ import glob import os +from pathlib import Path from subprocess import call -import shutil +import numpy as np +import openmc import pytest -from tests.testing_harness import TestHarness +from tests.testing_harness import TestHarness, config class TrackTestHarness(TestHarness): @@ -13,26 +15,33 @@ class TrackTestHarness(TestHarness): """Make sure statepoint.* and track* have been created.""" TestHarness._test_output_created(self) - outputs = glob.glob('track_1_1_*.h5') - assert len(outputs) == 2, 'Expected two track files.' + if config['mpi'] and int(config['mpi_np']) > 1: + outputs = Path.cwd().glob('tracks_p*.h5') + assert len(list(outputs)) == int(config['mpi_np']) + else: + assert Path('tracks.h5').is_file() def _get_results(self): - """Digest info in the statepoint and return as a string.""" - # Run the track-to-vtk conversion script. - call(['../../../scripts/openmc-track-to-vtk', '-o', 'poly'] + - glob.glob('track_1_1_*.h5')) + """Get data from track file and return as a string.""" - # Make sure the vtk file was created then return it's contents. - assert os.path.isfile('poly.pvtp'), 'poly.pvtp file not found.' + # For MPI mode, combine track files + if config['mpi']: + call(['../../../scripts/openmc-track-combine', '-o', 'tracks.h5'] + + glob.glob('tracks_p*.h5')) - with open('poly.pvtp', 'r') as fin: - outstr = fin.read() + # Get string of track file information + outstr = '' + tracks = openmc.Tracks('tracks.h5') + for track in tracks: + with np.printoptions(formatter={'float_kind': '{:.6e}'.format}): + for ptrack in track: + outstr += f"{ptrack.particle} {ptrack.states}\n" return outstr def _cleanup(self): TestHarness._cleanup(self) - output = glob.glob('track*') + glob.glob('poly*') + output = glob.glob('tracks*') + glob.glob('poly*') for f in output: if os.path.exists(f): os.remove(f) diff --git a/tests/regression_tests/unstructured_mesh/inputs_true0.dat b/tests/regression_tests/unstructured_mesh/inputs_true0.dat index 7411e9b940..1ed60d5079 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true0.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true0.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true1.dat b/tests/regression_tests/unstructured_mesh/inputs_true1.dat index e85aab1193..67dde56a94 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true1.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true1.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true10.dat b/tests/regression_tests/unstructured_mesh/inputs_true10.dat index 628e5e7bcc..9ca47a3566 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true10.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true10.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true11.dat b/tests/regression_tests/unstructured_mesh/inputs_true11.dat index 1c0a579e54..683e1fade0 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true11.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true11.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true12.dat b/tests/regression_tests/unstructured_mesh/inputs_true12.dat index a75344435f..8ce9f3cf28 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true12.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true12.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true13.dat b/tests/regression_tests/unstructured_mesh/inputs_true13.dat index c62c3c9113..bca0bee4c7 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true13.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true13.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true14.dat b/tests/regression_tests/unstructured_mesh/inputs_true14.dat index b7ac4c048c..226331ba82 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true14.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true14.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true15.dat b/tests/regression_tests/unstructured_mesh/inputs_true15.dat index 1d96efc7d3..a6a0841658 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true15.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true15.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true2.dat b/tests/regression_tests/unstructured_mesh/inputs_true2.dat index a2a2d4a27a..4b442c7575 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true2.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true2.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true3.dat b/tests/regression_tests/unstructured_mesh/inputs_true3.dat index c96f902016..52e53498e1 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true3.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true3.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true4.dat b/tests/regression_tests/unstructured_mesh/inputs_true4.dat index a39452da7c..995a1828f5 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true4.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true4.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true5.dat b/tests/regression_tests/unstructured_mesh/inputs_true5.dat index 288b7d1fcb..60229e5e50 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true5.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true5.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true6.dat b/tests/regression_tests/unstructured_mesh/inputs_true6.dat index 883e6cbf48..7a9257cb79 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true6.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true6.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true7.dat b/tests/regression_tests/unstructured_mesh/inputs_true7.dat index 267966e3ca..52802febfe 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true7.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true7.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true8.dat b/tests/regression_tests/unstructured_mesh/inputs_true8.dat index 9a7ad57113..e484c95a22 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true8.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true8.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/inputs_true9.dat b/tests/regression_tests/unstructured_mesh/inputs_true9.dat index 8aedad2377..5e83d71bd9 100644 --- a/tests/regression_tests/unstructured_mesh/inputs_true9.dat +++ b/tests/regression_tests/unstructured_mesh/inputs_true9.dat @@ -50,9 +50,9 @@ - - 0.0 1.0 - + + 1.0 1.0 + 0.0 1.0 diff --git a/tests/regression_tests/unstructured_mesh/test.py b/tests/regression_tests/unstructured_mesh/test.py index c921a78482..daebe79487 100644 --- a/tests/regression_tests/unstructured_mesh/test.py +++ b/tests/regression_tests/unstructured_mesh/test.py @@ -228,10 +228,10 @@ def test_unstructured_mesh(test_opts): # source setup r = openmc.stats.Uniform(a=0.0, b=0.0) - theta = openmc.stats.Discrete(x=[0.0], p=[1.0]) + cos_theta = openmc.stats.Discrete(x=[1.0], p=[1.0]) phi = openmc.stats.Discrete(x=[0.0], p=[1.0]) - space = openmc.stats.SphericalIndependent(r, theta, phi) + space = openmc.stats.SphericalIndependent(r, cos_theta, phi) energy = openmc.stats.Discrete(x=[15.e+06], p=[1.0]) source = openmc.Source(space=space, energy=energy) settings.source = source diff --git a/tests/unit_tests/test_cell.py b/tests/unit_tests/test_cell.py index 9234e7e4b5..faecf0b635 100644 --- a/tests/unit_tests/test_cell.py +++ b/tests/unit_tests/test_cell.py @@ -297,3 +297,20 @@ def test_to_xml_element(cell_with_lattice): elem = c.create_xml_subelement(root) assert elem.get('region') == str(c.region) assert elem.get('temperature') == str(c.temperature) + + +@pytest.mark.parametrize("rotation", [ + (90, 45, 0), + [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, -1.0, 0.0]] +]) +def test_rotation_from_xml(rotation): + # Make sure rotation attribute (matrix) round trips through XML correctly + s = openmc.ZCylinder(r=10.0) + cell = openmc.Cell(region=-s) + cell.rotation = rotation + root = ET.Element('geometry') + elem = cell.create_xml_subelement(root) + new_cell = openmc.Cell.from_xml_element( + elem, {s.id: s}, {'void': None}, openmc.Universe + ) + np.testing.assert_allclose(new_cell.rotation, cell.rotation) diff --git a/tests/unit_tests/test_heating_by_nuclide.py b/tests/unit_tests/test_heating_by_nuclide.py new file mode 100644 index 0000000000..b34d7dd943 --- /dev/null +++ b/tests/unit_tests/test_heating_by_nuclide.py @@ -0,0 +1,57 @@ +import openmc +import pytest + +from tests.testing_harness import config + + +@pytest.fixture +def model(): + # Create simple sphere model + model = openmc.Model() + mat = openmc.Material() + mat.add_nuclide('U235', 1.0) + mat.add_nuclide('H1', 1.0) + mat.set_density('g/cm3', 5.0) + sph = openmc.Sphere(r=10.0, boundary_type='vacuum') + cell = openmc.Cell(fill=mat, region=-sph) + model.geometry = openmc.Geometry([cell]) + model.settings.particles = 1000 + model.settings.inactive = 0 + model.settings.batches = 5 + model.settings.photon_transport = True + + # Add two tallies, one with heating by nuclide and one with total heating + particle_filter = openmc.ParticleFilter(['neutron', 'photon']) + heating_by_nuclide = openmc.Tally() + heating_by_nuclide.filters = [particle_filter] + heating_by_nuclide.nuclides = ['U235', 'H1'] + heating_by_nuclide.scores = ['heating'] + + heating_total = openmc.Tally() + heating_total.filters = [particle_filter] + heating_total.scores = ['heating'] + model.tallies.extend([heating_by_nuclide, heating_total]) + + return model + + +def test_heating_by_nuclide(model, run_in_tmpdir): + # If running in MPI mode, setup proper keyword arguments for run() + kwargs = {'openmc_exec': config['exe']} + if config['mpi']: + kwargs['mpi_args'] = [config['mpiexec'], '-n', config['mpi_np']] + sp_path = model.run(**kwargs) + + # Get tallies from resulting statepoint + with openmc.StatePoint(sp_path) as sp: + heating_by_nuclide = sp.tallies[model.tallies[0].id] + heating_total = sp.tallies[model.tallies[1].id] + + for particle in heating_by_nuclide.filters[0].bins: + # Get slice of each tally corresponding to a single particle + kwargs = {'filters': [openmc.ParticleFilter], 'filter_bins': [(particle,)]} + particle_slice_by_nuclide = heating_by_nuclide.get_values(**kwargs) + particle_slice_total = heating_total.get_values(**kwargs) + + # Summing over nuclides should equal total + assert particle_slice_by_nuclide.sum() == pytest.approx(particle_slice_total.sum()) diff --git a/tests/unit_tests/test_plots.py b/tests/unit_tests/test_plots.py index 718f098ba9..2db396cd5b 100644 --- a/tests/unit_tests/test_plots.py +++ b/tests/unit_tests/test_plots.py @@ -92,6 +92,8 @@ def test_xml_element(myplot): def test_plots(run_in_tmpdir): p1 = openmc.Plot(name='plot1') p1.origin = (5., 5., 5.) + p1.colors = {10: (255, 100, 0)} + p1.mask_components = [2, 4, 6] p2 = openmc.Plot(name='plot2') p2.origin = (-3., -3., -3.) plots = openmc.Plots([p1, p2]) @@ -107,4 +109,6 @@ def test_plots(run_in_tmpdir): new_plots = openmc.Plots.from_xml() assert len(plots) assert plots[0].origin == p1.origin + assert plots[0].colors == p1.colors + assert plots[0].mask_components == p1.mask_components assert plots[1].origin == p2.origin diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index e2c7259e75..b21c036fa1 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -14,6 +14,7 @@ def test_export_to_xml(run_in_tmpdir): s.keff_trigger = {'type': 'std_dev', 'threshold': 0.001} s.energy_mode = 'continuous-energy' s.max_order = 5 + s.max_tracks = 1234 s.source = openmc.Source(space=openmc.stats.Point()) s.output = {'summary': True, 'tallies': False, 'path': 'here'} s.verbosity = 7 @@ -42,7 +43,7 @@ def test_export_to_xml(run_in_tmpdir): s.temperature = {'default': 293.6, 'method': 'interpolation', 'multipole': True, 'range': (200., 1000.)} s.trace = (10, 1, 20) - s.track = [1, 1, 1, 2, 1, 1] + s.track = [(1, 1, 1), (2, 1, 1)] s.ufs_mesh = mesh s.resonance_scattering = {'enable': True, 'method': 'rvs', 'energy_min': 1.0, 'energy_max': 1000.0, @@ -71,6 +72,7 @@ def test_export_to_xml(run_in_tmpdir): assert s.keff_trigger == {'type': 'std_dev', 'threshold': 0.001} assert s.energy_mode == 'continuous-energy' assert s.max_order == 5 + assert s.max_tracks == 1234 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'} @@ -99,7 +101,7 @@ def test_export_to_xml(run_in_tmpdir): 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 s.track == [(1, 1, 1), (2, 1, 1)] assert isinstance(s.ufs_mesh, openmc.RegularMesh) assert s.ufs_mesh.lower_left == [-10., -10., -10.] assert s.ufs_mesh.upper_right == [10., 10., 10.] diff --git a/tests/unit_tests/test_source.py b/tests/unit_tests/test_source.py index d4d17a3dab..dbcf49efc8 100644 --- a/tests/unit_tests/test_source.py +++ b/tests/unit_tests/test_source.py @@ -1,6 +1,6 @@ import openmc import openmc.stats - +from math import pi def test_source(): space = openmc.stats.Point() @@ -26,6 +26,22 @@ def test_source(): assert src.strength == 1.0 +def test_spherical_uniform(): + r_outer = 2.0 + r_inner = 1.0 + thetas = (0.0, pi/2) + phis = (0.0, pi) + origin = (0.0, 1.0, 2.0) + + sph_indep_function = openmc.stats.spherical_uniform(r_outer, + r_inner, + thetas, + phis, + origin) + + assert isinstance(sph_indep_function, openmc.stats.SphericalIndependent) + + def test_source_file(): filename = 'source.h5' src = openmc.Source(filename=filename) @@ -35,6 +51,7 @@ def test_source_file(): assert 'strength' in elem.attrib assert 'file' in elem.attrib + def test_source_dlopen(): library = './libsource.so' src = openmc.Source(library=library) diff --git a/tests/unit_tests/test_stats.py b/tests/unit_tests/test_stats.py index bbcb12ff11..51a561bd36 100644 --- a/tests/unit_tests/test_stats.py +++ b/tests/unit_tests/test_stats.py @@ -27,6 +27,29 @@ def test_discrete(): assert len(d2) == 1 +def test_merge_discrete(): + x1 = [0.0, 1.0, 10.0] + p1 = [0.3, 0.2, 0.5] + d1 = openmc.stats.Discrete(x1, p1) + + x2 = [0.5, 1.0, 5.0] + p2 = [0.4, 0.5, 0.1] + d2 = openmc.stats.Discrete(x2, p2) + + # Merged distribution should have x values sorted and probabilities + # appropriately combined. Duplicate x values should appear once. + merged = openmc.stats.Discrete.merge([d1, d2], [0.6, 0.4]) + assert merged.x == pytest.approx([0.0, 0.5, 1.0, 5.0, 10.0]) + assert merged.p == pytest.approx( + [0.6*0.3, 0.4*0.4, 0.6*0.2 + 0.4*0.5, 0.4*0.1, 0.6*0.5]) + + # Probabilities add up but are not normalized + d1 = openmc.stats.Discrete([3.0], [1.0]) + triple = openmc.stats.Discrete.merge([d1, d1, d1], [1.0, 2.0, 3.0]) + assert triple.x == pytest.approx([3.0]) + assert triple.p == pytest.approx([6.0]) + + def test_uniform(): a, b = 10.0, 20.0 d = openmc.stats.Uniform(a, b) diff --git a/tests/unit_tests/test_tracks.py b/tests/unit_tests/test_tracks.py new file mode 100644 index 0000000000..e18015d81a --- /dev/null +++ b/tests/unit_tests/test_tracks.py @@ -0,0 +1,143 @@ +from pathlib import Path + +import numpy as np +import openmc +import pytest + +from tests.testing_harness import config + + +@pytest.fixture +def sphere_model(): + openmc.reset_auto_ids() + mat = openmc.Material() + mat.add_nuclide('Zr90', 1.0) + mat.set_density('g/cm3', 1.0) + + model = openmc.Model() + sph = openmc.Sphere(r=25.0, boundary_type='vacuum') + cell = openmc.Cell(fill=mat, region=-sph) + model.geometry = openmc.Geometry([cell]) + + model.settings.run_mode = 'fixed source' + model.settings.batches = 2 + model.settings.particles = 50 + + return model + + +def generate_track_file(model, **kwargs): + # If running in MPI mode, setup proper keyword arguments for run() + kwargs.setdefault('openmc_exec', config['exe']) + if config['mpi']: + kwargs['mpi_args'] = [config['mpiexec'], '-n', config['mpi_np']] + model.run(**kwargs) + + if config['mpi'] and int(config['mpi_np']) > 1: + # With MPI, we need to combine track files + track_files = Path.cwd().glob('tracks_p*.h5') + openmc.Tracks.combine(track_files, 'tracks.h5') + else: + track_file = Path('tracks.h5') + assert track_file.is_file() + + +@pytest.mark.parametrize("particle", ["neutron", "photon"]) +def test_tracks(sphere_model, particle, run_in_tmpdir): + # Set track identifiers + sphere_model.settings.track = [(1, 1, 1), (1, 1, 10), (2, 1, 15)] + + # Set source particle + sphere_model.settings.source = openmc.Source(particle=particle) + + # Run OpenMC to generate tracks.h5 file + generate_track_file(sphere_model) + + # Open track file and make sure we have correct number of tracks + tracks = openmc.Tracks('tracks.h5') + assert len(tracks) == len(sphere_model.settings.track) + + for track, identifier in zip(tracks, sphere_model.settings.track): + # Check attributes on Track object + assert isinstance(track, openmc.Track) + assert track.identifier == identifier + assert isinstance(track.particle_tracks, list) + if particle == 'neutron': + assert len(track.particle_tracks) == 1 + + # Check attributes on ParticleTrack object + particle_track = track.particle_tracks[0] + assert isinstance(particle_track, openmc.ParticleTrack) + + assert particle_track.particle.name.lower() == particle + assert isinstance(particle_track.states, np.ndarray) + + # Sanity checks on actual data + for state in particle_track.states: + assert np.linalg.norm([*state['r']]) <= 25.0001 + assert np.linalg.norm([*state['u']]) == pytest.approx(1.0) + assert 0.0 <= state['E'] <= 20.0e6 + assert state['time'] >= 0.0 + assert 0.0 <= state['wgt'] <= 1.0 + assert state['cell_id'] == 1 + assert state['material_id'] == 1 + + # Checks on 'sources' property + sources = track.sources + assert len(sources) == len(track.particle_tracks) + x = sources[0] + state = particle_track.states[0] + assert x.r == (*state['r'],) + assert x.u == (*state['u'],) + assert x.E == state['E'] + assert x.time == state['time'] + assert x.wgt == state['wgt'] + assert x.particle == particle_track.particle + + +def test_max_tracks(sphere_model, run_in_tmpdir): + # Set maximum number of tracks per process to write + sphere_model.settings.max_tracks = expected_num_tracks = 10 + if config['mpi']: + expected_num_tracks *= int(config['mpi_np']) + + # Run OpenMC to generate tracks.h5 file + generate_track_file(sphere_model, tracks=True) + + # Open track file and make sure we have correct number of tracks + tracks = openmc.Tracks('tracks.h5') + assert len(tracks) == expected_num_tracks + + +def test_filter(sphere_model, run_in_tmpdir): + # Set maximum number of tracks per process to write + sphere_model.settings.max_tracks = 25 + sphere_model.settings.photon_transport = True + + # Run OpenMC to generate tracks.h5 file + generate_track_file(sphere_model, tracks=True) + + tracks = openmc.Tracks('tracks.h5') + for track in tracks: + # Test filtering by particle + matches = track.filter(particle='photon') + for x in matches: + assert x.particle == openmc.ParticleType.PHOTON + + # Test general state filter + matches = track.filter(state_filter=lambda s: s['cell_id'] == 1) + assert isinstance(matches, openmc.Track) + assert matches.particle_tracks == track.particle_tracks + matches = track.filter(state_filter=lambda s: s['cell_id'] == 2) + assert matches.particle_tracks == [] + matches = track.filter(state_filter=lambda s: s['E'] < 0.0) + assert matches.particle_tracks == [] + + # Test filter method on Tracks + matches = tracks.filter(particle='neutron') + assert isinstance(matches, openmc.Tracks) + assert matches == tracks + matches = tracks.filter(state_filter=lambda s: s['E'] > 0.0) + assert matches == tracks + matches = tracks.filter(particle='bunnytron') + assert matches == []