Merge remote-tracking branch 'upstream/develop' into cpp_tallies

This commit is contained in:
Sterling Harper 2019-02-05 14:05:15 -05:00
commit 2d0ea1154b
109 changed files with 4104 additions and 3744 deletions

View file

@ -318,14 +318,11 @@ add_library(libopenmc SHARED
src/hdf5_interface.F90
src/initialize.F90
src/input_xml.F90
src/list_header.F90
src/material_header.F90
src/math.F90
src/mesh_header.F90
src/message_passing.F90
src/mgxs_data.F90
src/mgxs_interface.F90
src/multipole_header.F90
src/nuclide_header.F90
src/output.F90
src/particle_header.F90
@ -335,15 +332,12 @@ add_library(libopenmc SHARED
src/random_lcg.F90
src/reaction_header.F90
src/relaxng
src/sab_header.F90
src/set_header.F90
src/settings.F90
src/simulation_header.F90
src/simulation.F90
src/state_point.F90
src/stl_vector.F90
src/string.F90
src/summary.F90
src/surface_header.F90
src/timer_header.F90
src/tracking.F90

View file

@ -88,6 +88,14 @@ you care. This element has the following attributes/sub-elements:
*Default*: 0.0
--------------------------------
``<dagmc>`` Element
--------------------------------
When the DAGMC mode is enabled, the OpenMC geometry will be read from the file
``dagmc.h5m``. If a :ref:`geometry.xml <io_geometry>` file is present with
``dagmc`` set to ``true``, it will be ignored.
--------------------------------
``<electron_treatment>`` Element
--------------------------------

View file

@ -62,6 +62,11 @@ Coupling and Multi-physics
<https://doi.org/10.1007/s41365-018-0539-1>`_," *Nucl. Sci. Tech.*, **30**
(2019).
- April Novak, Paul Romano, Brycen Wendt, Ron Rahaman, Elia Merzari, Leslie
Kerby, Cody Permann, Richard Martineau, and Rachel N. Slaybaugh, "Preliminary
Coupling of OpenMC and Nek5000 within the MOOSE Framework," *Proc. PHYSOR*,
Cancun, Mexico, Apr. 22-26 (2018).
- Jun Chen, Liangzhi Cao, Chuanqi Zhao, and Zhouyu Liu, "`Development of
Subchannel Code SUBSC for high-fidelity multi-physics coupling application
<https://doi.org/10.1016/j.egypro.2017.08.121>`_", *Energy Procedia*, **127**,
@ -129,6 +134,10 @@ Geometry and Visualization
Miscellaneous
-------------
- Amanda L. Lund and Paul K. Romano, "`Implementation and Validation of Photon
Transport in OpenMC <https://doi.org/10.2172/1490825>`_", Argonne National
Laboratory, Technical Report ANL/MCS-TM-381 (2018).
- Bruno Merk, Dzianis Litskevich, R. Gregg, and A. R. Mount, "`Demand driven
salt clean-up in a molten salt fast reactor -- Defining a priority list
<https://doi.org/10.1371/journal.pone.0192020>`_", *PLOS One*, **13**,
@ -191,6 +200,10 @@ Miscellaneous
Multi-group Cross Section Generation
------------------------------------
- Changho Lee and Yeon Sang Jung, "Verification of the Cross Section Library
Generated Using OpenMC and MC\ :sup:`2`-3 for PROTEUS," *Proc. PHYSOR*, Cancun,
Mexico, Apr. 22-26 (2018).
- Zhaoyuan Liu, Kord Smith, Benoit Forget, and Javier Ortensi, "`Cumulative
migration method for computing rigorous diffusion coefficients and transport
cross sections from Monte Carlo
@ -454,3 +467,21 @@ Depletion
- Kai Huang, Hongchun Wu, Yunzhao Li, and Liangzhi Cao, "Generalized depletion
chain simplification based of significance analysis," *Proc. PHYSOR*, Sun
Valley, Idaho, May 1-5, 2016.
--------------------
Sensitivity Analysis
--------------------
- Xingjie Peng, Jingang Liang, Benoit Forget, and Kord Smith, "`Calculation of
adjoint-weighted reactor kinetics parameters in OpenMC
<https://doi.org/10.1016/j.anucene.2019.01.007>`_", *Ann. Nucl. Energy*,
**128**, 231-235 (2019).
- Zeyun Wu, Jingang Liang, Xingjie Peng, and Hany S. Abdel-Khalik, "`GPT-Free
Sensitivity Analysis for Monte Carlo Models
<https://doi.org/10.1080/00295450.2018.1556062>`_", *Nucl. Technol.* (2019).
- Xingjie Peng, Jingang Liang, Abdulla Alhajri, Benoit Forget, and Kord Smith,
"`Development of continuous-energy sensitivity analysis capability in OpenMC
<https://doi.org/10.1016/j.anucene.2017.06.061>`_", *Ann. Nucl. Energy*,
**110**, 362-383 (2017).

View file

@ -202,27 +202,20 @@ Coarse Mesh Finite Difference Acceleration
------------------------------------------
CMFD is implemented in OpenMC and allows users to accelerate fission source
convergence during inactive neutron batches. To run CMFD, the CMFDRun class
should be used, and :mod:`from openmc import cmfd` should be included at the
top of the Python input file. Additionally, this class has a dependence on the
C API.
convergence during inactive neutron batches. To use CMFD, the
:class:`openmc.cmfd.CMFDRun` class executes OpenMC through the C API, solving
the CMFD system between fission generations and modifying the source weights.
Note that the :mod:`openmc.cmfd` module is not imported by default with the
:mod:`openmc` namespace and needs to be imported explicitly.
.. autosummary::
:toctree: generated
:nosignatures:
:template: myclass.rst
openmc.CMFDMesh
openmc.CMFDRun
At the minimum, a CMFD mesh needs to be specified in order to run CMFD. Once
these properties are set, an OpenMC simulation can be run with CMFD turned on
with the function:
.. autosummary::
:toctree: generated
:nosignatures:
:template: myfunction.rst
openmc.CMFDRun.run
openmc.cmfd.CMFDMesh
openmc.cmfd.CMFDRun
At the minimum, a CMFD mesh needs to be specified in order to run CMFD. Once the
mesh and other optional properties are set, a simulation can be run with CMFD
turned on using :meth:`openmc.cmfd.CMFDRun.run`.

View file

@ -6,8 +6,10 @@
.. module:: openmc.deplete
Two functions are provided that implement different time-integration algorithms
for depletion calculations.
Several functions are provided that implement different time-integration
algorithms for depletion calculations, which are described in detail in Colin
Josey's thesis, `Development and analysis of high order neutron
transport-depletion coupling algorithms <http://hdl.handle.net/1721.1/113721>`_.
.. autosummary::
:toctree: generated
@ -16,6 +18,12 @@ for depletion calculations.
integrator.predictor
integrator.cecm
integrator.celi
integrator.leqi
integrator.cf4
integrator.epc_rk4
integrator.si_celi
integrator.si_leqi
Each of these functions expects a "transport operator" to be passed. An operator
specific to OpenMC is available using the following class:

View file

@ -398,3 +398,32 @@ if needed, lattices, the last step is to create an instance of
.. _constructive solid geometry: http://en.wikipedia.org/wiki/Constructive_solid_geometry
.. _quadratic surfaces: http://en.wikipedia.org/wiki/Quadric
--------------------------
Using CAD-based Geometry
--------------------------
OpenMC relies on the Direct Accelerated Geometry Monte Carlo toolkit (`DAGMC
<https://svalinn.github.io/DAGMC/>`_) to represent CAD-based geometry in a
surface mesh format. A DAGMC run can be enabled in OpenMC by setting the
``dagmc`` property to ``True`` in the model Settings either via the Python
:class:`openmc.settings` Python class::
settings = openmc.Settings()
settings.dagmc = True
or in the :ref:`settings.xml <io_settings>` file::
<dagmc>true</dagmc>
With ``dagmc`` set to true, OpenMC will load the DAGMC model (from a local file
named ``dagmc.h5m``) when initializing a simulation. If a `geometry.xml
<../io_formats/geometry.html>`_ is present as well, it will be ignored.
**Note:** DAGMC geometries used in OpenMC are currently required to be clean,
meaning that all surfaces have been `imprinted and merged
<https://svalinn.github.io/DAGMC/usersguide/trelis_workflow.html>`_
successfully and that the model is `watertight
<https://svalinn.github.io/DAGMC/usersguide/tools.html#make-watertight>`_. Future
implementations of DAGMC geometry will support small volume overlaps and
un-merged surfaces.

View file

@ -159,14 +159,25 @@ Prerequisites
sudo apt install mpich libmpich-dev
sudo apt install openmpi-bin libopenmpi-dev
* DAGMC_ toolkit for simulation using CAD-based geometries
OpenMC supports particle tracking in CAD-based geometries via the Direct
Accelerated Geometry Monte Carlo (DAGMC) toolkit (`installation
instructions
<https://svalinn.github.io/DAGMC/install/dag_multiple.html>`_). For use in
OpenMC, only the ``MOAB_DIR`` and ``BUILD_TALLY`` variables need to be
specified in the CMake configuration step.
|
* git_ version control software for obtaining source code
.. _gfortran: http://gcc.gnu.org/wiki/GFortran
.. _gcc: https://gcc.gnu.org/
.. _CMake: http://www.cmake.org
.. _OpenMPI: http://www.open-mpi.org
.. _MPICH: http://www.mpich.org
.. _HDF5: https://www.hdfgroup.org/solutions/hdf5/
.. _DAGMC: https://svalinn.github.io/DAGMC/index.html
Obtaining the Source
--------------------
@ -236,6 +247,12 @@ openmp
Enables shared-memory parallelism using the OpenMP API. The Fortran compiler
being used must support OpenMP. (Default: on)
dagmc
Enables use of CAD-based DAGMC_ geometries. Please see the note about DAGMC in
the optional dependencies list for more information on this feature. The
installation directory for DAGMC should also be defined as `DAGMC_ROOT` in the
CMake configuration command. (Default: off)
coverage
Compile and link code instrumented for coverage analysis. This is typically
used in conjunction with gcov_.

View file

@ -7,6 +7,7 @@
#ifdef __cplusplus
#include "openmc/bank.h"
extern "C" {
int openmc_fission_bank(openmc::Bank** ptr, int64_t* n);
int openmc_source_bank(openmc::Bank** ptr, int64_t* n);
@ -59,7 +60,7 @@ extern "C" {
int openmc_init(int argc, char* argv[], const void* intracomm);
int openmc_legendre_filter_get_order(int32_t index, int* order);
int openmc_legendre_filter_set_order(int32_t index, int order);
int openmc_load_nuclide(const char name[]);
int openmc_load_nuclide(const char* name);
int openmc_material_add_nuclide(int32_t index, const char name[], double density);
int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n);
int openmc_material_get_id(int32_t index, int32_t* id);

View file

@ -57,7 +57,15 @@ extern std::map<LibraryKey, std::size_t> library_map;
//! Read cross sections file (either XML or multigroup H5) and populate data
//! libraries
extern "C" void read_cross_sections_xml();
void read_cross_sections_xml();
//! Load nuclide and thermal scattering data from HDF5 files
//
//! \param[in] nuc_temps Temperatures for each nuclide in [K]
//! \param[in] thermal_temps Temperatures for each thermal scattering table in [K]
void read_ce_cross_sections(const std::vector<std::vector<double>>& nuc_temps,
const std::vector<std::vector<double>>& thermal_temps);
//! Read cross_sections.xml and populate data libraries
void read_ce_cross_sections_xml();

View file

@ -2,6 +2,10 @@
#ifndef OPENMC_DAGMC_H
#define OPENMC_DAGMC_H
namespace openmc {
extern "C" const bool dagmc_enabled;
}
#ifdef DAGMC
#include "DagMC.hpp"
@ -10,10 +14,6 @@
namespace openmc {
//==============================================================================
// Global variables
//==============================================================================
namespace model {
extern moab::DagMC* DAG;
@ -26,15 +26,11 @@ extern moab::DagMC* DAG;
extern "C" void load_dagmc_geometry();
extern "C" void free_memory_dagmc();
extern "C" pugi::xml_document* read_uwuw_materials();
bool get_uwuw_materials_xml(std::string& s);
} // namespace openmc
#endif // DAGMC
#endif // OPENMC_DAGMC_H
#ifdef DAGMC
extern "C" constexpr bool dagmc_enabled = true;
#else
extern "C" constexpr bool dagmc_enabled = false;
#endif

View file

@ -6,7 +6,7 @@
#include <cstdint>
#include <string>
#include <vector>
namespace openmc {
@ -14,13 +14,38 @@ namespace openmc {
//! Replace Universe, Lattice, and Material IDs with indices.
//==============================================================================
extern "C" void adjust_indices();
void adjust_indices();
//==============================================================================
//! Assign defaults to cells with undefined temperatures.
//==============================================================================
extern "C" void assign_temperatures();
void assign_temperatures();
//==============================================================================
//! \brief Obtain a list of temperatures that each nuclide/thermal scattering
//! table appears at in the model. Later, this list is used to determine the
//! actual temperatures to read (which may be different if interpolation is
//! used)
//!
//! \param[out] nuc_temps Vector of temperatures for each nuclide
//! \param[out] thermal_temps Vector of tempratures for each thermal scattering
//! table
//==============================================================================
void get_temperatures(std::vector<std::vector<double>>& nuc_temps,
std::vector<std::vector<double>>& thermal_temps);
//==============================================================================
//! \brief Perform final setup for geometry
//!
//! \param[out] nuc_temps Vector of temperatures for each nuclide
//! \param[out] thermal_temps Vector of tempratures for each thermal scattering
//! table
//==============================================================================
void finalize_geometry(std::vector<std::vector<double>>& nuc_temps,
std::vector<std::vector<double>>& thermal_temps);
//==============================================================================
//! Figure out which Universe is the root universe.
@ -36,7 +61,7 @@ extern "C" int32_t find_root_universe();
//! Populate all data structures needed for distribcells.
//==============================================================================
extern "C" void prepare_distribcell();
void prepare_distribcell();
//==============================================================================
//! Recursively search through the geometry and count cell instances.

View file

@ -137,6 +137,13 @@ void read_attribute(hid_t obj_id, const char* name, T& buffer)
read_attr(obj_id, name, H5TypeMap<T>::type_id, &buffer);
}
// array version
template<typename T, std::size_t N> inline void
read_attribute(hid_t obj_id, const char* name, std::array<T, N>& buffer)
{
read_attr(obj_id, name, H5TypeMap<T>::type_id, buffer.data());
}
// vector version
template<typename T>
void read_attribute(hid_t obj_id, const char* name, std::vector<T>& vec)
@ -351,6 +358,12 @@ write_attribute(hid_t obj_id, const char* name, const char* buffer)
write_attr_string(obj_id, name, buffer);
}
inline void
write_attribute(hid_t obj_id, const char* name, const std::string& buffer)
{
write_attr_string(obj_id, name, buffer.c_str());
}
template<typename T, std::size_t N> inline void
write_attribute(hid_t obj_id, const char* name, const std::array<T, N>& buffer)
{
@ -391,6 +404,29 @@ write_dataset(hid_t obj_id, const char* name, const std::array<T, N>& buffer)
write_dataset(obj_id, 1, dims, name, H5TypeMap<T>::type_id, buffer.data(), false);
}
inline void
write_dataset(hid_t obj_id, const char* name, const std::vector<std::string>& buffer)
{
auto n {buffer.size()};
hsize_t dims[] {n};
// Determine length of longest string, including \0
size_t m = 1;
for (const auto& s : buffer) {
m = std::max(m, s.size() + 1);
}
// Copy data into contiguous buffer
char temp[n][m];
std::fill(temp[0], temp[0] + n*m, '\0');
for (int i = 0; i < n; ++i) {
std::copy(buffer[i].begin(), buffer[i].end(), temp[i]);
}
// Write 2D data
write_string(obj_id, 1, dims, m, name, temp[0], false);
}
template<typename T> inline void
write_dataset(hid_t obj_id, const char* name, const std::vector<T>& buffer)
{

View file

@ -11,6 +11,7 @@ int parse_command_line(int argc, char* argv[]);
#ifdef OPENMC_MPI
void initialize_mpi(MPI_Comm intracomm);
#endif
void read_input_xml();
}

View file

@ -2,10 +2,13 @@
#define OPENMC_MATERIAL_H
#include <memory> // for unique_ptr
#include <string>
#include <unordered_map>
#include <vector>
#include <hdf5.h>
#include "pugixml.hpp"
#include "xtensor/xtensor.hpp"
#include "openmc/bremsstrahlung.h"
#include "openmc/particle.h"
@ -32,9 +35,57 @@ extern std::unordered_map<int32_t, int32_t> material_map;
class Material
{
public:
// Types
struct ThermalTable {
int index_table; //!< Index of table in data::thermal_scatt
int index_nuclide; //!< Index in nuclide_
double fraction; //!< How often to use table
};
// Constructors
Material() {};
explicit Material(pugi::xml_node material_node);
// Methods
void calculate_xs(const Particle& p) const;
//! Assign thermal scattering tables to specific nuclides within the material
//! so the code knows when to apply bound thermal scattering data
void init_thermal();
//! Set up mapping between global nuclides vector and indices in nuclide_
void init_nuclide_index();
//! Finalize the material, assigning tables, normalize density, etc.
void finalize();
//! Set total density of the material
int set_density(double density, std::string units);
//! Write material data to HDF5
void to_hdf5(hid_t group) const;
// Data
int32_t id_; //!< Unique ID
std::string name_; //!< Name of material
std::vector<int> nuclide_; //!< Indices in nuclides vector
std::vector<int> element_; //!< Indices in elements vector
xt::xtensor<double, 1> atom_density_; //!< Nuclide atom density in [atom/b-cm]
double density_; //!< Total atom density in [atom/b-cm]
double density_gpcc_; //!< Total atom density in [g/cm^3]
double volume_ {-1.0}; //!< Volume in [cm^3]
bool fissionable {false}; //!< Does this material contain fissionable nuclides
bool fissionable_ {false}; //!< Does this material contain fissionable nuclides
bool depletable_ {false}; //!< Is the material depletable?
std::vector<bool> p0_; //!< Indicate which nuclides are to be treated with iso-in-lab scattering
// To improve performance of tallying, we store an array (direct address
// table) that indicates for each nuclide in data::nuclides the index of the
// corresponding nuclide in the nuclide_ vector. If it is not present in the
// material, the entry is set to -1.
std::vector<int> mat_nuclide_index_;
// Thermal scattering tables
std::vector<ThermalTable> thermal_tables_;
//! \brief Default temperature for cells containing this material.
//!
@ -43,12 +94,15 @@ public:
std::unique_ptr<Bremsstrahlung> ttb_;
Material() {};
explicit Material(pugi::xml_node material_node);
private:
//! Initialize bremsstrahlung data
void init_bremsstrahlung();
//! Normalize density
void normalize_density();
void calculate_neutron_xs(const Particle& p) const;
void calculate_photon_xs(const Particle& p) const;
};
//==============================================================================

View file

@ -57,34 +57,25 @@ class Mgxs {
//! @param in_fissionable Is this item fissionable or not.
//! @param in_scatter_format Denotes whether Legendre, Tabular, or
//! Histogram scattering is used.
//! @param in_num_groups Number of energy groups.
//! @param in_num_delayed_groups Number of delayed groups.
//! @param in_is_isotropic Is this an isotropic or angular with respect to
//! the incoming particle.
//! @param in_polar Polar angle grid.
//! @param in_azimuthal Azimuthal angle grid.
void
init(const std::string& in_name, double in_awr, const std::vector<double>& in_kTs,
bool in_fissionable, int in_scatter_format, int in_num_groups,
int in_num_delayed_groups, bool in_is_isotropic,
bool in_fissionable, int in_scatter_format, bool in_is_isotropic,
const std::vector<double>& in_polar, const std::vector<double>& in_azimuthal);
//! \brief Initializes the Mgxs object metadata from the HDF5 file
//!
//! @param xs_id HDF5 group id for the cross section data.
//! @param in_num_groups Number of energy groups.
//! @param in_num_delayed_groups Number of delayed groups.
//! @param temperature Temperatures to read.
//! @param tolerance Tolerance of temperature selection method.
//! @param temps_to_read Resultant list of temperatures in the library
//! to read which correspond to the requested temperatures.
//! @param order_dim Resultant dimensionality of the scattering order.
//! @param method Method of choosing nearest temperatures.
void
metadata_from_hdf5(hid_t xs_id, int in_num_groups,
int in_num_delayed_groups, const std::vector<double>& temperature,
double tolerance, std::vector<int>& temps_to_read, int& order_dim,
int& method);
metadata_from_hdf5(hid_t xs_id, const std::vector<double>& temperature,
std::vector<int>& temps_to_read, int& order_dim);
//! \brief Performs the actual act of combining the microscopic data for a
//! single temperature.
@ -118,21 +109,8 @@ class Mgxs {
//! \brief Constructor that loads the Mgxs object from the HDF5 file
//!
//! @param xs_id HDF5 group id for the cross section data.
//! @param energy_groups Number of energy groups.
//! @param delayed_groups Number of delayed groups.
//! @param temperature Temperatures to read.
//! @param tolerance Tolerance of temperature selection method.
//! @param max_order Maximum order requested by the user;
//! this is only used for Legendre scattering.
//! @param legendre_to_tabular Flag to denote if any Legendre provided
//! should be converted to a Tabular representation.
//! @param legendre_to_tabular_points If a conversion is requested, this
//! provides the number of points to use in the tabular representation.
//! @param method Method of choosing nearest temperatures.
Mgxs(hid_t xs_id, int energy_groups,
int delayed_groups, const std::vector<double>& temperature, double tolerance,
int max_order, bool legendre_to_tabular,
int legendre_to_tabular_points, int& method);
Mgxs(hid_t xs_id, const std::vector<double>& temperature);
//! \brief Constructor that initializes and populates all data to build a
//! macroscopic cross section from microscopic cross section.
@ -141,11 +119,8 @@ class Mgxs {
//! @param mat_kTs temperatures (in units of eV) that data is needed.
//! @param micros Microscopic objects to combine.
//! @param atom_densities Atom densities of those microscopic quantities.
//! @param tolerance Tolerance of temperature selection method.
//! @param method Method of choosing nearest temperatures.
Mgxs(const std::string& in_name, const std::vector<double>& mat_kTs,
const std::vector<Mgxs*>& micros, const std::vector<double>& atom_densities,
double tolerance, int& method);
const std::vector<Mgxs*>& micros, const std::vector<double>& atom_densities);
//! \brief Provides a cross section value given certain parameters
//!

View file

@ -20,6 +20,7 @@ namespace data {
extern std::vector<Mgxs> nuclides_MG;
extern std::vector<Mgxs> macro_xs;
extern "C" int num_energy_groups;
extern "C" int num_delayed_groups;
extern std::vector<double> energy_bins;
extern std::vector<double> energy_bin_avg;
extern std::vector<double> rev_energy_bins;
@ -30,19 +31,15 @@ extern std::vector<double> rev_energy_bins;
// Mgxs data loading interface methods
//==============================================================================
extern "C" void
add_mgxs_c(hid_t file_id, const char* name, int energy_groups,
int delayed_groups, int n_temps, const double temps[], double tolerance,
int max_order, bool legendre_to_tabular, int legendre_to_tabular_points,
int& method);
void read_mgxs();
extern "C" bool
query_fissionable_c(int n_nuclides, const int i_nuclides[]);
void
add_mgxs(hid_t file_id, const std::string& name,
const std::vector<double>& temperature);
extern "C" void
create_macro_xs_c(const char* mat_name, int n_nuclides, const int i_nuclides[],
int n_temps, const double temps[], const double atom_densities[],
double tolerance, int& method);
void create_macro_xs();
std::vector<std::vector<double>> get_mat_kTs();
extern "C" void read_mg_cross_sections_header_c(hid_t file_id);

View file

@ -6,6 +6,7 @@
#include <array>
#include <memory> // for unique_ptr
#include <unordered_map>
#include <vector>
#include <hdf5.h>
@ -96,7 +97,7 @@ public:
};
// Constructors
Nuclide(hid_t group, const double* temperature, int n, int i_nuclide);
Nuclide(hid_t group, const std::vector<double>& temperature, int i_nuclide);
//! Initialize logarithmic grid for energy searches
void init_grid();
@ -168,6 +169,13 @@ private:
static int XS_PHOTON_PROD;
};
//==============================================================================
// Non-member functions
//==============================================================================
//! Checks for the right version of nuclear data within HDF5 files
void check_data_version(hid_t file_id);
//==============================================================================
// Global variables
//==============================================================================
@ -180,6 +188,7 @@ extern std::array<double, 2> energy_min;
extern std::array<double, 2> energy_max;
extern std::vector<std::unique_ptr<Nuclide>> nuclides;
extern std::unordered_map<std::string, int> nuclide_map;
} // namespace data

View file

@ -37,7 +37,7 @@ extern "C" void print_particle(Particle* p);
//! Display plot information.
//==============================================================================
extern "C" void print_plot();
void print_plot();
//==============================================================================
//! Display information regarding cell overlap checking.

View file

@ -35,7 +35,7 @@ constexpr double REL_MAX_LOST_PARTICLES {1.0e-6};
//! Particle types
enum class ParticleType {
neutron = 1, photon = 2, electron = 3, positron = 4
neutron, photon, electron, positron
};
extern "C" {

View file

@ -129,6 +129,7 @@ extern xt::xtensor<double, 1> compton_profile_pz; //! Compton profile momentum g
//! Photon interaction data for each element
extern std::vector<PhotonInteraction> elements;
extern std::unordered_map<std::string, int> element_map;
} // namespace data

View file

@ -37,7 +37,12 @@ void sample_electron_reaction(Particle* p);
//! MeV) are created and travel in opposite directions.
void sample_positron_reaction(Particle* p);
void sample_nuclide(const Particle* p, int mt, int* i_nuclide, int* i_nuc_mat);
//! Sample a nuclide based on their total cross sections and densities within
//! the current material
//!
//! \param[in] p Particle
//! \return Index in the data::nuclides vector
int sample_nuclide(const Particle* p);
//! Determine the average total, prompt, and delayed neutrons produced from
//! fission and creates appropriate bank sites.
@ -52,7 +57,7 @@ void sample_photon_product(int i_nuclide, double E, int* i_rx, int* i_product);
void absorption(Particle* p, int i_nuclide);
void scatter(Particle*, int i_nuclide, int i_nuc_mat);
void scatter(Particle*, int i_nuclide);
//! Treats the elastic scattering of a neutron with a target.
void elastic_scatter(int i_nuclide, const Reaction* rx, double kT, double* E,

View file

@ -140,8 +140,7 @@ class ScattDataLegendre: public ScattData {
// Friend convert_legendre_to_tabular so it has access to protected
// parameters
friend void
convert_legendre_to_tabular(ScattDataLegendre& leg,
ScattDataTabular& tab, int n_mu);
convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab);
public:
@ -223,8 +222,7 @@ class ScattDataTabular: public ScattData {
// Friend convert_legendre_to_tabular so it has access to protected
// parameters
friend void
convert_legendre_to_tabular(ScattDataLegendre& leg,
ScattDataTabular& tab, int n_mu);
convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab);
public:

View file

@ -97,7 +97,7 @@ extern double weight_survive; //!< Survival weight after Russian roulette
//! Read settings from XML file
//! \param[in] root XML node for <settings>
extern "C" void read_settings_xml();
void read_settings_xml();
extern "C" void read_settings_xml_f(pugi::xml_node_struct* root_ptr);

View file

@ -30,7 +30,7 @@ extern "C" double keff_std; //!< standard deviation of average k
extern "C" double k_col_abs; //!< sum over batches of k_collision * k_absorption
extern "C" double k_col_tra; //!< sum over batches of k_collision * k_tracklength
extern "C" double k_abs_tra; //!< sum over batches of k_absorption * k_tracklength
extern "C" double log_spacing; //!< lethargy spacing for energy grid searches
extern double log_spacing; //!< lethargy spacing for energy grid searches
extern "C" int n_lost_particles; //!< cumulative number of lost particles
extern "C" bool need_depletion_rx; //!< need to calculate depletion rx?
extern "C" int restart_batch; //!< batch at which a restart job resumed

16
include/openmc/summary.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef OPENMC_SUMMARY_H
#define OPENMC_SUMMARY_H
#include <hdf5.h>
namespace openmc {
void write_summary();
void write_header(hid_t file);
void write_nuclides(hid_t file);
void write_geometry(hid_t file);
void write_materials(hid_t file);
}
#endif // OPENMC_SUMMARY_H

View file

@ -4,6 +4,7 @@
#include <cstddef>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "xtensor/xtensor.hpp"
@ -36,6 +37,7 @@ class ThermalScattering;
namespace data {
extern std::vector<std::unique_ptr<ThermalScattering>> thermal_scatt;
extern std::unordered_map<std::string, int> thermal_scatt_map;
}
//==============================================================================

View file

@ -20,6 +20,7 @@ extern Timer time_bank_sendrecv;
extern Timer time_finalize;
extern Timer time_inactive;
extern Timer time_initialize;
extern Timer time_read_xs;
extern Timer time_tallies;
extern Timer time_total;
extern Timer time_transport;

View file

@ -4,6 +4,7 @@
#include "hdf5.h"
#include "xtensor/xtensor.hpp"
#include <array>
#include <complex>
#include <string>
#include <tuple>
@ -25,6 +26,9 @@ constexpr int FIT_S {0}; // Scattering
constexpr int FIT_A {1}; // Absorption
constexpr int FIT_F {2}; // Fission
// Multipole HDF5 file version
constexpr std::array<int, 2> WMP_VERSION {1, 1};
//========================================================================
// Windowed multipole data
//========================================================================
@ -68,6 +72,21 @@ public:
xt::xtensor<bool, 1> broaden_poly_; //!< Whether to broaden curvefit
};
//========================================================================
// Non-member functions
//========================================================================
//! Check to make sure WMP library data version matches
//!
//! \param[in] file HDF5 file object
void check_wmp_version(hid_t file);
//! \brief Checks for the existence of a multipole library in the directory and
//! loads it
//!
//! \param[in] i_nuclide Index in global nuclides array
void read_multipole_data(int i_nuclide);
} // namespace openmc
#endif // OPENMC_WMP_H

View file

@ -24,50 +24,42 @@ class XsData {
private:
//! \brief Reads scattering data from the HDF5 file
void
scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
int scatter_format, int final_scatter_format, int order_data,
int max_order, int legendre_to_tabular_points);
scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang,
int scatter_format, int final_scatter_format, int order_data);
//! \brief Reads fission data from the HDF5 file
void
fission_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
size_t delayed_groups, bool is_isotropic);
fission_from_hdf5(hid_t xsdata_grp, size_t n_ang, bool is_isotropic);
//! \brief Reads fission data formatted as chi and nu-fission vectors from
// the HDF5 file when beta is provided.
void
fission_vector_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups, size_t delayed_groups, bool is_isotropic);
fission_vector_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang, bool is_isotropic);
//! \brief Reads fission data formatted as chi and nu-fission vectors from
// the HDF5 file when beta is not provided.
void
fission_vector_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups, size_t delayed_groups);
fission_vector_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang);
//! \brief Reads fission data formatted as chi and nu-fission vectors from
// the HDF5 file when no delayed data is provided.
void
fission_vector_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups);
fission_vector_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang);
//! \brief Reads fission data formatted as a nu-fission matrix from
// the HDF5 file when beta is provided.
void
fission_matrix_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups, size_t delayed_groups, bool is_isotropic);
fission_matrix_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang, bool is_isotropic);
//! \brief Reads fission data formatted as a nu-fission matrix from
// the HDF5 file when beta is not provided.
void
fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups, size_t delayed_groups);
fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang);
//! \brief Reads fission data formatted as a nu-fission matrix from
// the HDF5 file when no delayed data is provided.
void
fission_matrix_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups);
fission_matrix_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang);
public:
@ -106,8 +98,7 @@ class XsData {
//! @param scatter_format The scattering representation of the file.
//! @param n_pol Number of polar angles.
//! @param n_azi Number of azimuthal angles.
XsData(size_t num_groups, size_t num_delayed_groups, bool fissionable,
int scatter_format, int n_pol, int n_azi);
XsData(bool fissionable, int scatter_format, int n_pol, int n_azi);
//! \brief Loads the XsData object from the HDF5 file
//!
@ -118,20 +109,13 @@ class XsData {
//! this is different from scatter_format if converting a Legendre to
//! a tabular representation.
//! @param order_data The dimensionality of the scattering data in the file.
//! @param max_order Maximum order requested by the user;
//! this is only used for Legendre scattering.
//! @param legendre_to_tabular Flag to denote if any Legendre provided
//! should be converted to a Tabular representation.
//! @param legendre_to_tabular_points If a conversion is requested, this
//! provides the number of points to use in the tabular representation.
//! @param is_isotropic Is this an isotropic or angular with respect to
//! the incoming particle.
//! @param n_pol Number of polar angles.
//! @param n_azi Number of azimuthal angles.
void
from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format,
int final_scatter_format, int order_data, int max_order,
int legendre_to_tabular_points, bool is_isotropic, int n_pol,
int final_scatter_format, int order_data, bool is_isotropic, int n_pol,
int n_azi);
//! \brief Combines the microscopic data to a macroscopic object.

View file

@ -1,5 +1,5 @@
from collections.abc import Mapping
from ctypes import c_int, c_int32, c_double, c_char_p, POINTER
from ctypes import c_int, c_int32, c_double, c_char_p, POINTER, c_size_t
from weakref import WeakValueDictionary
import numpy as np
@ -48,6 +48,8 @@ _dll.openmc_material_set_id.errcheck = _error_handler
_dll.openmc_material_set_volume.argtypes = [c_int32, c_double]
_dll.openmc_material_set_volume.restype = c_int
_dll.openmc_material_set_volume.errcheck = _error_handler
_dll.n_materials.argtypes = []
_dll.n_materials.restype = c_size_t
class Material(_FortranObjectWithID):
@ -161,7 +163,7 @@ class Material(_FortranObjectWithID):
_dll.openmc_material_get_densities(self._index, nuclides, densities, n)
# Convert to appropriate types and return
nuclide_list = [Nuclide(nuclides[i]).name for i in range(n.value)]
nuclide_list = [Nuclide(nuclides[i] + 1).name for i in range(n.value)]
density_array = as_array(densities, (n.value,))
return nuclide_list, density_array
@ -228,7 +230,7 @@ class _MaterialMapping(Mapping):
yield Material(index=i + 1).id
def __len__(self):
return c_int32.in_dll(_dll, 'n_materials').value
return _dll.n_materials()
def __repr__(self):
return repr(dict(self))

View file

@ -53,7 +53,7 @@ _CURRENTS = {
class CMFDMesh(object):
""""A structured Cartesian mesh used for CMFD acceleration.
"""A structured Cartesian mesh used for CMFD acceleration.
Attributes
----------
@ -199,14 +199,16 @@ class CMFDRun(object):
display : dict
Dictionary indicating which CMFD results to output. Note that CMFD
k-effective will always be outputted. Acceptable keys are:
* "balance" - Whether to output RMS [%] of the resdiual from the
neutron balance equation on CMFD tallies (bool)
* "dominance" - Whether to output the estimated dominance ratio from
the CMFD iterations (bool)
* "entropy" - Whether to output the *entropy* of the CMFD predicted
fission source (bool)
* "source" - Whether to ouput the RMS [%] between the OpenMC fission
source and CMFD fission source (bool)
* "balance" - Whether to output RMS [%] of the resdiual from the
neutron balance equation on CMFD tallies (bool)
* "dominance" - Whether to output the estimated dominance ratio from
the CMFD iterations (bool)
* "entropy" - Whether to output the *entropy* of the CMFD predicted
fission source (bool)
* "source" - Whether to ouput the RMS [%] between the OpenMC fission
source and CMFD fission source (bool)
downscatter : bool
Indicate whether an effective downscatter cross section should be used
when using 2-group CMFD.
@ -215,7 +217,7 @@ class CMFDRun(object):
of fission source neutrons on the next OpenMC batch. Defaults to False.
cmfd_ktol : float
Tolerance on the eigenvalue when performing CMFD power iteration
mesh : openmc.CMFDMesh
mesh : openmc.cmfd.CMFDMesh
Structured mesh to be used for acceleration
norm : float
Normalization factor applied to the CMFD fission source distribution
@ -244,16 +246,17 @@ class CMFDRun(object):
``run_adjoint`` must be true for an adjoint calculation to be
perfomed. Options are:
* "physical" - Create adjoint matrices from physical parameters of
CMFD problem
* "math" - Create adjoint matrices mathematically as the transpose of
loss and production CMFD matrices
* "physical" - Create adjoint matrices from physical parameters of
CMFD problem
* "math" - Create adjoint matrices mathematically as the transpose of
loss and production CMFD matrices
indices : numpy.ndarray
Stores spatial and group dimensions as [nx, ny, nz, ng]
cmfd_src : numpy.ndarray
CMFD source distribution calculated from solving CMFD equations
entropy : list of floats
"Shannon entropy" from cmfd fission source, stored for each generation
"Shannon entropy" from CMFD fission source, stored for each generation
that CMFD is invoked
balance : list of floats
RMS of neutron balance equations, stored for each generation that CMFD

View file

@ -5,6 +5,12 @@ Integrator
The integrator subcomponents.
"""
from .cf4 import *
from .cecm import *
from .celi import *
from .cram import *
from .epc_rk4 import *
from .leqi import *
from .predictor import *
from .si_celi import *
from .si_leqi import *

View file

@ -11,8 +11,10 @@ def cecm(operator, timesteps, power=None, power_density=None, print_out=True):
r"""Deplete using the CE/CM algorithm.
Implements the second order `CE/CM predictor-corrector algorithm
<https://doi.org/10.13182/NSE14-92>`_. This algorithm is mathematically
defined as:
<https://doi.org/10.13182/NSE14-92>`_.
"CE/CM" stands for constant extrapolation on predictor and constant
midpoint on corrector. This algorithm is mathematically defined as:
.. math::
y' &= A(y, t) y(t)
@ -59,20 +61,16 @@ def cecm(operator, timesteps, power=None, power_density=None, print_out=True):
# Generate initial conditions
with operator as vec:
chain = operator.chain
# Initialize time
# Initialize time and starting index
if operator.prev_res is None:
t = 0.0
else:
t = operator.prev_res[-1].time[-1]
# Initialize starting index for saving results
if operator.prev_res is None:
i_res = 0
else:
t = operator.prev_res[-1].time[-1]
i_res = len(operator.prev_res)
chain = operator.chain
for i, (dt, p) in enumerate(zip(timesteps, power)):
# Get beginning-of-timestep concentrations and reaction rates
# Avoid doing first transport run if already done in previous
@ -95,10 +93,10 @@ def cecm(operator, timesteps, power=None, power_density=None, print_out=True):
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = p / power_res
op_results[0].rates[0] *= ratio_power[0]
op_results[0].rates *= ratio_power[0]
# Deplete for first half of timestep
x_middle = deplete(chain, x[0], op_results[0], dt/2, print_out)
x_middle = deplete(chain, x[0], op_results[0].rates, dt/2, print_out)
# Get middle-of-timestep reaction rates
x.append(x_middle)
@ -106,7 +104,7 @@ def cecm(operator, timesteps, power=None, power_density=None, print_out=True):
# Deplete for full timestep using beginning-of-step materials
# and middle-of-timestep reaction rates
x_end = deplete(chain, x[0], op_results[1], dt, print_out)
x_end = deplete(chain, x[0], op_results[1].rates, dt, print_out)
# Create results, write to disk
Results.save(operator, x, op_results, [t, t + dt], p, i_res + i)

View file

@ -0,0 +1,166 @@
"""The CE/LI CFQ4 integrator."""
import copy
from collections.abc import Iterable
from .cram import deplete
from ..results import Results
# Functions to form the special matrix for depletion
def _celi_f1(chain, rates):
return 5/12 * chain.form_matrix(rates[0]) + \
1/12 * chain.form_matrix(rates[1])
def _celi_f2(chain, rates):
return 1/12 * chain.form_matrix(rates[0]) + \
5/12 * chain.form_matrix(rates[1])
def celi(operator, timesteps, power=None, power_density=None,
print_out=True):
r"""Deplete using the CE/LI CFQ4 algorithm.
Implements the CE/LI Predictor-Corrector algorithm using the `fourth order
commutator-free integrator <https://doi.org/10.1137/05063042>`_.
"CE/LI" stands for constant extrapolation on predictor and linear
interpolation on corrector. This algorithm is mathematically defined as:
.. math::
y' &= A(y, t) y(t)
A_0 &= A(y_n, t_n)
y_p &= \text{expm}(h A_0) y_n
A_1 &= A(y_p, t_n + h)
y_{n+1} &= \text{expm}(\frac{h}{12} A_0 + \frac{5h}{12} A1)
\text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n
Parameters
----------
operator : openmc.deplete.TransportOperator
The operator object to simulate on.
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not cumulative.
power : float or iterable of float, optional
Power of the reactor in [W]. A single value indicates that the power is
constant over all timesteps. An iterable indicates potentially different
power levels for each timestep. For a 2D problem, the power can be given
in [W/cm] as long as the "volume" assigned to a depletion material is
actually an area in [cm^2]. Either `power` or `power_density` must be
specified.
power_density : float or iterable of float, optional
Power density of the reactor in [W/gHM]. It is multiplied by initial
heavy metal inventory to get total power if `power` is not speficied.
print_out : bool, optional
Whether or not to print out time.
"""
if power is None:
if power_density is None:
raise ValueError(
"Neither power nor power density was specified.")
if not isinstance(power_density, Iterable):
power = power_density*operator.heavy_metal
else:
power = [i*operator.heavy_metal for i in power_density]
if not isinstance(power, Iterable):
power = [power]*len(timesteps)
# Generate initial conditions
with operator as vec:
# Initialize time and starting index
if operator.prev_res is None:
t = 0.0
i_res = 0
else:
t = operator.prev_res[-1].time[-1]
i_res = len(operator.prev_res)
for i, (dt, p) in enumerate(zip(timesteps, power)):
vec, t, _ = celi_inner(operator, vec, p, i, i_res, t, dt,
print_out)
# Perform one last simulation
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], power[-1])]
# Create results, write to disk
Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps))
def celi_inner(operator, vec, p, i, i_res, t, dt, print_out):
""" The inner loop of CE/LI CFQ4.
Parameters
----------
operator : Operator
The operator object to simulate on.
x : list of nuclide vector
Nuclide vector, beginning of time.
p : float
Power of the reactor in [W]
i : int
Current iteration number.
i_res : int
Starting index, for restart calculation.
t : float
Time at start of step.
dt : float
Time step.
print_out : bool
Whether or not to print out time.
Returns
-------
list of numpy.array
Nuclide vector, end of time.
float
Next time
OperatorResult
Operator result from beginning of step.
"""
chain = operator.chain
# Get beginning-of-timestep concentrations and reaction rates
# Avoid doing first transport run if already done in previous
# calculation
if i > 0 or operator.prev_res is None:
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], p)]
else:
# Get initial concentration
x = [operator.prev_res[-1].data[0]]
# Get rates
op_results = [operator.prev_res[-1]]
op_results[0].rates = op_results[0].rates[0]
# Set first stage value of keff
op_results[0].k = op_results[0].k[0]
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = p / power_res
op_results[0].rates *= ratio_power[0]
# Deplete to end
x_new = deplete(chain, x[0], op_results[0].rates, dt, print_out)
x.append(x_new)
op_results.append(operator(x[1], p))
# Deplete with two matrix exponentials
rates = list(zip(op_results[0].rates, op_results[1].rates))
x_end = deplete(chain, x[0], rates, dt, print_out,
matrix_func=_celi_f1)
x_end = deplete(chain, x_end, rates, dt, print_out,
matrix_func=_celi_f2)
# Create results, write to disk
Results.save(operator, x, op_results, [t, t + dt], p, i_res + i)
# return updated time and vectors
return x_end, t + dt, op_results[0]

View file

@ -0,0 +1,161 @@
"""The CF4 integrator."""
import copy
from collections.abc import Iterable
from .cram import deplete
from ..results import Results
# Functions to form the special matrix for depletion
def _cf4_f1(chain, rates):
return 1/2 * chain.form_matrix(rates)
def _cf4_f2(chain, rates):
return -1/2 * chain.form_matrix(rates[0]) + \
chain.form_matrix(rates[1])
def _cf4_f3(chain, rates):
return 1/4 * chain.form_matrix(rates[0]) + \
1/6 * chain.form_matrix(rates[1]) + \
1/6 * chain.form_matrix(rates[2]) + \
-1/12 * chain.form_matrix(rates[3])
def _cf4_f4(chain, rates):
return -1/12 * chain.form_matrix(rates[0]) + \
1/6 * chain.form_matrix(rates[1]) + \
1/6 * chain.form_matrix(rates[2]) + \
1/4 * chain.form_matrix(rates[3])
def cf4(operator, timesteps, power=None, power_density=None, print_out=True):
r"""Deplete using the CF4 algorithm.
Implements the fourth order `commutator-free Lie algorithm
<https://doi.org/10.1016/S0167-739X(02)00161-9>`_.
This algorithm is mathematically defined as:
.. math::
F_1 &= h A(y_0)
y_1 &= \text{expm}(1/2 F_1) y_0
F_2 &= h A(y_1)
y_2 &= \text{expm}(1/2 F_2) y_0
F_3 &= h A(y_2)
y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1
F_4 &= h A(y_3)
y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4)
\text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0
Parameters
----------
operator : openmc.deplete.TransportOperator
The operator object to simulate on.
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not cumulative.
power : float or iterable of float, optional
Power of the reactor in [W]. A single value indicates that the power is
constant over all timesteps. An iterable indicates potentially different
power levels for each timestep. For a 2D problem, the power can be given
in [W/cm] as long as the "volume" assigned to a depletion material is
actually an area in [cm^2]. Either `power` or `power_density` must be
specified.
power_density : float or iterable of float, optional
Power density of the reactor in [W/gHM]. It is multiplied by initial
heavy metal inventory to get total power if `power` is not speficied.
print_out : bool, optional
Whether or not to print out time.
"""
if power is None:
if power_density is None:
raise ValueError(
"Neither power nor power density was specified.")
if not isinstance(power_density, Iterable):
power = power_density*operator.heavy_metal
else:
power = [i*operator.heavy_metal for i in power_density]
if not isinstance(power, Iterable):
power = [power]*len(timesteps)
# Generate initial conditions
with operator as vec:
# Initialize time and starting index
if operator.prev_res is None:
t = 0.0
i_res = 0
else:
t = operator.prev_res[-1].time[-1]
i_res = len(operator.prev_res)
chain = operator.chain
for i, (dt, p) in enumerate(zip(timesteps, power)):
# Get beginning-of-timestep concentrations and reaction rates
# Avoid doing first transport run if already done in previous
# calculation
if i > 0 or operator.prev_res is None:
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], p)]
else:
# Get initial concentration
x = [operator.prev_res[-1].data[0]]
# Get rates
op_results = [operator.prev_res[-1]]
op_results[0].rates = op_results[0].rates[0]
# Set first stage value of keff
op_results[0].k = op_results[0].k[0]
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = p / power_res
op_results[0].rates *= ratio_power[0]
# Step 1: deplete with matrix 1/2*A(y0)
x_new = deplete(chain, x[0], op_results[0].rates, dt, print_out,
matrix_func=_cf4_f1)
x.append(x_new)
op_results.append(operator(x_new, p))
# Step 2: deplete with matrix 1/2*A(y1)
x_new = deplete(chain, x[0], op_results[1].rates, dt, print_out,
matrix_func=_cf4_f1)
x.append(x_new)
op_results.append(operator(x_new, p))
# Step 3: deplete with matrix -1/2*A(y0)+A(y2)
rates = list(zip(op_results[0].rates, op_results[2].rates))
x_new = deplete(chain, x[1], rates, dt, print_out,
matrix_func=_cf4_f2)
x.append(x_new)
op_results.append(operator(x_new, p))
# Step 4: deplete with two matrix exponentials
rates = list(zip(op_results[0].rates, op_results[1].rates,
op_results[2].rates, op_results[3].rates))
x_end = deplete(chain, x[0], rates, dt, print_out,
matrix_func=_cf4_f3)
x_end = deplete(chain, x_end, rates, dt, print_out,
matrix_func=_cf4_f4)
# Create results, write to disk
Results.save(operator, x, op_results, [t, t + dt], p, i_res + i)
# Advance time, update vector
t += dt
vec = copy.deepcopy(x_end)
# Perform one last simulation
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], power[-1])]
# Create results, write to disk
Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps))

View file

@ -14,7 +14,7 @@ import scipy.sparse.linalg as sla
from .. import comm
def deplete(chain, x, op_result, dt, print_out):
def deplete(chain, x, rates, dt, print_out=True, matrix_func=None):
"""Deplete materials using given reaction rates for a specified time
Parameters
@ -23,12 +23,14 @@ def deplete(chain, x, op_result, dt, print_out):
Depletion chain
x : list of numpy.ndarray
Atom number vectors for each material
op_result : openmc.deplete.OperatorResult
Result of applying transport operator (contains reaction rates)
rates : openmc.deplete.ReactionRates
Reaction rates (from transport operator)
dt : float
Time in [s] to deplete for
print_out : bool
print_out : bool, optional
Whether to show elapsed time
maxtrix_func : function, optional
Function to form the depletion matrix
Returns
-------
@ -38,16 +40,9 @@ def deplete(chain, x, op_result, dt, print_out):
"""
t_start = time.time()
# Set up iterators
n_mats = len(x)
chains = repeat(chain, n_mats)
vecs = (x[i] for i in range(n_mats))
rates = (op_result.rates[i, :, :] for i in range(n_mats))
dts = repeat(dt, n_mats)
# Use multiprocessing pool to distribute work
with Pool() as pool:
iters = zip(chains, vecs, rates, dts)
iters = zip(repeat(chain), x, rates, repeat(dt), repeat(matrix_func))
x_result = list(pool.starmap(_cram_wrapper, iters))
t_end = time.time()
@ -58,12 +53,12 @@ def deplete(chain, x, op_result, dt, print_out):
return x_result
def _cram_wrapper(chain, n0, rates, dt):
def _cram_wrapper(chain, n0, rates, dt, matrix_func=None):
"""Wraps depletion matrix creation / CRAM solve for multiprocess execution
Parameters
----------
chain : DepletionChain
chain : openmc.deplete.Chain
Depletion chain used to construct the burnup matrix
n0 : numpy.array
Vector to operate a matrix exponent on.
@ -71,13 +66,19 @@ def _cram_wrapper(chain, n0, rates, dt):
2D array indexed by nuclide then by cell.
dt : float
Time to integrate to.
maxtrix_func : function, optional
Function to form the depletion matrix
Returns
-------
numpy.array
Results of the matrix exponent.
"""
A = chain.form_matrix(rates)
if matrix_func is None:
A = chain.form_matrix(rates)
else:
A = matrix_func(chain, rates)
return CRAM48(A, n0, dt)

View file

@ -0,0 +1,147 @@
"""The EPC-RK4 integrator."""
import copy
from collections.abc import Iterable
from .cram import deplete
from ..results import Results
# Functions to form the special matrix for depletion
def _rk4_f1(chain, rates):
return 1/2 * chain.form_matrix(rates)
def _rk4_f4(chain, rates):
return 1/6 * chain.form_matrix(rates[0]) + \
1/3 * chain.form_matrix(rates[1]) + \
1/3 * chain.form_matrix(rates[2]) + \
1/6 * chain.form_matrix(rates[3])
def epc_rk4(operator, timesteps, power=None, power_density=None, print_out=True):
r"""Deplete using the EPC-RK4 algorithm.
Implements an extended predictor-corrector algorithm with traditional
Runge-Kutta 4 method.
This algorithm is mathematically defined as:
.. math::
F_1 &= h A(y_0)
y_1 &= \text{expm}(1/2 F_1) y_0
F_2 &= h A(y_1)
y_2 &= \text{expm}(1/2 F_2) y_0
F_3 &= h A(y_2)
y_3 &= \text{expm}(F_3) y_0
F_4 &= h A(y_3)
y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0
Parameters
----------
operator : openmc.deplete.TransportOperator
The operator object to simulate on.
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not cumulative.
power : float or iterable of float, optional
Power of the reactor in [W]. A single value indicates that the power is
constant over all timesteps. An iterable indicates potentially different
power levels for each timestep. For a 2D problem, the power can be given
in [W/cm] as long as the "volume" assigned to a depletion material is
actually an area in [cm^2]. Either `power` or `power_density` must be
specified.
power_density : float or iterable of float, optional
Power density of the reactor in [W/gHM]. It is multiplied by initial
heavy metal inventory to get total power if `power` is not speficied.
print_out : bool, optional
Whether or not to print out time.
"""
if power is None:
if power_density is None:
raise ValueError(
"Neither power nor power density was specified.")
if not isinstance(power_density, Iterable):
power = power_density*operator.heavy_metal
else:
power = [i*operator.heavy_metal for i in power_density]
if not isinstance(power, Iterable):
power = [power]*len(timesteps)
# Generate initial conditions
with operator as vec:
# Initialize time and starting index
if operator.prev_res is None:
t = 0.0
i_res = 0
else:
t = operator.prev_res[-1].time[-1]
i_res = len(operator.prev_res)
chain = operator.chain
for i, (dt, p) in enumerate(zip(timesteps, power)):
# Get beginning-of-timestep concentrations and reaction rates
# Avoid doing first transport run if already done in previous
# calculation
if i > 0 or operator.prev_res is None:
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], p)]
else:
# Get initial concentration
x = [operator.prev_res[-1].data[0]]
# Get rates
op_results = [operator.prev_res[-1]]
op_results[0].rates = op_results[0].rates[0]
# Set first stage value of keff
op_results[0].k = op_results[0].k[0]
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = p / power_res
op_results[0].rates *= ratio_power[0]
# Step 1: deplete with matrix 1/2*A(y0)
x_new = deplete(chain, x[0], op_results[0].rates, dt, print_out,
matrix_func=_rk4_f1)
x.append(x_new)
op_results.append(operator(x[1], p))
# Step 2: deplete with matrix 1/2*A(y1)
x_new = deplete(chain, x[0], op_results[1].rates, dt, print_out,
matrix_func=_rk4_f1)
x.append(x_new)
op_results.append(operator(x[2], p))
# Step 3: deplete with matrix A(y2)
x_new = deplete(chain, x[0], op_results[2].rates, dt, print_out)
x.append(x_new)
op_results.append(operator(x[3], p))
# Step 4: deplete with matrix 1/6*A(y0)+1/3*A(y1)+1/3*A(y2)+1/6*A(y3)
rates = list(zip(op_results[0].rates, op_results[1].rates,
op_results[2].rates, op_results[3].rates))
x_end = deplete(chain, x[0], rates, dt, print_out,
matrix_func=_rk4_f4)
# Create results, write to disk
Results.save(operator, x, op_results, [t, t + dt], p, i_res + i)
# Advance time, update vector
t += dt
vec = copy.deepcopy(x_end)
# Perform one last simulation
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], power[-1])]
# Create results, write to disk
Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps))

View file

@ -0,0 +1,170 @@
"""The LE/QI CFQ4 integrator."""
import copy
from collections.abc import Iterable
from itertools import repeat
from .celi import celi_inner
from .cram import deplete
from ..results import Results
# Functions to form the special matrix for depletion
def _leqi_f1(chain, inputs):
f1 = chain.form_matrix(inputs[0])
f2 = chain.form_matrix(inputs[1])
dt_l, dt = inputs[2], inputs[3]
return -dt / (12 * dt_l) * f1 + (dt + 6 * dt_l) / (12 * dt_l) * f2
def _leqi_f2(chain, inputs):
f1 = chain.form_matrix(inputs[0])
f2 = chain.form_matrix(inputs[1])
dt_l, dt = inputs[2], inputs[3]
return -5 * dt / (12 * dt_l) * f1 + (5 * dt + 6 * dt_l) / (12 * dt_l) * f2
def _leqi_f3(chain, inputs):
f1 = chain.form_matrix(inputs[0])
f2 = chain.form_matrix(inputs[1])
f3 = chain.form_matrix(inputs[2])
dt_l, dt = inputs[3], inputs[4]
return -dt**2 / (12 * dt_l * (dt + dt_l)) * f1 + \
(dt**2 + 6*dt*dt_l + 5*dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \
dt_l / (12 * (dt + dt_l)) * f3
def _leqi_f4(chain, inputs):
f1 = chain.form_matrix(inputs[0])
f2 = chain.form_matrix(inputs[1])
f3 = chain.form_matrix(inputs[2])
dt_l, dt = inputs[3], inputs[4]
return -dt**2 / (12 * dt_l * (dt + dt_l)) * f1 + \
(dt**2 + 2*dt*dt_l + dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \
(4 * dt * dt_l + 5 * dt_l**2) / (12 * dt_l * (dt + dt_l)) * f3
def leqi(operator, timesteps, power=None, power_density=None, print_out=True):
r"""Deplete using the LE/QI CFQ4 algorithm.
Implements the LE/QI Predictor-Corrector algorithm using the `fourth order
commutator-free integrator <https://doi.org/10.1137/05063042>`_.
"LE/QI" stands for linear extrapolation on predictor and quadratic
interpolation on corrector. This algorithm is mathematically defined as:
.. math::
y' &= A(y, t) y(t)
A_{last} &= A(y_{n-1}, t_n - h_1)
A_0 &= A(y_n, t_n)
F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0
F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0
y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n
A_1 &= A(y_p, t_n + h_2)
F_3 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} +
\frac{h_2 (5 h_1^2 + 6 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 +
\frac{h_2 h_1)}{12 (h_1 + h_2)} A_1
F_4 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} +
\frac{h_2 (h_1^2 + 2 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 +
\frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1
y_{n+1} &= \text{expm}(F_4) \text{expm}(F_3) y_n
It is initialized using the CE/LI algorithm.
Parameters
----------
operator : openmc.deplete.TransportOperator
The operator object to simulate on.
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not cumulative.
power : float or iterable of float, optional
Power of the reactor in [W]. A single value indicates that the power is
constant over all timesteps. An iterable indicates potentially different
power levels for each timestep. For a 2D problem, the power can be given
in [W/cm] as long as the "volume" assigned to a depletion material is
actually an area in [cm^2]. Either `power` or `power_density` must be
specified.
power_density : float or iterable of float, optional
Power density of the reactor in [W/gHM]. It is multiplied by initial
heavy metal inventory to get total power if `power` is not speficied.
print_out : bool, optional
Whether or not to print out time.
"""
if power is None:
if power_density is None:
raise ValueError(
"Neither power nor power density was specified.")
if not isinstance(power_density, Iterable):
power = power_density*operator.heavy_metal
else:
power = [i*operator.heavy_metal for i in power_density]
if not isinstance(power, Iterable):
power = [power]*len(timesteps)
# Generate initial conditions
with operator as vec:
# Initialize time and starting index
if operator.prev_res is None:
t = 0.0
i_res = 0
else:
t = operator.prev_res[-1].time[-1]
i_res = len(operator.prev_res)
chain = operator.chain
for i, (dt, p) in enumerate(zip(timesteps, power)):
# LE/QI needs the last step results to start
# Perform CE/LI CFQ4 or restore results for the first step
if i == 0:
if i_res <= 1:
dt_l = dt
x_new, t, op_res_last = celi_inner(operator, vec, p, i,
i_res, t, dt, print_out)
continue
else:
dt_l = t - operator.prev_res[-2].time[0]
op_res_last = operator.prev_res[-2]
op_res_last.rates = op_res_last.rates[0]
x_new = operator.prev_res[-1].data[0]
# Perform remaining LE/QI
x = [copy.deepcopy(x_new)]
op_results = [operator(x[0], p)]
inputs = list(zip(op_res_last.rates, op_results[0].rates,
repeat(dt_l), repeat(dt)))
x_new = deplete(chain, x[0], inputs, dt, print_out,
matrix_func=_leqi_f1)
x_new = deplete(chain, x_new, inputs, dt, print_out,
matrix_func=_leqi_f2)
x.append(x_new)
op_results.append(operator(x[1], p))
inputs = list(zip(op_res_last.rates, op_results[0].rates,
op_results[1].rates, repeat(dt_l), repeat(dt)))
x_new = deplete(chain, x[0], inputs, dt, print_out,
matrix_func=_leqi_f3)
x_new = deplete(chain, x_new, inputs, dt, print_out,
matrix_func=_leqi_f4)
# Create results, write to disk
Results.save(operator, x, op_results, [t, t+dt], p, i_res+i)
# update results
op_res_last = copy.deepcopy(op_results[0])
t += dt
dt_l = dt
# Perform one last simulation
x = [copy.deepcopy(x_new)]
op_results = [operator(x[0], power[-1])]
# Create results, write to disk
Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps))

View file

@ -55,20 +55,16 @@ def predictor(operator, timesteps, power=None, power_density=None,
# Generate initial conditions
with operator as vec:
chain = operator.chain
# Initialize time
# Initialize time and starting index
if operator.prev_res is None:
t = 0.0
else:
t = operator.prev_res[-1].time[-1]
# Initialize starting index for saving results
if operator.prev_res is None:
i_res = 0
else:
t = operator.prev_res[-1].time[-1]
i_res = len(operator.prev_res) - 1
chain = operator.chain
for i, (dt, p) in enumerate(zip(timesteps, power)):
# Get beginning-of-timestep concentrations and reaction rates
# Avoid doing first transport run if already done in previous
@ -90,10 +86,10 @@ def predictor(operator, timesteps, power=None, power_density=None,
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = p / power_res
op_results[0].rates[0] *= ratio_power[0]
op_results[0].rates *= ratio_power[0]
# Deplete for full timestep
x_end = deplete(chain, x[0], op_results[0], dt, print_out)
x_end = deplete(chain, x[0], op_results[0].rates, dt, print_out)
# Advance time, update vector
t += dt

View file

@ -0,0 +1,163 @@
"""The SI-CE/LI CFQ4 integrator."""
import copy
from collections.abc import Iterable
from .cram import deplete
from ..results import Results
from ..abc import OperatorResult
from .celi import _celi_f1, _celi_f2
def si_celi(operator, timesteps, power=None, power_density=None,
print_out=True, m=10):
r"""Deplete using the SI-CE/LI CFQ4 algorithm.
Implements the Stochastic Implicit CE/LI Predictor-Corrector algorithm using
the `fourth order commutator-free integrator <https://doi.org/10.1137/05063042>`_.
Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis
<http://hdl.handle.net/1721.1/113721>`_.
Parameters
----------
operator : openmc.deplete.TransportOperator
The operator object to simulate on.
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not cumulative.
power : float or iterable of float, optional
Power of the reactor in [W]. A single value indicates that the power is
constant over all timesteps. An iterable indicates potentially different
power levels for each timestep. For a 2D problem, the power can be given
in [W/cm] as long as the "volume" assigned to a depletion material is
actually an area in [cm^2]. Either `power` or `power_density` must be
specified.
power_density : float or iterable of float, optional
Power density of the reactor in [W/gHM]. It is multiplied by initial
heavy metal inventory to get total power if `power` is not speficied.
print_out : bool, optional
Whether or not to print out time.
m : int, optional
Number of stages.
"""
if power is None:
if power_density is None:
raise ValueError(
"Neither power nor power density was specified.")
if not isinstance(power_density, Iterable):
power = power_density*operator.heavy_metal
else:
power = [i*operator.heavy_metal for i in power_density]
if not isinstance(power, Iterable):
power = [power]*len(timesteps)
# Generate initial conditions
with operator as vec:
# Initialize time and starting index
if operator.prev_res is None:
t = 0.0
i_res = 0
else:
t = operator.prev_res[-1].time[-1]
i_res = len(operator.prev_res)
# Get the concentrations and reaction rates for the first
# beginning-of-timestep (BOS). Compute with m (stage number) times as
# many neutrons as later simulations for statistics reasons if no
# previous calculation results present
if operator.prev_res is None:
x = [copy.deepcopy(vec)]
if hasattr(operator, "settings"):
operator.settings.particles *= m
op_results = [operator(x[0], power[0])]
if hasattr(operator, "settings"):
operator.settings.particles //= m
else:
# Get initial concentration
x = [operator.prev_res[-1].data[0]]
# Get rates
op_results = [operator.prev_res[-1]]
op_results[0].rates = op_results[0].rates[0]
# Set first stage value of keff
op_results[0].k = op_results[0].k[0]
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = power[0] / power_res
op_results[0].rates *= ratio_power[0]
for i, (dt, p) in enumerate(zip(timesteps, power)):
x, t, op_results = si_celi_inner(operator, x, op_results, p,
i, i_res, t, dt, print_out, m)
# Create results for last point, write to disk
Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps))
def si_celi_inner(operator, x, op_results, p, i, i_res, t, dt, print_out, m=10):
""" The inner loop of SI-CE/LI CFQ4.
Parameters
----------
operator : Operator
The operator object to simulate on.
x : list of nuclide vector
Nuclide vector, beginning of time.
op_results : list of OperatorResult
Operator result at BOS.
p : float
Power of the reactor in [W]
i : int
Current iteration number.
i_res : int
Starting index, for restart calculation.
t : float
Time at start of step.
dt : float
Time step.
print_out : bool
Whether or not to print out time.
m : int, optional
Number of stages.
Returns
-------
list of nuclide vector (numpy.array)
Nuclide vector, end of time.
float
Next time
list of OperatorResult
Operator result at end of time.
"""
chain = operator.chain
# Deplete to end
x_new = deplete(chain, x[0], op_results[0].rates, dt, print_out)
x.append(x_new)
for j in range(m + 1):
op_res = operator(x_new, p)
if j <= 1:
op_res_bar = copy.deepcopy(op_res)
else:
rates = 1/j * op_res.rates + (1 - 1/j) * op_res_bar.rates
k = 1/j * op_res.k + (1 - 1/j) * op_res_bar.k
op_res_bar = OperatorResult(k, rates)
rates = list(zip(op_results[0].rates, op_res_bar.rates))
x_new = deplete(chain, x[0], rates, dt, print_out,
matrix_func=_celi_f1)
x_new = deplete(chain, x_new, rates, dt, print_out,
matrix_func=_celi_f2)
# Create results, write to disk
op_results.append(op_res_bar)
Results.save(operator, x, op_results, [t, t+dt], p, i_res+i)
# return updated time and vectors
return [x_new], t + dt, [op_res_bar]

View file

@ -0,0 +1,151 @@
"""The SI-LE/QI CFQ4 integrator."""
import copy
from collections.abc import Iterable
from itertools import repeat
from .si_celi import si_celi_inner
from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4
from .cram import deplete
from ..results import Results
from ..abc import OperatorResult
def si_leqi(operator, timesteps, power=None, power_density=None,
print_out=True, m=10):
r"""Deplete using the SI-LE/QI CFQ4 algorithm.
Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm using
the `fourth order commutator-free integrator <https://doi.org/10.1137/05063042>`_.
Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis
<http://hdl.handle.net/1721.1/113721>`_.
Parameters
----------
operator : openmc.deplete.TransportOperator
The operator object to simulate on.
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not cumulative.
power : float or iterable of float, optional
Power of the reactor in [W]. A single value indicates that the power is
constant over all timesteps. An iterable indicates potentially different
power levels for each timestep. For a 2D problem, the power can be given
in [W/cm] as long as the "volume" assigned to a depletion material is
actually an area in [cm^2]. Either `power` or `power_density` must be
specified.
power_density : float or iterable of float, optional
Power density of the reactor in [W/gHM]. It is multiplied by initial
heavy metal inventory to get total power if `power` is not speficied.
print_out : bool, optional
Whether or not to print out time.
m : int, optional
Number of stages.
"""
if power is None:
if power_density is None:
raise ValueError(
"Neither power nor power density was specified.")
if not isinstance(power_density, Iterable):
power = power_density*operator.heavy_metal
else:
power = [i*operator.heavy_metal for i in power_density]
if not isinstance(power, Iterable):
power = [power]*len(timesteps)
# Generate initial conditions
with operator as vec:
# Initialize time and starting index
if operator.prev_res is None:
t = 0.0
i_res = 0
else:
t = operator.prev_res[-1].time[-1]
i_res = len(operator.prev_res)
# Get the concentrations and reaction rates for the first
# beginning-of-timestep (BOS). Compute with m (stage number) times as
# many neutrons as later simulations for statistics reasons if no
# previous calculation results present
if operator.prev_res is None:
x = [copy.deepcopy(vec)]
if hasattr(operator, "settings"):
operator.settings.particles *= m
op_results = [operator(x[0], power[0])]
if hasattr(operator, "settings"):
operator.settings.particles //= m
else:
# Get initial concentration
x = [operator.prev_res[-1].data[0]]
# Get rates
op_results = [operator.prev_res[-1]]
op_results[0].rates = op_results[0].rates[0]
# Set first stage value of keff
op_results[0].k = op_results[0].k[0]
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = power[0] / power_res
op_results[0].rates *= ratio_power[0]
chain = operator.chain
for i, (dt, p) in enumerate(zip(timesteps, power)):
# LE/QI needs the last step results to start
# Perform SI-CE/LI CFQ4 or restore results for the first step
if i == 0:
dt_l = dt
if i_res <= 1:
op_res_last = copy.deepcopy(op_results[0])
x, t, op_results = si_celi_inner(operator, x, op_results, p,
i, i_res, t, dt, print_out)
continue
else:
dt_l = t - operator.prev_res[-2].time[0]
op_res_last = operator.prev_res[-2]
op_res_last.rates = op_res_last.rates[0]
x = [operator.prev_res[-1].data[0]]
# Perform remaining LE/QI
inputs = list(zip(op_res_last.rates, op_results[0].rates,
repeat(dt_l), repeat(dt)))
x_new = deplete(chain, x[0], inputs, dt, print_out,
matrix_func=_leqi_f1)
x_new = deplete(chain, x_new, inputs, dt, print_out,
matrix_func=_leqi_f2)
x.append(x_new)
# Loop on inner
for j in range(m + 1):
op_res = operator(x_new, p)
if j <= 1:
op_res_bar = copy.deepcopy(op_res)
else:
rates = 1/j * op_res.rates + (1 - 1/j) * op_res_bar.rates
k = 1/j * op_res.k + (1 - 1/j) * op_res_bar.k
op_res_bar = OperatorResult(k, rates)
inputs = list(zip(op_res_last.rates, op_results[0].rates,
op_res_bar.rates, repeat(dt_l), repeat(dt)))
x_new = deplete(chain, x[0], inputs, dt, print_out,
matrix_func=_leqi_f3)
x_new = deplete(chain, x_new, inputs, dt, print_out,
matrix_func=_leqi_f4)
# Create results, write to disk
op_results.append(op_res_bar)
Results.save(operator, x, op_results, [t, t+dt], p, i_res+i)
# update results
x = [x_new]
op_res_last = copy.deepcopy(op_results[0])
op_results = [op_res_bar]
t += dt
dt_l = dt
# Create results for last point, write to disk
Results.save(operator, x, op_results, [t, t], p, i_res+len(timesteps))

View file

@ -125,6 +125,10 @@ class Operator(TransportOperator):
else:
self.prev_res = None
# Differentiate burnable materials with multiple instances
if self.diff_burnable_mats:
self._differentiate_burnable_mats()
# Clear out OpenMC, create task lists, distribute
openmc.reset_auto_ids()
self.burnable_mats, volume, nuclides = self._get_burnable_mats()
@ -227,10 +231,6 @@ class Operator(TransportOperator):
"""
if self.diff_burnable_mats:
# Automatically distribute burnable materials
self._differentiate_burnable_mats()
burnable_mats = set()
model_nuclides = set()
volume = OrderedDict()

View file

@ -122,7 +122,7 @@ class Model(object):
for plot in plots:
self._plots.append(plot)
def deplete(self, timesteps, power, chain_file=None, method='cecm',
def deplete(self, timesteps, chain_file=None, method='cecm',
**kwargs):
"""Deplete model using specified timesteps/power
@ -131,17 +131,11 @@ class Model(object):
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not
cumulative.
power : float or iterable of float
Power of the reactor in [W]. A single value indicates that the power
is constant over all timesteps. An iterable indicates potentially
different power levels for each timestep. For a 2D problem, the
power can be given in [W/cm] as long as the "volume" assigned to a
depletion material is actually an area in [cm^2].
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists.
method : {'cecm', 'predictor'}
Integration method used for depletion
method : str
Integration method used for depletion (e.g., 'cecm', 'predictor')
**kwargs
Keyword arguments passed to integration function (e.g.,
:func:`openmc.deplete.integrator.cecm`)
@ -156,12 +150,9 @@ class Model(object):
op = dep.Operator(self.geometry, self.settings, chain_file)
# Perform depletion
if method == 'predictor':
dep.integrator.predictor(op, timesteps, power, **kwargs)
elif method == 'cecm':
dep.integrator.cecm(op, timesteps, power, **kwargs)
else:
check_value('method', method, ('cecm', 'predictor'))
check_value('method', method, ('cecm', 'predictor', 'cf4', 'epc_rk4',
'si_celi', 'si_leqi', 'celi', 'leqi'))
getattr(dep.integrator, method)(op, timesteps, **kwargs)
def export_to_xml(self):
"""Export model to XML files."""

View file

@ -62,7 +62,6 @@ contains
use geometry_header
use material_header
use photon_header
use sab_header
use settings
use simulation_header
use surface_header
@ -74,6 +73,9 @@ contains
subroutine free_memory_source() bind(C)
end subroutine
subroutine free_memory_material() bind(C)
end subroutine
subroutine free_memory_mesh() bind(C)
end subroutine free_memory_mesh
@ -85,6 +87,9 @@ contains
subroutine free_memory_cmfd() bind(C)
end subroutine free_memory_cmfd
subroutine sab_clear() bind(C)
end subroutine
end interface
call free_memory_geometry()
@ -95,7 +100,7 @@ contains
call free_memory_nuclide()
call free_memory_photon()
call free_memory_settings()
call free_memory_sab()
call sab_clear()
call free_memory_source()
call free_memory_mesh()
call free_memory_tally()

View file

@ -30,12 +30,10 @@ void thick_target_bremsstrahlung(Particle& p, double* E_lost)
{
if (p.material == MATERIAL_VOID) return;
// TODO: off-by-one
int photon = static_cast<int>(ParticleType::photon) - 1;
int photon = static_cast<int>(ParticleType::photon);
if (p.E < settings::energy_cutoff[photon]) return;
// Get bremsstrahlung data for this material and particle type
// TODO: off-by-one
BremsstrahlungData* mat;
if (p.type == static_cast<int>(ParticleType::positron)) {
mat = &model::materials[p.material -1]->ttb_->positron;

View file

@ -122,10 +122,10 @@ module constants
! Particle type
integer, parameter :: &
NEUTRON = 1, &
PHOTON = 2, &
ELECTRON = 3, &
POSITRON = 4
NEUTRON = 0, &
PHOTON = 1, &
ELECTRON = 2, &
POSITRON = 3
! Angular distribution type
integer, parameter :: &

View file

@ -2,15 +2,27 @@
#include "openmc/constants.h"
#include "openmc/container_util.h"
#ifdef DAGMC
#include "openmc/dagmc.h"
#endif
#include "openmc/error.h"
#include "openmc/file_utils.h"
#include "openmc/hdf5_interface.h"
#include "openmc/material.h"
#include "openmc/message_passing.h"
#include "openmc/nuclide.h"
#include "openmc/photon.h"
#include "openmc/settings.h"
#include "openmc/simulation.h"
#include "openmc/string_utils.h"
#include "openmc/thermal.h"
#include "openmc/xml_interface.h"
#include "openmc/wmp.h"
#include "pugixml.hpp"
#include <cstdlib> // for getenv
#include <unordered_set>
namespace openmc {
@ -81,15 +93,30 @@ extern "C" void read_mg_cross_sections_header();
void read_cross_sections_xml()
{
// Check if materials.xml exists
pugi::xml_document doc;
std::string filename = settings::path_input + "materials.xml";
#ifdef DAGMC
std::string s;
bool found_uwuw_mats = false;
if (settings::dagmc) {
found_uwuw_mats = get_uwuw_materials_xml(s);
}
if (found_uwuw_mats) {
// if we found uwuw materials, load those
doc.load_file(s.c_str());
} else {
#endif
// Check if materials.xml exists
if (!file_exists(filename)) {
fatal_error("Material XML file '" + filename + "' does not exist.");
}
// Parse materials.xml file
pugi::xml_document doc;
doc.load_file(filename.c_str());
#ifdef DAGMC
}
#endif
auto root = doc.document_element();
// Find cross_sections.xml file -- the first place to look is the
@ -136,9 +163,7 @@ void read_cross_sections_xml()
int i = 0;
for (const auto& lib : data::libraries) {
for (const auto& name : lib.materials_) {
std::string lower_name = name;
to_lower(lower_name);
LibraryKey key {lib.type_, lower_name};
LibraryKey key {lib.type_, name};
data::library_map.insert({key, i});
}
++i;
@ -146,9 +171,7 @@ void read_cross_sections_xml()
// Check that 0K nuclides are listed in the cross_sections.xml file
for (const auto& name : settings::res_scat_nuclides) {
std::string lower_name = name;
to_lower(lower_name);
LibraryKey key {Library::Type::neutron, lower_name};
LibraryKey key {Library::Type::neutron, name};
if (data::library_map.find(key) == data::library_map.end()) {
fatal_error("Could not find resonant scatterer " +
name + " in cross_sections.xml file!");
@ -156,6 +179,211 @@ void read_cross_sections_xml()
}
}
extern "C" void nuclide_from_hdf5(hid_t group, const Nuclide* ptr,
const double* temps, int n, int n_nuclide);
void
read_ce_cross_sections(const std::vector<std::vector<double>>& nuc_temps,
const std::vector<std::vector<double>>& thermal_temps)
{
std::unordered_set<std::string> already_read;
// Construct a vector of nuclide names because we haven't loaded nuclide data
// yet, but we need to know the name of the i-th nuclide
std::vector<std::string> nuclide_names(data::nuclide_map.size());
std::vector<std::string> thermal_names(data::thermal_scatt_map.size());
for (const auto& kv : data::nuclide_map) {
nuclide_names[kv.second] = kv.first;
}
for (const auto& kv : data::thermal_scatt_map) {
thermal_names[kv.second] = kv.first;
}
// Read cross sections
for (const auto& mat : model::materials) {
for (int i_nuc : mat->nuclide_) {
// Find name of corresponding nuclide. Because we haven't actually loaded
// data, we don't have the name available, so instead we search through
// all key/value pairs in nuclide_map
std::string& name = nuclide_names[i_nuc];
// If we've already read this nuclide, skip it
if (already_read.find(name) != already_read.end()) continue;
LibraryKey key {Library::Type::neutron, name};
int idx = data::library_map[key];
std::string& filename = data::libraries[idx].path_;
write_message("Reading " + name + " from " + filename, 6);
// Open file and make sure version is sufficient
hid_t file_id = file_open(filename, 'r');
check_data_version(file_id);
// Read nuclide data from HDF5
hid_t group = open_group(file_id, name.c_str());
int i_nuclide = data::nuclides.size();
data::nuclides.push_back(std::make_unique<Nuclide>(
group, nuc_temps[i_nuc], i_nuclide));
// Read from Fortran too
nuclide_from_hdf5(group, data::nuclides.back().get(),
&nuc_temps[i_nuc].front(), nuc_temps[i_nuc].size(), i_nuclide + 1);
close_group(group);
file_close(file_id);
// Determine if minimum/maximum energy for this nuclide is greater/less
// than the previous
if (data::nuclides[i_nuclide]->grid_.size() >= 1) {
int neutron = static_cast<int>(ParticleType::neutron);
data::energy_min[neutron] = std::max(data::energy_min[neutron],
data::nuclides[i_nuclide]->grid_[0].energy.front());
data::energy_max[neutron] = std::min(data::energy_max[neutron],
data::nuclides[i_nuclide]->grid_[0].energy.back());
}
// Add name and alias to dictionary
already_read.insert(name);
// Check if elemental data has been read, if needed
int pos = name.find_first_of("0123456789");
std::string element = name.substr(0, pos);
if (settings::photon_transport) {
if (already_read.find(element) == already_read.end()) {
// Read photon interaction data from HDF5 photon library
LibraryKey key {Library::Type::photon, element};
int idx = data::library_map[key];
std::string& filename = data::libraries[idx].path_;
int i_element = data::element_map[element];
write_message("Reading " + element + " from " + filename, 6);
// Open file and make sure version is sufficient
hid_t file_id = file_open(filename, 'r');
check_data_version(file_id);
// Read element data from HDF5
hid_t group = open_group(file_id, element.c_str());
data::elements.emplace_back(group, data::elements.size());
// Determine if minimum/maximum energy for this element is greater/less than
// the previous
const auto& elem {data::elements.back()};
if (elem.energy_.size() >= 1) {
int photon = static_cast<int>(ParticleType::photon);
int n = elem.energy_.size();
data::energy_min[photon] = std::max(data::energy_min[photon],
std::exp(elem.energy_(1)));
data::energy_max[photon] = std::min(data::energy_max[photon],
std::exp(elem.energy_(n - 1)));
}
close_group(group);
file_close(file_id);
// Add element to set
already_read.insert(element);
}
}
// Read multipole file into the appropriate entry on the nuclides array
if (settings::temperature_multipole) read_multipole_data(i_nuclide);
}
}
for (auto& mat : model::materials) {
for (const auto& table : mat->thermal_tables_) {
// Get name of S(a,b) table
int i_table = table.index_table;
std::string& name = thermal_names[i_table];
if (already_read.find(name) == already_read.end()) {
LibraryKey key {Library::Type::thermal, name};
int idx = data::library_map[key];
std::string& filename = data::libraries[idx].path_;
write_message("Reading " + name + " from " + filename, 6);
// Open file and make sure version matches
hid_t file_id = file_open(filename, 'r');
check_data_version(file_id);
// Read thermal scattering data from HDF5
hid_t group = open_group(file_id, name.c_str());
data::thermal_scatt.push_back(std::make_unique<ThermalScattering>(
group, thermal_temps[i_table]));
close_group(group);
file_close(file_id);
// Add name to dictionary
already_read.insert(name);
}
} // thermal_tables_
// Finish setting up materials (normalizing densities, etc.)
mat->finalize();
} // materials
// Set up logarithmic grid for nuclides
for (auto& nuc : data::nuclides) {
nuc->init_grid();
}
int neutron = static_cast<int>(ParticleType::neutron);
simulation::log_spacing = std::log(data::energy_max[neutron] /
data::energy_min[neutron]) / settings::n_log_bins;
if (settings::photon_transport && settings::electron_treatment == ELECTRON_TTB) {
// Determine if minimum/maximum energy for bremsstrahlung is greater/less
// than the current minimum/maximum
if (data::ttb_e_grid.size() >= 1) {
int photon = static_cast<int>(ParticleType::photon);
int n_e = data::ttb_e_grid.size();
data::energy_min[photon] = std::max(data::energy_min[photon], data::ttb_e_grid(1));
data::energy_max[photon] = std::min(data::energy_max[photon], data::ttb_e_grid(n_e - 1));
}
// Take logarithm of energies since they are log-log interpolated
data::ttb_e_grid = xt::log(data::ttb_e_grid);
}
// Show which nuclide results in lowest energy for neutron transport
for (const auto& nuc : data::nuclides) {
// If a nuclide is present in a material that's not used in the model, its
// grid has not been allocated
if (nuc->grid_.size() > 0) {
double max_E = nuc->grid_[0].energy.back();
int neutron = static_cast<int>(ParticleType::neutron);
if (max_E == data::energy_max[neutron]) {
write_message("Maximum neutron transport energy: " +
std::to_string(data::energy_max[neutron]) + " eV for " +
nuc->name_, 7);
if (mpi::master && data::energy_max[neutron] < 20.0e6) {
warning("Maximum neutron energy is below 20 MeV. This may bias "
" the results.");
}
break;
}
}
}
// If the user wants multipole, make sure we found a multipole library.
if (settings::temperature_multipole) {
bool mp_found = false;
for (const auto& nuc : data::nuclides) {
if (nuc->multipole_) {
mp_found = true;
break;
}
}
if (mpi::master && !mp_found) {
warning("Windowed multipole functionality is turned on, but no multipole "
"libraries were found. Make sure that windowed multipole data is "
"present in your cross_sections.xml file.");
}
}
}
void read_ce_cross_sections_xml()
{
// Check if cross_sections.xml exists

View file

@ -1,16 +1,38 @@
#include "openmc/dagmc.h"
#include "openmc/cell.h"
#include "openmc/constants.h"
#include "openmc/error.h"
#include "openmc/file_utils.h"
#include "openmc/string_utils.h"
#include "openmc/settings.h"
#include "openmc/geometry.h"
#ifdef DAGMC
#include "uwuw.hpp"
#include "dagmcmetadata.hpp"
#endif
#include <string>
#include <sstream>
#include <algorithm>
#include <fstream>
namespace openmc {
#ifdef DAGMC
const bool dagmc_enabled = true;
#else
const bool dagmc_enabled = false;
#endif
}
#ifdef DAGMC
const std::string DAGMC_FILENAME = "dagmc.h5m";
namespace openmc {
@ -20,28 +42,96 @@ moab::DagMC* DAG;
} // namespace model
bool get_uwuw_materials_xml(std::string& s) {
UWUW uwuw(DAGMC_FILENAME.c_str());
std::stringstream ss;
bool uwuw_mats_present = false;
if (uwuw.material_library.size() != 0) {
uwuw_mats_present = true;
// write header
ss << "<?xml version=\"1.0\"?>\n";
ss << "<materials>\n";
const auto& mat_lib = uwuw.material_library;
// write materials
for (auto mat : mat_lib) { ss << mat.second.openmc("atom"); }
// write footer
ss << "</materials>";
s = ss.str();
}
return uwuw_mats_present;
}
pugi::xml_document* read_uwuw_materials() {
pugi::xml_document* doc = nullptr;
std::string s;
bool found_uwuw_mats = get_uwuw_materials_xml(s);
if (found_uwuw_mats) {
doc = new pugi::xml_document();
pugi::xml_parse_result result = doc->load_string(s.c_str());
}
return doc;
}
bool write_uwuw_materials_xml() {
std::string s;
bool found_uwuw_mats = get_uwuw_materials_xml(s);
// if there is a material library in the file
if (found_uwuw_mats) {
// write a material.xml file
std::ofstream mats_xml("materials.xml");
mats_xml << s;
mats_xml.close();
}
return found_uwuw_mats;
}
void load_dagmc_geometry()
{
if (!model::DAG) {
model::DAG = new moab::DagMC();
}
int32_t dagmc_univ_id = 0; // universe is always 0 for DAGMC
/// Materials \\\
moab::ErrorCode rval = model::DAG->load_file("dagmc.h5m");
// create uwuw instance
UWUW uwuw(DAGMC_FILENAME.c_str());
// check for uwuw material definitions
bool using_uwuw = !uwuw.material_library.empty();
// notify user if UWUW materials are going to be used
if (using_uwuw) {
std::cout << "Found UWUW Materials in the DAGMC geometry file.\n";
}
int32_t dagmc_univ_id = 0; // universe is always 0 for DAGMC runs
// load the DAGMC geometry
moab::ErrorCode rval = model::DAG->load_file(DAGMC_FILENAME.c_str());
MB_CHK_ERR_CONT(rval);
// initialize acceleration data structures
rval = model::DAG->init_OBBTree();
MB_CHK_ERR_CONT(rval);
std::vector<std::string> prop_keywords;
prop_keywords.push_back("mat");
prop_keywords.push_back("boundary");
std::map<std::string, std::string> ph;
model::DAG->parse_properties(prop_keywords, ph, ":");
// parse model metadata
dagmcMetaData DMD(model::DAG);
if (using_uwuw) {
DMD.load_property_data();
}
std::vector<std::string> keywords {"temp", "mat", "density", "boundary"};
std::map<std::string, std::string> dum;
std::string delimiters = ":/";
rval = model::DAG->parse_properties(keywords, dum, delimiters.c_str());
MB_CHK_ERR_CONT(rval);
/// Cells (Volumes) \\\
// initialize cell objects
model::n_cells = model::DAG->num_entities(3);
@ -69,35 +159,80 @@ void load_dagmc_geometry()
model::universes[it->second]->cells_.push_back(i);
}
// check for temperature assignment
std::string temp_value;
if (model::DAG->has_prop(vol_handle, "temp")) {
rval = model::DAG->prop_value(vol_handle, "temp", temp_value);
MB_CHK_ERR_CONT(rval);
double temp = std::stod(temp_value);
c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * temp));
} else {
c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * settings::temperature_default));
}
// MATERIALS
if (model::DAG->is_implicit_complement(vol_handle)) {
// assuming implicit complement is void for now
c->material_.push_back(MATERIAL_VOID);
if (model::DAG->has_prop(vol_handle, "mat")) {
// if the implicit complement has been assigned a material, use it
std::string comp_mat = DMD.volume_material_property_data_eh[vol_handle];
// Note: material numbers are set by UWUW
int mat_number = uwuw.material_library[comp_mat].metadata["mat_number"].asInt();
c->material_.push_back(mat_number);
} else {
// if no material is found, the implicit complement is void
c->material_.push_back(MATERIAL_VOID);
}
continue;
}
if (model::DAG->has_prop(vol_handle, "mat")){
std::string mat_value;
// determine volume material assignment
std::string mat_value;
if (model::DAG->has_prop(vol_handle, "mat")) {
rval = model::DAG->prop_value(vol_handle, "mat", mat_value);
MB_CHK_ERR_CONT(rval);
to_lower(mat_value);
if (mat_value == "void" || mat_value == "vacuum") {
c->material_.push_back(MATERIAL_VOID);
} else {
c->material_.push_back(std::stoi(mat_value));
}
} else {
std::stringstream err_msg;
err_msg << "Volume " << c->id_ << " has no material assignment.";
fatal_error(err_msg.str());
}
std::string cmp_str = mat_value;
to_lower(cmp_str);
// material void checks
if (cmp_str.find("void") != std::string::npos ||
cmp_str.find("vacuum") != std::string::npos ||
cmp_str.find("graveyard") != std::string::npos) {
c->material_.push_back(MATERIAL_VOID);
} else {
if (using_uwuw) {
// lookup material in uwuw if the were present
std::string uwuw_mat = DMD.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[uwuw_mat].metadata["mat_number"].asInt();
c->material_.push_back(mat_number);
} else {
std::stringstream err_msg;
err_msg << "Material with value " << mat_value << " not found ";
err_msg << "in the UWUW material library";
fatal_error(err_msg);
}
} else {
// if not using UWUW materials, we'll find this material
// later in the materials.xml
c->material_.push_back(std::stoi(mat_value));
}
}
}
// Allocate the cell overlap count if necessary.
// allocate the cell overlap count if necessary
if (settings::check_overlaps) {
model::overlap_check_count.resize(model::cells.size(), 0);
}
/// Surfaces \\\
// initialize surface objects
int n_surfaces = model::DAG->num_entities(2);
model::surfaces.resize(n_surfaces);
@ -110,8 +245,9 @@ void load_dagmc_geometry()
s->id_ = model::DAG->id_by_index(2, i+1);
s->dagmc_ptr_ = model::DAG;
// set BCs
std::string bc_value;
if (model::DAG->has_prop(surf_handle, "boundary")) {
std::string bc_value;
rval = model::DAG->prop_value(surf_handle, "boundary", bc_value);
MB_CHK_ERR_CONT(rval);
to_lower(bc_value);
@ -130,7 +266,8 @@ void load_dagmc_geometry()
<< "\" specified on surface " << s->id_;
fatal_error(err_msg);
}
} else { // if no BC property is found, set to transmit
} else {
// if no condition is found, set to transmit
s->bc_ = BC_TRANSMIT;
}
@ -147,5 +284,6 @@ void free_memory_dagmc()
delete model::DAG;
}
}
#endif

View file

@ -13,6 +13,11 @@ module dagmc_header
subroutine free_memory_dagmc() bind(C)
end subroutine free_memory_dagmc
function read_uwuw_materials() result(doc) bind(C)
import C_PTR
type(C_PTR) :: doc
end function read_uwuw_materials
end interface
end module dagmc_header

View file

@ -16,7 +16,6 @@ using namespace openmc;
// Functions defined in Fortran
extern "C" void free_memory();
extern "C" void reset_timers_f();
int openmc_finalize()
{
@ -25,7 +24,6 @@ int openmc_finalize()
// Reset timers
reset_timers();
reset_timers_f();
// Reset global variables
settings::assume_separate = false;
@ -127,7 +125,6 @@ int openmc_hard_reset()
// Reset all tallies and timers
openmc_reset();
reset_timers();
reset_timers_f();
// Reset total generations and keff guess
simulation::keff = 1.0;

View file

@ -6,6 +6,7 @@
#include "openmc/cell.h"
#include "openmc/constants.h"
#include "openmc/container_util.h"
#include "openmc/error.h"
#include "openmc/geometry.h"
#include "openmc/lattice.h"
@ -112,6 +113,72 @@ assign_temperatures()
//==============================================================================
void
get_temperatures(std::vector<std::vector<double>>& nuc_temps,
std::vector<std::vector<double>>& thermal_temps)
{
for (const auto& cell : model::cells) {
// Skip non-material cells.
if (cell->fill_ != C_NONE) continue;
for (int j = 0; j < cell->material_.size(); ++j) {
// Skip void materials
int i_material = cell->material_[j];
if (i_material == MATERIAL_VOID) continue;
// Get temperature of cell (rounding to nearest integer)
double sqrtkT = cell->sqrtkT_.size() == 1 ?
cell->sqrtkT_[j] : cell->sqrtkT_[0];
double temperature = sqrtkT*sqrtkT / K_BOLTZMANN;
const auto& mat {model::materials[i_material]};
for (const auto& i_nuc : mat->nuclide_) {
// Add temperature if it hasn't already been added
if (!contains(nuc_temps[i_nuc], temperature)) {
nuc_temps[i_nuc].push_back(temperature);
}
}
for (const auto& table : mat->thermal_tables_) {
// Get index in data::thermal_scatt array
int i_sab = table.index_table;
// Add temperature if it hasn't already been added
if (!contains(thermal_temps[i_sab], temperature)) {
thermal_temps[i_sab].push_back(temperature);
}
}
}
}
}
//==============================================================================
void finalize_geometry(std::vector<std::vector<double>>& nuc_temps,
std::vector<std::vector<double>>& thermal_temps)
{
// Perform some final operations to set up the geometry
adjust_indices();
count_cell_instances(model::root_universe);
// Assign temperatures to cells that don't have temperatures already assigned
assign_temperatures();
// Determine desired temperatures for each nuclide and S(a,b) table
get_temperatures(nuc_temps, thermal_temps);
// Check to make sure there are not too many nested coordinate levels in the
// geometry since the coordinate list is statically allocated for performance
// reasons
if (maximum_levels(model::root_universe) > MAX_COORD) {
fatal_error("Too many nested coordinate levels in the geometry. "
"Try increasing the maximum number of coordinate levels by "
"providing the CMake -Dmaxcoord= option.");
}
}
//==============================================================================
int32_t
find_root_universe()
{

View file

@ -5,9 +5,7 @@ module geometry_header
use algorithm, only: find
use constants, only: K_BOLTZMANN, MATERIAL_VOID
use dict_header, only: DictIntInt
use material_header, only: Material, materials
use nuclide_header
use sab_header
use stl_vector, only: VectorReal
use string, only: to_lower
@ -176,70 +174,6 @@ contains
sqrtkT = cell_sqrtkT_c(this % ptr, i)
end function cell_sqrtkT
!===============================================================================
! GET_TEMPERATURES returns a list of temperatures that each nuclide/S(a,b) table
! appears at in the model. Later, this list is used to determine the actual
! temperatures to read (which may be different if interpolation is used)
!===============================================================================
subroutine get_temperatures(nuc_temps, sab_temps)
type(VectorReal), allocatable, intent(out) :: nuc_temps(:)
type(VectorReal), optional, allocatable, intent(out) :: sab_temps(:)
integer :: i, j, k
integer :: i_nuclide ! index in nuclides array
integer :: i_sab ! index in S(a,b) array
integer :: i_material
real(8) :: temperature ! temperature in Kelvin
allocate(nuc_temps(n_nuclides))
if (present(sab_temps)) allocate(sab_temps(n_sab_tables))
do i = 1, size(cells)
! Skip non-material cells.
if (cells(i) % fill() /= C_NONE) cycle
do j = 1, cells(i) % material_size()
! Skip void materials
if (cells(i) % material(j) == MATERIAL_VOID) cycle
! Get temperature of cell (rounding to nearest integer)
if (cells(i) % sqrtkT_size() > 1) then
temperature = cells(i) % sqrtkT(j-1)**2 / K_BOLTZMANN
else
temperature = cells(i) % sqrtkT(0)**2 / K_BOLTZMANN
end if
i_material = cells(i) % material(j)
associate (mat => materials(i_material))
NUC_NAMES_LOOP: do k = 1, size(mat % names)
! Get index in nuc_temps array
i_nuclide = nuclide_dict % get(to_lower(mat % names(k)))
! Add temperature if it hasn't already been added
if (find(nuc_temps(i_nuclide), temperature) == -1) then
call nuc_temps(i_nuclide) % push_back(temperature)
end if
end do NUC_NAMES_LOOP
if (present(sab_temps) .and. mat % n_sab > 0) then
SAB_NAMES_LOOP: do k = 1, size(mat % sab_names)
! Get index in nuc_temps array
i_sab = sab_dict % get(to_lower(mat % sab_names(k)))
! Add temperature if it hasn't already been added
if (find(sab_temps(i_sab), temperature) == -1) then
call sab_temps(i_sab) % push_back(temperature)
end if
end do SAB_NAMES_LOOP
end if
end associate
end do
end do
end subroutine get_temperatures
!===============================================================================
! FREE_MEMORY_GEOMETRY deallocates global arrays defined in this module
!===============================================================================

View file

@ -5,6 +5,7 @@
#include <cstring>
#include <sstream>
#include <string>
#include <vector>
#ifdef _OPENMP
#include <omp.h>
@ -12,20 +13,30 @@
#include "openmc/capi.h"
#include "openmc/constants.h"
#include "openmc/cross_sections.h"
#include "openmc/error.h"
#include "openmc/geometry_aux.h"
#include "openmc/hdf5_interface.h"
#include "openmc/message_passing.h"
#include "openmc/mgxs_interface.h"
#include "openmc/nuclide.h"
#include "openmc/output.h"
#include "openmc/random_lcg.h"
#include "openmc/settings.h"
#include "openmc/simulation.h"
#include "openmc/string_utils.h"
#include "openmc/summary.h"
#include "openmc/thermal.h"
#include "openmc/timer.h"
// data/functions from Fortran side
extern "C" void print_usage();
extern "C" void print_version();
extern "C" void read_command_line();
extern "C" void read_input_xml();
extern "C" void read_geometry_xml();
extern "C" void read_materials_xml();
extern "C" void read_plots_xml();
extern "C" void read_tallies_xml();
// Paths to various files
extern "C" {
@ -248,4 +259,51 @@ parse_command_line(int argc, char* argv[])
return 0;
}
void read_input_xml()
{
read_settings_xml();
read_cross_sections_xml();
read_materials_xml();
read_geometry_xml();
// Convert user IDs -> indices, assign temperatures
double_2dvec nuc_temps(data::nuclide_map.size());
double_2dvec thermal_temps(data::thermal_scatt_map.size());
finalize_geometry(nuc_temps, thermal_temps);
if (settings::run_mode != RUN_MODE_PLOTTING) {
simulation::time_read_xs.start();
if (settings::run_CE) {
// Read continuous-energy cross sections
read_ce_cross_sections(nuc_temps, thermal_temps);
} else {
// Create material macroscopic data for MGXS
read_mgxs();
create_macro_xs();
}
simulation::time_read_xs.stop();
}
read_tallies_xml();
// Initialize distribcell_filters
prepare_distribcell();
if (settings::run_mode == RUN_MODE_PLOTTING) {
// Read plots.xml if it exists
read_plots_xml();
if (mpi::master && settings::verbosity >= 5) print_plot();
} else {
// Write summary information
if (mpi::master && settings::output_summary) write_summary();
// Warn if overlap checking is on
if (mpi::master && settings::check_overlaps) {
warning("Cell overlap checking is ON.");
}
}
}
} // namespace openmc

View file

@ -15,27 +15,22 @@ module input_xml
use material_header
use mesh_header
use message_passing
use mgxs_data, only: create_macro_xs, read_mgxs
use mgxs_interface
use nuclide_header
use multipole_header
use output, only: title, header
use photon_header
use random_lcg, only: prn
use surface_header
use set_header, only: SetChar
use settings
use stl_vector, only: VectorInt, VectorReal, VectorChar
use string, only: to_lower, to_str, str_to_int, str_to_real, &
starts_with, ends_with, split_string, &
zero_padded, to_c_string
use summary, only: write_summary
use tally
use tally_header, only: openmc_extend_tallies
use tally_derivative_header
use tally_filter_header
use tally_filter
use timer_header, only: time_read_xs
use volume_header
use xml_interface
@ -43,20 +38,11 @@ module input_xml
save
interface
subroutine adjust_indices() bind(C)
end subroutine adjust_indices
subroutine assign_temperatures() bind(C)
end subroutine assign_temperatures
subroutine count_cell_instances(univ_indx) bind(C)
import C_INT32_T
integer(C_INT32_T), intent(in), value :: univ_indx
end subroutine count_cell_instances
subroutine prepare_distribcell() bind(C)
end subroutine prepare_distribcell
subroutine read_surfaces(node_ptr) bind(C)
import C_PTR
type(C_PTR) :: node_ptr
@ -67,9 +53,6 @@ module input_xml
type(C_PTR) :: node_ptr
end subroutine read_cells
subroutine read_cross_sections_xml() bind(C)
end subroutine
subroutine read_lattices(node_ptr) bind(C)
import C_PTR
type(C_PTR) :: node_ptr
@ -99,9 +82,6 @@ module input_xml
type(C_PTR) :: node_ptr
end subroutine read_plots
subroutine print_plot() bind(C)
end subroutine print_plot
subroutine set_particle_energy_bounds(particle, E_min, E_max) bind(C)
import C_INT, C_DOUBLE
integer(C_INT), value :: particle
@ -112,86 +92,6 @@ module input_xml
contains
!===============================================================================
! READ_INPUT_XML calls each of the separate subroutines for reading settings,
! geometry, materials, and tallies.
!===============================================================================
subroutine read_input_xml() bind(C)
type(VectorReal), allocatable :: nuc_temps(:) ! List of T to read for each nuclide
type(VectorReal), allocatable :: sab_temps(:) ! List of T to read for each S(a,b)
call read_settings_xml()
call read_cross_sections_xml()
call read_materials_xml()
call read_geometry_xml()
! Convert user IDs -> indices, assign temperatures
call finalize_geometry(nuc_temps, sab_temps)
if (run_mode /= MODE_PLOTTING) then
call time_read_xs % start()
if (run_CE) then
! Read continuous-energy cross sections
call read_ce_cross_sections(nuc_temps, sab_temps)
else
! Create material macroscopic data for MGXS
call read_mgxs()
call create_macro_xs()
end if
call time_read_xs % stop()
end if
call read_tallies_xml()
! Initialize distribcell_filters
call prepare_distribcell()
if (run_mode == MODE_PLOTTING) then
! Read plots.xml if it exists
call read_plots_xml()
if (master .and. verbosity >= 5) call print_plot()
else
! Normalize atom/weight percents
call normalize_ao()
! Write summary information
if (master .and. output_summary) call write_summary()
! Warn if overlap checking is on
if (master .and. check_overlaps) &
call warning("Cell overlap checking is ON.")
end if
end subroutine read_input_xml
subroutine finalize_geometry(nuc_temps, sab_temps)
type(VectorReal), allocatable, intent(out) :: nuc_temps(:)
type(VectorReal), optional, allocatable, intent(out) :: sab_temps(:)
! Perform some final operations to set up the geometry
call adjust_indices()
call count_cell_instances(root_universe)
! Assign temperatures to cells that don't have temperatures already assigned
call assign_temperatures()
! Determine desired temperatures for each nuclide and S(a,b) table
call get_temperatures(nuc_temps, sab_temps)
! Check to make sure there are not too many nested coordinate levels in the
! geometry since the coordinate list is statically allocated for performance
! reasons
if (maximum_levels(root_universe) > MAX_COORD) then
call fatal_error("Too many nested coordinate levels in the geometry. &
&Try increasing the maximum number of coordinate levels by &
&providing the CMake -Dmaxcoord= option.")
end if
end subroutine finalize_geometry
!===============================================================================
! READ_SETTINGS_XML reads data from a settings.xml file and parses it, checking
! for errors and placing properly-formatted data in the right data structures
@ -280,7 +180,7 @@ contains
! for errors and placing properly-formatted data in the right data structures
!===============================================================================
subroutine read_geometry_xml()
subroutine read_geometry_xml() bind(C)
integer :: i, n
integer :: univ_id
@ -482,417 +382,56 @@ contains
end do
end subroutine allocate_cells
subroutine read_materials_xml()
integer :: i ! loop index for materials
integer :: j ! loop index for nuclides
integer :: k ! loop index
integer :: n ! number of nuclides
integer :: n_sab ! number of sab tables for a material
integer :: index_nuclide ! index in nuclides
integer :: index_element ! index in elements
integer :: index_sab ! index in sab_tables
subroutine read_materials_xml() bind(C)
logical :: file_exists ! does materials.xml exist?
character(20) :: name ! name of nuclide, e.g. U235
character(3) :: element ! name of element, e.g. Zr
character(MAX_WORD_LEN) :: units ! units on density
character(MAX_LINE_LEN) :: filename ! absolute path to materials.xml
character(MAX_WORD_LEN), allocatable :: sarray(:)
real(8) :: val ! value entered for density
real(8) :: temp_dble ! temporary double prec. real
logical :: sum_density ! density is sum of nuclide densities
type(VectorChar) :: names ! temporary list of nuclide names
type(VectorChar) :: list_iso_lab ! temporary list of isotropic lab scatterers
type(VectorReal) :: densities ! temporary list of nuclide densities
type(Material), pointer :: mat => null()
type(XMLDocument) :: doc
type(XMLNode) :: root
type(XMLNode) :: node_mat
type(XMLNode) :: node_dens
type(XMLNode) :: node_nuc
type(XMLNode) :: node_sab
type(XMLNode), allocatable :: node_mat_list(:)
type(XMLNode), allocatable :: node_nuc_list(:)
type(XMLNode), allocatable :: node_ele_list(:)
type(XMLNode), allocatable :: node_macro_list(:)
type(XMLNode), allocatable :: node_sab_list(:)
interface
function nuclides_size() bind(C) result(n)
import C_INT
integer(C_INT) :: n
end function
function elements_size() bind(C) result(n)
import C_INT
integer(C_INT) :: n
end function
end interface
! Display output message
call write_message("Reading materials XML file...", 5)
! Check if materials.xml exists
filename = trim(path_input) // "materials.xml"
inquire(FILE=filename, EXIST=file_exists)
if (.not. file_exists) then
call fatal_error("Material XML file '" // trim(filename) // "' does not &
&exist!")
doc % ptr = C_NULL_PTR
#ifdef DAGMC
if (dagmc) then
doc % ptr = read_uwuw_materials()
end if
#endif
if (.not. c_associated(doc % ptr)) then
! Check if materials.xml exists
filename = trim(path_input) // "materials.xml"
inquire(FILE=filename, EXIST=file_exists)
if (.not. file_exists) then
call fatal_error("Material XML file '" // trim(filename) // "' does not &
&exist!")
end if
! Parse materials.xml file
call doc % load_file(filename)
root = doc % document_element()
end if
root = doc % document_element()
call read_materials(root % ptr)
! Get pointer to list of XML <material>
call get_node_list(root, "material", node_mat_list)
! Allocate materials array
n_materials = size(node_mat_list)
allocate(materials(n_materials))
! Initialize count for number of nuclides/S(a,b) tables
index_nuclide = 0
index_element = 0
index_sab = 0
do i = 1, n_materials
mat => materials(i)
mat % ptr = material_pointer(i - 1)
! Get pointer to i-th material node
node_mat = node_mat_list(i)
! Check if material is depletable
if (check_for_node(node_mat, "depletable")) then
call get_node_value(node_mat, "depletable", mat % depletable)
end if
! Copy material name
if (check_for_node(node_mat, "name")) then
call get_node_value(node_mat, "name", mat % name)
end if
! Get pointer to density element
if (check_for_node(node_mat, "density")) then
node_dens = node_mat % child("density")
else
call fatal_error("Must specify density element in material " &
// trim(to_str(mat % id())))
end if
! Copy units
call get_node_value(node_dens, "units", units)
! If the units is 'sum', then the total density of the material is taken
! to be the sum of the atom fractions listed on the nuclides
if (units == 'sum') then
sum_density = .true.
else if (units == 'macro') then
if (check_for_node(node_dens, "value")) then
call get_node_value(node_dens, "value", val)
else
val = ONE
end if
! Set density
mat % density = val
sum_density = .false.
else
call get_node_value(node_dens, "value", val)
! Check for erroneous density
sum_density = .false.
if (val <= ZERO) then
call fatal_error("Need to specify a positive density on material " &
// trim(to_str(mat % id())) // ".")
end if
! Adjust material density based on specified units
select case(to_lower(units))
case ('g/cc', 'g/cm3')
mat % density = -val
case ('kg/m3')
mat % density = -0.001_8 * val
case ('atom/b-cm')
mat % density = val
case ('atom/cm3', 'atom/cc')
mat % density = 1.0e-24_8 * val
case default
call fatal_error("Unkwown units '" // trim(units) &
// "' specified on material " // trim(to_str(mat % id())))
end select
end if
! Issue error if elements are provided
call get_node_list(node_mat, "element", node_ele_list)
if (size(node_ele_list) > 0) then
call fatal_error("Unable to add an element to material " &
// trim(to_str(mat % id())) // " since the element option has &
&been removed from the xml input. Elements can only be added via &
&the Python API, which will expand elements into their natural &
&nuclides.")
end if
! =======================================================================
! READ AND PARSE <nuclide> TAGS
! Check to ensure material has at least one nuclide
if (.not. check_for_node(node_mat, "nuclide") .and. &
.not. check_for_node(node_mat, "macroscopic")) then
call fatal_error("No macroscopic data or nuclides specified on &
&material " // trim(to_str(mat % id())))
end if
! Create list of macroscopic x/s based on those specified, just treat
! them as nuclides. This is all really a facade so the user thinks they
! are entering in macroscopic data but the code treats them the same
! as nuclides internally.
! Get pointer list of XML <macroscopic>
call get_node_list(node_mat, "macroscopic", node_macro_list)
if (run_CE .and. (size(node_macro_list) > 0)) then
call fatal_error("Macroscopic can not be used in continuous-energy&
& mode!")
else if (size(node_macro_list) > 1) then
call fatal_error("Only one macroscopic object permitted per material, " &
// trim(to_str(mat % id())))
else if (size(node_macro_list) == 1) then
node_nuc = node_macro_list(1)
! Check for empty name on nuclide
if (.not. check_for_node(node_nuc, "name")) then
call fatal_error("No name specified on macroscopic data in material " &
// trim(to_str(mat % id())))
end if
! store nuclide name
call get_node_value(node_nuc, "name", name)
name = trim(name)
! save name to list
call names % push_back(name)
! Set density for macroscopic data
if (units == 'macro') then
call densities % push_back(ONE)
else
call fatal_error("Units can only be macro for macroscopic data " &
// trim(name))
end if
else
! Get pointer list of XML <nuclide>
call get_node_list(node_mat, "nuclide", node_nuc_list)
! Create list of nuclides based on those specified
INDIVIDUAL_NUCLIDES: do j = 1, size(node_nuc_list)
! Combine nuclide identifier and cross section and copy into names
node_nuc = node_nuc_list(j)
! Check for empty name on nuclide
if (.not. check_for_node(node_nuc, "name")) then
call fatal_error("No name specified on nuclide in material " &
// trim(to_str(mat % id())))
end if
! store nuclide name
call get_node_value(node_nuc, "name", name)
name = trim(name)
! save name to list
call names % push_back(name)
! Check if no atom/weight percents were specified or if both atom and
! weight percents were specified
if (units == 'macro') then
call densities % push_back(ONE)
else
if (.not. check_for_node(node_nuc, "ao") .and. &
.not. check_for_node(node_nuc, "wo")) then
call fatal_error("No atom or weight percent specified for &
&nuclide" // trim(name))
elseif (check_for_node(node_nuc, "ao") .and. &
check_for_node(node_nuc, "wo")) then
call fatal_error("Cannot specify both atom and weight percents &
&for a nuclide: " // trim(name))
end if
! Copy atom/weight percents
if (check_for_node(node_nuc, "ao")) then
call get_node_value(node_nuc, "ao", temp_dble)
call densities % push_back(temp_dble)
else
call get_node_value(node_nuc, "wo", temp_dble)
call densities % push_back(-temp_dble)
end if
end if
end do INDIVIDUAL_NUCLIDES
end if
! =======================================================================
! READ AND PARSE <isotropic> element
if (check_for_node(node_mat, "isotropic")) then
n = node_word_count(node_mat, "isotropic")
allocate(sarray(n))
call get_node_array(node_mat, "isotropic", sarray)
do j = 1, n
call list_iso_lab % push_back(sarray(j))
end do
deallocate(sarray)
end if
! ========================================================================
! COPY NUCLIDES TO ARRAYS IN MATERIAL
! allocate arrays in Material object
n = names % size()
mat % n_nuclides = n
allocate(mat % names(n))
allocate(mat % nuclide(n))
allocate(mat % element(n))
allocate(mat % atom_density(n))
ALL_NUCLIDES: do j = 1, mat % n_nuclides
! Check that this nuclide is listed in the cross_sections.xml file
name = trim(names % data(j))
if (.not. library_present(LIBRARY_NEUTRON, (to_lower(name)))) then
call fatal_error("Could not find nuclide " // trim(name) &
// " in cross_sections data file!")
end if
! If this nuclide hasn't been encountered yet, we need to add its name
! and alias to the nuclide_dict
if (.not. nuclide_dict % has(to_lower(name))) then
index_nuclide = index_nuclide + 1
mat % nuclide(j) = index_nuclide
call nuclide_dict % set(to_lower(name), index_nuclide)
else
mat % nuclide(j) = nuclide_dict % get(to_lower(name))
end if
! If the corresponding element hasn't been encountered yet and photon
! transport will be used, we need to add its symbol to the element_dict
if (photon_transport) then
element = name(1:scan(name, '0123456789') - 1)
! Make sure photon cross section data is available
if (.not. library_present(LIBRARY_PHOTON, to_lower(element))) then
call fatal_error("Could not find element " // trim(element) &
// " in cross_sections data file!")
end if
if (.not. element_dict % has(element)) then
index_element = index_element + 1
mat % element(j) = index_element
call element_dict % set(element, index_element)
else
mat % element(j) = element_dict % get(element)
end if
end if
! Copy name and atom/weight percent
mat % names(j) = name
mat % atom_density(j) = densities % data(j)
end do ALL_NUCLIDES
if (run_CE) then
! By default, isotropic-in-lab is not used
if (list_iso_lab % size() > 0) then
mat % has_isotropic_nuclides = .true.
allocate(mat % p0(n))
mat % p0(:) = .false.
! Apply isotropic-in-lab treatment to specified nuclides
do j = 1, list_iso_lab % size()
do k = 1, n
if (names % data(k) == list_iso_lab % data(j)) then
mat % p0(k) = .true.
end if
end do
end do
end if
end if
! Check to make sure either all atom percents or all weight percents are
! given
if (.not. (all(mat % atom_density >= ZERO) .or. &
all(mat % atom_density <= ZERO))) then
call fatal_error("Cannot mix atom and weight percents in material " &
// to_str(mat % id()))
end if
! Determine density if it is a sum value
if (sum_density) mat % density = sum(mat % atom_density)
! Clear lists
call names % clear()
call densities % clear()
call list_iso_lab % clear()
! =======================================================================
! READ AND PARSE <sab> TAG FOR S(a,b) DATA
if (run_CE) then
! Get pointer list to XML <sab>
call get_node_list(node_mat, "sab", node_sab_list)
n_sab = size(node_sab_list)
if (n_sab > 0) then
! Set number of S(a,b) tables
mat % n_sab = n_sab
! Allocate names and indices for nuclides and tables -- for now we
! allocate these as the number of S(a,b) tables listed. Since a single
! table might apply to multiple nuclides, they are resized later if a
! table is indeed applied to multiple nuclides.
allocate(mat % sab_names(n_sab))
allocate(mat % i_sab_tables(n_sab))
allocate(mat % sab_fracs(n_sab))
do j = 1, n_sab
! Get pointer to S(a,b) table
node_sab = node_sab_list(j)
! Determine name of S(a,b) table
if (.not. check_for_node(node_sab, "name")) then
call fatal_error("Need to specify <name> for S(a,b) table.")
end if
call get_node_value(node_sab, "name", name)
name = trim(name)
mat % sab_names(j) = name
! Read the fraction of nuclei affected by this S(a,b) table
if (check_for_node(node_sab, "fraction")) then
call get_node_value(node_sab, "fraction", mat % sab_fracs(j))
else
mat % sab_fracs(j) = ONE
end if
! Check that this nuclide is listed in the cross_sections.xml file
if (.not. library_present(LIBRARY_THERMAL, to_lower(name))) then
call fatal_error("Could not find S(a,b) table " // trim(name) &
// " in cross_sections.xml file!")
end if
! If this S(a,b) table hasn't been encountered yet, we need to add its
! name and alias to the sab_dict
if (.not. sab_dict % has(to_lower(name))) then
index_sab = index_sab + 1
mat % i_sab_tables(j) = index_sab
call sab_dict % set(to_lower(name), index_sab)
else
mat % i_sab_tables(j) = sab_dict % get(to_lower(name))
end if
end do
end if
end if
! Add material to dictionary
call material_dict % set(mat % id(), i)
end do
! Set total number of nuclides and S(a,b) tables
n_nuclides = index_nuclide
n_elements = index_element
n_sab_tables = index_sab
! Set total number of nuclides and elements
n_nuclides = nuclides_size()
n_elements = elements_size()
allocate(nuclides(n_nuclides))
! Close materials XML file
call doc % clear()
@ -904,7 +443,7 @@ contains
! for errors and placing properly-formatted data in the right data structures
!===============================================================================
subroutine read_tallies_xml()
subroutine read_tallies_xml() bind(C)
integer :: i ! loop over user-specified tallies
integer :: j ! loop over words
@ -1230,17 +769,18 @@ contains
end if
! If a specific nuclide was specified
word = to_lower(sarray(j))
word = sarray(j)
! Search through nuclides
if (.not. nuclide_dict % has(word)) then
k = nuclide_map_get(to_c_string(word))
if (k == -1) then
call fatal_error("Could not find the nuclide " &
// trim(word) // " specified in tally " &
// trim(to_str(t % id())) // " in any material.")
end if
! Set bin to index in nuclides array
nuclide_bins(j) = nuclide_dict % get(word)
nuclide_bins(j) = k
end do
end if
@ -1410,7 +950,7 @@ contains
! READ_PLOTS_XML reads data from a plots.xml file
!===============================================================================
subroutine read_plots_xml()
subroutine read_plots_xml() bind(C)
logical :: file_exists ! does plots.xml file exist?
character(MAX_LINE_LEN) :: filename ! absolute path to plots.xml
@ -1511,348 +1051,12 @@ contains
call read_mg_cross_sections_header_c(file_id)
! Get the minimum and maximum energies
energy_min(NEUTRON) = energy_bins(num_energy_groups + 1)
energy_max(NEUTRON) = energy_bins(1)
call set_particle_energy_bounds(NEUTRON, energy_min(NEUTRON), &
energy_max(NEUTRON))
call set_particle_energy_bounds(NEUTRON, &
energy_bins(num_energy_groups + 1), energy_bins(1))
! Close MGXS HDF5 file
call file_close(file_id)
end subroutine read_mg_cross_sections_header
!===============================================================================
! NORMALIZE_AO Normalize the nuclide atom percents
!===============================================================================
subroutine normalize_ao()
integer :: i ! index in materials array
integer :: j ! index over nuclides in material
real(8) :: sum_percent ! summation
real(8) :: awr ! atomic weight ratio
real(8) :: x ! atom percent
logical :: percent_in_atom ! nuclides specified in atom percent?
logical :: density_in_atom ! density specified in atom/b-cm?
do i = 1, size(materials)
associate (mat => materials(i))
percent_in_atom = (mat % atom_density(1) > ZERO)
density_in_atom = (mat % density > ZERO)
sum_percent = ZERO
do j = 1, size(mat % nuclide)
! determine atomic weight ratio
if (run_CE) then
awr = nuclides(mat % nuclide(j)) % awr
else
awr = get_awr_c(mat % nuclide(j))
end if
! if given weight percent, convert all values so that they are divided
! by awr. thus, when a sum is done over the values, it's actually
! sum(w/awr)
if (.not. percent_in_atom) then
mat % atom_density(j) = -mat % atom_density(j) / awr
end if
end do
! determine normalized atom percents. if given atom percents, this is
! straightforward. if given weight percents, the value is w/awr and is
! divided by sum(w/awr)
sum_percent = sum(mat % atom_density)
mat % atom_density = mat % atom_density / sum_percent
! Change density in g/cm^3 to atom/b-cm. Since all values are now in
! atom percent, the sum needs to be re-evaluated as 1/sum(x*awr)
if (.not. density_in_atom) then
sum_percent = ZERO
do j = 1, mat % n_nuclides
if (run_CE) then
awr = nuclides(mat % nuclide(j)) % awr
else
awr = get_awr_c(mat % nuclide(j))
end if
x = mat % atom_density(j)
sum_percent = sum_percent + x*awr
end do
sum_percent = ONE / sum_percent
mat%density = -mat % density * N_AVOGADRO &
/ MASS_NEUTRON * sum_percent
end if
! Calculate nuclide atom densities
mat % atom_density = mat % density * mat % atom_density
! Calculate density in g/cm^3.
mat % density_gpcc = ZERO
do j = 1, mat % n_nuclides
if (run_CE) then
awr = nuclides(mat % nuclide(j)) % awr
else
awr = ONE
end if
mat % density_gpcc = mat % density_gpcc &
+ mat % atom_density(j) * awr * MASS_NEUTRON / N_AVOGADRO
end do
end associate
end do
end subroutine normalize_ao
subroutine read_ce_cross_sections(nuc_temps, sab_temps)
type(VectorReal), intent(in) :: nuc_temps(:)
type(VectorReal), intent(in) :: sab_temps(:)
integer :: i, j
integer :: i_nuclide
integer :: i_element
integer :: i_sab
integer(C_INT) :: n
integer(HID_T) :: file_id
integer(HID_T) :: group_id
real(C_DOUBLE) :: dummy
logical :: mp_found ! if windowed multipole libraries were found
character(MAX_WORD_LEN) :: name
character(MAX_FILE_LEN) :: filename
character(3) :: element
type(SetChar) :: already_read
type(SetChar) :: element_already_read
interface
subroutine photon_from_hdf5(group) bind(C)
import HID_T
integer(HID_T), value :: group
end subroutine
subroutine read_ce_cross_sections_c() bind(C)
end subroutine
end interface
allocate(nuclides(n_nuclides))
! Read cross sections
do i = 1, size(materials)
do j = 1, size(materials(i) % names)
name = materials(i) % names(j)
if (.not. already_read % contains(name)) then
filename = library_path(LIBRARY_NEUTRON, to_lower(name))
i_nuclide = nuclide_dict % get(to_lower(name))
call write_message('Reading ' // trim(name) // ' from ' // &
trim(filename), 6)
! Open file and make sure version is sufficient
file_id = file_open(filename, 'r')
call check_data_version(file_id)
! Read nuclide data from HDF5
group_id = open_group(file_id, name)
call nuclides(i_nuclide) % from_hdf5(group_id, nuc_temps(i_nuclide), &
temperature_method, temperature_tolerance, temperature_range, &
master, i_nuclide)
call close_group(group_id)
call file_close(file_id)
! Determine if minimum/maximum energy for this nuclide is greater/less
! than the previous
if (size(nuclides(i_nuclide) % grid) >= 1) then
energy_min(NEUTRON) = max(energy_min(NEUTRON), &
nuclides(i_nuclide) % grid(1) % energy(1))
energy_max(NEUTRON) = min(energy_max(NEUTRON), nuclides(i_nuclide) % &
grid(1) % energy(size(nuclides(i_nuclide) % grid(1) % energy)))
call set_particle_energy_bounds(NEUTRON, energy_min(NEUTRON), &
energy_max(NEUTRON))
end if
! Add name and alias to dictionary
call already_read % add(name)
! Check if elemental data has been read, if needed
element = name(1:scan(name, '0123456789') - 1)
if (photon_transport) then
if (.not. element_already_read % contains(element)) then
! Read photon interaction data from HDF5 photon library
filename = library_path(LIBRARY_PHOTON, to_lower(element))
i_element = element_dict % get(element)
call write_message('Reading ' // trim(element) // ' from ' // &
trim(filename), 6)
! Open file and make sure version is sufficient
file_id = file_open(filename, 'r')
call check_data_version(file_id)
! Read element data from HDF5
group_id = open_group(file_id, element)
call photon_from_hdf5(group_id)
call close_group(group_id)
call file_close(file_id)
! Add element to set
call element_already_read % add(element)
end if
end if
! Read multipole file into the appropriate entry on the nuclides array
if (temperature_multipole) call read_multipole_data(i_nuclide)
end if
! Check if material is fissionable
if (nuclides(materials(i) % nuclide(j)) % fissionable) then
call materials(i) % set_fissionable(.true.)
end if
end do
end do
call read_ce_cross_sections_c()
! Set up logarithmic grid for nuclides
do i = 1, size(nuclides)
call nuclides(i) % init_grid()
end do
log_spacing = log(energy_max(NEUTRON)/energy_min(NEUTRON)) / n_log_bins
do i = 1, size(materials)
! Skip materials with no S(a,b) tables
if (.not. allocated(materials(i) % sab_names)) cycle
do j = 1, size(materials(i) % sab_names)
! Get name of S(a,b) table
name = materials(i) % sab_names(j)
if (.not. already_read % contains(name)) then
filename = library_path(LIBRARY_THERMAL, to_lower(name))
i_sab = sab_dict % get(to_lower(name))
call write_message('Reading ' // trim(name) // ' from ' // &
trim(filename), 6)
! Open file and make sure version matches
file_id = file_open(filename, 'r')
call check_data_version(file_id)
! Read S(a,b) data from HDF5
group_id = open_group(file_id, name)
n = sab_temps(i_sab) % size()
if (n > 0) then
call sab_from_hdf5(group_id, sab_temps(i_sab) % data(1), n)
else
! In this case, data(1) doesn't exist, so we just pass a dummy value
call sab_from_hdf5(group_id, dummy, n)
end if
call close_group(group_id)
call file_close(file_id)
! Add name to dictionary
call already_read % add(name)
end if
end do
! Associate S(a,b) tables with specific nuclides
call materials(i) % assign_sab_tables()
end do
! Show which nuclide results in lowest energy for neutron transport
do i = 1, size(nuclides)
! If a nuclide is present in a material that's not used in the model, its
! grid has not been allocated
if (size(nuclides(i) % grid) > 0) then
if (nuclides(i) % grid(1) % energy(size(nuclides(i) % grid(1) % energy)) &
== energy_max(NEUTRON)) then
call write_message("Maximum neutron transport energy: " // &
trim(to_str(energy_max(NEUTRON))) // " eV for " // &
trim(adjustl(nuclides(i) % name)), 7)
if (master .and. energy_max(NEUTRON) < 20.e6) call warning("Maximum &
&neutron energy is below 20 MeV. This may bias the results.")
exit
end if
end if
end do
! If the user wants multipole, make sure we found a multipole library.
if (temperature_multipole) then
mp_found = .false.
do i = 1, size(nuclides)
if (nuclides(i) % mp_present) then
mp_found = .true.
exit
end if
end do
if (master .and. .not. mp_found) call warning("Windowed multipole &
&functionality is turned on, but no multipole libraries were found. &
&Make sure that windowed multipole data is present in your &
&cross_sections.xml file.")
end if
call already_read % clear()
call element_already_read % clear()
end subroutine read_ce_cross_sections
!===============================================================================
! READ_MULTIPOLE_DATA checks for the existence of a multipole library in the
! directory and loads it using multipole_read
!===============================================================================
subroutine read_multipole_data(i_table)
integer, intent(in) :: i_table ! index in nuclides/sab_tables
logical :: file_exists ! Does multipole library exist?
character(7) :: readable ! Is multipole library readable?
character(MAX_FILE_LEN) :: filename ! Path to multipole xs library
integer(HID_T) :: file_id
integer(HID_T) :: group_id
interface
subroutine nuclide_load_multipole(ptr, group) bind(C)
import C_PTR, HID_T
type(C_PTR), value :: ptr
integer(HID_T), value :: group
end subroutine
end interface
associate (nuc => nuclides(i_table))
! Look for WMP data in cross_sections.xml
if (library_present(LIBRARY_WMP, to_lower(nuc % name))) then
filename = library_path(LIBRARY_WMP, to_lower(nuc % name))
else
nuc % mp_present = .false.
return
end if
! Check if Multipole library exists and is readable
inquire(FILE=filename, EXIST=file_exists, READ=readable)
if (.not. file_exists) then
nuc % mp_present = .false.
return
elseif (readable(1:3) == 'NO') then
call fatal_error("Multipole library '" // trim(filename) // "' is not &
&readable! Change file permissions with chmod command.")
end if
! Display message
call write_message("Reading " // trim(nuc % name) // " WMP data from " &
// filename, 6)
! Open file and make sure version is sufficient
file_id = file_open(filename, 'r')
call check_wmp_version(file_id)
! Read nuclide data from HDF5
group_id = open_group(file_id, nuc % name)
nuc % mp_present = .true.
call nuclide_load_multipole(nuc % ptr, group_id)
call close_group(group_id)
! Close the file
call file_close(file_id)
end associate
end subroutine read_multipole_data
end module input_xml

View file

@ -1,311 +0,0 @@
module list_header
!===============================================================================
! LIST_HEADER module
!
! This module contains a linked list structure with convenience methods such as
! append, contains, remove, index, get_item, size, etc. This is an updated
! implementation with type-bound procedures (F2003).
!===============================================================================
use constants, only: ERROR_INT, ERROR_REAL, MAX_WORD_LEN
implicit none
!===============================================================================
! LISTELEM* types hold one piece of data and a pointer to the next piece of data
!===============================================================================
type :: ListElemChar
character(MAX_WORD_LEN) :: data
type(ListElemChar), pointer :: next => null()
type(ListElemChar), pointer :: prev => null()
end type ListElemChar
!===============================================================================
! LIST* types contain the linked list with convenience methods. We originally
! considered using unlimited polymorphism to provide a single type, but compiler
! support is still spotty, and in many cases it doesn't prevent duplication of
! code. For the time being, a separate derived type exists for each datatype.
!===============================================================================
type, public :: ListChar
private
integer :: count = 0 ! Number of elements in list
! Used in get_item for fast sequential lookups
integer :: last_index = huge(0)
type(ListElemChar), pointer :: last_elem => null()
! Pointers to beginning and end of list
type(ListElemChar), public, pointer :: head => null()
type(ListElemChar), public, pointer :: tail => null()
contains
procedure :: append => list_append_char ! Add item to end of list
procedure :: clear => list_clear_char ! Remove all items
procedure :: contains => list_contains_char ! Does list contain?
procedure :: get_item => list_get_item_char ! Get i-th item in list
procedure :: index => list_index_char ! Determine index of given item
procedure :: insert => list_insert_char ! Insert item in i-th position
procedure :: remove => list_remove_char ! Remove specified item
procedure :: size => list_size_char ! Size of list
end type ListChar
contains
!===============================================================================
! LIST_APPEND appends an item to the end of the list. If the list is empty, it
! becomes the first item.
!===============================================================================
subroutine list_append_char(this, data)
class(ListChar) :: this
character(*) :: data
type(ListElemChar), pointer :: elem
! Create element and set dat
allocate(elem)
elem % data = data
if (.not. associated(this % head)) then
! If list is empty, set head and tail to new element
this % head => elem
this % tail => elem
else
! Otherwise append element at end of list
this % tail % next => elem
elem % prev => this % tail
this % tail => this % tail % next
end if
this % count = this % count + 1
end subroutine list_append_char
!===============================================================================
! LIST_CLEAR removes all elements from the list
!===============================================================================
subroutine list_clear_char(this)
class(ListChar) :: this
type(ListElemChar), pointer :: current => null()
type(ListElemChar), pointer :: next => null()
if (this % count > 0) then
current => this % head
do while (associated(current))
! Set pointer to next element
next => current % next
! Deallocate memory for current element
deallocate(current)
! Move to next element
current => next
end do
nullify(this % head)
nullify(this % tail)
this % count = 0
end if
end subroutine list_clear_char
!===============================================================================
! LIST_CONTAINS determines whether the list contains a specified item. Since it
! relies on the index method, it is O(n).
!===============================================================================
function list_contains_char(this, data) result(in_list)
class(ListChar) :: this
character(*) :: data
logical :: in_list
in_list = (this % index(data) > 0)
end function list_contains_char
!===============================================================================
! LIST_GET_ITEM returns the item in the list at position 'i_list'. If the index
! is out of bounds, an error code is returned.
! ===============================================================================
function list_get_item_char(this, i_list) result(data)
class(ListChar) :: this
integer :: i_list
character(MAX_WORD_LEN) :: data
integer :: last_index
if (i_list < 1 .or. i_list > this % count) then
! Check for index out of bounds
data = ""
elseif (i_list == 1) then
data = this % head % data
this % last_index = 1
this % last_elem => this % head
elseif (i_list == this % count) then
data = this % tail % data
this % last_index = this % count
this % last_elem => this % tail
else
if (i_list < this % last_index) then
this % last_index = 1
this % last_elem => this % head
end if
do last_index = this % last_index + 1, i_list
this % last_elem => this % last_elem % next
this % last_index = last_index
end do
data = this % last_elem % data
end if
end function list_get_item_char
!===============================================================================
! LIST_INDEX determines the first index in the list that contains 'data'. If
! 'data' is not present in the list, the return value is -1.
!===============================================================================
function list_index_char(this, data) result(i_list)
class(ListChar) :: this
character(*) :: data
integer :: i_list
type(ListElemChar), pointer :: elem
i_list = 0
elem => this % head
do while (associated(elem))
i_list = i_list + 1
if (data == elem % data) exit
elem => elem % next
end do
! Check if we reached the end of the list
if (.not. associated(elem)) i_list = -1
end function list_index_char
!===============================================================================
! LIST_INSERT inserts 'data' at index 'i_list' within the list. If 'i_list'
! exceeds the size of the list, the data is appends at the end of the list.
!===============================================================================
subroutine list_insert_char(this, i_list, data)
class(ListChar) :: this
integer :: i_list
character(*) :: data
integer :: i
type(ListElemChar), pointer :: elem => null()
type(ListElemChar), pointer :: new_elem => null()
if (i_list > this % count) then
! Check whether specified index is greater than number of elements -- if
! so, just append it to the end of the list
call this % append(data)
else if (i_list == 1) then
! Check for new head element
allocate(new_elem)
new_elem % data = data
new_elem % next => this % head
this % head => new_elem
this % count = this % count + 1
else
! Default case with new element somewhere in middle of list
if (i_list >= this % last_index) then
i = this % last_index
elem => this % last_elem
else
i = 0
elem => this % head
end if
do while (associated(elem))
if (i == i_list) then
! Allocate new element
allocate(new_elem)
new_elem % data = data
! Put it before the i-th element
new_elem % prev => elem % prev
new_elem % next => elem
new_elem % prev % next => new_elem
new_elem % next % prev => new_elem
this % count = this % count + 1
this % last_index = i_list
this % last_elem => new_elem
exit
end if
i = i + 1
elem => elem % next
end do
end if
end subroutine list_insert_char
!===============================================================================
! LIST_REMOVE removes the first item in the list that contains 'data'. If 'data'
! is not in the list, no action is taken.
!===============================================================================
subroutine list_remove_char(this, data)
class(ListChar) :: this
character(*) :: data
type(ListElemChar), pointer :: elem => null()
elem => this % head
do while (associated(elem))
! Check for matching data
if (elem % data == data) then
! Determine whether the current element is the head, tail, or a middle
! element
if (associated(elem, this % head)) then
this % head => elem % next
if (associated(elem, this % tail)) nullify(this % tail)
if (associated(this % head)) nullify(this % head % prev)
deallocate(elem)
else if (associated(elem, this % tail)) then
this % tail => elem % prev
deallocate(this % tail % next)
else
elem % prev % next => elem % next
elem % next % prev => elem % prev
deallocate(elem)
end if
! Decrease count and exit
this % count = this % count - 1
exit
end if
! Advance pointers
elem => elem % next
end do
end subroutine list_remove_char
!===============================================================================
! LIST_SIZE returns the number of elements in the list
!===============================================================================
function list_size_char(this) result(size)
class(ListChar) :: this
integer :: size
size = this % count
end function list_size_char
end module list_header

View file

@ -1,7 +1,8 @@
#include "openmc/material.h"
#include <algorithm> // for min, max
#include <algorithm> // for min, max, sort, fill
#include <cmath>
#include <iterator>
#include <string>
#include <sstream>
@ -9,12 +10,21 @@
#include "xtensor/xoperation.hpp"
#include "xtensor/xview.hpp"
#include "openmc/capi.h"
#include "openmc/cross_sections.h"
#include "openmc/container_util.h"
#include "openmc/error.h"
#include "openmc/hdf5_interface.h"
#include "openmc/math_functions.h"
#include "openmc/message_passing.h"
#include "openmc/mgxs_interface.h"
#include "openmc/nuclide.h"
#include "openmc/photon.h"
#include "openmc/search.h"
#include "openmc/settings.h"
#include "openmc/simulation.h"
#include "openmc/string_utils.h"
#include "openmc/thermal.h"
#include "openmc/xml_interface.h"
namespace openmc {
@ -42,6 +52,230 @@ Material::Material(pugi::xml_node node)
fatal_error("Must specify id of material in materials XML file.");
}
if (check_for_node(node, "name")) {
name_ = get_node_value(node, "name");
}
if (check_for_node(node, "depletable")) {
depletable_ = get_node_value_bool(node, "depletable");
}
bool sum_density {false};
pugi::xml_node density_node = node.child("density");
std::string units;
if (density_node) {
units = get_node_value(density_node, "units");
if (units == "sum") {
sum_density = true;
} else if (units == "macro") {
if (check_for_node(density_node, "value")) {
density_ = std::stod(get_node_value(density_node, "value"));
} else {
density_ = 1.0;
}
} else {
double val = std::stod(get_node_value(density_node, "value"));
if (val <= 0.0) {
fatal_error("Need to specify a positive density on material "
+ std::to_string(id_) + ".");
}
if (units == "g/cc" || units == "g/cm3") {
density_ = -val;
} else if (units == "kg/m3") {
density_ = -1.0e-3 * val;
} else if (units == "atom/b-cm") {
density_ = val;
} else if (units == "atom/cc" || units == "atom/cm3") {
density_ = 1.0e-24 * val;
} else {
fatal_error("Unknown units '" + units + "' specified on material "
+ std::to_string(id_) + ".");
}
}
} else {
fatal_error("Must specify <density> element in material "
+ std::to_string(id_) + ".");
}
if (node.child("element")) {
fatal_error("Unable to add an element to material " + std::to_string(id_) +
" since the element option has been removed from the xml input. "
"Elements can only be added via the Python API, which will expand "
"elements into their natural nuclides.");
}
// =======================================================================
// READ AND PARSE <nuclide> TAGS
// Check to ensure material has at least one nuclide
if (!check_for_node(node, "nuclide") && !check_for_node(node, "macroscopic")) {
fatal_error("No macroscopic data or nuclides specified on material "
+ std::to_string(id_));
}
// Create list of macroscopic x/s based on those specified, just treat
// them as nuclides. This is all really a facade so the user thinks they
// are entering in macroscopic data but the code treats them the same
// as nuclides internally.
// Get pointer list of XML <macroscopic>
auto node_macros = node.children("macroscopic");
int num_macros = std::distance(node_macros.begin(), node_macros.end());
std::vector<std::string> names;
std::vector<double> densities;
if (settings::run_CE && num_macros > 0) {
fatal_error("Macroscopic can not be used in continuous-energy mode.");
} else if (num_macros > 1) {
fatal_error("Only one macroscopic object permitted per material, "
+ std::to_string(id_));
} else if (num_macros == 1) {
pugi::xml_node node_nuc = *node_macros.begin();
// Check for empty name on nuclide
if (!check_for_node(node_nuc, "name")) {
fatal_error("No name specified on macroscopic data in material "
+ std::to_string(id_));
}
// store nuclide name
std::string name = get_node_value(node_nuc, "name", false, true);
names.push_back(name);
// Set density for macroscopic data
if (units == "macro") {
densities.push_back(1.0);
} else {
fatal_error("Units can only be macro for macroscopic data " + name);
}
} else {
// Create list of nuclides based on those specified
for (auto node_nuc : node.children("nuclide")) {
// Check for empty name on nuclide
if (!check_for_node(node_nuc, "name")) {
fatal_error("No name specified on nuclide in material "
+ std::to_string(id_));
}
// store nuclide name
std::string name = get_node_value(node_nuc, "name", false, true);
names.push_back(name);
// Check if no atom/weight percents were specified or if both atom and
// weight percents were specified
if (units == "macro") {
densities.push_back(1.0);
} else {
bool has_ao = check_for_node(node_nuc, "ao");
bool has_wo = check_for_node(node_nuc, "wo");
if (!has_ao && !has_wo) {
fatal_error("No atom or weight percent specified for nuclide: " + name);
} else if (has_ao && has_wo) {
fatal_error("Cannot specify both atom and weight percents for a "
"nuclide: " + name);
}
// Copy atom/weight percents
if (has_ao) {
densities.push_back(std::stod(get_node_value(node_nuc, "ao")));
} else {
densities.push_back(-std::stod(get_node_value(node_nuc, "wo")));
}
}
}
}
// =======================================================================
// READ AND PARSE <isotropic> element
std::vector<std::string> iso_lab;
if (check_for_node(node, "isotropic")) {
iso_lab = get_node_array<std::string>(node, "isotropic");
}
// ========================================================================
// COPY NUCLIDES TO ARRAYS IN MATERIAL
// allocate arrays in Material object
auto n = names.size();
nuclide_.reserve(n);
atom_density_ = xt::empty<double>({n});
if (settings::photon_transport) element_.reserve(n);
for (int i = 0; i < n; ++i) {
const auto& name {names[i]};
// Check that this nuclide is listed in the cross_sections.xml file
LibraryKey key {Library::Type::neutron, name};
if (data::library_map.find(key) == data::library_map.end()) {
fatal_error("Could not find nuclide " + name + " in cross_sections.xml.");
}
// If this nuclide hasn't been encountered yet, we need to add its name
// and alias to the nuclide_dict
if (data::nuclide_map.find(name) == data::nuclide_map.end()) {
int index = data::nuclide_map.size();
data::nuclide_map[name] = index;
nuclide_.push_back(index);
} else {
nuclide_.push_back(data::nuclide_map[name]);
}
// If the corresponding element hasn't been encountered yet and photon
// transport will be used, we need to add its symbol to the element_dict
if (settings::photon_transport) {
int pos = name.find_first_of("0123456789");
std::string element = name.substr(0, pos);
// Make sure photon cross section data is available
LibraryKey key {Library::Type::photon, element};
if (data::library_map.find(key) == data::library_map.end()) {
fatal_error("Could not find element " + element
+ " in cross_sections.xml.");
}
if (data::element_map.find(element) == data::element_map.end()) {
int index = data::element_map.size();
data::element_map[element] = index;
element_.push_back(index);
} else {
element_.push_back(data::element_map[element]);
}
}
// Copy atom/weight percent
atom_density_(i) = densities[i];
}
if (settings::run_CE) {
// By default, isotropic-in-lab is not used
if (iso_lab.size() > 0) {
p0_.resize(n);
// Apply isotropic-in-lab treatment to specified nuclides
for (int j = 0; j < n; ++j) {
for (const auto& nuc : iso_lab) {
if (names[j] == nuc) {
p0_[j] = true;
break;
}
}
}
}
}
// Check to make sure either all atom percents or all weight percents are
// given
if (!(xt::all(atom_density_ >= 0.0) || xt::all(atom_density_ <= 0.0))) {
fatal_error("Cannot mix atom and weight percents in material "
+ std::to_string(id_));
}
// Determine density if it is a sum value
if (sum_density) density_ = xt::sum(atom_density_)();
if (check_for_node(node, "temperature")) {
temperature_ = std::stod(get_node_value(node, "temperature"));
}
@ -49,6 +283,170 @@ Material::Material(pugi::xml_node node)
if (check_for_node(node, "volume")) {
volume_ = std::stod(get_node_value(node, "volume"));
}
// =======================================================================
// READ AND PARSE <sab> TAG FOR THERMAL SCATTERING DATA
if (settings::run_CE) {
// Loop over <sab> elements
std::vector<std::string> sab_names;
for (auto node_sab : node.children("sab")) {
// Determine name of thermal scattering table
if (!check_for_node(node_sab, "name")) {
fatal_error("Need to specify <name> for thermal scattering table.");
}
std::string name = get_node_value(node_sab, "name");
sab_names.push_back(name);
// Read the fraction of nuclei affected by this thermal scattering table
double fraction = 1.0;
if (check_for_node(node_sab, "fraction")) {
fraction = std::stod(get_node_value(node_sab, "fraction"));
}
// Check that the thermal scattering table is listed in the
// cross_sections.xml file
LibraryKey key {Library::Type::thermal, name};
if (data::library_map.find(key) == data::library_map.end()) {
fatal_error("Could not find thermal scattering data " + name +
" in cross_sections.xml file.");
}
// Determine index of thermal scattering data in global
// data::thermal_scatt array
int index_table;
if (data::thermal_scatt_map.find(name) == data::thermal_scatt_map.end()) {
index_table = data::thermal_scatt_map.size();
data::thermal_scatt_map[name] = index_table;
} else {
index_table = data::thermal_scatt_map[name];
}
// Add entry to thermal tables vector. For now, we put the nuclide index
// as zero since we don't know which nuclides the table is being applied
// to yet (this is assigned in init_thermal)
thermal_tables_.push_back({index_table, 0, fraction});
}
}
}
void Material::finalize()
{
// Set fissionable if any nuclide is fissionable
for (const auto& i_nuc : nuclide_) {
if (data::nuclides[i_nuc]->fissionable_) {
fissionable_ = true;
break;
}
}
// Generate material bremsstrahlung data for electrons and positrons
if (settings::photon_transport && settings::electron_treatment == ELECTRON_TTB) {
this->init_bremsstrahlung();
}
// Assign thermal scattering tables
this->init_thermal();
// Normalize density
this->normalize_density();
}
void Material::normalize_density()
{
bool percent_in_atom = (atom_density_(0) > 0.0);
bool density_in_atom = (density_ > 0.0);
for (int i = 0; i < nuclide_.size(); ++i) {
// determine atomic weight ratio
int i_nuc = nuclide_[i];
double awr = settings::run_CE ?
data::nuclides[i_nuc]->awr_ : data::nuclides_MG[i_nuc].awr;
// if given weight percent, convert all values so that they are divided
// by awr. thus, when a sum is done over the values, it's actually
// sum(w/awr)
if (!percent_in_atom) atom_density_(i) = -atom_density_(i) / awr;
}
// determine normalized atom percents. if given atom percents, this is
// straightforward. if given weight percents, the value is w/awr and is
// divided by sum(w/awr)
atom_density_ /= xt::sum(atom_density_)();
// Change density in g/cm^3 to atom/b-cm. Since all values are now in
// atom percent, the sum needs to be re-evaluated as 1/sum(x*awr)
if (!density_in_atom) {
double sum_percent = 0.0;
for (int i = 0; i < nuclide_.size(); ++i) {
int i_nuc = nuclide_[i];
double awr = settings::run_CE ?
data::nuclides[i_nuc]->awr_ : data::nuclides_MG[i_nuc].awr;
sum_percent += atom_density_(i)*awr;
}
sum_percent = 1.0 / sum_percent;
density_ = -density_ * N_AVOGADRO / MASS_NEUTRON * sum_percent;
}
// Calculate nuclide atom densities
atom_density_ *= density_;
// Calculate density in g/cm^3.
density_gpcc_ = 0.0;
for (int i = 0; i < nuclide_.size(); ++i) {
int i_nuc = nuclide_[i];
double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_ : 1.0;
density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO;
}
}
void Material::init_thermal()
{
std::vector<ThermalTable> tables;
for (const auto& table : thermal_tables_) {
// In order to know which nuclide the S(a,b) table applies to, we need
// to search through the list of nuclides for one which has a matching
// name
bool found = false;
for (int j = 0; j < nuclide_.size(); ++j) {
const auto& name {data::nuclides[nuclide_[j]]->name_};
if (contains(data::thermal_scatt[table.index_table]->nuclides_, name)) {
tables.push_back({table.index_table, j, table.fraction});
found = true;
}
}
// Check to make sure thermal scattering table matched a nuclide
if (!found) {
fatal_error("Thermal scattering table " + data::thermal_scatt[
table.index_table]->name_ + " did not match any nuclide on material "
+ std::to_string(id_));
}
}
// Make sure each nuclide only appears in one table.
for (int j = 0; j < tables.size(); ++j) {
for (int k = j+1; k < tables.size(); ++k) {
if (tables[j].index_nuclide == tables[k].index_nuclide) {
int index = nuclide_[tables[j].index_nuclide];
auto name = data::nuclides[index]->name_;
fatal_error(name + " in material " + std::to_string(id_) + " was found "
"in multiple thermal scattering tables. Each nuclide can appear in "
"only one table per material.");
}
}
}
// If there are multiple S(a,b) tables, we need to make sure that the
// entries in i_sab_nuclides are sorted or else they won't be applied
// correctly in the cross_section module.
std::sort(tables.begin(), tables.end(), [](ThermalTable a, ThermalTable b) {
return a.index_nuclide < b.index_nuclide;
});
// Update the list of thermal tables
thermal_tables_ = tables;
}
void Material::init_bremsstrahlung()
@ -60,14 +458,8 @@ void Material::init_bremsstrahlung()
auto n_k = data::ttb_k_grid.size();
auto n_e = data::ttb_e_grid.size();
// Get pointers to nuclides, elements, densities
int32_t index;
openmc_get_material_index(id_, &index);
int* nuclide_;
double* atom_density_;
int n;
openmc_material_get_densities(index, &nuclide_, &atom_density_, &n);
int* element_ = material_element(index);
// Determine number of elements
int n = element_.size();
for (int particle = 0; particle < 2; ++particle) {
// Loop over logic twice, once for electron, once for positron
@ -99,10 +491,8 @@ void Material::init_bremsstrahlung()
// fixed in the future.
for (int i = 0; i < n; ++i) {
// Get pointer to current element
// TODO: off-by-one
const auto& elm = data::elements[element_[i] - 1];
// TODO: off-by-one
double awr = data::nuclides[nuclide_[i] - 1]->awr_;
const auto& elm = data::elements[element_[i]];
double awr = data::nuclides[nuclide_[i]]->awr_;
// Get atomic density and mass density of nuclide given atom/weight percent
double atom_density = (atom_density_[0] > 0.0) ?
@ -241,6 +631,236 @@ void Material::init_bremsstrahlung()
}
}
void Material::init_nuclide_index()
{
int n = settings::run_CE ?
data::nuclides.size() : data::nuclides_MG.size();
mat_nuclide_index_.resize(n);
std::fill(mat_nuclide_index_.begin(), mat_nuclide_index_.end(), -1);
for (int i = 0; i < nuclide_.size(); ++i) {
mat_nuclide_index_[nuclide_[i]] = i;
}
}
void Material::calculate_xs(const Particle& p) const
{
// Set all material macroscopic cross sections to zero
simulation::material_xs.total = 0.0;
simulation::material_xs.absorption = 0.0;
simulation::material_xs.fission = 0.0;
simulation::material_xs.nu_fission = 0.0;
if (p.type == static_cast<int>(ParticleType::neutron)) {
this->calculate_neutron_xs(p);
} else if (p.type == static_cast<int>(ParticleType::photon)) {
this->calculate_photon_xs(p);
}
}
void Material::calculate_neutron_xs(const Particle& p) const
{
int neutron = static_cast<int>(ParticleType::neutron);
// Find energy index on energy grid
int i_grid = std::log(p.E/data::energy_min[neutron])/simulation::log_spacing;
// Determine if this material has S(a,b) tables
bool check_sab = (thermal_tables_.size() > 0);
// Initialize position in i_sab_nuclides
int j = 0;
// Add contribution from each nuclide in material
for (int i = 0; i < nuclide_.size(); ++i) {
// ======================================================================
// CHECK FOR S(A,B) TABLE
int i_sab = C_NONE;
double sab_frac = 0.0;
// Check if this nuclide matches one of the S(a,b) tables specified.
// This relies on thermal_tables_ being sorted by .index_nuclide
if (check_sab) {
const auto& sab {thermal_tables_[j]};
if (i == sab.index_nuclide) {
// Get index in sab_tables
i_sab = sab.index_table;
sab_frac = sab.fraction;
// If particle energy is greater than the highest energy for the
// S(a,b) table, then don't use the S(a,b) table
if (p.E > data::thermal_scatt[i_sab]->threshold()) i_sab = C_NONE;
// Increment position in thermal_tables_
++j;
// Don't check for S(a,b) tables if there are no more left
if (j == thermal_tables_.size()) check_sab = false;
}
}
// ======================================================================
// CALCULATE MICROSCOPIC CROSS SECTION
// Determine microscopic cross sections for this nuclide
int i_nuclide = nuclide_[i];
// Calculate microscopic cross section for this nuclide
const auto& micro {simulation::micro_xs[i_nuclide]};
if (p.E != micro.last_E
|| p.sqrtkT != micro.last_sqrtkT
|| i_sab != micro.index_sab
|| sab_frac != micro.sab_frac) {
data::nuclides[i_nuclide]->calculate_xs(i_sab, p.E, i_grid,
p.sqrtkT, sab_frac);
}
// ======================================================================
// ADD TO MACROSCOPIC CROSS SECTION
// Copy atom density of nuclide in material
double atom_density = atom_density_(i);
// Add contributions to cross sections
simulation::material_xs.total += atom_density * micro.total;
simulation::material_xs.absorption += atom_density * micro.absorption;
simulation::material_xs.fission += atom_density * micro.fission;
simulation::material_xs.nu_fission += atom_density * micro.nu_fission;
}
}
void Material::calculate_photon_xs(const Particle& p) const
{
simulation::material_xs.coherent = 0.0;
simulation::material_xs.incoherent = 0.0;
simulation::material_xs.photoelectric = 0.0;
simulation::material_xs.pair_production = 0.0;
// Add contribution from each nuclide in material
for (int i = 0; i < nuclide_.size(); ++i) {
// ========================================================================
// CALCULATE MICROSCOPIC CROSS SECTION
// Determine microscopic cross sections for this nuclide
int i_element = element_[i];
// Calculate microscopic cross section for this nuclide
const auto& micro {simulation::micro_photon_xs[i_element]};
if (p.E != micro.last_E) {
data::elements[i_element].calculate_xs(p.E);
}
// ========================================================================
// ADD TO MACROSCOPIC CROSS SECTION
// Copy atom density of nuclide in material
double atom_density = atom_density_(i);
// Add contributions to material macroscopic cross sections
simulation::material_xs.total += atom_density * micro.total;
simulation::material_xs.coherent += atom_density * micro.coherent;
simulation::material_xs.incoherent += atom_density * micro.incoherent;
simulation::material_xs.photoelectric += atom_density * micro.photoelectric;
simulation::material_xs.pair_production += atom_density * micro.pair_production;
}
}
int Material::set_density(double density, std::string units)
{
if (nuclide_.empty()) {
set_errmsg("No nuclides exist in material yet.");
return OPENMC_E_ALLOCATE;
}
if (units == "atom/b-cm") {
// Set total density based on value provided
density_ = density;
// Determine normalized atom percents
double sum_percent = xt::sum(atom_density_)();
atom_density_ /= sum_percent;
// Recalculate nuclide atom densities based on given density
atom_density_ *= density;
// Calculate density in g/cm^3.
density_gpcc_ = 0.0;
for (int i = 0; i < nuclide_.size(); ++i) {
int i_nuc = nuclide_[i];
double awr = data::nuclides[i_nuc]->awr_;
density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO;
}
} else if (units == "g/cm3" || units == "g/cc") {
// Determine factor by which to change densities
double previous_density_gpcc = density_gpcc_;
double f = density / previous_density_gpcc;
// Update densities
density_gpcc_ = density;
density_ *= f;
atom_density_ *= f;
} else {
set_errmsg("Invalid units '" + units + "' specified.");
return OPENMC_E_INVALID_ARGUMENT;
}
return 0;
}
void Material::to_hdf5(hid_t group) const
{
hid_t material_group = create_group(group, "material " + std::to_string(id_));
write_attribute(material_group, "depletable", static_cast<int>(depletable_));
if (volume_ > 0.0) {
write_attribute(material_group, "volume", volume_);
}
write_dataset(material_group, "name", name_);
write_dataset(material_group, "atom_density", density_);
// Copy nuclide/macro name for each nuclide to vector
std::vector<std::string> nuc_names;
std::vector<std::string> macro_names;
std::vector<double> nuc_densities;
if (settings::run_CE) {
for (int i = 0; i < nuclide_.size(); ++i) {
int i_nuc = nuclide_[i];
nuc_names.push_back(data::nuclides[i_nuc]->name_);
nuc_densities.push_back(atom_density_(i));
}
} else {
for (int i = 0; i < nuclide_.size(); ++i) {
int i_nuc = nuclide_[i];
if (data::nuclides_MG[i_nuc].awr != MACROSCOPIC_AWR) {
nuc_names.push_back(data::nuclides_MG[i_nuc].name);
nuc_densities.push_back(atom_density_(i));
} else {
macro_names.push_back(data::nuclides_MG[i_nuc].name);
}
}
}
// Write vector to 'nuclides'
if (!nuc_names.empty()) {
write_dataset(material_group, "nuclides", nuc_names);
write_dataset(material_group, "nuclide_densities", nuc_densities);
}
// Write vector to 'macroscopics'
if (!macro_names.empty()) {
write_dataset(material_group, "macroscopics", macro_names);
}
if (!thermal_tables_.empty()) {
std::vector<std::string> sab_names;
for (const auto& table : thermal_tables_) {
sab_names.push_back(data::thermal_scatt[table.index_table]->name_);
}
write_dataset(material_group, "sab_names", sab_names);
}
close_group(material_group);
}
//==============================================================================
// Non-method functions
//==============================================================================
@ -268,35 +888,114 @@ read_materials(pugi::xml_node* node)
}
}
extern "C" void read_ce_cross_sections_c()
{
for (auto& mat : model::materials) {
// Generate material bremsstrahlung data for electrons and positrons
if (settings::photon_transport && settings::electron_treatment == ELECTRON_TTB) {
mat->init_bremsstrahlung();
}
}
if (settings::photon_transport && settings::electron_treatment == ELECTRON_TTB) {
// Determine if minimum/maximum energy for bremsstrahlung is greater/less
// than the current minimum/maximum
if (data::ttb_e_grid.size() >= 1) {
// TODO: off-by-one
int photon = static_cast<int>(ParticleType::photon) - 1;
int n_e = data::ttb_e_grid.size();
data::energy_min[photon] = std::max(data::energy_min[photon], data::ttb_e_grid(1));
data::energy_max[photon] = std::min(data::energy_max[photon], data::ttb_e_grid(n_e - 1));
}
// Take logarithm of energies since they are log-log interpolated
data::ttb_e_grid = xt::log(data::ttb_e_grid);
}
}
//==============================================================================
// C API
//==============================================================================
extern "C" int
openmc_get_material_index(int32_t id, int32_t* index)
{
auto it = model::material_map.find(id);
if (it == model::material_map.end()) {
set_errmsg("No material exists with ID=" + std::to_string(id) + ".");
return OPENMC_E_INVALID_ID;
} else {
*index = it->second + 1;
return 0;
}
}
extern "C" int
openmc_material_add_nuclide(int32_t index, const char* name, double density)
{
int err = 0;
if (index >= 1 && index <= model::materials.size()) {
Material* m = model::materials[index - 1];
// Check if nuclide is already in material
for (int i = 0; i < m->nuclide_.size(); ++i) {
int i_nuc = m->nuclide_[i];
if (data::nuclides[i_nuc]->name_ == name) {
double awr = data::nuclides[i_nuc]->awr_;
m->density_ += density - m->atom_density_(i);
m->density_gpcc_ += (density - m->atom_density_(i))
* awr * MASS_NEUTRON / N_AVOGADRO;
m->atom_density_(i) = density;
return 0;
}
}
// If nuclide wasn't found, extend nuclide/density arrays
err = openmc_load_nuclide(name);
if (err == 0) {
// Append new nuclide/density
int i_nuc = data::nuclide_map[name];
m->nuclide_.push_back(i_nuc);
auto n = m->nuclide_.size();
// Create copy of atom_density_ array with one extra entry
xt::xtensor<double, 1> atom_density = xt::zeros<double>({n});
xt::view(atom_density, xt::range(0, n-1)) = m->atom_density_;
atom_density(n) = density;
m->atom_density_ = atom_density;
m->density_ += density;
m->density_gpcc_ += density * data::nuclides[i_nuc]->awr_
* MASS_NEUTRON / N_AVOGADRO;
}
} else {
set_errmsg("Index in materials array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
return err;
}
extern "C" int
openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n)
{
if (index >= 1 && index <= model::materials.size()) {
auto& mat = model::materials[index - 1];
if (!mat->nuclide_.empty()) {
*nuclides = mat->nuclide_.data();
*densities = mat->atom_density_.data();
*n = mat->nuclide_.size();
return 0;
} else {
set_errmsg("Material atom density array has not been allocated.");
return OPENMC_E_ALLOCATE;
}
} else {
set_errmsg("Index in materials array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
}
extern "C" int
openmc_material_get_fissionable(int32_t index, bool* fissionable)
{
if (index >= 1 && index <= model::materials.size()) {
*fissionable = model::materials[index - 1]->fissionable_;
return 0;
} else {
set_errmsg("Index in materials array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
}
extern "C" int
openmc_material_get_id(int32_t index, int32_t* id)
{
if (index >= 1 && index <= model::materials.size()) {
*id = model::materials[index - 1]->id_;
return 0;
} else {
set_errmsg("Index in materials array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
}
extern "C" int
openmc_material_get_volume(int32_t index, double* volume)
{
@ -317,6 +1016,67 @@ openmc_material_get_volume(int32_t index, double* volume)
}
}
extern "C" int
openmc_material_set_density(int32_t index, double density, const char* units)
{
if (index >= 1 && index <= model::materials.size()) {
return model::materials[index - 1]->set_density(density, units);
} else {
set_errmsg("Index in materials array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
}
extern "C" int
openmc_material_set_densities(int32_t index, int n, const char** name, const double* density)
{
if (index >= 1 && index <= model::materials.size()) {
// TODO: off-by-one
auto& mat {model::materials[index - 1]};
if (n != mat->nuclide_.size()) {
mat->nuclide_.resize(n);
mat->atom_density_ = xt::zeros<double>({n});
}
double sum_density = 0.0;
for (int i = 0; i < n; ++i) {
std::string nuc {name[i]};
if (data::nuclide_map.find(nuc) == data::nuclide_map.end()) {
int err = openmc_load_nuclide(nuc.c_str());
if (err < 0) return err;
}
mat->nuclide_[i] = data::nuclide_map[nuc];
mat->atom_density_(i) = density[i];
sum_density += density[i];
}
// Set total density to the sum of the vector
int err = mat->set_density(sum_density, "atom/b-cm");
// Assign S(a,b) tables
mat->init_thermal();
return 0;
} else {
set_errmsg("Index in materials array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
}
extern "C" int
openmc_material_set_id(int32_t index, int32_t id)
{
if (index >= 1 && index <= model::materials.size()) {
model::materials[index - 1]->id_ = id;
model::material_map[id] = index - 1;
return 0;
} else {
set_errmsg("Index in materials array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
}
extern "C" int
openmc_material_set_volume(int32_t index, double volume)
{
@ -335,43 +1095,57 @@ openmc_material_set_volume(int32_t index, double volume)
}
}
extern "C" int
openmc_extend_materials(int32_t n, int32_t* index_start, int32_t* index_end)
{
if (index_start) *index_start = model::materials.size() + 1;
if (index_end) *index_end = model::materials.size() + n;
for (int32_t i = 0; i < n; i++) {
model::materials.push_back(new Material());
}
return 0;
}
//==============================================================================
// Fortran compatibility functions
//==============================================================================
extern "C" {
Material* material_pointer(int32_t indx) {return model::materials[indx];}
size_t n_materials() { return model::materials.size(); }
int32_t material_id(Material* mat) {return mat->id_;}
int32_t material_id(int32_t i_mat) {return model::materials[i_mat - 1]->id_;}
void material_set_id(Material* mat, int32_t id, int32_t index)
int material_nuclide(int32_t i_mat, int idx)
{
mat->id_ = id;
//TODO: off-by-one
model::material_map[id] = index - 1;
return model::materials[i_mat - 1]->nuclide_[idx - 1] + 1;
}
bool material_fissionable(Material* mat) {return mat->fissionable;}
void material_set_fissionable(Material* mat, bool fissionable)
int material_nuclide_size(int32_t i_mat)
{
mat->fissionable = fissionable;
return model::materials[i_mat - 1]->nuclide_.size();
}
void material_init_bremsstrahlung(Material* mat)
double material_atom_density(int32_t i_mat, int idx)
{
mat->init_bremsstrahlung();
return model::materials[i_mat - 1]->atom_density_(idx - 1);
}
void extend_materials_c(int32_t n)
double material_density_gpcc(int32_t i_mat)
{
model::materials.reserve(model::materials.size() + n);
for (int32_t i = 0; i < n; i++) {
model::materials.push_back(new Material());
}
return model::materials[i_mat - 1]->density_gpcc_;
}
void free_memory_material_c()
int material_nuclide_index(int32_t i_mat, int i_nuc)
{
return model::materials[i_mat - 1]->mat_nuclide_index_[i_nuc - 1] + 1;
}
void material_calculate_xs(const Particle* p)
{
model::materials[p->material - 1]->calculate_xs(*p);
}
void free_memory_material()
{
for (Material *mat : model::materials) {delete mat;}
model::materials.clear();

View file

@ -2,860 +2,63 @@ module material_header
use, intrinsic :: ISO_C_BINDING
use constants
use dict_header, only: DictIntInt
use error
use nuclide_header
use particle_header, only: Particle
use photon_header
use sab_header
use simulation_header, only: log_spacing
use stl_vector, only: VectorReal, VectorInt
use string, only: to_str, to_f_string, to_c_string
implicit none
private
public :: free_memory_material
public :: openmc_extend_materials
public :: openmc_get_material_index
public :: openmc_material_add_nuclide
public :: openmc_material_get_id
public :: openmc_material_get_densities
public :: openmc_material_get_volume
public :: openmc_material_set_density
public :: openmc_material_set_densities
public :: openmc_material_set_id
public :: material_pointer
public :: material_calculate_xs
public :: material_id
public :: material_nuclide
public :: material_nuclide_size
public :: material_nuclide_index
public :: material_atom_density
public :: material_density_gpcc
interface
function material_pointer(mat_ind) bind(C) result(ptr)
import C_PTR, C_INT32_T
integer(C_INT32_T), intent(in), value :: mat_ind
type(C_PTR) :: ptr
end function material_pointer
function material_id_c(mat_ptr) bind(C, name='material_id') result(id)
import C_PTR, C_INT32_T
type(C_PTR), intent(in), value :: mat_ptr
integer(C_INT32_T) :: id
end function material_id_c
subroutine material_set_id_c(mat_ptr, id, index) &
bind(C, name='material_set_id')
import C_PTR, C_INT32_T
type(C_PTR), intent(in), value :: mat_ptr
integer(C_INT32_T), intent(in), value :: id
integer(C_INT32_T), intent(in), value :: index
end subroutine material_set_id_c
function material_fissionable_c(mat_ptr) &
bind(C, name='material_fissionable') result(fissionable)
import C_PTR, C_BOOL
type(C_PTR), intent(in), value :: mat_ptr
logical(C_BOOL) :: fissionable
end function material_fissionable_c
subroutine material_set_fissionable_c(mat_ptr, fissionable) &
bind(C, name='material_set_fissionable')
import C_PTR, C_BOOL
type(C_PTR), intent(in), value :: mat_ptr
logical(C_BOOL), intent(in), value :: fissionable
end subroutine material_set_fissionable_c
subroutine extend_materials_c(n) bind(C)
function material_id(i_mat) bind(C) result(id)
import C_INT32_T
integer(C_INT32_T), intent(in), value :: n
end subroutine extend_materials_c
integer(C_INT32_T), value :: i_mat
integer(C_INT32_T) :: id
end function
function openmc_material_get_volume(index, volume) result(err) bind(C)
import C_INT32_T, C_DOUBLE, C_INT
integer(C_INT32_T), value :: index
real(C_DOUBLE), intent(out) :: volume
integer(C_INT) :: err
end function openmc_material_get_volume
subroutine material_calculate_xs(p) bind(C)
import Particle
type(Particle), intent(in) :: p
end subroutine
function material_nuclide(i_mat, idx) bind(C) result(nuc)
import C_INT32_T, C_INT
integer(C_INT32_T), value :: i_mat
integer(C_INT), value :: idx
integer(C_INT) :: nuc
end function
function material_nuclide_size(i_mat) bind(C) result(n)
import C_INT32_T, C_INT
integer(C_INT32_T), value :: i_mat
integer(C_INT) :: n
end function
function material_nuclide_index(i_mat, i_nuc) bind(C) result(idx)
import C_INT32_T, C_INT
integer(C_INT32_T), value :: i_mat
integer(C_INT), value :: i_nuc
integer(C_INT) :: idx
end function
function material_atom_density(i_mat, idx) bind(C) result(density)
import C_INT32_T, C_INT, C_DOUBLE
integer(C_INT32_T), value :: i_mat
integer(C_INT), value :: idx
real(C_DOUBLE) :: density
end function
function material_density_gpcc(i_mat) bind(C) result(density)
import C_INT32_T, C_DOUBLE
integer(C_INT32_T), value :: i_mat
real(C_DOUBLE) :: density
end function
end interface
!===============================================================================
! MATERIAL describes a material by its constituent nuclides
!===============================================================================
type, public :: Material
type(C_PTR) :: ptr
character(len=104) :: name = "" ! User-defined name
integer :: n_nuclides = 0 ! number of nuclides
integer, allocatable :: nuclide(:) ! index in nuclides array
integer, allocatable :: element(:) ! index in elements array
real(8) :: density ! total atom density in atom/b-cm
real(C_DOUBLE), allocatable :: atom_density(:) ! nuclide atom density in atom/b-cm
real(8) :: density_gpcc ! total density in g/cm^3
! To improve performance of tallying, we store an array (direct address
! table) that indicates for each nuclide in the global nuclides(:) array the
! index of the corresponding nuclide in the Material % nuclide(:) array. If
! it is not present in the material, the entry is set to zero.
integer, allocatable :: mat_nuclide_index(:)
! S(a,b) data
integer :: n_sab = 0 ! number of S(a,b) tables
integer, allocatable :: i_sab_nuclides(:) ! index of corresponding nuclide
integer, allocatable :: i_sab_tables(:) ! index in sab_tables
real(8), allocatable :: sab_fracs(:) ! how often to use S(a,b)
! Temporary names read during initialization
character(20), allocatable :: names(:) ! isotope names
character(20), allocatable :: sab_names(:) ! name of S(a,b) table
! Does this material contain fissionable nuclides? Is it depletable?
logical :: depletable = .false.
! enforce isotropic scattering in lab for specific nuclides
logical :: has_isotropic_nuclides = .false.
logical, allocatable :: p0(:)
contains
procedure :: id => material_id
procedure :: set_id => material_set_id
procedure :: fissionable => material_fissionable
procedure :: set_fissionable => material_set_fissionable
procedure :: set_density => material_set_density
procedure :: init_nuclide_index => material_init_nuclide_index
procedure :: assign_sab_tables => material_assign_sab_tables
procedure :: calculate_xs => material_calculate_xs
procedure, private :: calculate_neutron_xs
procedure, private :: calculate_photon_xs
end type Material
integer(C_INT32_T), public, bind(C) :: n_materials ! # of materials
type(Material), public, allocatable, target :: materials(:)
! Dictionary that maps user IDs to indices in 'materials'
type(DictIntInt), public :: material_dict
contains
!===============================================================================
! MATERIAL_SET_DENSITY sets the total density of a material in atom/b-cm.
!===============================================================================
function material_id(this) result(id)
class(Material), intent(in) :: this
integer(C_INT32_T) :: id
id = material_id_c(this % ptr)
end function material_id
subroutine material_set_id(this, id, index)
class(Material), intent(in) :: this
integer(C_INT32_T), intent(in) :: id
integer(C_INT32_T), intent(in) :: index
call material_set_id_c(this % ptr, id, index)
end subroutine material_set_id
function material_fissionable(this) result(fissionable)
class(Material), intent(in) :: this
logical(C_BOOL) :: fissionable
fissionable = material_fissionable_c(this % ptr)
end function material_fissionable
subroutine material_set_fissionable(this, fissionable)
class(Material),intent(in) :: this
logical, intent(in) :: fissionable
call material_set_fissionable_c(this % ptr, logical(fissionable, C_BOOL))
end subroutine material_set_fissionable
function material_set_density(this, density, units) result(err)
class(Material), intent(inout) :: this
real(8), intent(in) :: density
character(*), intent(in) :: units
integer :: err
integer :: i
real(8) :: sum_percent
real(8) :: awr
real(8) :: previous_density_gpcc
real(8) :: f
if (allocated(this % atom_density)) then
err = 0
select case (units)
case ('atom/b-cm')
! Set total density based on value provided
this % density = density
! Determine normalized atom percents
sum_percent = sum(this % atom_density)
this % atom_density(:) = this % atom_density / sum_percent
! Recalculate nuclide atom densities based on given density
this % atom_density(:) = density * this % atom_density
! Calculate density in g/cm^3.
this % density_gpcc = ZERO
do i = 1, this % n_nuclides
awr = nuclides(this % nuclide(i)) % awr
this % density_gpcc = this % density_gpcc &
+ this % atom_density(i) * awr * MASS_NEUTRON / N_AVOGADRO
end do
case ('g/cm3', 'g/cc')
! Determine factor by which to change densities
previous_density_gpcc = this % density_gpcc
f = density / previous_density_gpcc
! Update densities
this % density_gpcc = density
this % density = f * this % density
this % atom_density(:) = f * this % atom_density(:)
case default
err = E_INVALID_ARGUMENT
call set_errmsg("Invalid units '" // trim(units) // "' specified.")
end select
else
err = E_ALLOCATE
call set_errmsg("Material atom density array hasn't been allocated.")
end if
end function material_set_density
!===============================================================================
! INIT_NUCLIDE_INDEX creates a mapping from indices in the global nuclides(:)
! array to the Material % nuclides array
!===============================================================================
subroutine material_init_nuclide_index(this)
class(Material), intent(inout) :: this
integer :: i
! Allocate nuclide index array and set to zeros
if (allocated(this % mat_nuclide_index)) &
deallocate(this % mat_nuclide_index)
allocate(this % mat_nuclide_index(n_nuclides))
this % mat_nuclide_index(:) = 0
! Assign entries in the index array
do i = 1, this % n_nuclides
this % mat_nuclide_index(this % nuclide(i)) = i
end do
end subroutine material_init_nuclide_index
!===============================================================================
! ASSIGN_SAB_TABLES assigns S(alpha,beta) tables to specific nuclides within
! materials so the code knows when to apply bound thermal scattering data
!===============================================================================
subroutine material_assign_sab_tables(this)
class(Material), intent(inout) :: this
integer :: j ! index over nuclides in material
integer :: k ! index over S(a,b) tables in material
integer :: m ! position for sorting
integer :: i_sab
integer :: temp_nuclide ! temporary value for sorting
integer :: temp_table ! temporary value for sorting
real(8) :: temp_frac ! temporary value for sorting
logical :: found
type(VectorInt) :: i_sab_tables
type(VectorInt) :: i_sab_nuclides
type(VectorReal) :: sab_fracs
if (.not. allocated(this % i_sab_tables)) return
ASSIGN_SAB: do k = 1, size(this % i_sab_tables)
! In order to know which nuclide the S(a,b) table applies to, we need
! to search through the list of nuclides for one which has a matching
! name
found = .false.
i_sab = this % i_sab_tables(k)
FIND_NUCLIDE: do j = 1, size(this % nuclide)
if (sab_has_nuclide(i_sab, to_c_string(nuclides(this % nuclide(j)) % name))) then
call i_sab_tables % push_back(i_sab)
call i_sab_nuclides % push_back(j)
call sab_fracs % push_back(this % sab_fracs(k))
found = .true.
end if
end do FIND_NUCLIDE
! Check to make sure S(a,b) table matched a nuclide
if (.not. found) then
call fatal_error("S(a,b) table " // trim(this % &
sab_names(k)) // " did not match any nuclide on material " &
// trim(to_str(this % id())))
end if
end do ASSIGN_SAB
! Make sure each nuclide only appears in one table.
do j = 1, i_sab_nuclides % size()
do k = j+1, i_sab_nuclides % size()
if (i_sab_nuclides % data(j) == i_sab_nuclides % data(k)) then
call fatal_error(trim( &
nuclides(this % nuclide(i_sab_nuclides % data(j))) % name) &
// " in material " // trim(to_str(this % id())) // " was found &
&in multiple S(a,b) tables. Each nuclide can only appear in &
&one S(a,b) table per material.")
end if
end do
end do
! Update i_sab_tables and i_sab_nuclides
deallocate(this % i_sab_tables)
deallocate(this % sab_fracs)
if (allocated(this % i_sab_nuclides)) deallocate(this % i_sab_nuclides)
m = i_sab_tables % size()
allocate(this % i_sab_tables(m))
allocate(this % i_sab_nuclides(m))
allocate(this % sab_fracs(m))
this % i_sab_tables(:) = i_sab_tables % data(1:m)
this % i_sab_nuclides(:) = i_sab_nuclides % data(1:m)
this % sab_fracs(:) = sab_fracs % data(1:m)
! Clear entries in vectors for next material
call i_sab_tables % clear()
call i_sab_nuclides % clear()
call sab_fracs % clear()
! If there are multiple S(a,b) tables, we need to make sure that the
! entries in i_sab_nuclides are sorted or else they won't be applied
! correctly in the cross_section module. The algorithm here is a simple
! insertion sort -- don't need anything fancy!
if (size(this % i_sab_tables) > 1) then
SORT_SAB: do k = 2, size(this % i_sab_tables)
! Save value to move
m = k
temp_nuclide = this % i_sab_nuclides(k)
temp_table = this % i_sab_tables(k)
temp_frac = this % i_sab_tables(k)
MOVE_OVER: do
! Check if insertion value is greater than (m-1)th value
if (temp_nuclide >= this % i_sab_nuclides(m-1)) exit
! Move values over until hitting one that's not larger
this % i_sab_nuclides(m) = this % i_sab_nuclides(m-1)
this % i_sab_tables(m) = this % i_sab_tables(m-1)
this % sab_fracs(m) = this % sab_fracs(m-1)
m = m - 1
! Exit if we've reached the beginning of the list
if (m == 1) exit
end do MOVE_OVER
! Put the original value into its new position
this % i_sab_nuclides(m) = temp_nuclide
this % i_sab_tables(m) = temp_table
this % sab_fracs(m) = temp_frac
end do SORT_SAB
end if
! Deallocate temporary arrays for names of nuclides and S(a,b) tables
if (allocated(this % names)) deallocate(this % names)
end subroutine material_assign_sab_tables
!===============================================================================
! MATERIAL_CALCULATE_XS determines the macroscopic cross sections for the
! material the particle is currently traveling through.
!===============================================================================
subroutine material_calculate_xs(this, p)
class(Material), intent(in) :: this
type(Particle), intent(in) :: p
! Set all material macroscopic cross sections to zero
material_xs % total = ZERO
material_xs % absorption = ZERO
material_xs % fission = ZERO
material_xs % nu_fission = ZERO
if (p % type == NEUTRON) then
call this % calculate_neutron_xs(p)
elseif (p % type == PHOTON) then
call this % calculate_photon_xs(p)
end if
end subroutine material_calculate_xs
!===============================================================================
! CALCULATE_NEUTRON_XS determines the neutron cross section for the material the
! particle is traveling through
!===============================================================================
subroutine calculate_neutron_xs(this, p)
class(Material), intent(in) :: this
type(Particle), intent(in) :: p
integer :: i ! loop index over nuclides
integer :: i_nuclide ! index into nuclides array
integer :: i_sab ! index into sab_tables array
integer :: j ! index in this % i_sab_nuclides
integer :: i_grid ! index into logarithmic mapping array or material
! union grid
real(8) :: atom_density ! atom density of a nuclide
real(8) :: sab_frac ! fraction of atoms affected by S(a,b)
logical :: check_sab ! should we check for S(a,b) table?
! Find energy index on energy grid
i_grid = int(log(p % E/energy_min(NEUTRON))/log_spacing)
! Determine if this material has S(a,b) tables
check_sab = (this % n_sab > 0)
! Initialize position in i_sab_nuclides
j = 1
! Add contribution from each nuclide in material
do i = 1, this % n_nuclides
! ======================================================================
! CHECK FOR S(A,B) TABLE
i_sab = 0
sab_frac = ZERO
! Check if this nuclide matches one of the S(a,b) tables specified.
! This relies on i_sab_nuclides being in sorted order
if (check_sab) then
if (i == this % i_sab_nuclides(j)) then
! Get index in sab_tables
i_sab = this % i_sab_tables(j)
sab_frac = this % sab_fracs(j)
! If particle energy is greater than the highest energy for the
! S(a,b) table, then don't use the S(a,b) table
if (p % E > sab_threshold(i_sab)) then
i_sab = 0
end if
! Increment position in i_sab_nuclides
j = j + 1
! Don't check for S(a,b) tables if there are no more left
if (j > size(this % i_sab_tables)) check_sab = .false.
end if
end if
! ======================================================================
! CALCULATE MICROSCOPIC CROSS SECTION
! Determine microscopic cross sections for this nuclide
i_nuclide = this % nuclide(i)
! Calculate microscopic cross section for this nuclide
if (p % E /= micro_xs(i_nuclide) % last_E &
.or. p % sqrtkT /= micro_xs(i_nuclide) % last_sqrtkT &
.or. i_sab /= micro_xs(i_nuclide) % index_sab + 1 &
.or. sab_frac /= micro_xs(i_nuclide) % sab_frac) then
call nuclides(i_nuclide) % calculate_xs(i_sab, p % E, i_grid, &
p % sqrtkT, sab_frac)
end if
! ======================================================================
! ADD TO MACROSCOPIC CROSS SECTION
! Copy atom density of nuclide in material
atom_density = this % atom_density(i)
! Add contributions to material macroscopic total cross section
material_xs % total = material_xs % total + &
atom_density * micro_xs(i_nuclide) % total
! Add contributions to material macroscopic absorption cross section
material_xs % absorption = material_xs % absorption + &
atom_density * micro_xs(i_nuclide) % absorption
! Add contributions to material macroscopic fission cross section
material_xs % fission = material_xs % fission + &
atom_density * micro_xs(i_nuclide) % fission
! Add contributions to material macroscopic nu-fission cross section
material_xs % nu_fission = material_xs % nu_fission + &
atom_density * micro_xs(i_nuclide) % nu_fission
end do
end subroutine calculate_neutron_xs
!===============================================================================
! CALCULATE_PHOTON_XS determines the macroscopic photon cross sections for the
! material the particle is currently traveling through.
!===============================================================================
subroutine calculate_photon_xs(this, p)
class(Material), intent(in) :: this
type(Particle), intent(in) :: p
integer :: i ! loop index over nuclides
integer :: i_element ! index into elements array
real(8) :: atom_density ! atom density of a nuclide
interface
subroutine photon_calculate_xs(i_element, E) bind(C)
import C_INT, C_DOUBLE
integer(C_INT), value :: i_element
real(C_DOUBLE), value :: E
end subroutine
end interface
material_xs % coherent = ZERO
material_xs % incoherent = ZERO
material_xs % photoelectric = ZERO
material_xs % pair_production = ZERO
! Add contribution from each nuclide in material
do i = 1, this % n_nuclides
! ========================================================================
! CALCULATE MICROSCOPIC CROSS SECTION
! Determine microscopic cross sections for this nuclide
i_element = this % element(i)
! Calculate microscopic cross section for this nuclide
if (p % E /= micro_photon_xs(i_element) % last_E) then
call photon_calculate_xs(i_element, p % E)
end if
! ========================================================================
! ADD TO MACROSCOPIC CROSS SECTION
! Copy atom density of nuclide in material
atom_density = this % atom_density(i)
! Add contributions to material macroscopic total cross section
material_xs % total = material_xs % total + &
atom_density * micro_photon_xs(i_element) % total
! Add contributions to material macroscopic coherent cross section
material_xs % coherent = material_xs % coherent + &
atom_density * micro_photon_xs(i_element) % coherent
! Add contributions to material macroscopic incoherent cross section
material_xs % incoherent = material_xs % incoherent + &
atom_density * micro_photon_xs(i_element) % incoherent
! Add contributions to material macroscopic photoelectric cross section
material_xs % photoelectric = material_xs % photoelectric + &
atom_density * micro_photon_xs(i_element) % photoelectric
! Add contributions to material macroscopic pair production cross section
material_xs % pair_production = material_xs % pair_production + &
atom_density * micro_photon_xs(i_element) % pair_production
end do
end subroutine calculate_photon_xs
!===============================================================================
! FREE_MEMORY_MATERIAL deallocates global arrays defined in this module
!===============================================================================
subroutine free_memory_material()
interface
subroutine free_memory_material_c() bind(C)
end subroutine free_memory_material_c
end interface
call free_memory_material_c()
n_materials = 0
if (allocated(materials)) deallocate(materials)
call material_dict % clear()
end subroutine free_memory_material
!===============================================================================
! C API FUNCTIONS
!===============================================================================
function openmc_extend_materials(n, index_start, index_end) result(err) bind(C)
! Extend the materials array by n elements
integer(C_INT32_T), value, intent(in) :: n
integer(C_INT32_T), optional, intent(out) :: index_start
integer(C_INT32_T), optional, intent(out) :: index_end
integer(C_INT) :: err
integer :: i
type(Material), allocatable :: temp(:) ! temporary materials array
if (n_materials == 0) then
! Allocate materials array
allocate(materials(n))
else
! Allocate materials array with increased size
allocate(temp(n_materials + n))
! Move original materials to temporary array
temp(1:n_materials) = materials(:)
! Move allocation from temporary array
call move_alloc(FROM=temp, TO=materials)
end if
! Return indices in materials array
if (present(index_start)) index_start = n_materials + 1
if (present(index_end)) index_end = n_materials + n
n_materials = n_materials + n
! Extend the C++ materials array and get pointers to the C++ objects
call extend_materials_c(n)
do i = n_materials - n, n_materials
materials(i) % ptr = material_pointer(i - 1)
end do
err = 0
end function openmc_extend_materials
function openmc_get_material_index(id, index) result(err) bind(C)
! Returns the index in the materials array of a material with a given ID
integer(C_INT32_T), value :: id
integer(C_INT32_T), intent(out) :: index
integer(C_INT) :: err
if (allocated(materials)) then
if (material_dict % has(id)) then
index = material_dict % get(id)
err = 0
else
err = E_INVALID_ID
call set_errmsg("No material exists with ID=" // trim(to_str(id)) // ".")
end if
else
err = E_ALLOCATE
call set_errmsg("Memory has not been allocated for materials.")
end if
end function openmc_get_material_index
function openmc_material_add_nuclide(index, name, density) result(err) bind(C)
! Add a nuclide at a specified density in atom/b-cm to a material
integer(C_INT32_T), value, intent(in) :: index
character(kind=C_CHAR) :: name(*)
real(C_DOUBLE), value, intent(in) :: density
integer(C_INT) :: err
integer :: j, k, n
real(8) :: awr
integer, allocatable :: new_nuclide(:)
real(8), allocatable :: new_density(:)
character(:), allocatable :: name_
name_ = to_f_string(name)
err = E_UNASSIGNED
if (index >= 1 .and. index <= size(materials)) then
associate (m => materials(index))
! Check if nuclide is already in material
do j = 1, m % n_nuclides
k = m % nuclide(j)
if (nuclides(k) % name == name_) then
awr = nuclides(k) % awr
m % density = m % density + density - m % atom_density(j)
m % density_gpcc = m % density_gpcc + (density - &
m % atom_density(j)) * awr * MASS_NEUTRON / N_AVOGADRO
m % atom_density(j) = density
err = 0
end if
end do
! If nuclide wasn't found, extend nuclide/density arrays
if (err /= 0) then
! If nuclide hasn't been loaded, load it now
err = openmc_load_nuclide(name)
if (err == 0) then
! Extend arrays
n = m % n_nuclides
allocate(new_nuclide(n + 1))
if (n > 0) new_nuclide(1:n) = m % nuclide
call move_alloc(FROM=new_nuclide, TO=m % nuclide)
allocate(new_density(n + 1))
if (n > 0) new_density(1:n) = m % atom_density
call move_alloc(FROM=new_density, TO=m % atom_density)
! Append new nuclide/density
k = nuclide_dict % get(to_lower(name_))
m % nuclide(n + 1) = k
m % atom_density(n + 1) = density
m % density = m % density + density
m % density_gpcc = m % density_gpcc + &
density * nuclides(k) % awr * MASS_NEUTRON / N_AVOGADRO
m % n_nuclides = n + 1
end if
end if
end associate
else
err = E_OUT_OF_BOUNDS
call set_errmsg("Index in materials array is out of bounds.")
end if
end function openmc_material_add_nuclide
function openmc_material_get_densities(index, nuclides, densities, n) &
result(err) bind(C)
! returns an array of nuclide densities in a material
integer(C_INT32_T), value :: index
type(C_PTR), intent(out) :: nuclides
type(C_PTR), intent(out) :: densities
integer(C_INT), intent(out) :: n
integer(C_INT) :: err
if (index >= 1 .and. index <= size(materials)) then
associate (m => materials(index))
if (allocated(m % atom_density)) then
nuclides = C_LOC(m % nuclide(1))
densities = C_LOC(m % atom_density(1))
n = size(m % atom_density)
err = 0
else
err = E_ALLOCATE
call set_errmsg("Material atom density array has not been allocated.")
end if
end associate
else
err = E_OUT_OF_BOUNDS
call set_errmsg("Index in materials array is out of bounds.")
end if
end function openmc_material_get_densities
function openmc_material_get_id(index, id) result(err) bind(C)
! returns the ID of a material
integer(C_INT32_T), value :: index
integer(C_INT32_T), intent(out) :: id
integer(C_INT) :: err
if (index >= 1 .and. index <= size(materials)) then
id = materials(index) % id()
err = 0
else
err = E_OUT_OF_BOUNDS
call set_errmsg("Index in materials array is out of bounds.")
end if
end function openmc_material_get_id
function openmc_material_get_fissionable(index, fissionable) result(err) bind(C)
! returns whether a material is fissionable
integer(C_INT32_T), value :: index
logical(C_BOOL), intent(out) :: fissionable
integer(C_INT) :: err
if (index >= 1 .and. index <= size(materials)) then
fissionable = materials(index) % fissionable()
err = 0
else
err = E_OUT_OF_BOUNDS
call set_errmsg("Index in materials array is out of bounds.")
end if
end function openmc_material_get_fissionable
function openmc_material_set_id(index, id) result(err) bind(C)
! Set the ID of a material
integer(C_INT32_T), value, intent(in) :: index
integer(C_INT32_T), value, intent(in) :: id
integer(C_INT) :: err
if (index >= 1 .and. index <= n_materials) then
call materials(index) % set_id(id, index)
call material_dict % set(id, index)
err = 0
else
err = E_OUT_OF_BOUNDS
call set_errmsg("Index in materials array is out of bounds.")
end if
end function openmc_material_set_id
function openmc_material_set_density(index, density, units) result(err) bind(C)
! Set the total density of a material
integer(C_INT32_T), value, intent(in) :: index
real(C_DOUBLE), value, intent(in) :: density
character(kind=C_CHAR), intent(in) :: units(*)
integer(C_INT) :: err
character(:), allocatable :: units_
! Convert C string to Fortran string
units_ = to_f_string(units)
err = E_UNASSIGNED
if (index >= 1 .and. index <= size(materials)) then
associate (m => materials(index))
err = m % set_density(density, units_)
end associate
else
err = E_OUT_OF_BOUNDS
call set_errmsg("Index in materials array is out of bounds.")
end if
end function openmc_material_set_density
function openmc_material_set_densities(index, n, name, density) result(err) bind(C)
! Sets the densities for a list of nuclides in a material. If the nuclides
! don't already exist in the material, they will be added
integer(C_INT32_T), value, intent(in) :: index
integer(C_INT), value, intent(in) :: n
type(C_PTR), intent(in) :: name(n)
real(C_DOUBLE), intent(in) :: density(n)
integer(C_INT) :: err
integer :: i
integer :: stat
character(C_CHAR), pointer :: string(:)
character(len=:, kind=C_CHAR), allocatable :: name_
if (index >= 1 .and. index <= size(materials)) then
associate (m => materials(index))
! If nuclide/density arrays are not correct size, reallocate
if (n /= m % n_nuclides) then
deallocate(m % nuclide, m % atom_density, STAT=stat)
allocate(m % nuclide(n), m % atom_density(n))
end if
do i = 1, n
! Convert C string to Fortran string
call c_f_pointer(name(i), string, [10])
name_ = to_lower(to_f_string(string))
if (.not. nuclide_dict % has(name_)) then
err = openmc_load_nuclide(string)
if (err < 0) return
end if
m % nuclide(i) = nuclide_dict % get(name_)
m % atom_density(i) = density(i)
end do
m % n_nuclides = n
! Set total density to the sum of the vector
err = m % set_density(sum(density), 'atom/b-cm')
! Assign S(a,b) tables
call m % assign_sab_tables()
end associate
else
err = E_OUT_OF_BOUNDS
call set_errmsg("Index in materials array is out of bounds.")
end if
end function openmc_material_set_densities
!===============================================================================
! Fortran compatibility
!===============================================================================
function material_isotropic(i_material, i_nuc_mat) result(iso) bind(C)
integer(C_INT), value :: i_material
integer(C_INT), value :: i_nuc_mat
logical(C_BOOL) :: iso
iso = .false.
associate (mat => materials(i_material))
if (mat % has_isotropic_nuclides) then
iso = mat % p0(i_nuc_mat)
end if
end associate
end function
function material_element(i_material) result(ptr) bind(C)
integer(C_INT), value :: i_material
type(C_PTR) :: ptr
ptr = C_LOC(materials(i_material) % element(1))
end function
end module material_header

View file

@ -16,7 +16,9 @@
#include "openmc/error.h"
#include "openmc/math_functions.h"
#include "openmc/mgxs_interface.h"
#include "openmc/random_lcg.h"
#include "openmc/settings.h"
#include "openmc/string_utils.h"
@ -41,7 +43,7 @@ std::vector<Mgxs> macro_xs;
void
Mgxs::init(const std::string& in_name, double in_awr,
const std::vector<double>& in_kTs, bool in_fissionable, int in_scatter_format,
int in_num_groups, int in_num_delayed_groups, bool in_is_isotropic,
bool in_is_isotropic,
const std::vector<double>& in_polar, const std::vector<double>& in_azimuthal)
{
// Set the metadata
@ -51,8 +53,8 @@ Mgxs::init(const std::string& in_name, double in_awr,
kTs = xt::adapt(in_kTs);
fissionable = in_fissionable;
scatter_format = in_scatter_format;
num_groups = in_num_groups;
num_delayed_groups = in_num_delayed_groups;
num_groups = data::num_energy_groups;
num_delayed_groups = data::num_delayed_groups;
xs.resize(in_kTs.size());
is_isotropic = in_is_isotropic;
n_pol = in_polar.size();
@ -73,9 +75,8 @@ Mgxs::init(const std::string& in_name, double in_awr,
//==============================================================================
void
Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups,
int in_num_delayed_groups, const std::vector<double>& temperature,
double tolerance, std::vector<int>& temps_to_read, int& order_dim, int& method)
Mgxs::metadata_from_hdf5(hid_t xs_id, const std::vector<double>& temperature,
std::vector<int>& temps_to_read, int& order_dim)
{
// get name
char char_name[MAX_WORD_LEN];
@ -114,20 +115,20 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups,
// If only one temperature is available, lets just use nearest temperature
// interpolation
if ((num_temps == 1) && (method == TEMPERATURE_INTERPOLATION)) {
if ((num_temps == 1) && (settings::temperature_method == TEMPERATURE_INTERPOLATION)) {
warning("Cross sections for " + strtrim(name) + " are only available " +
"at one temperature. Reverting to the nearest temperature " +
"method.");
method = TEMPERATURE_NEAREST;
settings::temperature_method = TEMPERATURE_NEAREST;
}
switch(method) {
switch(settings::temperature_method) {
case TEMPERATURE_NEAREST:
// Determine actual temperatures to read
for (const auto& T : temperature) {
auto i_closest = xt::argmin(xt::abs(available_temps - T))[0];
double temp_actual = available_temps[i_closest];
if (std::fabs(temp_actual - T) < tolerance) {
if (std::fabs(temp_actual - T) < settings::temperature_tolerance) {
if (std::find(temps_to_read.begin(), temps_to_read.end(), std::round(temp_actual))
== temps_to_read.end()) {
temps_to_read.push_back(std::round(temp_actual));
@ -276,39 +277,34 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups,
// Finally use this data to initialize the MGXS Object
init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format,
in_num_groups, in_num_delayed_groups, in_is_isotropic, in_polar,
in_is_isotropic, in_polar,
in_azimuthal);
}
//==============================================================================
Mgxs::Mgxs(hid_t xs_id, int energy_groups, int delayed_groups,
const std::vector<double>& temperature, double tolerance, int max_order,
bool legendre_to_tabular, int legendre_to_tabular_points, int& method)
Mgxs::Mgxs(hid_t xs_id, const std::vector<double>& temperature)
{
// Call generic data gathering routine (will populate the metadata)
int order_data;
std::vector<int> temps_to_read;
metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature,
tolerance, temps_to_read, order_data, method);
metadata_from_hdf5(xs_id, temperature, temps_to_read, order_data);
// Set number of energy and delayed groups
int final_scatter_format = scatter_format;
if (legendre_to_tabular) {
if (settings::legendre_to_tabular) {
if (scatter_format == ANGLE_LEGENDRE) final_scatter_format = ANGLE_TABULAR;
}
// Load the more specific XsData information
for (int t = 0; t < temps_to_read.size(); t++) {
xs[t] = XsData(energy_groups, delayed_groups, fissionable,
final_scatter_format, n_pol, n_azi);
xs[t] = XsData(fissionable, final_scatter_format, n_pol, n_azi);
// Get the temperature as a string and then open the HDF5 group
std::string temp_str = std::to_string(temps_to_read[t]) + "K";
hid_t xsdata_grp = open_group(xs_id, temp_str.c_str());
xs[t].from_hdf5(xsdata_grp, fissionable, scatter_format,
final_scatter_format, order_data, max_order,
legendre_to_tabular_points, is_isotropic, n_pol, n_azi);
final_scatter_format, order_data, is_isotropic, n_pol, n_azi);
close_group(xsdata_grp);
} // end temperature loop
@ -320,8 +316,7 @@ Mgxs::Mgxs(hid_t xs_id, int energy_groups, int delayed_groups,
//==============================================================================
Mgxs::Mgxs(const std::string& in_name, const std::vector<double>& mat_kTs,
const std::vector<Mgxs*>& micros, const std::vector<double>& atom_densities,
double tolerance, int& method)
const std::vector<Mgxs*>& micros, const std::vector<double>& atom_densities)
{
// Get the minimum data needed to initialize:
// Dont need awr, but lets just initialize it anyways
@ -341,13 +336,12 @@ Mgxs::Mgxs(const std::string& in_name, const std::vector<double>& mat_kTs,
std::vector<double> in_azimuthal = micros[0]->azimuthal;
init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format,
in_num_groups, in_num_delayed_groups, in_is_isotropic, in_polar,
in_azimuthal);
in_is_isotropic, in_polar, in_azimuthal);
// Create the xs data for each temperature
for (int t = 0; t < mat_kTs.size(); t++) {
xs[t] = XsData(in_num_groups, in_num_delayed_groups, in_fissionable,
in_scatter_format, in_polar.size(), in_azimuthal.size());
xs[t] = XsData(in_fissionable, in_scatter_format, in_polar.size(),
in_azimuthal.size());
// Find the right temperature index to use
double temp_desired = mat_kTs[t];
@ -357,13 +351,13 @@ Mgxs::Mgxs(const std::string& in_name, const std::vector<double>& mat_kTs,
std::vector<int> micro_t(micros.size(), 0);
std::vector<double> micro_t_interp(micros.size(), 0.);
for (int m = 0; m < micros.size(); m++) {
switch(method) {
switch(settings::temperature_method) {
case TEMPERATURE_NEAREST:
{
micro_t[m] = xt::argmin(xt::abs(micros[m]->kTs - temp_desired))[0];
auto temp_actual = micros[m]->kTs[micro_t[m]];
if (std::abs(temp_actual - temp_desired) >= K_BOLTZMANN * tolerance) {
if (std::abs(temp_actual - temp_desired) >= K_BOLTZMANN * settings::temperature_tolerance) {
std::stringstream msg;
msg << "MGXS Library does not contain cross section for " << name
<< " at or near " << std::round(temp_desired / K_BOLTZMANN) << "K.";
@ -395,7 +389,7 @@ Mgxs::Mgxs(const std::string& in_name, const std::vector<double>& mat_kTs,
// If we are doing nearest temperature interpolation, then we don't need
// to do the 2nd temperature
int num_interp_points = 2;
if (method == TEMPERATURE_NEAREST) num_interp_points = 1;
if (settings::temperature_method == TEMPERATURE_NEAREST) num_interp_points = 1;
for (int interp_point = 0; interp_point < num_interp_points; interp_point++) {
std::vector<double> interp(micros.size());
std::vector<double> temp_indices(micros.size());

View file

@ -1,178 +0,0 @@
module mgxs_data
use, intrinsic :: ISO_C_BINDING
use constants
use algorithm, only: find
use dict_header, only: DictCharInt
use error, only: fatal_error, write_message
use geometry_header, only: get_temperatures, cells
use hdf5_interface
use material_header, only: Material, materials, n_materials
use mgxs_interface
use nuclide_header, only: n_nuclides
use set_header, only: SetChar
use settings
use stl_vector, only: VectorReal, VectorChar
use string, only: to_lower
implicit none
contains
!===============================================================================
! READ_XS reads all the cross sections for the problem and stores them in
! nuclides and sab_tables arrays
!===============================================================================
subroutine read_mgxs()
integer :: i ! index in materials array
integer :: j ! index over nuclides in material
integer :: i_nuclide ! index in nuclides array
character(20) :: name ! name of library to load
type(Material), pointer :: mat
type(SetChar) :: already_read
integer(HID_T) :: file_id
logical :: file_exists
type(VectorReal), allocatable, target :: temps(:)
character(MAX_WORD_LEN) :: word
integer, allocatable :: array(:)
! Check if MGXS Library exists
inquire(FILE=path_cross_sections, EXIST=file_exists)
if (.not. file_exists) then
! Could not find MGXS Library file
call fatal_error("Cross sections HDF5 file '" &
// trim(path_cross_sections) // "' does not exist!")
end if
call write_message("Loading cross section data...", 5)
! Get temperatures
call get_temperatures(temps)
! Open file for reading
file_id = file_open(path_cross_sections, 'r', parallel=.true.)
! Read filetype
call read_attribute(word, file_id, "filetype")
if (word /= 'mgxs') then
call fatal_error("Provided MGXS Library is not a MGXS Library file.")
end if
! Read revision number for the MGXS Library file and make sure it matches
! with the current version
call read_attribute(array, file_id, "version")
if (any(array /= VERSION_MGXS_LIBRARY)) then
call fatal_error("MGXS Library file version does not match current &
&version supported by OpenMC.")
end if
! ==========================================================================
! READ ALL MGXS CROSS SECTION TABLES
! Loop over all files
MATERIAL_LOOP: do i = 1, n_materials
mat => materials(i)
NUCLIDE_LOOP: do j = 1, mat % n_nuclides
name = trim(mat % names(j)) // C_NULL_CHAR
i_nuclide = mat % nuclide(j)
if (.not. already_read % contains(name)) then
call add_mgxs_c(file_id, name, num_energy_groups, num_delayed_groups, &
temps(i_nuclide) % size(), temps(i_nuclide) % data, &
temperature_tolerance, max_order, &
logical(legendre_to_tabular, C_BOOL), &
legendre_to_tabular_points, temperature_method)
call already_read % add(name)
end if
end do NUCLIDE_LOOP
call mat % set_fissionable( &
logical(query_fissionable_c(mat % n_nuclides, mat % nuclide)))
end do MATERIAL_LOOP
call file_close(file_id)
! Avoid some valgrind leak errors
call already_read % clear()
end subroutine read_mgxs
!===============================================================================
! CREATE_MACRO_XS generates the macroscopic xs from the microscopic input data
!===============================================================================
subroutine create_macro_xs()
integer :: i_mat ! index in materials array
type(Material), pointer :: mat ! current material
type(VectorReal), allocatable :: kTs(:)
character(MAX_WORD_LEN) :: name ! name of material
! Get temperatures to read for each material
call get_mat_kTs(kTs)
! Force all nuclides in a material to be the same representation.
! Therefore type(nuclides(mat % nuclide(1)) % obj) dictates type(macroxs).
! At the same time, we will find the scattering type, as that will dictate
! how we allocate the scatter object within macroxs.allocate(macro_xs(n_materials))
do i_mat = 1, n_materials
! Get the material
mat => materials(i_mat)
name = trim(mat % name) // C_NULL_CHAR
call create_macro_xs_c(name, mat % n_nuclides, mat % nuclide, &
kTs(i_mat) % size(), kTs(i_mat) % data, mat % atom_density, &
temperature_tolerance, temperature_method)
end do
end subroutine create_macro_xs
!===============================================================================
! GET_MAT_kTs returns a list of temperatures (in eV) that each
! material appears at in the model.
!===============================================================================
subroutine get_mat_kTs(kTs)
type(VectorReal), allocatable, intent(out) :: kTs(:)
integer :: i, j ! Cell and material index
integer :: i_material ! Index in materials array
real(8) :: kT ! temperature in eV
allocate(kTs(size(materials)))
do i = 1, size(cells)
! Skip non-material cells
if (cells(i) % fill() /= C_NONE) cycle
do j = 1, cells(i) % material_size()
! Skip void materials
if (cells(i) % material(j) == MATERIAL_VOID) cycle
! Get temperature of cell (rounding to nearest integer)
if (cells(i) % sqrtkT_size() > 1) then
kT = cells(i) % sqrtkT(j-1)**2
else
kT = cells(i) % sqrtkT(0)**2
end if
i_material = cells(i) % material(j)
! Add temperature if it hasn't already been added
if (find(kTs(i_material), kT) == -1) then
call kTs(i_material) % push_back(kT)
end if
end do
end do
end subroutine get_mat_kTs
end module mgxs_data

View file

@ -7,48 +7,6 @@ module mgxs_interface
implicit none
interface
subroutine add_mgxs_c(file_id, name, energy_groups, delayed_groups, &
n_temps, temps, tolerance, max_order, legendre_to_tabular, &
legendre_to_tabular_points, method) bind(C)
use ISO_C_BINDING
import HID_T
implicit none
integer(HID_T), value, intent(in) :: file_id
character(kind=C_CHAR),intent(in) :: name(*)
integer(C_INT), value, intent(in) :: energy_groups
integer(C_INT), value, intent(in) :: delayed_groups
integer(C_INT), value, intent(in) :: n_temps
real(C_DOUBLE), intent(in) :: temps(1:n_temps)
real(C_DOUBLE), value, intent(in) :: tolerance
integer(C_INT), value, intent(in) :: max_order
logical(C_BOOL),value, intent(in) :: legendre_to_tabular
integer(C_INT), value, intent(in) :: legendre_to_tabular_points
integer(C_INT), intent(inout) :: method
end subroutine add_mgxs_c
function query_fissionable_c(n_nuclides, i_nuclides) result(result) bind(C)
use ISO_C_BINDING
implicit none
integer(C_INT), value, intent(in) :: n_nuclides
integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides)
logical(C_BOOL) :: result
end function query_fissionable_c
subroutine create_macro_xs_c(name, n_nuclides, i_nuclides, n_temps, temps, &
atom_densities, tolerance, method) bind(C)
use ISO_C_BINDING
implicit none
character(kind=C_CHAR),intent(in) :: name(*)
integer(C_INT), value, intent(in) :: n_nuclides
integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides)
integer(C_INT), value, intent(in) :: n_temps
real(C_DOUBLE), intent(in) :: temps(1:n_temps)
real(C_DOUBLE), intent(in) :: atom_densities(1:n_nuclides)
real(C_DOUBLE), value, intent(in) :: tolerance
integer(C_INT), intent(inout) :: method
end subroutine create_macro_xs_c
subroutine calculate_xs_c(i_mat, gin, sqrtkT, uvw, total_xs, abs_xs, &
nu_fiss_xs) bind(C)
use ISO_C_BINDING
@ -130,7 +88,7 @@ module mgxs_interface
integer(C_INT), bind(C) :: num_energy_groups
! Number of delayed groups
integer(C_INT) :: num_delayed_groups
integer(C_INT), bind(C) :: num_delayed_groups
! Energy group structure with decreasing energy
real(8), allocatable :: energy_bins(:)

View file

@ -1,10 +1,19 @@
#include "openmc/mgxs_interface.h"
#include <string>
#include <unordered_set>
#include "openmc/cell.h"
#include "openmc/cross_sections.h"
#include "openmc/container_util.h"
#include "openmc/error.h"
#include "openmc/file_utils.h"
#include "openmc/geometry_aux.h"
#include "openmc/hdf5_interface.h"
#include "openmc/material.h"
#include "openmc/math_functions.h"
#include "openmc/nuclide.h"
#include "openmc/settings.h"
namespace openmc {
@ -25,75 +34,152 @@ std::vector<double> rev_energy_bins;
// Mgxs data loading interface methods
//==============================================================================
void
add_mgxs_c(hid_t file_id, const char* name, int energy_groups,
int delayed_groups, int n_temps, const double temps[], double tolerance,
int max_order, bool legendre_to_tabular, int legendre_to_tabular_points,
int& method)
void read_mgxs()
{
// Convert temps to a vector for the from_hdf5 function
std::vector<double> temperature(temps, temps + n_temps);
// Check if MGXS Library exists
if (!file_exists(settings::path_cross_sections)) {
// Could not find MGXS Library file
fatal_error("Cross sections HDF5 file '" + settings::path_cross_sections +
"' does not exist.");
}
write_message("Loading cross section data...", 5);
// Get temperatures
std::vector<std::vector<double>> nuc_temps(data::nuclide_map.size());
std::vector<std::vector<double>> dummy;
get_temperatures(nuc_temps, dummy);
// Open file for reading
hid_t file_id = file_open(settings::path_cross_sections, 'r');
// Read filetype
std::string type;
read_attribute(file_id, "filetype", type);
if (type != "mgxs") {
fatal_error("Provided MGXS Library is not a MGXS Library file.");
}
// Read revision number for the MGXS Library file and make sure it matches
// with the current version
std::array<int, 2> array;
read_attribute(file_id, "version", array);
if (array != VERSION_MGXS_LIBRARY) {
fatal_error("MGXS Library file version does not match current version "
"supported by OpenMC.");
}
// ==========================================================================
// READ ALL MGXS CROSS SECTION TABLES
std::unordered_set<std::string> already_read;
// Build vector of nuclide names
std::vector<std::string> nuclide_names(data::nuclide_map.size());
for (const auto& kv : data::nuclide_map) {
nuclide_names[kv.second] = kv.first;
}
// Loop over all files
for (const auto& mat : model::materials) {
for (int i_nuc : mat->nuclide_) {
std::string& name = nuclide_names[i_nuc];
if (already_read.find(name) == already_read.end()) {
add_mgxs(file_id, name, nuc_temps[i_nuc]);
already_read.insert(name);
}
if (data::nuclides_MG[i_nuc].fissionable) {
mat->fissionable_ = true;
}
}
}
file_close(file_id);
}
//==============================================================================
void
add_mgxs(hid_t file_id, const std::string& name,
const std::vector<double>& temperature)
{
write_message("Loading " + std::string(name) + " data...", 6);
// Check to make sure cross section set exists in the library
hid_t xs_grp;
if (object_exists(file_id, name)) {
xs_grp = open_group(file_id, name);
if (object_exists(file_id, name.c_str())) {
xs_grp = open_group(file_id, name.c_str());
} else {
fatal_error("Data for " + std::string(name) + " does not exist in "
+ "provided MGXS Library");
}
Mgxs mg(xs_grp, energy_groups, delayed_groups, temperature, tolerance,
max_order, legendre_to_tabular, legendre_to_tabular_points, method);
data::nuclides_MG.push_back(mg);
data::nuclides_MG.emplace_back(xs_grp, temperature);
close_group(xs_grp);
}
//==============================================================================
bool
query_fissionable_c(int n_nuclides, const int i_nuclides[])
void create_macro_xs()
{
bool result = false;
for (int n = 0; n < n_nuclides; n++) {
if (data::nuclides_MG[i_nuclides[n] - 1].fissionable) result = true;
// Get temperatures to read for each material
auto kTs = get_mat_kTs();
// Force all nuclides in a material to be the same representation.
// Therefore type(nuclides[mat->nuclide_[0]]) dictates type(macroxs).
// At the same time, we will find the scattering type, as that will dictate
// how we allocate the scatter object within macroxs.
for (int i = 0; i < model::materials.size(); ++i) {
if (kTs[i].size() > 0) {
// Convert atom_densities to a vector
Material* mat = model::materials[i];
std::vector<double> atom_densities(mat->atom_density_.begin(),
mat->atom_density_.end());
// Build array of pointers to nuclides_MG's Mgxs objects needed for this
// material
std::vector<Mgxs*> mgxs_ptr;
for (int i_nuclide : mat->nuclide_) {
mgxs_ptr.push_back(&data::nuclides_MG[i_nuclide]);
}
data::macro_xs.emplace_back(mat->name_, kTs[i], mgxs_ptr, atom_densities);
} else {
// Preserve the ordering of materials by including a blank entry
data::macro_xs.emplace_back();
}
}
return result;
}
//==============================================================================
void
create_macro_xs_c(const char* mat_name, int n_nuclides, const int i_nuclides[],
int n_temps, const double temps[], const double atom_densities[],
double tolerance, int& method)
std::vector<std::vector<double>> get_mat_kTs()
{
if (n_temps > 0) {
// // Convert temps to a vector
std::vector<double> temperature(temps, temps + n_temps);
std::vector<std::vector<double>> kTs(model::materials.size());
// Convert atom_densities to a vector
std::vector<double> atom_densities_vec(atom_densities,
atom_densities + n_nuclides);
for (const auto& cell : model::cells) {
// Skip non-material cells
if (cell->fill_ != C_NONE) continue;
// Build array of pointers to nuclides_MG's Mgxs objects needed for this
// material
std::vector<Mgxs*> mgxs_ptr(n_nuclides);
for (int n = 0; n < n_nuclides; n++) {
mgxs_ptr[n] = &data::nuclides_MG[i_nuclides[n] - 1];
for (int j = 0; j < cell->material_.size(); ++j) {
// Skip void materials
int i_material = cell->material_[j];
if (i_material == MATERIAL_VOID) continue;
// Get temperature of cell (rounding to nearest integer)
double sqrtkT = cell->sqrtkT_.size() == 1 ?
cell->sqrtkT_[j] : cell->sqrtkT_[0];
double kT = sqrtkT * sqrtkT;
// Add temperature if it hasn't already been added
if (!contains(kTs[i_material], kT)) {
kTs[i_material].push_back(kT);
}
}
Mgxs macro(mat_name, temperature, mgxs_ptr, atom_densities_vec,
tolerance, method);
data::macro_xs.emplace_back(macro);
} else {
// Preserve the ordering of materials by including a blank entry
Mgxs macro;
data::macro_xs.emplace_back(macro);
}
return kTs;
}
//==============================================================================

View file

@ -1,37 +0,0 @@
module multipole_header
use constants
use hdf5_interface
use error, only: fatal_error
use string, only: to_str
implicit none
contains
!===============================================================================
! CHECK_WMP_VERSION checks for the right version of WMP data within HDF5
! files
!===============================================================================
subroutine check_wmp_version(file_id)
integer(HID_T), intent(in) :: file_id
integer, allocatable :: version(:)
if (attribute_exists(file_id, 'version')) then
call read_attribute(version, file_id, 'version')
if (version(1) /= WMP_VERSION(1)) then
call fatal_error("WMP data format uses version " // trim(to_str(&
version(1))) // "." // trim(to_str(version(2))) // " whereas &
&your installation of OpenMC expects version " // trim(to_str(&
WMP_VERSION(1))) // ".x data.")
end if
else
call fatal_error("WMP data does not indicate a version. Your &
&installation of OpenMC expects version " // trim(to_str(&
WMP_VERSION(1))) // ".x data.")
end if
end subroutine check_wmp_version
end module multipole_header

View file

@ -1,6 +1,8 @@
#include "openmc/nuclide.h"
#include "openmc/capi.h"
#include "openmc/container_util.h"
#include "openmc/cross_sections.h"
#include "openmc/endf.h"
#include "openmc/error.h"
#include "openmc/hdf5_interface.h"
@ -29,6 +31,7 @@ namespace data {
std::array<double, 2> energy_min {0.0, 0.0};
std::array<double, 2> energy_max {INFTY, INFTY};
std::vector<std::unique_ptr<Nuclide>> nuclides;
std::unordered_map<std::string, int> nuclide_map;
} // namespace data
namespace simulation {
@ -46,7 +49,7 @@ int Nuclide::XS_FISSION {2};
int Nuclide::XS_NU_FISSION {3};
int Nuclide::XS_PHOTON_PROD {4};
Nuclide::Nuclide(hid_t group, const double* temperature, int n, int i_nuclide)
Nuclide::Nuclide(hid_t group, const std::vector<double>& temperature, int i_nuclide)
: i_nuclide_{i_nuclide}
{
// Get name of nuclide from group, removing leading '/'
@ -81,6 +84,7 @@ Nuclide::Nuclide(hid_t group, const double* temperature, int n, int i_nuclide)
// temperature range was given, in which case all temperatures in the range
// are loaded irrespective of what temperatures actually appear in the model
std::vector<int> temps_to_read;
int n = temperature.size();
double T_min = n > 0 ? settings::temperature_range[0] : 0.0;
double T_max = n > 0 ? settings::temperature_range[1] : INFTY;
if (T_max > 0.0) {
@ -94,8 +98,7 @@ Nuclide::Nuclide(hid_t group, const double* temperature, int n, int i_nuclide)
switch (settings::temperature_method) {
case TEMPERATURE_NEAREST:
// Find nearest temperatures
for (int i = 0; i < n; ++i) {
double T_desired = temperature[i];
for (double T_desired : temperature) {
// Determine closest temperature
double min_delta_T = INFTY;
@ -129,9 +132,7 @@ Nuclide::Nuclide(hid_t group, const double* temperature, int n, int i_nuclide)
case TEMPERATURE_INTERPOLATION:
// If temperature interpolation or multipole is selected, get a list of
// bounding temperatures for each actual temperature present in the model
for (int i = 0; i < n; ++i) {
double T_desired = temperature[i];
for (double T_desired : temperature) {
bool found_pair = false;
for (int j = 0; j < temps_available.size() - 1; ++j) {
if (temps_available[j] <= T_desired && T_desired < temps_available[j + 1]) {
@ -391,7 +392,7 @@ void Nuclide::create_derived()
void Nuclide::init_grid()
{
int neutron = static_cast<int>(ParticleType::neutron) - 1;
int neutron = static_cast<int>(ParticleType::neutron);
double E_min = data::energy_min[neutron];
double E_max = data::energy_max[neutron];
int M = settings::n_log_bins;
@ -865,6 +866,76 @@ void Nuclide::calculate_urr_xs(int i_temp, double E) const
}
//==============================================================================
// Non-member functions
//==============================================================================
void check_data_version(hid_t file_id)
{
if (attribute_exists(file_id, "version")) {
std::vector<int> version;
read_attribute(file_id, "version", version);
if (version[0] != HDF5_VERSION[0]) {
fatal_error("HDF5 data format uses version " + std::to_string(version[0])
+ "." + std::to_string(version[1]) + " whereas your installation of "
"OpenMC expects version " + std::to_string(HDF5_VERSION[0])
+ ".x data.");
}
} else {
fatal_error("HDF5 data does not indicate a version. Your installation of "
"OpenMC expects version " + std::to_string(HDF5_VERSION[0]) +
".x data.");
}
}
//==============================================================================
// C API
//==============================================================================
extern "C" void extend_nuclides();
extern "C" int openmc_load_nuclide(const char* name)
{
if (data::nuclide_map.find(name) == data::nuclide_map.end()) {
const auto& it = data::library_map.find({Library::Type::neutron, name});
if (it != data::library_map.end()) {
// Extend nuclides array on Fortran side
extend_nuclides();
// Get filename for library containing nuclide
int idx = it->second;
std::string& filename = data::libraries[idx].path_;
write_message("Reading " + std::string{name} + " from " + filename, 6);
// Open file and make sure version is sufficient
hid_t file_id = file_open(filename, 'r');
check_data_version(file_id);
// Read nuclide data from HDF5
hid_t group = open_group(file_id, name);
std::vector<double> temperature;
int i_nuclide = data::nuclides.size();
data::nuclides.push_back(std::make_unique<Nuclide>(
group, temperature, i_nuclide));
close_group(group);
file_close(file_id);
// Add entry to nuclide dictionary
data::nuclide_map[name] = i_nuclide;
// Initialize nuclide grid
data::nuclides.back()->init_grid();
// Read multipole file into the appropriate entry on the nuclides array
if (settings::temperature_multipole) read_multipole_data(i_nuclide);
} else {
set_errmsg("Nuclide '" + std::string{name} + "' is not present in library.");
return OPENMC_E_DATA;
}
}
return 0;
}
//==============================================================================
// Fortran compatibility functions
//==============================================================================
@ -872,19 +943,16 @@ void Nuclide::calculate_urr_xs(int i_temp, double E) const
extern "C" void
set_particle_energy_bounds(int particle, double E_min, double E_max)
{
data::energy_min[particle - 1] = E_min;
data::energy_max[particle - 1] = E_max;
data::energy_min[particle] = E_min;
data::energy_max[particle] = E_max;
}
extern "C" Nuclide* nuclide_from_hdf5_c(hid_t group, const double* temperature, int n)
{
data::nuclides.push_back(std::make_unique<Nuclide>(group, temperature, n,
data::nuclides.size()));
return data::nuclides.back().get();
}
extern "C" int nuclides_size() { return data::nuclide_map.size(); }
extern "C" void nuclide_init_grid_c(Nuclide* nuc) { nuc->init_grid(); }
extern "C" double nuclide_awr(int i_nuc) { return data::nuclides[i_nuc - 1]->awr_; }
extern "C" Reaction* nuclide_reaction(Nuclide* nuc, int i_rx)
{
return nuc->reactions_[i_rx-1].get();
@ -916,11 +984,6 @@ extern "C" double nuclide_fission_q_recov(Nuclide* nuc, double E)
return nuc->fission_q_recov_ ? (*nuc->fission_q_recov_)(E) : 0.0;
}
extern "C" void nuclide_load_multipole(Nuclide* nuc, hid_t group)
{
nuc->multipole_ = std::make_unique<WindowedMultipole>(group);
}
extern "C" void multipole_deriv_eval(Nuclide* nuc, double E, double sqrtkT,
double* sig_s, double* sig_a, double* sig_f)
{
@ -933,7 +996,17 @@ extern "C" bool multipole_in_range(Nuclide* nuc, double E)
E <= nuc->multipole_->E_max_;
}
extern "C" void nuclides_clear() { data::nuclides.clear(); }
extern "C" void nuclides_clear()
{
data::nuclides.clear();
data::nuclide_map.clear();
}
extern "C" int nuclide_map_get(const char* name)
{
auto it = data::nuclide_map.find(name);
return it == data::nuclide_map.end() ? -1 : it->second + 1;
}
extern "C" NuclideMicroXS* micro_xs_ptr();
extern "C" ElementMicroXS* micro_photon_xs_ptr();

View file

@ -5,14 +5,11 @@ module nuclide_header
use algorithm, only: sort, find, binary_search
use constants
use dict_header, only: DictIntInt, DictCharInt
use endf, only: reaction_name, is_fission, is_disappearance, &
is_inelastic_scatter
use endf, only: is_fission, is_disappearance
use endf_header, only: Function1D, Polynomial, Tabulated1D
use error
use hdf5_interface
use message_passing
use random_lcg, only: prn, future_prn, prn_set_stream
use reaction_header, only: Reaction
use settings
use stl_vector, only: VectorInt, VectorReal
@ -66,9 +63,6 @@ module nuclide_header
! Fission information
integer, allocatable :: index_fission(:) ! indices in reactions
! Multipole data
logical :: mp_present = .false.
! Reactions
type(Reaction), allocatable :: reactions(:)
@ -79,7 +73,6 @@ module nuclide_header
type(C_PTR) :: ptr
contains
procedure :: from_hdf5 => nuclide_from_hdf5
procedure :: init_grid => nuclide_init_grid
procedure :: nu => nuclide_nu
procedure, private :: create_derived => nuclide_create_derived
@ -151,18 +144,12 @@ module nuclide_header
! Nuclear data for each nuclide
type(Nuclide), allocatable, target :: nuclides(:)
integer(C_INT), bind(C) :: n_nuclides
type(DictCharInt) :: nuclide_dict
! Cross section caches
type(NuclideMicroXS), allocatable, target :: micro_xs(:) ! Cache for each nuclide
type(MaterialMacroXS), bind(C) :: material_xs ! Cache for current material
!$omp threadprivate(micro_xs, material_xs)
! Minimum/maximum energies
real(8) :: energy_min(2) = [ZERO, ZERO]
real(8) :: energy_max(2) = [INFINITY, INFINITY]
interface
function library_present_c(type, name) result(b) bind(C, name='library_present')
import C_INT, C_CHAR, C_BOOL
@ -195,6 +182,12 @@ module nuclide_header
logical(C_BOOL) :: b
end function
function nuclide_awr(i_nuc) result(awr) bind(C)
import C_INT, C_DOUBLE
integer(C_INT), value :: i_nuc
real(C_DOUBLE) :: awr
end function
function nuclide_fission_q_prompt(ptr, E) result(q) bind(C)
import C_PTR, C_DOUBLE
type(C_PTR), value :: ptr
@ -208,6 +201,12 @@ module nuclide_header
real(C_DOUBLE), value :: E
real(C_DOUBLE) :: q
end function
function nuclide_map_get(name) result(idx) bind(C)
import C_CHAR, C_INT
character(kind=C_CHAR), intent(in) :: name(*)
integer(C_INT) :: idx
end function
end interface
contains
@ -238,27 +237,22 @@ contains
ptr = C_LOC(micro_xs(1))
end function
subroutine nuclide_from_hdf5(this, group_id, temperature, method, tolerance, &
minmax, master, i_nuclide)
class(Nuclide), intent(inout) :: this
integer(HID_T), intent(in) :: group_id
type(VectorReal), intent(in), target :: temperature ! list of desired temperatures
integer, intent(inout) :: method
real(8), intent(in) :: tolerance
real(8), intent(in) :: minmax(2) ! range of temperatures
logical(C_BOOL), intent(in) :: master ! if this is the master proc
integer, intent(in) :: i_nuclide ! Nuclide index in nuclides
subroutine nuclide_from_hdf5(group_id, ptr, temps, n, i_nuclide) bind(C)
integer(HID_T), value :: group_id
type(C_PTR), value :: ptr
type(C_PTR), value :: temps
integer(C_INT), value :: n
integer(C_INT), value :: i_nuclide
real(C_DOUBLE), pointer :: temperature(:) ! list of desired temperatures
integer :: i
integer :: i_closest
integer :: n_temperature
integer(HID_T) :: nu_group
integer(HID_T) :: energy_group, energy_dset
integer(HID_T) :: kT_group
integer(HID_T) :: rxs_group
integer(HID_T) :: rx_group
integer(HID_T) :: fer_group ! fission_energy_release group
integer(HID_T) :: fer_dset
integer(HSIZE_T) :: j
integer(HSIZE_T) :: dims(1)
character(MAX_WORD_LEN) :: temp_str
@ -270,23 +264,11 @@ contains
type(VectorInt) :: MTs
type(VectorInt) :: temps_to_read
interface
function nuclide_from_hdf5_c(group, temperature, n) result(ptr) bind(C)
import HID_T, C_DOUBLE, C_INT, C_PTR
integer(HID_T), value :: group
type(C_PTR), value :: temperature
integer(C_INT), value :: n
type(C_PTR) :: ptr
end function
end interface
! Get array passed
call c_f_pointer(temps, temperature, [n])
! Read data on C++ side
if (temperature % size() > 0) then
this % ptr = nuclide_from_hdf5_c(group_id, C_LOC(temperature % data(1)), &
temperature % size())
else
this % ptr = nuclide_from_hdf5_c(group_id, C_NULL_PTR, 0)
end if
associate (this => nuclides(i_nuclide))
this % ptr = ptr
! Get name of nuclide from group
this % name = get_name(group_id)
@ -311,35 +293,35 @@ contains
call sort(temps_available)
! If only one temperature is available, revert to nearest temperature
if (size(temps_available) == 1 .and. method == TEMPERATURE_INTERPOLATION) then
if (size(temps_available) == 1 .and. temperature_method == TEMPERATURE_INTERPOLATION) then
if (master) then
call warning("Cross sections for " // trim(this % name) // " are only &
&available at one temperature. Reverting to nearest temperature &
&method.")
end if
method = TEMPERATURE_NEAREST
temperature_method = TEMPERATURE_NEAREST
end if
! Determine actual temperatures to read -- start by checking whether a
! temperature range was given, in which case all temperatures in the range
! are loaded irrespective of what temperatures actually appear in the model
if (minmax(2) > ZERO) then
if (temperature_range(2) > ZERO) then
do i = 1, size(temps_available)
temp_actual = temps_available(i)
if (minmax(1) <= temp_actual .and. temp_actual <= minmax(2)) then
if (temperature_range(1) <= temp_actual .and. temp_actual <= temperature_range(2)) then
call temps_to_read % push_back(nint(temp_actual))
end if
end do
end if
select case (method)
select case (temperature_method)
case (TEMPERATURE_NEAREST)
! Find nearest temperatures
do i = 1, temperature % size()
temp_desired = temperature % data(i)
do i = 1, n
temp_desired = temperature(i)
i_closest = minloc(abs(temps_available - temp_desired), dim=1)
temp_actual = temps_available(i_closest)
if (abs(temp_actual - temp_desired) < tolerance) then
if (abs(temp_actual - temp_desired) < temperature_tolerance) then
if (find(temps_to_read, nint(temp_actual)) == -1) then
call temps_to_read % push_back(nint(temp_actual))
@ -362,8 +344,8 @@ contains
case (TEMPERATURE_INTERPOLATION)
! If temperature interpolation or multipole is selected, get a list of
! bounding temperatures for each actual temperature present in the model
TEMP_LOOP: do i = 1, temperature % size()
temp_desired = temperature % data(i)
TEMP_LOOP: do i = 1, n
temp_desired = temperature(i)
do j = 1, size(temps_available) - 1
if (temps_available(j) <= temp_desired .and. &
@ -441,6 +423,7 @@ contains
! Finalize with the nuclide index
this % i_nuclide = i_nuclide
end associate
end subroutine nuclide_from_hdf5
@ -640,31 +623,6 @@ contains
call nuclide_calculate_elastic_xs_c(this % ptr)
end subroutine nuclide_calculate_elastic_xs
!===============================================================================
! CHECK_DATA_VERSION checks for the right version of nuclear data within HDF5
! files
!===============================================================================
subroutine check_data_version(file_id)
integer(HID_T), intent(in) :: file_id
integer, allocatable :: version(:)
if (attribute_exists(file_id, 'version')) then
call read_attribute(version, file_id, 'version')
if (version(1) /= HDF5_VERSION(1)) then
call fatal_error("HDF5 data format uses version " // trim(to_str(&
version(1))) // "." // trim(to_str(version(2))) // " whereas &
&your installation of OpenMC expects version " // trim(to_str(&
HDF5_VERSION(1))) // ".x data.")
end if
else
call fatal_error("HDF5 data does not indicate a version. Your &
&installation of OpenMC expects version " // trim(to_str(&
HDF5_VERSION(1))) // ".x data.")
end if
end subroutine check_data_version
!===============================================================================
! FREE_MEMORY_NUCLIDE deallocates global arrays defined in this module
!===============================================================================
@ -686,7 +644,6 @@ contains
end if
n_nuclides = 0
call nuclide_dict % clear()
call library_clear()
end subroutine free_memory_nuclide
@ -706,11 +663,10 @@ contains
! Copy array of C_CHARs to normal Fortran string
name_ = to_f_string(name)
err = 0
if (allocated(nuclides)) then
if (nuclide_dict % has(to_lower(name_))) then
index = nuclide_dict % get(to_lower(name_))
err = 0
else
index = nuclide_map_get(name)
if (index == -1) then
err = E_DATA
call set_errmsg("No nuclide named '" // trim(name_) // &
"' has been loaded.")
@ -722,63 +678,6 @@ contains
end function openmc_get_nuclide_index
function openmc_load_nuclide(name) result(err) bind(C)
! Load a nuclide from the cross section library
character(kind=C_CHAR), intent(in) :: name(*)
integer(C_INT) :: err
integer :: n
integer(HID_T) :: file_id
integer(HID_T) :: group_id
character(:), allocatable :: name_
character(MAX_FILE_LEN) :: filename
real(8) :: minmax(2) = [ZERO, INFINITY]
type(VectorReal) :: temperature
type(Nuclide), allocatable :: new_nuclides(:)
! Copy array of C_CHARs to normal Fortran string
name_ = to_f_string(name)
err = 0
if (.not. nuclide_dict % has(to_lower(name_))) then
if (library_present(LIBRARY_NEUTRON, to_lower(name_))) then
! allocate extra space in nuclides array
n = n_nuclides
allocate(new_nuclides(n + 1))
new_nuclides(1:n) = nuclides(:)
call move_alloc(FROM=new_nuclides, TO=nuclides)
n = n + 1
filename = library_path(LIBRARY_NEUTRON, to_lower(name_))
! Open file and make sure version is sufficient
file_id = file_open(filename, 'r')
call check_data_version(file_id)
! Read nuclide data from HDF5
group_id = open_group(file_id, name_)
call nuclides(n) % from_hdf5(group_id, temperature, &
temperature_method, temperature_tolerance, minmax, &
master, n)
call close_group(group_id)
call file_close(file_id)
! Add entry to nuclide dictionary
call nuclide_dict % set(to_lower(name_), n)
n_nuclides = n
! Initialize nuclide grid
call nuclides(n) % init_grid()
else
err = E_DATA
call set_errmsg("Nuclide '" // trim(name_) // "' is not present &
&in library.")
end if
end if
end function openmc_load_nuclide
function openmc_nuclide_name(index, name) result(err) bind(C)
! Return the name of a nuclide with a given index
integer(C_INT), value, intent(in) :: index
@ -803,4 +702,16 @@ contains
end if
end function openmc_nuclide_name
subroutine extend_nuclides() bind(C)
integer :: n
type(Nuclide), allocatable :: new_nuclides(:)
! allocate extra space in nuclides array
n = n_nuclides
allocate(new_nuclides(n + 1))
new_nuclides(1:n) = nuclides(:)
call move_alloc(FROM=new_nuclides, TO=nuclides)
n = n + 1
end subroutine
end module nuclide_header

View file

@ -320,7 +320,7 @@ contains
! display time elapsed for various sections
write(ou,100) "Total time for initialization", time_initialize_elapsed()
write(ou,100) " Reading cross sections", time_read_xs % elapsed
write(ou,100) " Reading cross sections", time_read_xs_elapsed()
write(ou,100) "Total time in simulation", time_inactive_elapsed() + &
time_active_elapsed()
write(ou,100) " Time in transport only", time_transport_elapsed()

View file

@ -203,11 +203,6 @@ Particle::write_restart() const
void reset_coord(LocalCoord* c) { c->reset(); }
void particle_clear(Particle* p) { p->clear(); }
void particle_create_secondary(Particle* p, const double* uvw, double E,
int type, bool run_CE)
{
p->create_secondary(uvw, E, type, run_CE);
}
void particle_initialize(Particle* p) { p->initialize(); }
void particle_from_source(Particle* p, const Bank* src)
{

View file

@ -115,15 +115,6 @@ module particle_header
type(Particle), intent(inout) :: p
end subroutine particle_clear
subroutine particle_create_secondary(p, uvw, E, type, run_CE) bind(C)
import Particle, C_DOUBLE, C_INT, C_BOOL
type(Particle), intent(inout) :: p
real(C_DOUBLE), intent(in) :: uvw(3)
real(C_DOUBLE), value :: E
integer(C_INT), value :: type
logical(C_BOOL), value :: run_CE
end subroutine particle_create_secondary
subroutine particle_initialize(p) bind(C)
import Particle
type(Particle), intent(inout) :: p

View file

@ -28,6 +28,7 @@ namespace data {
xt::xtensor<double, 1> compton_profile_pz;
std::vector<PhotonInteraction> elements;
std::unordered_map<std::string, int> element_map;
} // namespace data
@ -222,7 +223,7 @@ PhotonInteraction::PhotonInteraction(hid_t group, int i_element)
}
// Truncate the bremsstrahlung data at the cutoff energy
int photon = static_cast<int>(ParticleType::photon) - 1;
int photon = static_cast<int>(ParticleType::photon);
const auto& E {electron_energy};
double cutoff = settings::energy_cutoff[photon];
if (cutoff > E(0)) {
@ -781,8 +782,7 @@ extern "C" void photon_from_hdf5(hid_t group)
// the previous
const auto& element {data::elements.back()};
if (element.energy_.size() >= 1) {
// TODO: off-by-one
int photon = static_cast<int>(ParticleType::photon) - 1;
int photon = static_cast<int>(ParticleType::photon);
int n = element.energy_.size();
data::energy_min[photon] = std::max(data::energy_min[photon],
std::exp(element.energy_(1)));
@ -791,6 +791,8 @@ extern "C" void photon_from_hdf5(hid_t group)
}
}
extern "C" int elements_size() { return data::element_map.size(); }
extern "C" void photon_calculate_xs(int i_element, double E)
{
data::elements[i_element - 1].calculate_xs(E);

View file

@ -3,12 +3,9 @@ module photon_header
use, intrinsic :: ISO_C_BINDING
use constants
use dict_header, only: DictCharInt
integer :: n_elements ! Number of photon cross section tables
type(DictCharInt) :: element_dict
!===============================================================================
! ELEMENTMICROXS contains cached microscopic photon cross sections for a
! particular element at the current energy
@ -42,7 +39,6 @@ contains
! Deallocate photon cross section data
n_elements = 0
call element_dict % clear()
end subroutine free_memory_photon
function micro_photon_xs_ptr() result(ptr) bind(C)

View file

@ -52,7 +52,7 @@ void collision(Particle* p)
}
// Kill particle if energy falls below cutoff
if (p->E < settings::energy_cutoff[p->type - 1]) {
if (p->E < settings::energy_cutoff[p->type]) {
p->alive = false;
p->wgt = 0.0;
p->last_wgt = 0.0;
@ -74,9 +74,8 @@ void collision(Particle* p)
void sample_neutron_reaction(Particle* p)
{
int i_nuclide;
int i_nuc_mat;
sample_nuclide(p, SCORE_TOTAL, &i_nuclide, &i_nuc_mat);
// Sample a nuclide within the material
int i_nuclide = sample_nuclide(p);
// Save which nuclide particle had collision with
// TODO: off-by-one
@ -120,7 +119,7 @@ void sample_neutron_reaction(Particle* p)
// Sample a scattering reaction and determine the secondary energy of the
// exiting neutron
scatter(p, i_nuclide, i_nuc_mat);
scatter(p, i_nuclide);
// Advance URR seed stream 'N' times after energy changes
if (p->E != p->last_E) {
@ -220,7 +219,7 @@ void sample_photon_reaction(Particle* p)
// Kill photon if below energy cutoff -- an extra check is made here because
// photons with energy below the cutoff may have been produced by neutrons
// reactions or atomic relaxation
int photon = static_cast<int>(ParticleType::photon) - 1;
int photon = static_cast<int>(ParticleType::photon);
if (p->E < settings::energy_cutoff[photon]) {
p->E = 0.0;
p->alive = false;
@ -420,63 +419,30 @@ void sample_positron_reaction(Particle* p)
p->alive = false;
}
void sample_nuclide(const Particle* p, int mt, int* i_nuclide, int* i_nuc_mat)
int sample_nuclide(const Particle* p)
{
// Sample cumulative distribution function
double cutoff;
switch (mt) {
case SCORE_TOTAL:
cutoff = prn() * simulation::material_xs.total;
break;
case SCORE_SCATTER:
cutoff = prn() * (simulation::material_xs.total -
simulation::material_xs.absorption);
break;
case SCORE_FISSION:
cutoff = prn() * simulation::material_xs.fission;
break;
}
double cutoff = prn() * simulation::material_xs.total;
// Get pointers to nuclide/density arrays
int* nuclides;
double* densities;
int n;
openmc_material_get_densities(p->material, &nuclides, &densities, &n);
// TODO: off-by-one
const auto& mat {model::materials[p->material - 1]};
int n = mat->nuclide_.size();
*i_nuc_mat = 0;
double prob = 0.0;
while (prob < cutoff) {
// Check to make sure that a nuclide was sampled
if (*i_nuc_mat > n) {
p->write_restart();
fatal_error("Did not sample any nuclide during collision.");
}
// Find atom density
// TODO: off-by-one
*i_nuclide = nuclides[*i_nuc_mat] - 1;
double atom_density = densities[*i_nuc_mat];
// Determine microscopic cross section
double sigma;
switch (mt) {
case SCORE_TOTAL:
sigma = atom_density * simulation::micro_xs[*i_nuclide].total;
break;
case SCORE_SCATTER:
sigma = atom_density * (simulation::micro_xs[*i_nuclide].total -
simulation::micro_xs[*i_nuclide].absorption);
break;
case SCORE_FISSION:
sigma = atom_density * simulation::micro_xs[*i_nuclide].fission;
break;
}
for (int i = 0; i < n; ++i) {
// Get atom density
int i_nuclide = mat->nuclide_[i];
double atom_density = mat->atom_density_[i];
// Increment probability to compare to cutoff
prob += sigma;
++(*i_nuc_mat);
prob += atom_density * simulation::micro_xs[i_nuclide].total;
if (prob >= cutoff) return i_nuclide;
}
// If we reach here, no nuclide was sampled
p->write_restart();
throw std::runtime_error{"Did not sample any nuclide during collision."};
}
int sample_element(Particle* p)
@ -485,11 +451,8 @@ int sample_element(Particle* p)
double cutoff = prn() * simulation::material_xs.total;
// Get pointers to elements, densities
int* nuclide;
double* density;
int n;
openmc_material_get_densities(p->material, &nuclide, &density, &n);
int* element = material_element(p->material);
const auto& mat {model::materials[p->material - 1]};
int n = mat->nuclide_.size();
int i = 0;
double prob = 0.0;
@ -502,9 +465,8 @@ int sample_element(Particle* p)
}
// Find atom density
// TODO: off-by-one
i_element = element[i] - 1;
double atom_density = density[i];
i_element = mat->element_[i];
double atom_density = mat->atom_density_[i];
// Determine microscopic cross section
double sigma = atom_density * simulation::micro_photon_xs[i_element].total;
@ -627,7 +589,7 @@ void absorption(Particle* p, int i_nuclide)
}
}
void scatter(Particle* p, int i_nuclide, int i_nuc_mat)
void scatter(Particle* p, int i_nuclide)
{
// copy incoming direction
Direction u_old {p->coord[0].uvw};
@ -712,21 +674,26 @@ void scatter(Particle* p, int i_nuclide, int i_nuc_mat)
p->event = EVENT_SCATTER;
// Sample new outgoing angle for isotropic-in-lab scattering
if (material_isotropic(p->material, i_nuc_mat)) {
// Sample isotropic-in-lab outgoing direction
double mu = 2.0*prn() - 1.0;
double phi = 2.0*PI*prn();
Direction u_new;
u_new.x = mu;
u_new.y = std::sqrt(1.0 - mu*mu)*std::cos(phi);
u_new.z = std::sqrt(1.0 - mu*mu)*std::sin(phi);
// TODO: off-by-one
const auto& mat {model::materials[p->material - 1]};
if (!mat->p0_.empty()) {
int i_nuc_mat = mat->mat_nuclide_index_[i_nuclide];
if (mat->p0_[i_nuc_mat]) {
// Sample isotropic-in-lab outgoing direction
double mu = 2.0*prn() - 1.0;
double phi = 2.0*PI*prn();
Direction u_new;
u_new.x = mu;
u_new.y = std::sqrt(1.0 - mu*mu)*std::cos(phi);
u_new.z = std::sqrt(1.0 - mu*mu)*std::sin(phi);
p->mu = u_old.dot(u_new);
p->mu = u_old.dot(u_new);
// Change direction of particle
p->coord[0].uvw[0] = u_new.x;
p->coord[0].uvw[1] = u_new.y;
p->coord[0].uvw[2] = u_new.z;
// Change direction of particle
p->coord[0].uvw[0] = u_new.x;
p->coord[0].uvw[1] = u_new.y;
p->coord[0].uvw[2] = u_new.z;
}
}
}
@ -1067,8 +1034,7 @@ void sample_fission_neutron(int i_nuclide, const Reaction* rx, double E_in, Bank
rx->products_[group].sample(E_in, site->E, mu);
// resample if energy is greater than maximum neutron energy
// TODO: off-by-one
constexpr int neutron = static_cast<int>(ParticleType::neutron) - 1;
constexpr int neutron = static_cast<int>(ParticleType::neutron);
if (site->E < data::energy_max[neutron]) break;
// check for large number of resamples
@ -1093,8 +1059,7 @@ void sample_fission_neutron(int i_nuclide, const Reaction* rx, double E_in, Bank
rx->products_[0].sample(E_in, site->E, mu);
// resample if energy is greater than maximum neutron energy
// TODO: off-by-one
constexpr int neutron = static_cast<int>(ParticleType::neutron) - 1;
constexpr int neutron = static_cast<int>(ParticleType::neutron);
if (site->E < data::energy_max[neutron]) break;
// check for large number of resamples

View file

@ -46,7 +46,7 @@ sample_reaction(Particle* p)
// change when sampling fission sites. The following block handles all
// absorption (including fission)
if (model::materials[p->material - 1]->fissionable) {
if (model::materials[p->material - 1]->fissionable_) {
if (settings::run_mode == RUN_MODE_EIGENVALUE) {
create_fission_sites(
p, simulation::fission_bank.data(), &simulation::n_bank,

View file

@ -302,16 +302,7 @@ int reaction_product_emission_mode(Reaction* rx, int product)
int reaction_product_particle(Reaction* rx, int product)
{
switch (rx->products_[product - 1].particle_) {
case ParticleType::neutron:
return 1;
case ParticleType::photon:
return 2;
case ParticleType::electron:
return 3;
case ParticleType::positron:
return 4;
}
return static_cast<int>(rx->products_[product - 1].particle_);
}
void reaction_product_sample(Reaction* rx, int product, double E_in, double* E_out, double* mu)

View file

@ -1,54 +0,0 @@
module sab_header
use, intrinsic :: ISO_C_BINDING
use dict_header, only: DictCharInt
use hdf5_interface
implicit none
private
public :: free_memory_sab, sab_from_hdf5, sab_has_nuclide, sab_threshold
! S(a,b) tables
integer, public :: n_sab_tables
type(DictCharInt), public :: sab_dict
interface
subroutine sab_from_hdf5(group_id, temperature, n) bind(C)
import HID_T, C_DOUBLE, C_INT
integer(HID_T), value :: group_id
real(C_DOUBLE), intent(in) :: temperature
integer(C_INT), value :: n
end subroutine
subroutine sab_clear() bind(C)
end subroutine
function sab_has_nuclide(i_sab, name) result(val) bind(C)
import C_INT, C_CHAR, C_BOOL
integer(C_INT), value :: i_sab
character(kind=C_CHAR), intent(in) :: name(*)
logical(C_BOOL) :: val
end function
function sab_threshold(i_sab) result(threshold) bind(C)
import C_INT, C_DOUBLE
integer(C_INT), value :: i_sab
real(C_DOUBLE) :: threshold
end function
end interface
contains
!===============================================================================
! FREE_MEMORY_SAB deallocates global arrays defined in this module
!===============================================================================
subroutine free_memory_sab()
n_sab_tables = 0
call sab_clear()
call sab_dict % clear()
end subroutine free_memory_sab
end module sab_header

View file

@ -10,6 +10,7 @@
#include "openmc/error.h"
#include "openmc/math_functions.h"
#include "openmc/random_lcg.h"
#include "openmc/settings.h"
namespace openmc {
@ -850,10 +851,10 @@ ScattDataTabular::combine(const std::vector<ScattData*>& those_scatts,
//==============================================================================
void
convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab,
int n_mu)
convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab)
{
// See if the user wants us to figure out how many points to use
int n_mu = settings::legendre_to_tabular_points;
if (n_mu == C_NONE) {
// then we will use 2 pts if its P0, or the default if a higher order
// TODO use an error minimization algorithm that also picks n_mu

View file

@ -1,117 +0,0 @@
module set_header
!===============================================================================
! SET_HEADER module
!
! This module provides an implementation of sets based on the list
! implementation in list_header. The underlying datatype is a list, so adding an
! element just checks if the element is already in the list, and if not it's
! added. This results in much worse performance than an implementation based on
! hash tables or binary trees, but for our purposes, we don't expect to have
! gigantic sets where performance is critical.
!===============================================================================
use constants, only: MAX_WORD_LEN
use list_header
implicit none
!===============================================================================
! SET contains a list of elements and methods to add, remove, and perform other
! basic tasks.
!===============================================================================
type :: SetChar
private
type(ListChar) :: elements
contains
procedure :: add => set_add_char
procedure :: clear => set_clear_char
procedure :: contains => set_contains_char
procedure :: get_item => set_get_item_char
procedure :: remove => set_remove_char
procedure :: size => set_size_char
end type SetChar
contains
!===============================================================================
! SET_ADD adds an item to a set if it is not already present in the set
!===============================================================================
subroutine set_add_char(this, data)
class(SetChar) :: this
character(*) :: data
if (.not. this % elements % contains(data)) then
call this % elements % append(data)
end if
end subroutine set_add_char
!===============================================================================
! SET_CLEAR removes all items in a set
!===============================================================================
subroutine set_clear_char(this)
class(SetChar) :: this
call this % elements % clear()
end subroutine set_clear_char
!===============================================================================
! SET_CONTAINS determines if a specified item is in a set
!===============================================================================
function set_contains_char(this, data) result(in_set)
class(SetChar) :: this
character(*) :: data
logical :: in_set
in_set = this % elements % contains(data)
end function set_contains_char
!===============================================================================
! SET_GET_ITEM returns the i-th item in the set
!===============================================================================
function set_get_item_char(this, i_list) result(data)
class(SetChar) :: this
integer :: i_list
character(MAX_WORD_LEN) :: data
data = this % elements % get_item(i_list)
end function set_get_item_char
!===============================================================================
! SET_REMOVE removes the specified item from the set. If it is not in the set,
! no action is taken.
!===============================================================================
subroutine set_remove_char(this, data)
class(SetChar) :: this
character(*) :: data
call this % elements % remove(data)
end subroutine set_remove_char
!===============================================================================
! SET_SIZE returns the number of elements in the set
!===============================================================================
function set_size_char(this) result(size)
class(SetChar) :: this
integer :: size
size = this % elements % size()
end function set_size_char
end module set_header

View file

@ -399,13 +399,6 @@ void read_settings_xml()
#endif
}
#ifdef _OPENMP
if (dagmc && omp_get_max_threads() > 1) {
warning("Forcing number of threads to 1 for DAGMC simulation.");
omp_set_num_threads(1);
}
#endif
// ==========================================================================
// EXTERNAL SOURCE

View file

@ -2,7 +2,6 @@ module simulation
use, intrinsic :: ISO_C_BINDING
use material_header, only: n_materials, materials
use nuclide_header, only: micro_xs, n_nuclides
use photon_header, only: micro_photon_xs, n_elements
use tally_filter_header, only: filter_matches, n_filters, filter_match_pointer
@ -20,11 +19,6 @@ contains
integer :: i
! Set up material nuclide index mapping
do i = 1, n_materials
call materials(i) % init_nuclide_index()
end do
!$omp parallel
! Allocate array for microscopic cross section cache
allocate(micro_xs(n_nuclides))
@ -46,12 +40,7 @@ contains
subroutine simulation_finalize_f() bind(C)
integer :: i ! loop index
! Free up simulation-specific memory
do i = 1, n_materials
deallocate(materials(i) % mat_nuclide_index)
end do
!$omp parallel
deallocate(micro_xs, micro_photon_xs, filter_matches)
!$omp end parallel

View file

@ -5,6 +5,7 @@
#include "openmc/container_util.h"
#include "openmc/eigenvalue.h"
#include "openmc/error.h"
#include "openmc/material.h"
#include "openmc/message_passing.h"
#include "openmc/nuclide.h"
#include "openmc/output.h"
@ -91,6 +92,11 @@ int openmc_simulation_init()
// Allocate tally results arrays if they're not allocated yet
allocate_tally_results();
// Set up material nuclide index mapping
for (auto& mat : model::materials) {
mat->init_nuclide_index();
}
// Call Fortran initialization
simulation_init_f();
set_micro_xs();
@ -144,6 +150,9 @@ int openmc_simulation_finalize()
}
// Deallocate Fortran variables, set tallies to inactive
for (auto& mat : model::materials) {
mat->mat_nuclide_index_.clear();
}
simulation_finalize_f();
// Increment total number of generations

View file

@ -15,8 +15,6 @@ module simulation_header
! Number of lost particles
integer(C_INT), bind(C) :: n_lost_particles
real(C_DOUBLE), bind(C) :: log_spacing ! spacing on logarithmic grid
! ============================================================================
! SIMULATION VARIABLES

View file

@ -174,14 +174,11 @@ Bank SourceDistribution::sample() const
// Determine material
auto c = model::cells[cell_index - 1];
int32_t mat_index = c->material_[instance];
auto m = model::materials[mat_index];
if (mat_index == MATERIAL_VOID) {
found = false;
} else {
bool fissionable;
openmc_material_get_fissionable(mat_index + 1, &fissionable);
if (!fissionable) found = false;
if (!model::materials[mat_index]->fissionable_) found = false;
}
}
}
@ -212,10 +209,10 @@ Bank SourceDistribution::sample() const
auto energy_ptr = dynamic_cast<Discrete*>(energy_.get());
if (energy_ptr) {
auto energies = xt::adapt(energy_ptr->x());
if (xt::any(energies > data::energy_max[p-1])) {
if (xt::any(energies > data::energy_max[p])) {
fatal_error("Source energy above range of energies of at least "
"one cross section table");
} else if (xt::any(energies < data::energy_min[p-1])) {
} else if (xt::any(energies < data::energy_min[p])) {
fatal_error("Source energy below range of energies of at least "
"one cross section table");
}
@ -226,7 +223,7 @@ Bank SourceDistribution::sample() const
site.E = energy_->sample();
// Resample if energy falls outside minimum or maximum particle energy
if (site.E < data::energy_max[p-1] && site.E > data::energy_min[p-1]) break;
if (site.E < data::energy_max[p] && site.E > data::energy_min[p]) break;
}
// Set delayed group

View file

@ -375,7 +375,7 @@ contains
call write_dataset(runtime_group, "total initialization", &
time_initialize_elapsed())
call write_dataset(runtime_group, "reading cross sections", &
time_read_xs % get_value())
time_read_xs_elapsed())
call write_dataset(runtime_group, "simulation", &
time_inactive_elapsed() + time_active_elapsed())
call write_dataset(runtime_group, "transport", &

View file

@ -1,288 +0,0 @@
module summary
use constants
use endf, only: reaction_name
use error, only: write_message
use geometry_header
use hdf5_interface
use material_header, only: Material, n_materials, openmc_material_get_volume
use message_passing
use mgxs_interface
use nuclide_header
use output, only: time_stamp
use settings, only: run_CE
use surface_header
use string, only: to_str
use tally_header, only: TallyObject
implicit none
private
public :: write_summary
contains
!===============================================================================
! WRITE_SUMMARY
!===============================================================================
subroutine write_summary()
interface
subroutine write_geometry(file_id) bind(C)
import HID_T
integer(HID_T), intent(in), value :: file_id
end subroutine write_geometry
end interface
integer(HID_T) :: file_id
! Display output message
call write_message("Writing summary.h5 file...", 5)
! Create a new file using default properties.
file_id = file_open("summary.h5", 'w')
call write_header(file_id)
call write_nuclides(file_id)
call write_geometry(file_id)
call write_materials(file_id)
! Terminate access to the file.
call file_close(file_id)
end subroutine write_summary
!===============================================================================
! WRITE_HEADER
!===============================================================================
subroutine write_header(file_id)
integer(HID_T), intent(in) :: file_id
! Write filetype and version info
call write_attribute(file_id, "filetype", "summary")
call write_attribute(file_id, "version", VERSION_SUMMARY)
call write_attribute(file_id, "openmc_version", VERSION)
#ifdef GIT_SHA1
call write_attribute(file_id, "git_sha1", GIT_SHA1)
#endif
! Write current date and time
call write_attribute(file_id, "date_and_time", time_stamp())
end subroutine write_header
!===============================================================================
! WRITE_NUCLIDES
!===============================================================================
subroutine write_nuclides(file_id)
integer(HID_T), intent(in) :: file_id
integer(HID_T) :: nuclide_group
integer(HID_T) :: macro_group
integer :: i
character(kind=C_CHAR, len=20), allocatable :: nuc_names(:)
character(kind=C_CHAR, len=20), allocatable :: macro_names(:)
real(C_DOUBLE), allocatable :: awrs(:)
integer :: num_nuclides
integer :: num_macros
integer :: j
integer :: k
! Find how many of these nuclides are macroscopic objects
if (run_CE) then
! Then none are macroscopic
num_nuclides = n_nuclides
num_macros = 0
else
num_nuclides = 0
num_macros = 0
do i = 1, n_nuclides
if (get_awr_c(i) /= MACROSCOPIC_AWR) then
num_nuclides = num_nuclides + 1
else
num_macros = num_macros + 1
end if
end do
end if
! Build array of nuclide names and awrs while only sorting nuclides from
! macroscopics
if (num_nuclides > 0) then
allocate(nuc_names(num_nuclides))
allocate(awrs(num_nuclides))
end if
if (num_macros > 0) then
allocate(macro_names(num_macros))
end if
j = 1
k = 1
do i = 1, n_nuclides
if (run_CE) then
nuc_names(i) = nuclides(i) % name
awrs(i) = nuclides(i) % awr
else
if (get_awr_c(i) /= MACROSCOPIC_AWR) then
call get_name_c(i, len(nuc_names(j)), nuc_names(j))
nuc_names(j) = trim(nuc_names(j))
awrs(j) = get_awr_c(i)
j = j + 1
else
call get_name_c(i, len(macro_names(k)), macro_names(k))
macro_names(k) = trim(macro_names(k))
k = k + 1
end if
end if
end do
nuclide_group = create_group(file_id, "nuclides")
call write_attribute(nuclide_group, "n_nuclides", num_nuclides)
macro_group = create_group(file_id, "macroscopics")
call write_attribute(macro_group, "n_macroscopics", num_macros)
! Write nuclide names and awrs
if (num_nuclides > 0) then
! Write useful data from nuclide objects
call write_dataset(nuclide_group, "names", nuc_names)
call write_dataset(nuclide_group, "awrs", awrs)
end if
if (num_macros > 0) then
! Write useful data from macroscopic objects
call write_dataset(macro_group, "names", macro_names)
end if
call close_group(nuclide_group)
call close_group(macro_group)
if (allocated(nuc_names)) deallocate(nuc_names, awrs)
if (allocated(macro_names)) deallocate(macro_names)
end subroutine write_nuclides
!===============================================================================
! WRITE_MATERIALS
!===============================================================================
subroutine write_materials(file_id)
integer(HID_T), intent(in) :: file_id
integer :: i
integer :: j
integer :: k
integer :: n
integer :: err
character(kind=C_CHAR, len=20), allocatable :: nuc_names(:)
character(kind=C_CHAR, len=20), allocatable :: macro_names(:)
real(8) :: volume
real(8), allocatable :: nuc_densities(:)
integer :: num_nuclides
integer :: num_macros
integer(HID_T) :: materials_group
integer(HID_T) :: material_group
type(Material), pointer :: m
materials_group = create_group(file_id, "materials")
! write number of materials
call write_dataset(file_id, "n_materials", n_materials)
! Write information on each material
do i = 1, n_materials
m => materials(i)
material_group = create_group(materials_group, "material " // &
trim(to_str(m%id())))
if (m % depletable) then
call write_attribute(material_group, "depletable", 1)
else
call write_attribute(material_group, "depletable", 0)
end if
err = openmc_material_get_volume(i, volume)
if (err == 0 .and. volume > ZERO) then
call write_attribute(material_group, "volume", volume)
end if
! Write name for this material
call write_dataset(material_group, "name", m % name)
! Write atom density with units
call write_dataset(material_group, "atom_density", m % density)
if (run_CE) then
num_nuclides = m % n_nuclides
num_macros = 0
else
! Find the number of macroscopic and nuclide data in this material
num_nuclides = 0
num_macros = 0
do j = 1, m % n_nuclides
if (get_awr_c(m % nuclide(j)) /= MACROSCOPIC_AWR) then
num_nuclides = num_nuclides + 1
else
num_macros = num_macros + 1
end if
end do
end if
! Copy ZAID or macro name for each nuclide to temporary array
if (num_nuclides > 0) then
allocate(nuc_names(num_nuclides))
allocate(nuc_densities(num_nuclides))
end if
if (run_CE) then
do j = 1, m % n_nuclides
nuc_names(j) = nuclides(m%nuclide(j))%name
nuc_densities(j) = m % atom_density(j)
end do
else
if (num_macros > 0) then
allocate(macro_names(num_macros))
end if
k = 1
n = 1
do j = 1, m % n_nuclides
if (get_awr_c(m % nuclide(j)) /= MACROSCOPIC_AWR) then
call get_name_c(m % nuclide(j), len(nuc_names(k)), nuc_names(k))
nuc_names(k) = trim(nuc_names(k))
nuc_densities(k) = m % atom_density(j)
k = k + 1
else
call get_name_c(m % nuclide(j), len(macro_names(n)), macro_names(n))
macro_names(n) = trim(macro_names(n))
n = n + 1
end if
end do
end if
! Write temporary array to 'nuclides'
if (num_nuclides > 0) then
call write_dataset(material_group, "nuclides", nuc_names)
! Deallocate temporary array
deallocate(nuc_names)
! Write nuclide atom densities
call write_dataset(material_group, "nuclide_densities", nuc_densities)
deallocate(nuc_densities)
end if
! Write temporary array to 'macroscopics'
if (num_macros > 0) then
call write_dataset(material_group, "macroscopics", macro_names)
! Deallocate temporary array
deallocate(macro_names)
end if
if (m%n_sab > 0) then
call write_dataset(material_group, "sab_names", m%sab_names)
end if
call close_group(material_group)
end do
call close_group(materials_group)
end subroutine write_materials
end module summary

View file

@ -1,15 +1,93 @@
#include "openmc/summary.h"
#include "openmc/cell.h"
#include "openmc/hdf5_interface.h"
#include "openmc/lattice.h"
#include "openmc/material.h"
#include "openmc/mgxs_interface.h"
#include "openmc/nuclide.h"
#include "openmc/output.h"
#include "openmc/surface.h"
#include "openmc/settings.h"
namespace openmc {
extern "C" void
write_geometry(hid_t file_id) {
void write_summary()
{
// Display output message
write_message("Writing summary.h5 file...", 5);
auto geom_group = create_group(file_id, "geometry");
// Create a new file using default properties.
hid_t file = file_open("summary.h5", 'w');
write_header(file);
write_nuclides(file);
write_geometry(file);
write_materials(file);
// Terminate access to the file.
file_close(file);
}
void write_header(hid_t file)
{
// Write filetype and version info
write_attribute(file, "filetype", "summary");
write_attribute(file, "version", VERSION_SUMMARY);
write_attribute(file, "openmc_version", VERSION);
#ifdef GIT_SHA1
write_attribute(file, "git_sha1", GIT_SHA1);
#endif
// Write current date and time
write_attribute(file, "date_and_time", time_stamp());
}
void write_nuclides(hid_t file)
{
// Build vectors of nuclide names and awrs while only sorting nuclides from
// macroscopics
std::vector<std::string> nuc_names;
std::vector<std::string> macro_names;
std::vector<double> awrs;
for (int i = 0; i < data::nuclides.size(); ++i) {
if (settings::run_CE) {
const auto& nuc {data::nuclides[i]};
nuc_names.push_back(nuc->name_);
awrs.push_back(nuc->awr_);
} else {
const auto& nuc {data::nuclides_MG[i]};
if (nuc.awr != MACROSCOPIC_AWR) {
nuc_names.push_back(nuc.name);
awrs.push_back(nuc.awr);
} else {
macro_names.push_back(nuc.name);
}
}
}
hid_t nuclide_group = create_group(file, "nuclides");
write_attribute(nuclide_group, "n_nuclides", nuc_names.size());
hid_t macro_group = create_group(file, "macroscopics");
write_attribute(macro_group, "n_macroscopics", macro_names.size());
// Write nuclide names and awrs
if (!nuc_names.empty()) {
// Write useful data from nuclide objects
write_dataset(nuclide_group, "names", nuc_names);
write_dataset(nuclide_group, "awrs", awrs);
}
if (!macro_names.empty()) {
// Write useful data from macroscopic objects
write_dataset(macro_group, "names", macro_names);
}
close_group(nuclide_group);
close_group(macro_group);
}
void write_geometry(hid_t file)
{
auto geom_group = create_group(file, "geometry");
#ifdef DAGMC
if (settings::dagmc) {
@ -42,4 +120,16 @@ write_geometry(hid_t file_id) {
close_group(geom_group);
}
void write_materials(hid_t file)
{
// write number of materials
write_dataset(file, "n_materials", model::materials.size());
hid_t materials_group = create_group(file, "materials");
for (const auto& mat : model::materials) {
mat->to_hdf5(materials_group);
}
close_group(materials_group);
}
} // namespace openmc

View file

@ -8,6 +8,7 @@ void
ParticleFilter::from_xml(pugi::xml_node node)
{
particles_ = get_node_array<int>(node, "bins");
for (auto& p : particles_) --p;
n_bins_ = particles_.size();
}
@ -34,8 +35,7 @@ ParticleFilter::to_statepoint(hid_t filter_group) const
std::string
ParticleFilter::text_label(int bin) const
{
//TODO: off-by-one
return "Particle " + std::to_string(particles_[bin-1]);
return "Particle " + std::to_string(particles_[bin]);
}
//==============================================================================
@ -43,6 +43,6 @@ ParticleFilter::text_label(int bin) const
//==============================================================================
extern "C" int particle_filter_particles(ParticleFilter* filt, int i)
{return filt->particles_[i-1];}
{return filt->particles_[i];}
} // namespace openmc

View file

@ -8,6 +8,7 @@ module tally
use dict_header, only: EMPTY
use error, only: fatal_error
use geometry_header
use material_header
use math, only: t_percentile
use message_passing
use mgxs_interface
@ -422,13 +423,13 @@ contains
! Loop over all nuclides in the current material
if (p % material /= MATERIAL_VOID) then
do l = 1, materials(p % material) % n_nuclides
do l = 1, material_nuclide_size(p % material)
! Get atom density
atom_density_ = materials(p % material) % atom_density(l)
atom_density_ = material_atom_density(p % material, l)
! Get index in nuclides array
i_nuc = materials(p % material) % nuclide(l)
i_nuc = material_nuclide(p % material, l)
! Accumulate the contribution from each nuclide
score = score + micro_xs(i_nuc) % fission * nuclides(i_nuc) % &
@ -581,13 +582,13 @@ contains
! Loop over all nuclides in the current material
if (p % material /= MATERIAL_VOID) then
do l = 1, materials(p % material) % n_nuclides
do l = 1, material_nuclide_size(p % material)
! Get atom density
atom_density_ = materials(p % material) % atom_density(l)
atom_density_ = material_atom_density(p % material, l)
! Get index in nuclides array
i_nuc = materials(p % material) % nuclide(l)
i_nuc = material_nuclide(p % material, l)
! Loop over all delayed group bins and tally to them
! individually
@ -615,13 +616,13 @@ contains
! Loop over all nuclides in the current material
if (p % material /= MATERIAL_VOID) then
do l = 1, materials(p % material) % n_nuclides
do l = 1, material_nuclide_size(p % material)
! Get atom density
atom_density_ = materials(p % material) % atom_density(l)
atom_density_ = material_atom_density(p % material, l)
! Get index in nuclides array
i_nuc = materials(p % material) % nuclide(l)
i_nuc = material_nuclide(p % material, l)
! Accumulate the contribution from each nuclide
score = score + micro_xs(i_nuc) % fission * nuclides(i_nuc) %&
@ -833,13 +834,13 @@ contains
! Loop over all nuclides in the current material
if (p % material /= MATERIAL_VOID) then
do l = 1, materials(p % material) % n_nuclides
do l = 1, material_nuclide_size(p % material)
! Get atom density
atom_density_ = materials(p % material) % atom_density(l)
atom_density_ = material_atom_density(p % material, l)
! Get index in nuclides array
i_nuc = materials(p % material) % nuclide(l)
i_nuc = material_nuclide(p % material, l)
if (nuclides(i_nuc) % fissionable) then
@ -878,13 +879,13 @@ contains
! Loop over all nuclides in the current material
if (p % material /= MATERIAL_VOID) then
do l = 1, materials(p % material) % n_nuclides
do l = 1, material_nuclide_size(p % material)
! Get atom density
atom_density_ = materials(p % material) % atom_density(l)
atom_density_ = material_atom_density(p % material, l)
! Get index in nuclides array
i_nuc = materials(p % material) % nuclide(l)
i_nuc = material_nuclide(p % material, l)
if (nuclides(i_nuc) % fissionable) then
@ -964,10 +965,10 @@ contains
if (p % material == MATERIAL_VOID) then
score = ZERO
else
do l = 1, materials(p % material) % n_nuclides
do l = 1, material_nuclide_size(p % material)
! Determine atom density and index of nuclide
atom_density_ = materials(p % material) % atom_density(l)
i_nuc = materials(p % material) % nuclide(l)
atom_density_ = material_atom_density(p % material, l)
i_nuc = material_nuclide(p % material, l)
! If nuclide is fissionable, accumulate kappa fission
associate(nuc => nuclides(i_nuc))
@ -1001,12 +1002,12 @@ contains
else
score = ZERO
if (p % material /= MATERIAL_VOID) then
do l = 1, materials(p % material) % n_nuclides
do l = 1, material_nuclide_size(p % material)
! Get atom density
atom_density_ = materials(p % material) % atom_density(l)
atom_density_ = material_atom_density(p % material, l)
! Get index in nuclides array
i_nuc = materials(p % material) % nuclide(l)
i_nuc = material_nuclide(p % material, l)
if (micro_xs(i_nuc) % elastic == CACHE_INVALID) then
call nuclides(i_nuc) % calculate_elastic_xs()
end if
@ -1075,9 +1076,9 @@ contains
end associate
else
if (p % material /= MATERIAL_VOID) then
do l = 1, materials(p % material) % n_nuclides
atom_density_ = materials(p % material) % atom_density(l)
i_nuc = materials(p % material) % nuclide(l)
do l = 1, material_nuclide_size(p % material)
atom_density_ = material_atom_density(p % material, l)
i_nuc = material_nuclide(p % material, l)
associate (nuc => nuclides(i_nuc))
if (score_bin == SCORE_FISS_Q_PROMPT) then
@ -1122,13 +1123,11 @@ contains
else
score = ZERO
if (p % material /= MATERIAL_VOID) then
associate (mat => materials(p % material))
do l = 1, materials(p % material) % n_nuclides
i_nuc = mat % nuclide(l)
atom_density_ = mat % atom_density(l)
do l = 1, material_nuclide_size(p % material)
i_nuc = material_nuclide(p % material, l)
atom_density_ = material_atom_density(p % material, l)
score = score + micro_xs(i_nuc) % reaction(m) * atom_density_ * flux
end do
end associate
end if
end if
end if
@ -1176,12 +1175,12 @@ contains
else
if (p % material /= MATERIAL_VOID) then
do l = 1, materials(p % material) % n_nuclides
do l = 1, material_nuclide_size(p % material)
! Get atom density
atom_density_ = materials(p % material) % atom_density(l)
atom_density_ = material_atom_density(p % material, l)
! Get index in nuclides array
i_nuc = materials(p % material) % nuclide(l)
i_nuc = material_nuclide(p % material, l)
m = nuclides(i_nuc) % reaction_index(score_bin)
if (m /= 0) then
@ -2045,23 +2044,18 @@ contains
integer :: i ! loop index for nuclides in material
integer :: i_nuclide ! index in nuclides array
real(8) :: atom_density ! atom density of single nuclide in atom/b-cm
type(Material), pointer :: mat
associate (t => tallies(i_tally) % obj)
! Get pointer to current material. We need this in order to determine what
! nuclides are in the material
mat => materials(p % material)
! ==========================================================================
! SCORE ALL INDIVIDUAL NUCLIDE REACTION RATES
NUCLIDE_LOOP: do i = 1, mat % n_nuclides
NUCLIDE_LOOP: do i = 1, material_nuclide_size(p % material)
! Determine index in nuclides array and atom density for i-th nuclide in
! current material
i_nuclide = mat % nuclide(i)
atom_density = mat % atom_density(i)
i_nuclide = material_nuclide(p % material, i)
atom_density = material_atom_density(p % material, i)
! Determine score for each bin
call score_general(p, i_tally, (i_nuclide-1)*t % n_score_bins(), filter_index, &
@ -2320,6 +2314,7 @@ contains
type(TallyDerivative), pointer :: deriv
integer :: l
integer :: i_nuc
logical :: scoring_diff_nuclide
real(8) :: flux_deriv
real(8) :: dsig_s, dsig_a, dsig_f, cum_dsig
@ -2358,9 +2353,9 @@ contains
case (SCORE_TOTAL, SCORE_SCATTER, SCORE_ABSORPTION, SCORE_FISSION, &
SCORE_NU_FISSION)
if (materials(p % material) % id() == deriv % diff_material) then
if (material_id(p % material) == deriv % diff_material) then
score = score * (flux_deriv + ONE &
/ materials(p % material) % density_gpcc)
/ material_density_gpcc(p % material))
else
score = score * flux_deriv
end if
@ -2379,9 +2374,9 @@ contains
case (SCORE_TOTAL, SCORE_SCATTER, SCORE_ABSORPTION, SCORE_FISSION, &
SCORE_NU_FISSION)
if (materials(p % material) % id() == deriv % diff_material) then
if (material_id(p % material) == deriv % diff_material) then
score = score * (flux_deriv + ONE &
/ materials(p % material) % density_gpcc)
/ material_density_gpcc(p % material))
else
score = score * flux_deriv
end if
@ -2421,17 +2416,15 @@ contains
case (SCORE_TOTAL, SCORE_SCATTER, SCORE_ABSORPTION, SCORE_FISSION, &
SCORE_NU_FISSION)
if (materials(p % material) % id() == deriv % diff_material &
if (material_id(p % material) == deriv % diff_material &
.and. p % event_nuclide == deriv % diff_nuclide) then
associate(mat => materials(p % material))
! Search for the index of the perturbed nuclide.
do l = 1, mat % n_nuclides
if (mat % nuclide(l) == deriv % diff_nuclide) exit
do l = 1, material_nuclide_size(p % material)
if (material_nuclide(p % material, l) == deriv % diff_nuclide) exit
end do
score = score * (flux_deriv &
+ ONE / mat % atom_density(l))
end associate
+ ONE / material_atom_density(p % material, l))
else
score = score * flux_deriv
end if
@ -2443,7 +2436,7 @@ contains
case (ESTIMATOR_COLLISION)
scoring_diff_nuclide = &
(materials(p % material) % id() == deriv % diff_material) &
(material_id(p % material) == deriv % diff_material) &
.and. (i_nuclide == deriv % diff_nuclide)
select case (score_bin)
@ -2453,7 +2446,7 @@ contains
case (SCORE_TOTAL)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % total /= ZERO) then
score = score * (flux_deriv &
+ micro_xs(deriv % diff_nuclide) % total &
@ -2467,7 +2460,7 @@ contains
case (SCORE_SCATTER)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % total - material_xs % absorption /= ZERO) then
score = score * (flux_deriv &
+ (micro_xs(deriv % diff_nuclide) % total &
@ -2483,7 +2476,7 @@ contains
case (SCORE_ABSORPTION)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % absorption /= ZERO) then
score = score * (flux_deriv &
+ micro_xs(deriv % diff_nuclide) % absorption &
@ -2497,7 +2490,7 @@ contains
case (SCORE_FISSION)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % fission /= ZERO) then
score = score * (flux_deriv &
+ micro_xs(deriv % diff_nuclide) % fission &
@ -2511,7 +2504,7 @@ contains
case (SCORE_NU_FISSION)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % nu_fission /= ZERO) then
score = score * (flux_deriv &
+ micro_xs(deriv % diff_nuclide) % nu_fission &
@ -2557,12 +2550,11 @@ contains
score = score * flux_deriv
case (SCORE_TOTAL)
if (materials(p % material) % id() == deriv % diff_material .and. &
if (material_id(p % material) == deriv % diff_material .and. &
micro_xs(p % event_nuclide) % total > ZERO) then
associate(mat => materials(p % material))
! Search for the index of the perturbed nuclide.
do l = 1, mat % n_nuclides
if (mat % nuclide(l) == p % event_nuclide) exit
do l = 1, material_nuclide_size(p % material)
if (material_nuclide(p % material, l) == p % event_nuclide) exit
end do
dsig_s = ZERO
@ -2574,21 +2566,19 @@ contains
end if
end associate
score = score * (flux_deriv &
+ (dsig_s + dsig_a) * mat % atom_density(l) &
+ (dsig_s + dsig_a) * material_atom_density(p % material, l) &
/ material_xs % total)
end associate
else
score = score * flux_deriv
end if
case (SCORE_SCATTER)
if (materials(p % material) % id() == deriv % diff_material .and. &
if (material_id(p % material) == deriv % diff_material .and. &
(micro_xs(p % event_nuclide) % total &
- micro_xs(p % event_nuclide) % absorption) > ZERO) then
associate(mat => materials(p % material))
! Search for the index of the perturbed nuclide.
do l = 1, mat % n_nuclides
if (mat % nuclide(l) == p % event_nuclide) exit
do l = 1, material_nuclide_size(p % material)
if (material_nuclide(p % material, l) == p % event_nuclide) exit
end do
dsig_s = ZERO
@ -2598,20 +2588,18 @@ contains
p % sqrtkT, dsig_s, dsig_a, dsig_f)
end if
end associate
score = score * (flux_deriv + dsig_s * mat % atom_density(l) / &
score = score * (flux_deriv + dsig_s * material_atom_density(p % material, l) / &
(material_xs % total - material_xs % absorption))
end associate
else
score = score * flux_deriv
end if
case (SCORE_ABSORPTION)
if (materials(p % material) % id() == deriv % diff_material .and. &
if (material_id(p % material) == deriv % diff_material .and. &
micro_xs(p % event_nuclide) % absorption > ZERO) then
associate(mat => materials(p % material))
! Search for the index of the perturbed nuclide.
do l = 1, mat % n_nuclides
if (mat % nuclide(l) == p % event_nuclide) exit
do l = 1, material_nuclide_size(p % material)
if (material_nuclide(p % material, l) == p % event_nuclide) exit
end do
dsig_a = ZERO
@ -2621,20 +2609,18 @@ contains
p % sqrtkT, dsig_s, dsig_a, dsig_f)
end if
end associate
score = score * (flux_deriv + dsig_a * mat % atom_density(l) &
score = score * (flux_deriv + dsig_a * material_atom_density(p % material, l) &
/ material_xs % absorption)
end associate
else
score = score * flux_deriv
end if
case (SCORE_FISSION)
if (materials(p % material) % id() == deriv % diff_material .and. &
if (material_id(p % material) == deriv % diff_material .and. &
micro_xs(p % event_nuclide) % fission > ZERO) then
associate(mat => materials(p % material))
! Search for the index of the perturbed nuclide.
do l = 1, mat % n_nuclides
if (mat % nuclide(l) == p % event_nuclide) exit
do l = 1, material_nuclide_size(p % material)
if (material_nuclide(p % material, l) == p % event_nuclide) exit
end do
dsig_f = ZERO
@ -2645,19 +2631,17 @@ contains
end if
end associate
score = score * (flux_deriv &
+ dsig_f * mat % atom_density(l) / material_xs % fission)
end associate
+ dsig_f * material_atom_density(p % material, l) / material_xs % fission)
else
score = score * flux_deriv
end if
case (SCORE_NU_FISSION)
if (materials(p % material) % id() == deriv % diff_material .and. &
if (material_id(p % material) == deriv % diff_material .and. &
micro_xs(p % event_nuclide) % nu_fission > ZERO) then
associate(mat => materials(p % material))
! Search for the index of the perturbed nuclide.
do l = 1, mat % n_nuclides
if (mat % nuclide(l) == p % event_nuclide) exit
do l = 1, material_nuclide_size(p % material)
if (material_nuclide(p % material, l) == p % event_nuclide) exit
end do
dsig_f = ZERO
@ -2668,10 +2652,9 @@ contains
end if
end associate
score = score * (flux_deriv &
+ dsig_f * mat % atom_density(l) / material_xs % nu_fission&
+ dsig_f * material_atom_density(p % material, l) / material_xs % nu_fission&
* micro_xs(p % event_nuclide) % nu_fission &
/ micro_xs(p % event_nuclide) % fission)
end associate
else
score = score * flux_deriv
end if
@ -2690,25 +2673,24 @@ contains
case (SCORE_TOTAL)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % total > ZERO) then
cum_dsig = ZERO
associate(mat => materials(p % material))
do l = 1, mat % n_nuclides
associate (nuc => nuclides(mat % nuclide(l)))
do l = 1, material_nuclide_size(p % material)
i_nuc = material_nuclide(p % material, l)
associate (nuc => nuclides(i_nuc))
if (multipole_in_range(nuc % ptr, p % last_E) .and. &
micro_xs(mat % nuclide(l)) % total > ZERO) then
micro_xs(i_nuc) % total > ZERO) then
call multipole_deriv_eval(nuc % ptr, p % last_E, &
p % sqrtkT, dsig_s, dsig_a, dsig_f)
cum_dsig = cum_dsig + (dsig_s + dsig_a) &
* mat % atom_density(l)
* material_atom_density(p % material, l)
end if
end associate
end do
end associate
score = score * (flux_deriv &
+ cum_dsig / material_xs % total)
else if (materials(p % material) % id() == deriv % diff_material &
else if (material_id(p % material) == deriv % diff_material &
.and. material_xs % total > ZERO) then
dsig_s = ZERO
dsig_a = ZERO
@ -2726,25 +2708,24 @@ contains
case (SCORE_SCATTER)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
(material_xs % total - material_xs % absorption) > ZERO) then
cum_dsig = ZERO
associate(mat => materials(p % material))
do l = 1, mat % n_nuclides
associate (nuc => nuclides(mat % nuclide(l)))
do l = 1, material_nuclide_size(p % material)
i_nuc = material_nuclide(p % material, l)
associate (nuc => nuclides(i_nuc))
if (multipole_in_range(nuc % ptr, p % last_E) .and. &
(micro_xs(mat % nuclide(l)) % total &
- micro_xs(mat % nuclide(l)) % absorption) > ZERO) then
(micro_xs(i_nuc) % total &
- micro_xs(i_nuc) % absorption) > ZERO) then
call multipole_deriv_eval(nuc % ptr, p % last_E, &
p % sqrtkT, dsig_s, dsig_a, dsig_f)
cum_dsig = cum_dsig + dsig_s * mat % atom_density(l)
cum_dsig = cum_dsig + dsig_s * material_atom_density(p % material, l)
end if
end associate
end do
end associate
score = score * (flux_deriv + cum_dsig &
/ (material_xs % total - material_xs % absorption))
else if ( materials(p % material) % id() == deriv % diff_material &
else if ( material_id(p % material) == deriv % diff_material &
.and. (material_xs % total - material_xs % absorption) > ZERO)&
then
dsig_s = ZERO
@ -2763,24 +2744,23 @@ contains
case (SCORE_ABSORPTION)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % absorption > ZERO) then
cum_dsig = ZERO
associate(mat => materials(p % material))
do l = 1, mat % n_nuclides
associate (nuc => nuclides(mat % nuclide(l)))
do l = 1, material_nuclide_size(p % material)
i_nuc = material_nuclide(p % material, l)
associate (nuc => nuclides(i_nuc))
if (multipole_in_range(nuc % ptr, p % last_E) .and. &
micro_xs(mat % nuclide(l)) % absorption > ZERO) then
micro_xs(i_nuc) % absorption > ZERO) then
call multipole_deriv_eval(nuc % ptr, p % last_E, &
p % sqrtkT, dsig_s, dsig_a, dsig_f)
cum_dsig = cum_dsig + dsig_a * mat % atom_density(l)
cum_dsig = cum_dsig + dsig_a * material_atom_density(p % material, l)
end if
end associate
end do
end associate
score = score * (flux_deriv &
+ cum_dsig / material_xs % absorption)
else if (materials(p % material) % id() == deriv % diff_material &
else if (material_id(p % material) == deriv % diff_material &
.and. material_xs % absorption > ZERO) then
dsig_a = ZERO
associate (nuc => nuclides(i_nuclide))
@ -2797,24 +2777,23 @@ contains
case (SCORE_FISSION)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % fission > ZERO) then
cum_dsig = ZERO
associate(mat => materials(p % material))
do l = 1, mat % n_nuclides
associate (nuc => nuclides(mat % nuclide(l)))
do l = 1, material_nuclide_size(p % material)
i_nuc = material_nuclide(p % material, l)
associate (nuc => nuclides(i_nuc))
if (multipole_in_range(nuc % ptr, p % last_E) .and. &
micro_xs(mat % nuclide(l)) % fission > ZERO) then
micro_xs(i_nuc) % fission > ZERO) then
call multipole_deriv_eval(nuc % ptr, p % last_E, &
p % sqrtkT, dsig_s, dsig_a, dsig_f)
cum_dsig = cum_dsig + dsig_f * mat % atom_density(l)
cum_dsig = cum_dsig + dsig_f * material_atom_density(p % material, l)
end if
end associate
end do
end associate
score = score * (flux_deriv &
+ cum_dsig / material_xs % fission)
else if (materials(p % material) % id() == deriv % diff_material &
else if (material_id(p % material) == deriv % diff_material &
.and. material_xs % fission > ZERO) then
dsig_f = ZERO
associate (nuc => nuclides(i_nuclide))
@ -2831,26 +2810,25 @@ contains
case (SCORE_NU_FISSION)
if (i_nuclide == -1 .and. &
materials(p % material) % id() == deriv % diff_material .and. &
material_id(p % material) == deriv % diff_material .and. &
material_xs % nu_fission > ZERO) then
cum_dsig = ZERO
associate(mat => materials(p % material))
do l = 1, mat % n_nuclides
associate (nuc => nuclides(mat % nuclide(l)))
do l = 1, material_nuclide_size(p % material)
i_nuc = material_nuclide(p % material, l)
associate (nuc => nuclides(i_nuc))
if (multipole_in_range(nuc % ptr, p % last_E) .and. &
micro_xs(mat % nuclide(l)) % nu_fission > ZERO) then
micro_xs(i_nuc) % nu_fission > ZERO) then
call multipole_deriv_eval(nuc % ptr, p % last_E, &
p % sqrtkT, dsig_s, dsig_a, dsig_f)
cum_dsig = cum_dsig + dsig_f * mat % atom_density(l) &
* micro_xs(mat % nuclide(l)) % nu_fission &
/ micro_xs(mat % nuclide(l)) % fission
cum_dsig = cum_dsig + dsig_f * material_atom_density(p % material, l) &
* micro_xs(i_nuc) % nu_fission &
/ micro_xs(i_nuc) % fission
end if
end associate
end do
end associate
score = score * (flux_deriv &
+ cum_dsig / material_xs % nu_fission)
else if (materials(p % material) % id() == deriv % diff_material &
else if (material_id(p % material) == deriv % diff_material &
.and. material_xs % nu_fission > ZERO) then
dsig_f = ZERO
associate (nuc => nuclides(i_nuclide))
@ -2900,32 +2878,27 @@ contains
select case (deriv % variable)
case (DIFF_DENSITY)
associate (mat => materials(p % material))
if (mat % id() == deriv % diff_material) then
if (material_id(p % material) == deriv % diff_material) then
! phi is proportional to e^(-Sigma_tot * dist)
! (1 / phi) * (d_phi / d_rho) = - (d_Sigma_tot / d_rho) * dist
! (1 / phi) * (d_phi / d_rho) = - Sigma_tot / rho * dist
deriv % flux_deriv = deriv % flux_deriv &
- distance * material_xs % total / mat % density_gpcc
- distance * material_xs % total / material_density_gpcc(p % material)
end if
end associate
case (DIFF_NUCLIDE_DENSITY)
associate (mat => materials(p % material))
if (mat % id() == deriv % diff_material) then
if (material_id(p % material) == deriv % diff_material) then
! phi is proportional to e^(-Sigma_tot * dist)
! (1 / phi) * (d_phi / d_N) = - (d_Sigma_tot / d_N) * dist
! (1 / phi) * (d_phi / d_N) = - sigma_tot * dist
deriv % flux_deriv = deriv % flux_deriv &
- distance * micro_xs(deriv % diff_nuclide) % total
end if
end associate
case (DIFF_TEMPERATURE)
associate (mat => materials(p % material))
if (mat % id() == deriv % diff_material) then
do l=1, mat % n_nuclides
associate (nuc => nuclides(mat % nuclide(l)))
if (material_id(p % material) == deriv % diff_material) then
do l=1, material_nuclide_size(p % material)
associate (nuc => nuclides(material_nuclide(p % material, l)))
if (multipole_in_range(nuc % ptr, p % E)) then
! phi is proportional to e^(-Sigma_tot * dist)
! (1 / phi) * (d_phi / d_T) = - (d_Sigma_tot / d_T) * dist
@ -2933,12 +2906,11 @@ contains
call multipole_deriv_eval(nuc % ptr, p % E, &
p % sqrtkT, dsig_s, dsig_a, dsig_f)
deriv % flux_deriv = deriv % flux_deriv &
- distance * (dsig_s + dsig_a) * mat % atom_density(l)
- distance * (dsig_s + dsig_a) * material_atom_density(p % material, l)
end if
end associate
end do
end if
end associate
end select
!end associate
end do
@ -2964,7 +2936,7 @@ contains
type(Particle), intent(in) :: p
type(TallyDerivative), pointer :: deriv
integer :: i, j, l
integer :: i, j, l, i_nuc
real(8) :: dsig_s, dsig_a, dsig_f
! A void material cannot be perturbed so it will not affect flux derivatives
@ -2976,26 +2948,23 @@ contains
select case (deriv % variable)
case (DIFF_DENSITY)
associate (mat => materials(p % material))
if (mat % id() == deriv % diff_material) then
if (material_id(p % material) == deriv % diff_material) then
! phi is proportional to Sigma_s
! (1 / phi) * (d_phi / d_rho) = (d_Sigma_s / d_rho) / Sigma_s
! (1 / phi) * (d_phi / d_rho) = 1 / rho
deriv % flux_deriv = deriv % flux_deriv &
+ ONE / mat % density_gpcc
+ ONE / material_density_gpcc(p % material)
end if
end associate
case (DIFF_NUCLIDE_DENSITY)
associate (mat => materials(p % material))
if (mat % id() == deriv % diff_material &
if (material_id(p % material) == deriv % diff_material &
.and. p % event_nuclide == deriv % diff_nuclide) then
! Find the index in this material for the diff_nuclide.
do j = 1, mat % n_nuclides
if (mat % nuclide(j) == deriv % diff_nuclide) exit
do j = 1, material_nuclide_size(p % material)
if (material_nuclide(p % material, j) == deriv % diff_nuclide) exit
end do
! Make sure we found the nuclide.
if (mat % nuclide(j) /= deriv % diff_nuclide) then
if (material_nuclide(p % material, j) /= deriv % diff_nuclide) then
call fatal_error("Couldn't find the right nuclide.")
end if
! phi is proportional to Sigma_s
@ -3003,16 +2972,15 @@ contains
! (1 / phi) * (d_phi / d_N) = sigma_s / Sigma_s
! (1 / phi) * (d_phi / d_N) = 1 / N
deriv % flux_deriv = deriv % flux_deriv &
+ ONE / mat % atom_density(j)
+ ONE / material_atom_density(p % material, j)
end if
end associate
case (DIFF_TEMPERATURE)
associate (mat => materials(p % material))
if (mat % id() == deriv % diff_material) then
do l=1, mat % n_nuclides
associate (nuc => nuclides(mat % nuclide(l)))
if (mat % nuclide(l) == p % event_nuclide .and. &
if (material_id(p % material) == deriv % diff_material) then
do l=1, material_nuclide_size(p % material)
i_nuc = material_nuclide(p % material, l)
associate (nuc => nuclides(i_nuc))
if (i_nuc == p % event_nuclide .and. &
multipole_in_range(nuc % ptr, p % last_E)) then
! phi is proportional to Sigma_s
! (1 / phi) * (d_phi / d_T) = (d_Sigma_s / d_T) / Sigma_s
@ -3020,8 +2988,8 @@ contains
call multipole_deriv_eval(nuc % ptr, p % last_E, &
p % sqrtkT, dsig_s, dsig_a, dsig_f)
deriv % flux_deriv = deriv % flux_deriv + dsig_s&
/ (micro_xs(mat % nuclide(l)) % total &
- micro_xs(mat % nuclide(l)) % absorption)
/ (micro_xs(i_nuc) % total &
- micro_xs(i_nuc) % absorption)
! Note that this is an approximation! The real scattering
! cross section is Sigma_s(E'->E, uvw'->uvw) =
! Sigma_s(E') * P(E'->E, uvw'->uvw). We are assuming that
@ -3033,7 +3001,6 @@ contains
end associate
end do
end if
end associate
end select
!end associate
end do
@ -3180,20 +3147,4 @@ contains
end if
end function openmc_tally_allocate
!===============================================================================
! Functions for C++ interop
!===============================================================================
function material_nuclide_index(i_material, i_nuclide) result(i) bind(C)
integer(C_INT), value :: i_material, i_nuclide
integer(C_INT) :: i
i = materials(i_material) % mat_nuclide_index(i_nuclide)
end function
function material_atom_density(i_material, i) result(dens) bind(C)
integer(C_INT), value :: i_material, i
real(C_DOUBLE) :: dens
dens = materials(i_material) % atom_density(i)
end function
end module tally

View file

@ -7,10 +7,10 @@ module tally_header
use dict_header, only: DictIntInt
use hdf5_interface, only: HID_T, HSIZE_T
use message_passing, only: n_procs, master
use nuclide_header, only: nuclide_dict
use nuclide_header, only: nuclide_map_get
use settings, only: reduce_tallies, run_mode
use stl_vector, only: VectorInt
use string, only: to_lower, to_f_string, str_to_int, to_str
use string, only: to_lower, to_f_string, str_to_int, to_str, to_c_string
use tally_filter_header, only: TallyFilterContainer, filters, n_filters
use tally_filter
@ -883,6 +883,7 @@ contains
integer, allocatable :: bins(:)
integer :: i
integer :: idx
character(C_CHAR), pointer :: string(:)
character(len=:, kind=C_CHAR), allocatable :: nuclide_
@ -894,14 +895,15 @@ contains
do i = 1, n
! Convert C string to Fortran string
call c_f_pointer(nuclides(i), string, [10])
nuclide_ = to_lower(to_f_string(string))
nuclide_ = to_f_string(string)
select case (nuclide_)
case ('total')
bins(i) = -1
case default
if (nuclide_dict % has(nuclide_)) then
bins(i) = nuclide_dict % get(nuclide_)
idx = nuclide_map_get(to_c_string(nuclide_))
if (idx /= -1) then
bins(i) = idx
else
err = E_DATA
call set_errmsg("Nuclide '" // trim(to_f_string(string)) // &

View file

@ -26,6 +26,7 @@ namespace openmc {
namespace data {
std::vector<std::unique_ptr<ThermalScattering>> thermal_scatt;
std::unordered_map<std::string, int> thermal_scatt_map;
}
//==============================================================================
@ -602,7 +603,11 @@ sab_from_hdf5(hid_t group, const double* temperature, int n)
return data::thermal_scatt.back().get();
}
extern "C" void sab_clear() { data::thermal_scatt.clear(); }
extern "C" void sab_clear()
{
data::thermal_scatt.clear();
data::thermal_scatt_map.clear();
}
extern "C" bool sab_has_nuclide(int i_sab, const char* name)
{

View file

@ -15,6 +15,7 @@ Timer time_bank_sendrecv;
Timer time_finalize;
Timer time_inactive;
Timer time_initialize;
Timer time_read_xs;
Timer time_tallies;
Timer time_total;
Timer time_transport;
@ -64,6 +65,7 @@ extern "C" double time_bank_sendrecv_elapsed() { return simulation::time_bank_se
extern "C" double time_finalize_elapsed() { return simulation::time_finalize.elapsed(); }
extern "C" double time_inactive_elapsed() { return simulation::time_inactive.elapsed(); }
extern "C" double time_initialize_elapsed() { return simulation::time_initialize.elapsed(); }
extern "C" double time_read_xs_elapsed() { return simulation::time_read_xs.elapsed(); }
extern "C" double time_tallies_elapsed() { return simulation::time_tallies.elapsed(); }
extern "C" double time_total_elapsed() { return simulation::time_total.elapsed(); }
extern "C" double time_transport_elapsed() { return simulation::time_transport.elapsed(); }
@ -81,6 +83,7 @@ void reset_timers()
simulation::time_finalize.reset();
simulation::time_inactive.reset();
simulation::time_initialize.reset();
simulation::time_read_xs.reset();
simulation::time_tallies.reset();
simulation::time_total.reset();
simulation::time_transport.reset();

View file

@ -35,6 +35,10 @@ module timer_header
import C_DOUBLE
real(C_DOUBLE) :: t
end function
function time_read_xs_elapsed() result(t) bind(C)
import C_DOUBLE
real(C_DOUBLE) :: t
end function
function time_tallies_elapsed() result(t) bind(C)
import C_DOUBLE
real(C_DOUBLE) :: t
@ -72,8 +76,6 @@ module timer_header
! ============================================================================
! TIMING VARIABLES
type(Timer) :: time_read_xs ! timer for reading cross sections
contains
!===============================================================================
@ -137,12 +139,4 @@ contains
self % elapsed = ZERO
end subroutine timer_reset
!===============================================================================
! RESET_TIMERS resets timers on the Fortran side
!===============================================================================
subroutine reset_timers_f() bind(C)
call time_read_xs % reset()
end subroutine
end module timer_header

View file

@ -11,7 +11,7 @@ module tracking
use geometry, only: next_cell
#endif
use material_header, only: materials, Material
use material_header, only: material_calculate_xs
use message_passing
use mgxs_interface
use nuclide_header
@ -131,7 +131,7 @@ contains
! If the material is the same as the last material and the
! temperature hasn't changed, we don't need to lookup cross
! sections again.
call materials(p % material) % calculate_xs(p)
call material_calculate_xs(p)
end if
else
! Get the MG data
@ -497,7 +497,7 @@ contains
p % coord(1) % cell = i_cell-1 ! decrement for C++ indexing
p % cell_instance = 1
p % material = cells(i_cell) % material(1)
p % sqrtKT = cells(i_cell) % sqrtKT(1)
p % sqrtKT = cells(i_cell) % sqrtKT(0)
return
end if
#endif

View file

@ -13,7 +13,7 @@ module volume_calc
use hdf5_interface, only: file_open, file_close, write_attribute, &
create_group, close_group, write_dataset, HID_T
use output, only: header, time_stamp
use material_header, only: materials
use material_header
use message_passing
use nuclide_header, only: nuclides
use particle_header
@ -215,7 +215,7 @@ contains
i_material = p % material
if (i_material /= MATERIAL_VOID) then
do i_domain = 1, size(this % domain_id)
if (materials(i_material) % id() == this % domain_id(i_domain)) then
if (material_id(i_material) == this % domain_id(i_domain)) then
call check_hit(i_domain, i_material, indices, hits, n_mat)
end if
end do
@ -328,16 +328,14 @@ contains
i_material = master_indices(i_domain) % data(j)
if (i_material == MATERIAL_VOID) cycle
associate (mat => materials(i_material))
do k = 1, size(mat % nuclide)
! Accumulate nuclide density
i_nuclide = mat % nuclide(k)
atoms(1, i_nuclide) = atoms(1, i_nuclide) + &
mat % atom_density(k) * f
atoms(2, i_nuclide) = atoms(2, i_nuclide) + &
mat % atom_density(k)**2 * var_f
end do
end associate
do k = 1, material_nuclide_size(i_material)
! Accumulate nuclide density
i_nuclide = material_nuclide(i_material, k)
atoms(1, i_nuclide) = atoms(1, i_nuclide) + &
material_atom_density(i_material, k) * f
atoms(2, i_nuclide) = atoms(2, i_nuclide) + &
material_atom_density(i_material, k)**2 * var_f
end do
end do
! Determine volume

View file

@ -1,13 +1,20 @@
#include "openmc/wmp.h"
#include "openmc/constants.h"
#include "openmc/cross_sections.h"
#include "openmc/hdf5_interface.h"
#include "openmc/math_functions.h"
#include "openmc/nuclide.h"
#include <cmath>
#include <sstream>
namespace openmc {
//========================================================================
// WindowedeMultipole implementation
//========================================================================
WindowedMultipole::WindowedMultipole(hid_t group)
{
// Get name of nuclide from group, removing leading '/'
@ -184,4 +191,53 @@ WindowedMultipole::evaluate_deriv(double E, double sqrtkT)
return std::make_tuple(sig_s, sig_a, sig_f);
}
//========================================================================
// Non-member functions
//========================================================================
void check_wmp_version(hid_t file)
{
if (attribute_exists(file, "version")) {
std::array<int, 2> version;
read_attribute(file, "version", version);
if (version[0] != WMP_VERSION[0]) {
std::stringstream msg;
msg << "WMP data format uses version " << version[0] << "." <<
version[1] << " whereas your installation of OpenMC expects version "
<< WMP_VERSION[0] << ".x data.";
fatal_error(msg);
}
} else {
fatal_error("WMP data does not indicate a version. Your installation of "
"OpenMC expects version " + std::to_string(WMP_VERSION[0]) + ".x data.");
}
}
void read_multipole_data(int i_nuclide)
{
// Look for WMP data in cross_sections.xml
const auto& nuc {data::nuclides[i_nuclide]};
auto it = data::library_map.find({Library::Type::wmp, nuc->name_});
// If no WMP library for this nuclide, just return
if (it == data::library_map.end()) return;
// Check if WMP library exists
int idx = it->second;
std::string& filename = data::libraries[idx].path_;
// Display message
write_message("Reading " + nuc->name_ + " WMP data from " + filename, 6);
// Open file and make sure version is sufficient
hid_t file = file_open(filename, 'r');
check_wmp_version(file);
// Read nuclide data from HDF5
hid_t group = open_group(file, nuc->name_.c_str());
nuc->multipole_ = std::make_unique<WindowedMultipole>(group);
close_group(group);
file_close(file);
}
} // namespace openmc

View file

@ -13,7 +13,9 @@
#include "openmc/constants.h"
#include "openmc/error.h"
#include "openmc/math_functions.h"
#include "openmc/mgxs_interface.h"
#include "openmc/random_lcg.h"
#include "openmc/settings.h"
namespace openmc {
@ -22,10 +24,11 @@ namespace openmc {
// XsData class methods
//==============================================================================
XsData::XsData(size_t energy_groups, size_t num_delayed_groups, bool fissionable,
int scatter_format, int n_pol, int n_azi)
XsData::XsData(bool fissionable, int scatter_format, int n_pol, int n_azi)
{
size_t n_ang = n_pol * n_azi;
size_t n_dg = data::num_delayed_groups;
size_t n_g = data::num_energy_groups;
// check to make sure scatter format is OK before we allocate
if (scatter_format != ANGLE_HISTOGRAM && scatter_format != ANGLE_TABULAR &&
@ -33,7 +36,7 @@ XsData::XsData(size_t energy_groups, size_t num_delayed_groups, bool fissionable
fatal_error("Invalid scatter_format!");
}
// allocate all [temperature][angle][in group] quantities
std::vector<size_t> shape = {n_ang, energy_groups};
std::vector<size_t> shape {n_ang, n_g};
total = xt::zeros<double>(shape);
absorption = xt::zeros<double>(shape);
inverse_velocity = xt::zeros<double>(shape);
@ -45,20 +48,20 @@ XsData::XsData(size_t energy_groups, size_t num_delayed_groups, bool fissionable
}
// allocate decay_rate; [temperature][angle][delayed group]
shape[1] = num_delayed_groups;
shape[1] = n_dg;
decay_rate = xt::zeros<double>(shape);
if (fissionable) {
shape = {n_ang, num_delayed_groups, energy_groups};
shape = {n_ang, n_dg, n_g};
// allocate delayed_nu_fission; [temperature][angle][delay group][in group]
delayed_nu_fission = xt::zeros<double>(shape);
// chi_prompt; [temperature][angle][in group][out group]
shape = {n_ang, energy_groups, energy_groups};
shape = {n_ang, n_g, n_g};
chi_prompt = xt::zeros<double>(shape);
// chi_delayed; [temperature][angle][delay group][in group][out group]
shape = {n_ang, num_delayed_groups, energy_groups, energy_groups};
shape = {n_ang, n_dg, n_g, n_g};
chi_delayed = xt::zeros<double>(shape);
}
@ -78,18 +81,15 @@ XsData::XsData(size_t energy_groups, size_t num_delayed_groups, bool fissionable
void
XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format,
int final_scatter_format, int order_data, int max_order,
int legendre_to_tabular_points, bool is_isotropic, int n_pol, int n_azi)
int final_scatter_format, int order_data, bool is_isotropic, int n_pol, int n_azi)
{
// Reconstruct the dimension information so it doesn't need to be passed
size_t n_ang = n_pol * n_azi;
size_t energy_groups = total.shape()[1];
size_t delayed_groups = decay_rate.shape()[1];
// Set the fissionable-specific data
if (fissionable) {
fission_from_hdf5(xsdata_grp, n_ang, energy_groups, delayed_groups,
is_isotropic);
fission_from_hdf5(xsdata_grp, n_ang, is_isotropic);
}
// Get the non-fission-specific data
read_nd_vector(xsdata_grp, "decay_rate", decay_rate);
@ -97,8 +97,8 @@ XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format,
read_nd_vector(xsdata_grp, "inverse-velocity", inverse_velocity);
// Get scattering data
scatter_from_hdf5(xsdata_grp, n_ang, energy_groups, scatter_format,
final_scatter_format, order_data, max_order, legendre_to_tabular_points);
scatter_from_hdf5(xsdata_grp, n_ang, scatter_format,
final_scatter_format, order_data);
// Check absorption to ensure it is not 0 since it is often the
// denominator in tally methods
@ -123,12 +123,15 @@ XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format,
void
XsData::fission_vector_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups, size_t delayed_groups, bool is_isotropic)
bool is_isotropic)
{
// Data is provided as nu-fission and chi with a beta for delayed info
size_t n_g = data::num_energy_groups;
size_t n_dg = data::num_delayed_groups;
// Get chi
xt::xtensor<double, 2> temp_chi({n_ang, energy_groups}, 0.);
xt::xtensor<double, 2> temp_chi({n_ang, n_g}, 0.);
read_nd_vector(xsdata_grp, "chi", temp_chi, true);
// Normalize chi by summing over the outgoing groups for each incoming angle
@ -141,7 +144,7 @@ XsData::fission_vector_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
xt::all());
// Get nu-fission
xt::xtensor<double, 2> temp_nufiss({n_ang, energy_groups}, 0.);
xt::xtensor<double, 2> temp_nufiss({n_ang, n_g}, 0.);
read_nd_vector(xsdata_grp, "nu-fission", temp_nufiss, true);
// Get beta (strategy will depend upon the number of dimensions in beta)
@ -151,7 +154,7 @@ XsData::fission_vector_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
int ndim_target = 1;
if (!is_isotropic) ndim_target += 2;
if (beta_ndims == ndim_target) {
xt::xtensor<double, 2> temp_beta({n_ang, delayed_groups}, 0.);
xt::xtensor<double, 2> temp_beta({n_ang, n_dg}, 0.);
read_nd_vector(xsdata_grp, "beta", temp_beta, true);
// Set prompt_nu_fission = (1. - beta_total)*nu_fission
@ -162,8 +165,7 @@ XsData::fission_vector_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
xt::view(temp_beta, xt::all(), xt::all(), xt::newaxis()) *
xt::view(temp_nufiss, xt::all(), xt::newaxis(), xt::all());
} else if (beta_ndims == ndim_target + 1) {
xt::xtensor<double, 3> temp_beta({n_ang, delayed_groups, energy_groups},
0.);
xt::xtensor<double, 3> temp_beta({n_ang, n_dg, n_g}, 0.);
read_nd_vector(xsdata_grp, "beta", temp_beta, true);
// Set prompt_nu_fission = (1. - beta_total)*nu_fission
@ -176,20 +178,22 @@ XsData::fission_vector_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
}
void
XsData::fission_vector_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups, size_t delayed_groups)
XsData::fission_vector_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang)
{
// Data is provided separately as prompt + delayed nu-fission and chi
size_t n_g = data::num_energy_groups;
size_t n_dg = data::num_delayed_groups;
// Get chi-prompt
xt::xtensor<double, 2> temp_chi_p({n_ang, energy_groups}, 0.);
xt::xtensor<double, 2> temp_chi_p({n_ang, n_g}, 0.);
read_nd_vector(xsdata_grp, "chi-prompt", temp_chi_p, true);
// Normalize chi by summing over the outgoing groups for each incoming angle
temp_chi_p /= xt::view(xt::sum(temp_chi_p, {1}), xt::all(), xt::newaxis());
// Get chi-delayed
xt::xtensor<double, 3> temp_chi_d({n_ang, delayed_groups, energy_groups}, 0.);
xt::xtensor<double, 3> temp_chi_d({n_ang, n_dg, n_g}, 0.);
read_nd_vector(xsdata_grp, "chi-delayed", temp_chi_d, true);
// Normalize chi by summing over the outgoing groups for each incoming angle
@ -209,14 +213,15 @@ XsData::fission_vector_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
}
void
XsData::fission_vector_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups)
XsData::fission_vector_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang)
{
// No beta is provided and there is no prompt/delay distinction.
// Therefore, the code only considers the data as prompt.
size_t n_g = data::num_energy_groups;
// Get chi
xt::xtensor<double, 2> temp_chi({n_ang, energy_groups}, 0.);
xt::xtensor<double, 2> temp_chi({n_ang, n_g}, 0.);
read_nd_vector(xsdata_grp, "chi", temp_chi, true);
// Normalize chi by summing over the outgoing groups for each incoming angle
@ -232,13 +237,15 @@ XsData::fission_vector_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang,
//==============================================================================
void
XsData::fission_matrix_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups, size_t delayed_groups, bool is_isotropic)
XsData::fission_matrix_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
{
// Data is provided as nu-fission and chi with a beta for delayed info
size_t n_g = data::num_energy_groups;
size_t n_dg = data::num_delayed_groups;
// Get nu-fission matrix
xt::xtensor<double, 3> temp_matrix({n_ang, energy_groups, energy_groups}, 0.);
xt::xtensor<double, 3> temp_matrix({n_ang, n_g, n_g}, 0.);
read_nd_vector(xsdata_grp, "nu-fission", temp_matrix, true);
// Get beta (strategy will depend upon the number of dimensions in beta)
@ -248,7 +255,7 @@ XsData::fission_matrix_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
int ndim_target = 1;
if (!is_isotropic) ndim_target += 2;
if (beta_ndims == ndim_target) {
xt::xtensor<double, 2> temp_beta({n_ang, delayed_groups}, 0.);
xt::xtensor<double, 2> temp_beta({n_ang, n_dg}, 0.);
read_nd_vector(xsdata_grp, "beta", temp_beta, true);
xt::xtensor<double, 1> temp_beta_sum({n_ang}, 0.);
@ -274,10 +281,10 @@ XsData::fission_matrix_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
xt::view(temp_matrix, xt::all(), xt::newaxis(), xt::all(), xt::all());
} else if (beta_ndims == ndim_target + 1) {
xt::xtensor<double, 3> temp_beta({n_ang, delayed_groups, energy_groups}, 0.);
xt::xtensor<double, 3> temp_beta({n_ang, n_dg, n_g}, 0.);
read_nd_vector(xsdata_grp, "beta", temp_beta, true);
xt::xtensor<double, 2> temp_beta_sum({n_ang, energy_groups}, 0.);
xt::xtensor<double, 2> temp_beta_sum({n_ang, n_g}, 0.);
temp_beta_sum = xt::sum(temp_beta, {1});
// prompt_nu_fission is the sum of this matrix over outgoing groups and
@ -308,13 +315,15 @@ XsData::fission_matrix_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
}
void
XsData::fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups, size_t delayed_groups)
XsData::fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang)
{
// Data is provided separately as prompt + delayed nu-fission and chi
size_t n_g = data::num_energy_groups;
size_t n_dg = data::num_delayed_groups;
// Get the prompt nu-fission matrix
xt::xtensor<double, 3> temp_matrix_p({n_ang, energy_groups, energy_groups}, 0.);
xt::xtensor<double, 3> temp_matrix_p({n_ang, n_g, n_g}, 0.);
read_nd_vector(xsdata_grp, "prompt-nu-fission", temp_matrix_p, true);
// prompt_nu_fission is the sum over outgoing groups
@ -326,8 +335,7 @@ XsData::fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
xt::view(prompt_nu_fission, xt::all(), xt::all(), xt::newaxis());
// Get the delayed nu-fission matrix
xt::xtensor<double, 4> temp_matrix_d({n_ang, delayed_groups, energy_groups,
energy_groups}, 0.);
xt::xtensor<double, 4> temp_matrix_d({n_ang, n_dg, n_g, n_g}, 0.);
read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_matrix_d, true);
// delayed_nu_fission is the sum over outgoing groups
@ -340,14 +348,15 @@ XsData::fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang,
}
void
XsData::fission_matrix_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang,
size_t energy_groups)
XsData::fission_matrix_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang)
{
// No beta is provided and there is no prompt/delay distinction.
// Therefore, the code only considers the data as prompt.
size_t n_g = data::num_energy_groups;
// Get nu-fission matrix
xt::xtensor<double, 3> temp_matrix({n_ang, energy_groups, energy_groups}, 0.);
xt::xtensor<double, 3> temp_matrix({n_ang, n_g, n_g}, 0.);
read_nd_vector(xsdata_grp, "nu-fission", temp_matrix, true);
// prompt_nu_fission is the sum over outgoing groups
@ -362,8 +371,7 @@ XsData::fission_matrix_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang,
//==============================================================================
void
XsData::fission_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
size_t delayed_groups, bool is_isotropic)
XsData::fission_from_hdf5(hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
{
// Get the fission and kappa_fission data xs; these are optional
read_nd_vector(xsdata_grp, "fission", fission);
@ -373,33 +381,29 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
// as a nu-fission matrix or a set of chi and nu-fission vectors
if (object_exists(xsdata_grp, "chi") ||
object_exists(xsdata_grp, "chi-prompt")) {
if (delayed_groups == 0) {
fission_vector_no_delayed_from_hdf5(xsdata_grp, n_ang, energy_groups);
if (data::num_delayed_groups == 0) {
fission_vector_no_delayed_from_hdf5(xsdata_grp, n_ang);
} else {
if (object_exists(xsdata_grp, "beta")) {
fission_vector_beta_from_hdf5(xsdata_grp, n_ang, energy_groups,
delayed_groups, is_isotropic);
fission_vector_beta_from_hdf5(xsdata_grp, n_ang, is_isotropic);
} else {
fission_vector_no_beta_from_hdf5(xsdata_grp, n_ang, energy_groups,
delayed_groups);
fission_vector_no_beta_from_hdf5(xsdata_grp, n_ang);
}
}
} else {
if (delayed_groups == 0) {
fission_matrix_no_delayed_from_hdf5(xsdata_grp, n_ang, energy_groups);
if (data::num_delayed_groups == 0) {
fission_matrix_no_delayed_from_hdf5(xsdata_grp, n_ang);
} else {
if (object_exists(xsdata_grp, "beta")) {
fission_matrix_beta_from_hdf5(xsdata_grp, n_ang, energy_groups,
delayed_groups, is_isotropic);
fission_matrix_beta_from_hdf5(xsdata_grp, n_ang, is_isotropic);
} else {
fission_matrix_no_beta_from_hdf5(xsdata_grp, n_ang, energy_groups,
delayed_groups);
fission_matrix_no_beta_from_hdf5(xsdata_grp, n_ang);
}
}
}
// Combine prompt_nu_fission and delayed_nu_fission into nu_fission
if (delayed_groups == 0) {
if (data::num_delayed_groups == 0) {
nu_fission = prompt_nu_fission;
} else {
nu_fission = prompt_nu_fission + xt::sum(delayed_nu_fission, {1});
@ -409,9 +413,8 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
//==============================================================================
void
XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
int scatter_format, int final_scatter_format, int order_data,
int max_order, int legendre_to_tabular_points)
XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang,
int scatter_format, int final_scatter_format, int order_data)
{
if (!object_exists(xsdata_grp, "scatter_data")) {
fatal_error("Must provide scatter_data group!");
@ -419,9 +422,10 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
hid_t scatt_grp = open_group(xsdata_grp, "scatter_data");
// Get the outgoing group boundary indices
xt::xtensor<int, 2> gmin({n_ang, energy_groups}, 0.);
size_t n_g = data::num_energy_groups;
xt::xtensor<int, 2> gmin({n_ang, n_g}, 0.);
read_nd_vector(scatt_grp, "g_min", gmin, true);
xt::xtensor<int, 2> gmax({n_ang, energy_groups}, 0.);
xt::xtensor<int, 2> gmax({n_ang, n_g}, 0.);
read_nd_vector(scatt_grp, "g_max", gmax, true);
// Make gmin and gmax start from 0 vice 1 as they do in the library
@ -432,7 +436,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
// data.
size_t length = order_data * xt::sum(gmax - gmin + 1)();
double_4dvec input_scatt(n_ang, double_3dvec(energy_groups));
double_4dvec input_scatt(n_ang, double_3dvec(n_g));
xt::xtensor<double, 1> temp_arr({length}, 0.);
read_nd_vector(scatt_grp, "scatter_matrix", temp_arr, true);
@ -440,7 +444,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
// strip off the superfluous orders if needed
int order_dim;
if (scatter_format == ANGLE_LEGENDRE) {
order_dim = std::min(order_data - 1, max_order) + 1;
order_dim = std::min(order_data - 1, settings::max_order) + 1;
} else {
order_dim = order_data;
}
@ -449,7 +453,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
// scatt data
size_t temp_idx = 0;
for (size_t a = 0; a < n_ang; a++) {
for (size_t gin = 0; gin < energy_groups; gin++) {
for (size_t gin = 0; gin < n_g; gin++) {
input_scatt[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
for (size_t i_gout = 0; i_gout < input_scatt[a][gin].size(); i_gout++) {
input_scatt[a][gin][i_gout].resize(order_dim);
@ -463,7 +467,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
}
// Get multiplication matrix
double_3dvec temp_mult(n_ang, double_2dvec(energy_groups));
double_3dvec temp_mult(n_ang, double_2dvec(n_g));
if (object_exists(scatt_grp, "multiplicity_matrix")) {
temp_arr.resize({length / order_data});
read_nd_vector(scatt_grp, "multiplicity_matrix", temp_arr);
@ -471,7 +475,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
// convert the flat temp_arr to a jagged array for passing to scatt data
size_t temp_idx = 0;
for (size_t a = 0; a < n_ang; a++) {
for (size_t gin = 0; gin < energy_groups; gin++) {
for (size_t gin = 0; gin < n_g; gin++) {
temp_mult[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
for (size_t i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) {
temp_mult[a][gin][i_gout] = temp_arr[temp_idx++];
@ -481,7 +485,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
} else {
// Use a default: multiplicities are 1.0.
for (size_t a = 0; a < n_ang; a++) {
for (size_t gin = 0; gin < energy_groups; gin++) {
for (size_t gin = 0; gin < n_g; gin++) {
temp_mult[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
for (size_t i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) {
temp_mult[a][gin][i_gout] = 1.;
@ -504,8 +508,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang, size_t energy_groups,
// Now create a tabular version of legendre_scatt
convert_legendre_to_tabular(legendre_scatt,
*static_cast<ScattDataTabular*>(scatter[a].get()),
legendre_to_tabular_points);
*static_cast<ScattDataTabular*>(scatter[a].get()));
scatter_format = final_scatter_format;
}

View file

@ -1,5 +1,5 @@
k-combined:
1.115067E+00 5.423808E-02
1.028803E+00 3.340602E-02
tally 1:
8.543144E+00
1.530584E+01
8.346847E+00
1.453407E+01

View file

@ -38,8 +38,8 @@ def test_dagmc():
water = openmc.Material()
water.add_nuclide('H1', 2.0, 'ao')
water.add_nuclide('O16', 1.0, 'ao')
water.add_s_alpha_beta('c_H_in_H2O')
water.set_density('g/cc', 1.0)
water.add_s_alpha_beta('c_H_in_H2O')
water.id = 41
mats = openmc.Materials([u235, water])

View file

@ -1,2 +1,2 @@
k-combined:
8.447580E-01 1.806149E-02
8.403447E-01 2.461538E-02

View file

Binary file not shown.

View file

@ -0,0 +1,23 @@
<?xml version='1.0' encoding='utf-8'?>
<settings>
<run_mode>eigenvalue</run_mode>
<particles>100</particles>
<batches>5</batches>
<inactive>0</inactive>
<source strength="1.0">
<space type="box">
<parameters>-4 -4 -4 4 4 4</parameters>
</space>
</source>
<dagmc>true</dagmc>
</settings>
<?xml version='1.0' encoding='utf-8'?>
<tallies>
<filter id="1" type="cell">
<bins>1</bins>
</filter>
<tally id="1">
<filters>1</filters>
<scores>total</scores>
</tally>
</tallies>

Some files were not shown because too many files have changed in this diff Show more