diff --git a/CMakeLists.txt b/CMakeLists.txt index 4efc2e38b..480f6f772 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 68848014d..e5c0ca160 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -88,6 +88,14 @@ you care. This element has the following attributes/sub-elements: *Default*: 0.0 +-------------------------------- +```` Element +-------------------------------- + +When the DAGMC mode is enabled, the OpenMC geometry will be read from the file +``dagmc.h5m``. If a :ref:`geometry.xml ` file is present with +``dagmc`` set to ``true``, it will be ignored. + -------------------------------- ```` Element -------------------------------- diff --git a/docs/source/publications.rst b/docs/source/publications.rst index 49430d84b..012560a28 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -62,6 +62,11 @@ Coupling and Multi-physics `_," *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 `_", *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 `_", 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 `_", *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 + `_", *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 + `_", *Nucl. Technol.* (2019). + +- Xingjie Peng, Jingang Liang, Abdulla Alhajri, Benoit Forget, and Kord Smith, + "`Development of continuous-energy sensitivity analysis capability in OpenMC + `_", *Ann. Nucl. Energy*, + **110**, 362-383 (2017). diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index f39421b28..8cf65a997 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -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`. diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index d4055f0fd..532e06655 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -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 `_. .. 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: diff --git a/docs/source/usersguide/geometry.rst b/docs/source/usersguide/geometry.rst index bb800699b..70a665c53 100644 --- a/docs/source/usersguide/geometry.rst +++ b/docs/source/usersguide/geometry.rst @@ -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 +`_) 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 ` file:: + + true + +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 + `_ + successfully and that the model is `watertight + `_. Future + implementations of DAGMC geometry will support small volume overlaps and + un-merged surfaces. diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 48dcd8c78..d3e59c06e 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -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 + `_). 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_. diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 6101ef1ad..38653d32d 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -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); diff --git a/include/openmc/cross_sections.h b/include/openmc/cross_sections.h index d8a3799c5..56b4ba31c 100644 --- a/include/openmc/cross_sections.h +++ b/include/openmc/cross_sections.h @@ -57,7 +57,15 @@ extern std::map 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>& nuc_temps, + const std::vector>& thermal_temps); //! Read cross_sections.xml and populate data libraries void read_ce_cross_sections_xml(); diff --git a/include/openmc/dagmc.h b/include/openmc/dagmc.h index 51d97d737..4a129ef09 100644 --- a/include/openmc/dagmc.h +++ b/include/openmc/dagmc.h @@ -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 diff --git a/include/openmc/geometry_aux.h b/include/openmc/geometry_aux.h index e456f700c..3e5cb93e1 100644 --- a/include/openmc/geometry_aux.h +++ b/include/openmc/geometry_aux.h @@ -6,7 +6,7 @@ #include #include - +#include 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>& nuc_temps, + std::vector>& 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>& nuc_temps, + std::vector>& 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. diff --git a/include/openmc/hdf5_interface.h b/include/openmc/hdf5_interface.h index b0c3acd4d..6881cac77 100644 --- a/include/openmc/hdf5_interface.h +++ b/include/openmc/hdf5_interface.h @@ -137,6 +137,13 @@ void read_attribute(hid_t obj_id, const char* name, T& buffer) read_attr(obj_id, name, H5TypeMap::type_id, &buffer); } +// array version +template inline void +read_attribute(hid_t obj_id, const char* name, std::array& buffer) +{ + read_attr(obj_id, name, H5TypeMap::type_id, buffer.data()); +} + // vector version template void read_attribute(hid_t obj_id, const char* name, std::vector& 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 inline void write_attribute(hid_t obj_id, const char* name, const std::array& buffer) { @@ -391,6 +404,29 @@ write_dataset(hid_t obj_id, const char* name, const std::array& buffer) write_dataset(obj_id, 1, dims, name, H5TypeMap::type_id, buffer.data(), false); } +inline void +write_dataset(hid_t obj_id, const char* name, const std::vector& 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 inline void write_dataset(hid_t obj_id, const char* name, const std::vector& buffer) { diff --git a/include/openmc/initialize.h b/include/openmc/initialize.h index d2f4ff0c6..eba616474 100644 --- a/include/openmc/initialize.h +++ b/include/openmc/initialize.h @@ -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(); } diff --git a/include/openmc/material.h b/include/openmc/material.h index d796b08ed..4792e22fc 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -2,10 +2,13 @@ #define OPENMC_MATERIAL_H #include // for unique_ptr +#include #include #include +#include #include "pugixml.hpp" +#include "xtensor/xtensor.hpp" #include "openmc/bremsstrahlung.h" #include "openmc/particle.h" @@ -32,9 +35,57 @@ extern std::unordered_map 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 nuclide_; //!< Indices in nuclides vector + std::vector element_; //!< Indices in elements vector + xt::xtensor 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 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 mat_nuclide_index_; + + // Thermal scattering tables + std::vector thermal_tables_; //! \brief Default temperature for cells containing this material. //! @@ -43,12 +94,15 @@ public: std::unique_ptr 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; }; //============================================================================== diff --git a/include/openmc/mgxs.h b/include/openmc/mgxs.h index e80355bee..9d680434c 100644 --- a/include/openmc/mgxs.h +++ b/include/openmc/mgxs.h @@ -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& 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& in_polar, const std::vector& 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& temperature, - double tolerance, std::vector& temps_to_read, int& order_dim, - int& method); + metadata_from_hdf5(hid_t xs_id, const std::vector& temperature, + std::vector& 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& 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& 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& mat_kTs, - const std::vector& micros, const std::vector& atom_densities, - double tolerance, int& method); + const std::vector& micros, const std::vector& atom_densities); //! \brief Provides a cross section value given certain parameters //! diff --git a/include/openmc/mgxs_interface.h b/include/openmc/mgxs_interface.h index 17be1a18e..a6c9e19c1 100644 --- a/include/openmc/mgxs_interface.h +++ b/include/openmc/mgxs_interface.h @@ -20,6 +20,7 @@ namespace data { extern std::vector nuclides_MG; extern std::vector macro_xs; extern "C" int num_energy_groups; +extern "C" int num_delayed_groups; extern std::vector energy_bins; extern std::vector energy_bin_avg; extern std::vector rev_energy_bins; @@ -30,19 +31,15 @@ extern std::vector 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& 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> get_mat_kTs(); extern "C" void read_mg_cross_sections_header_c(hid_t file_id); diff --git a/include/openmc/nuclide.h b/include/openmc/nuclide.h index bc4112281..dd4dfc678 100644 --- a/include/openmc/nuclide.h +++ b/include/openmc/nuclide.h @@ -6,6 +6,7 @@ #include #include // for unique_ptr +#include #include #include @@ -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& 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 energy_min; extern std::array energy_max; extern std::vector> nuclides; +extern std::unordered_map nuclide_map; } // namespace data diff --git a/include/openmc/output.h b/include/openmc/output.h index 67bb2a546..6c8226c65 100644 --- a/include/openmc/output.h +++ b/include/openmc/output.h @@ -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. diff --git a/include/openmc/particle.h b/include/openmc/particle.h index 6f2d58832..84e628fe2 100644 --- a/include/openmc/particle.h +++ b/include/openmc/particle.h @@ -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" { diff --git a/include/openmc/photon.h b/include/openmc/photon.h index 592ae6b97..19d7bc8a3 100644 --- a/include/openmc/photon.h +++ b/include/openmc/photon.h @@ -129,6 +129,7 @@ extern xt::xtensor compton_profile_pz; //! Compton profile momentum g //! Photon interaction data for each element extern std::vector elements; +extern std::unordered_map element_map; } // namespace data diff --git a/include/openmc/physics.h b/include/openmc/physics.h index 650f65fea..5d480e0bf 100644 --- a/include/openmc/physics.h +++ b/include/openmc/physics.h @@ -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, diff --git a/include/openmc/scattdata.h b/include/openmc/scattdata.h index 8ea81f252..1a3a252a5 100644 --- a/include/openmc/scattdata.h +++ b/include/openmc/scattdata.h @@ -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: diff --git a/include/openmc/settings.h b/include/openmc/settings.h index e95bcb107..79db8032d 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -97,7 +97,7 @@ extern double weight_survive; //!< Survival weight after Russian roulette //! Read settings from XML file //! \param[in] root XML node for -extern "C" void read_settings_xml(); +void read_settings_xml(); extern "C" void read_settings_xml_f(pugi::xml_node_struct* root_ptr); diff --git a/include/openmc/simulation.h b/include/openmc/simulation.h index cba0a2b71..f9e1f7c96 100644 --- a/include/openmc/simulation.h +++ b/include/openmc/simulation.h @@ -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 diff --git a/include/openmc/summary.h b/include/openmc/summary.h new file mode 100644 index 000000000..ff2ab71a8 --- /dev/null +++ b/include/openmc/summary.h @@ -0,0 +1,16 @@ +#ifndef OPENMC_SUMMARY_H +#define OPENMC_SUMMARY_H + +#include + +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 diff --git a/include/openmc/thermal.h b/include/openmc/thermal.h index 24520eeeb..17b832a15 100644 --- a/include/openmc/thermal.h +++ b/include/openmc/thermal.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "xtensor/xtensor.hpp" @@ -36,6 +37,7 @@ class ThermalScattering; namespace data { extern std::vector> thermal_scatt; +extern std::unordered_map thermal_scatt_map; } //============================================================================== diff --git a/include/openmc/timer.h b/include/openmc/timer.h index ac8c81e0c..9149c4ef7 100644 --- a/include/openmc/timer.h +++ b/include/openmc/timer.h @@ -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; diff --git a/include/openmc/wmp.h b/include/openmc/wmp.h index 63754bbbc..97377242a 100644 --- a/include/openmc/wmp.h +++ b/include/openmc/wmp.h @@ -4,6 +4,7 @@ #include "hdf5.h" #include "xtensor/xtensor.hpp" +#include #include #include #include @@ -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 WMP_VERSION {1, 1}; + //======================================================================== // Windowed multipole data //======================================================================== @@ -68,6 +72,21 @@ public: xt::xtensor 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 diff --git a/include/openmc/xsdata.h b/include/openmc/xsdata.h index fa75b10aa..a629d786a 100644 --- a/include/openmc/xsdata.h +++ b/include/openmc/xsdata.h @@ -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. diff --git a/openmc/capi/material.py b/openmc/capi/material.py index 4d40be04b..5057c8ff4 100644 --- a/openmc/capi/material.py +++ b/openmc/capi/material.py @@ -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)) diff --git a/openmc/cmfd.py b/openmc/cmfd.py index c13b6991d..03bc22eae 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -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 diff --git a/openmc/deplete/integrator/__init__.py b/openmc/deplete/integrator/__init__.py index cf8caffdf..db906b3c1 100644 --- a/openmc/deplete/integrator/__init__.py +++ b/openmc/deplete/integrator/__init__.py @@ -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 * diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index ac519cf87..42024863a 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -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 - `_. This algorithm is mathematically - defined as: + `_. + + "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) diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py new file mode 100644 index 000000000..0ba86d585 --- /dev/null +++ b/openmc/deplete/integrator/celi.py @@ -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 `_. + + "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] diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py new file mode 100644 index 000000000..e93b22d5c --- /dev/null +++ b/openmc/deplete/integrator/cf4.py @@ -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 + `_. + 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)) diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/integrator/cram.py index 85954603a..8a8940df8 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/integrator/cram.py @@ -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) diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py new file mode 100644 index 000000000..3789f3698 --- /dev/null +++ b/openmc/deplete/integrator/epc_rk4.py @@ -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)) diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py new file mode 100644 index 000000000..9d221e877 --- /dev/null +++ b/openmc/deplete/integrator/leqi.py @@ -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 `_. + + "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)) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 969144f68..f670a125e 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -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 diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py new file mode 100644 index 000000000..4f34bace6 --- /dev/null +++ b/openmc/deplete/integrator/si_celi.py @@ -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 `_. + + Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis + `_. + + 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] diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py new file mode 100644 index 000000000..05002880a --- /dev/null +++ b/openmc/deplete/integrator/si_leqi.py @@ -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 `_. + + Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis + `_. + + 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)) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 754e22f7d..66cac392d 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -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() diff --git a/openmc/model/model.py b/openmc/model/model.py index 28d19713c..b6a89792d 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -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.""" diff --git a/src/api.F90 b/src/api.F90 index b170ac338..b3fdfc42b 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -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() diff --git a/src/bremsstrahlung.cpp b/src/bremsstrahlung.cpp index 1ba1073a5..ce699a3f4 100644 --- a/src/bremsstrahlung.cpp +++ b/src/bremsstrahlung.cpp @@ -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(ParticleType::photon) - 1; + int photon = static_cast(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(ParticleType::positron)) { mat = &model::materials[p.material -1]->ttb_->positron; diff --git a/src/constants.F90 b/src/constants.F90 index 45ad98100..5e6b8629b 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -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 :: & diff --git a/src/cross_sections.cpp b/src/cross_sections.cpp index ed963b23f..23af2f624 100644 --- a/src/cross_sections.cpp +++ b/src/cross_sections.cpp @@ -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 // for getenv +#include 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>& nuc_temps, + const std::vector>& thermal_temps) +{ + std::unordered_set 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 nuclide_names(data::nuclide_map.size()); + std::vector 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( + 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(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(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( + 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(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(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(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 diff --git a/src/dagmc.cpp b/src/dagmc.cpp index e6ccee24d..1acef1262 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -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 #include #include +#include + +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 << "\n"; + ss << "\n"; + const auto& mat_lib = uwuw.material_library; + // write materials + for (auto mat : mat_lib) { ss << mat.second.openmc("atom"); } + // write footer + ss << ""; + 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 prop_keywords; - prop_keywords.push_back("mat"); - prop_keywords.push_back("boundary"); - - std::map ph; - model::DAG->parse_properties(prop_keywords, ph, ":"); + // parse model metadata + dagmcMetaData DMD(model::DAG); + if (using_uwuw) { + DMD.load_property_data(); + } + std::vector keywords {"temp", "mat", "density", "boundary"}; + std::map 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 diff --git a/src/dagmc_header.F90 b/src/dagmc_header.F90 index 9895791ac..4a97f7f0c 100644 --- a/src/dagmc_header.F90 +++ b/src/dagmc_header.F90 @@ -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 diff --git a/src/finalize.cpp b/src/finalize.cpp index 2a9f5923e..e67708ee1 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -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; diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index eca9b6c13..9073c511d 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -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>& nuc_temps, + std::vector>& 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>& nuc_temps, + std::vector>& 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() { diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 953360c0f..1ac6ba423 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -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 !=============================================================================== diff --git a/src/initialize.cpp b/src/initialize.cpp index 3a4a3a480..69986fdc0 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef _OPENMP #include @@ -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 diff --git a/src/input_xml.F90 b/src/input_xml.F90 index e1aa1b503..d714f57eb 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -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 - 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 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 - 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 - 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 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 TAG FOR S(a,b) DATA - if (run_CE) then - ! Get pointer list to XML - 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 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 diff --git a/src/list_header.F90 b/src/list_header.F90 deleted file mode 100644 index 383607834..000000000 --- a/src/list_header.F90 +++ /dev/null @@ -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 diff --git a/src/material.cpp b/src/material.cpp index 53dc6779c..51b273ca9 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -1,7 +1,8 @@ #include "openmc/material.h" -#include // for min, max +#include // for min, max, sort, fill #include +#include #include #include @@ -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 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 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 + auto node_macros = node.children("macroscopic"); + int num_macros = std::distance(node_macros.begin(), node_macros.end()); + + std::vector names; + std::vector 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 element + + std::vector iso_lab; + if (check_for_node(node, "isotropic")) { + iso_lab = get_node_array(node, "isotropic"); + } + + // ======================================================================== + // COPY NUCLIDES TO ARRAYS IN MATERIAL + + // allocate arrays in Material object + auto n = names.size(); + nuclide_.reserve(n); + atom_density_ = xt::empty({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 TAG FOR THERMAL SCATTERING DATA + if (settings::run_CE) { + // Loop over elements + + std::vector 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 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 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(ParticleType::neutron)) { + this->calculate_neutron_xs(p); + } else if (p.type == static_cast(ParticleType::photon)) { + this->calculate_photon_xs(p); + } +} + +void Material::calculate_neutron_xs(const Particle& p) const +{ + int neutron = static_cast(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(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 nuc_names; + std::vector macro_names; + std::vector 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 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(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 atom_density = xt::zeros({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({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(); diff --git a/src/material_header.F90 b/src/material_header.F90 index 0a1409612..81baeb861 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -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 diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 85107cbf9..43554f573 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -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 macro_xs; void Mgxs::init(const std::string& in_name, double in_awr, const std::vector& 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& in_polar, const std::vector& 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& temperature, - double tolerance, std::vector& temps_to_read, int& order_dim, int& method) +Mgxs::metadata_from_hdf5(hid_t xs_id, const std::vector& temperature, + std::vector& 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& 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& temperature) { // Call generic data gathering routine (will populate the metadata) int order_data; std::vector 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& mat_kTs, - const std::vector& micros, const std::vector& atom_densities, - double tolerance, int& method) + const std::vector& micros, const std::vector& 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& mat_kTs, std::vector 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& mat_kTs, std::vector micro_t(micros.size(), 0); std::vector 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& 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 interp(micros.size()); std::vector temp_indices(micros.size()); diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 deleted file mode 100644 index fee40ebcf..000000000 --- a/src/mgxs_data.F90 +++ /dev/null @@ -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 diff --git a/src/mgxs_interface.F90 b/src/mgxs_interface.F90 index 77f629b80..88b3c20e0 100644 --- a/src/mgxs_interface.F90 +++ b/src/mgxs_interface.F90 @@ -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(:) diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index 710d76a52..df08a6bb1 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -1,10 +1,19 @@ #include "openmc/mgxs_interface.h" #include +#include +#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 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 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> nuc_temps(data::nuclide_map.size()); + std::vector> 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 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 already_read; + + // Build vector of nuclide names + std::vector 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& 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 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_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> get_mat_kTs() { - if (n_temps > 0) { - // // Convert temps to a vector - std::vector temperature(temps, temps + n_temps); + std::vector> kTs(model::materials.size()); - // Convert atom_densities to a vector - std::vector 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_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; } //============================================================================== diff --git a/src/multipole_header.F90 b/src/multipole_header.F90 deleted file mode 100644 index f621b2e1c..000000000 --- a/src/multipole_header.F90 +++ /dev/null @@ -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 diff --git a/src/nuclide.cpp b/src/nuclide.cpp index 075e6f55b..b64a1681a 100644 --- a/src/nuclide.cpp +++ b/src/nuclide.cpp @@ -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 energy_min {0.0, 0.0}; std::array energy_max {INFTY, INFTY}; std::vector> nuclides; +std::unordered_map 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& 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 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(ParticleType::neutron) - 1; + int neutron = static_cast(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 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 temperature; + int i_nuclide = data::nuclides.size(); + data::nuclides.push_back(std::make_unique( + 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(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(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(); diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 75254cda1..78d436bdd 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -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 diff --git a/src/output.F90 b/src/output.F90 index 0a3781599..ea06645a8 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -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() diff --git a/src/particle.cpp b/src/particle.cpp index c8a753801..e5a38a983 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -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) { diff --git a/src/particle_header.F90 b/src/particle_header.F90 index db2317f4b..268fbb285 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -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 diff --git a/src/photon.cpp b/src/photon.cpp index e9c81af12..0dff1d47f 100644 --- a/src/photon.cpp +++ b/src/photon.cpp @@ -28,6 +28,7 @@ namespace data { xt::xtensor compton_profile_pz; std::vector elements; +std::unordered_map 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(ParticleType::photon) - 1; + int photon = static_cast(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(ParticleType::photon) - 1; + int photon = static_cast(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); diff --git a/src/photon_header.F90 b/src/photon_header.F90 index d077b72a2..f5d9aad66 100644 --- a/src/photon_header.F90 +++ b/src/photon_header.F90 @@ -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) diff --git a/src/physics.cpp b/src/physics.cpp index 90f0708f3..4d0fbf27b 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -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(ParticleType::photon) - 1; + int photon = static_cast(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(ParticleType::neutron) - 1; + constexpr int neutron = static_cast(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(ParticleType::neutron) - 1; + constexpr int neutron = static_cast(ParticleType::neutron); if (site->E < data::energy_max[neutron]) break; // check for large number of resamples diff --git a/src/physics_mg.cpp b/src/physics_mg.cpp index 1d475aaa7..ab5232c2a 100644 --- a/src/physics_mg.cpp +++ b/src/physics_mg.cpp @@ -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, diff --git a/src/reaction.cpp b/src/reaction.cpp index 918d61fea..4e418e7fe 100644 --- a/src/reaction.cpp +++ b/src/reaction.cpp @@ -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(rx->products_[product - 1].particle_); } void reaction_product_sample(Reaction* rx, int product, double E_in, double* E_out, double* mu) diff --git a/src/sab_header.F90 b/src/sab_header.F90 deleted file mode 100644 index 277b952dc..000000000 --- a/src/sab_header.F90 +++ /dev/null @@ -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 diff --git a/src/scattdata.cpp b/src/scattdata.cpp index 21e7f9df7..a8f63d0f6 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -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& 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 diff --git a/src/set_header.F90 b/src/set_header.F90 deleted file mode 100644 index a5f57c1d5..000000000 --- a/src/set_header.F90 +++ /dev/null @@ -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 diff --git a/src/settings.cpp b/src/settings.cpp index 0457ea13f..06410a424 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -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 diff --git a/src/simulation.F90 b/src/simulation.F90 index f00b58ed1..3de296f9d 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -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 diff --git a/src/simulation.cpp b/src/simulation.cpp index 4565a6391..fbbcca9da 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -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 diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index b72377f85..218473a06 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -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 diff --git a/src/source.cpp b/src/source.cpp index bfa119823..3997c3cc0 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -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(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 diff --git a/src/state_point.F90 b/src/state_point.F90 index 72c2e090b..163bc1121 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -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", & diff --git a/src/summary.F90 b/src/summary.F90 deleted file mode 100644 index b16730052..000000000 --- a/src/summary.F90 +++ /dev/null @@ -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 diff --git a/src/summary.cpp b/src/summary.cpp index 398f304fb..1eeab5d04 100644 --- a/src/summary.cpp +++ b/src/summary.cpp @@ -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 nuc_names; + std::vector macro_names; + std::vector 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 diff --git a/src/tallies/filter_particle.cpp b/src/tallies/filter_particle.cpp index 27558c59b..b8bc32286 100644 --- a/src/tallies/filter_particle.cpp +++ b/src/tallies/filter_particle.cpp @@ -8,6 +8,7 @@ void ParticleFilter::from_xml(pugi::xml_node node) { particles_ = get_node_array(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 diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 9d45eae87..fde57084d 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -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 diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 60e339e2b..d0bfbb8da 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -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)) // & diff --git a/src/thermal.cpp b/src/thermal.cpp index fa6b73669..a7d0a19a5 100644 --- a/src/thermal.cpp +++ b/src/thermal.cpp @@ -26,6 +26,7 @@ namespace openmc { namespace data { std::vector> thermal_scatt; +std::unordered_map 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) { diff --git a/src/timer.cpp b/src/timer.cpp index 7dab802b0..8e5d5d481 100644 --- a/src/timer.cpp +++ b/src/timer.cpp @@ -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(); diff --git a/src/timer_header.F90 b/src/timer_header.F90 index 2064f7ae6..f1c89242e 100644 --- a/src/timer_header.F90 +++ b/src/timer_header.F90 @@ -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 diff --git a/src/tracking.F90 b/src/tracking.F90 index d16c368ea..d3c85bd79 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -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 diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index 5379a3b2e..55c872cb3 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -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 diff --git a/src/wmp.cpp b/src/wmp.cpp index 26361a7c8..f514b9e83 100644 --- a/src/wmp.cpp +++ b/src/wmp.cpp @@ -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 +#include 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 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(group); + close_group(group); + file_close(file); +} + } // namespace openmc diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 8abc1edfd..347793e5b 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -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 shape = {n_ang, energy_groups}; + std::vector shape {n_ang, n_g}; total = xt::zeros(shape); absorption = xt::zeros(shape); inverse_velocity = xt::zeros(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(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(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(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(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 temp_chi({n_ang, energy_groups}, 0.); + xt::xtensor 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 temp_nufiss({n_ang, energy_groups}, 0.); + xt::xtensor 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 temp_beta({n_ang, delayed_groups}, 0.); + xt::xtensor 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 temp_beta({n_ang, delayed_groups, energy_groups}, - 0.); + xt::xtensor 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 temp_chi_p({n_ang, energy_groups}, 0.); + xt::xtensor 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 temp_chi_d({n_ang, delayed_groups, energy_groups}, 0.); + xt::xtensor 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 temp_chi({n_ang, energy_groups}, 0.); + xt::xtensor 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 temp_matrix({n_ang, energy_groups, energy_groups}, 0.); + xt::xtensor 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 temp_beta({n_ang, delayed_groups}, 0.); + xt::xtensor temp_beta({n_ang, n_dg}, 0.); read_nd_vector(xsdata_grp, "beta", temp_beta, true); xt::xtensor 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 temp_beta({n_ang, delayed_groups, energy_groups}, 0.); + xt::xtensor temp_beta({n_ang, n_dg, n_g}, 0.); read_nd_vector(xsdata_grp, "beta", temp_beta, true); - xt::xtensor temp_beta_sum({n_ang, energy_groups}, 0.); + xt::xtensor 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 temp_matrix_p({n_ang, energy_groups, energy_groups}, 0.); + xt::xtensor 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 temp_matrix_d({n_ang, delayed_groups, energy_groups, - energy_groups}, 0.); + xt::xtensor 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 temp_matrix({n_ang, energy_groups, energy_groups}, 0.); + xt::xtensor 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 gmin({n_ang, energy_groups}, 0.); + size_t n_g = data::num_energy_groups; + xt::xtensor gmin({n_ang, n_g}, 0.); read_nd_vector(scatt_grp, "g_min", gmin, true); - xt::xtensor gmax({n_ang, energy_groups}, 0.); + xt::xtensor 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 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(scatter[a].get()), - legendre_to_tabular_points); + *static_cast(scatter[a].get())); scatter_format = final_scatter_format; } diff --git a/tests/regression_tests/dagmc/dagmc.h5m b/tests/regression_tests/dagmc/dagmc.h5m index 5f84286de..49c28c4f5 100644 Binary files a/tests/regression_tests/dagmc/dagmc.h5m and b/tests/regression_tests/dagmc/dagmc.h5m differ diff --git a/tests/regression_tests/dagmc/results_true.dat b/tests/regression_tests/dagmc/results_true.dat index fc13a9cda..36475c5bf 100644 --- a/tests/regression_tests/dagmc/results_true.dat +++ b/tests/regression_tests/dagmc/results_true.dat @@ -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 diff --git a/tests/regression_tests/dagmc/test.py b/tests/regression_tests/dagmc/test.py index 9766c9732..6c9be63bf 100644 --- a/tests/regression_tests/dagmc/test.py +++ b/tests/regression_tests/dagmc/test.py @@ -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]) diff --git a/tests/regression_tests/salphabeta/results_true.dat b/tests/regression_tests/salphabeta/results_true.dat index 2a99303f6..e1121bce5 100644 --- a/tests/regression_tests/salphabeta/results_true.dat +++ b/tests/regression_tests/salphabeta/results_true.dat @@ -1,2 +1,2 @@ k-combined: -8.447580E-01 1.806149E-02 +8.403447E-01 2.461538E-02 diff --git a/tests/regression_tests/uwuw/__init__.py b/tests/regression_tests/uwuw/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/uwuw/dagmc.h5m b/tests/regression_tests/uwuw/dagmc.h5m new file mode 100644 index 000000000..8fca1fff1 Binary files /dev/null and b/tests/regression_tests/uwuw/dagmc.h5m differ diff --git a/tests/regression_tests/uwuw/inputs_true.dat b/tests/regression_tests/uwuw/inputs_true.dat new file mode 100644 index 000000000..87ceb944d --- /dev/null +++ b/tests/regression_tests/uwuw/inputs_true.dat @@ -0,0 +1,23 @@ + + + eigenvalue + 100 + 5 + 0 + + + -4 -4 -4 4 4 4 + + + true + + + + + 1 + + + 1 + total + + diff --git a/tests/regression_tests/uwuw/results_true.dat b/tests/regression_tests/uwuw/results_true.dat new file mode 120000 index 000000000..3711895ab --- /dev/null +++ b/tests/regression_tests/uwuw/results_true.dat @@ -0,0 +1 @@ +../dagmc/results_true.dat \ No newline at end of file diff --git a/tests/regression_tests/uwuw/test.py b/tests/regression_tests/uwuw/test.py new file mode 100644 index 000000000..f6713c2e4 --- /dev/null +++ b/tests/regression_tests/uwuw/test.py @@ -0,0 +1,41 @@ +import openmc +import openmc.capi +from openmc.stats import Box +from openmc.material import Materials + +import pytest +from tests.testing_harness import PyAPITestHarness + +pytestmark = pytest.mark.skipif( + not openmc.capi._dagmc_enabled(), + reason="DAGMC CAD geometry is not enabled.") + +class UWUWTest(PyAPITestHarness): + + def _build_inputs(self): + model = openmc.model.Model() + + # settings + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 100 + + source = openmc.Source(space=Box([-4, -4, -4], + [ 4, 4, 4])) + model.settings.source = source + + model.settings.dagmc = True + + model.settings.export_to_xml() + + # tally + tally = openmc.Tally() + tally.scores = ['total'] + tally.filters = [openmc.CellFilter(1)] + model.tallies = [tally] + + model.tallies.export_to_xml() + +def test_uwuw(): + harness = UWUWTest('statepoint.5.h5') + harness.main() diff --git a/tests/unit_tests/test_deplete_celi.py b/tests/unit_tests/test_deplete_celi.py new file mode 100644 index 000000000..0b23b1a43 --- /dev/null +++ b/tests/unit_tests/test_deplete_celi.py @@ -0,0 +1,37 @@ +"""Regression tests for openmc.deplete.integrator.celi algorithm. + +These tests integrate a simple test problem described in dummy_geometry.py. +""" + +from pytest import approx +import openmc.deplete + +from tests import dummy_operator + + +def test_celi(run_in_tmpdir): + """Integral regression test of integrator algorithm using celi""" + + op = dummy_operator.DummyOperator() + op.output_dir = "test_integrator_regression" + + # Perform simulation using the celi algorithm + dt = [0.75, 0.75] + power = 1.0 + openmc.deplete.celi(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [1.82078767, 0.97122898] + s2 = [2.68441779, 0.05125966] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) diff --git a/tests/unit_tests/test_deplete_cf4.py b/tests/unit_tests/test_deplete_cf4.py new file mode 100644 index 000000000..784d228be --- /dev/null +++ b/tests/unit_tests/test_deplete_cf4.py @@ -0,0 +1,37 @@ +"""Regression tests for openmc.deplete.integrator.cf4 algorithm. + +These tests integrate a simple test problem described in dummy_geometry.py. +""" + +from pytest import approx +import openmc.deplete + +from tests import dummy_operator + + +def test_cf4(run_in_tmpdir): + """Integral regression test of integrator algorithm using CF4""" + + op = dummy_operator.DummyOperator() + op.output_dir = "test_integrator_regression" + + # Perform simulation using the cf4 algorithm + dt = [0.75, 0.75] + power = 1.0 + openmc.deplete.cf4(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [2.06101629, 1.37783588] + s2 = [2.57241318, 2.63731630] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) diff --git a/tests/unit_tests/test_deplete_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py new file mode 100644 index 000000000..dd15bf8e1 --- /dev/null +++ b/tests/unit_tests/test_deplete_epc_rk4.py @@ -0,0 +1,37 @@ +"""Regression tests for openmc.deplete.integrator.epc_rk4 algorithm. + +These tests integrate a simple test problem described in dummy_geometry.py. +""" + +from pytest import approx +import openmc.deplete + +from tests import dummy_operator + + +def test_epc_rk4(run_in_tmpdir): + """Integral regression test of integrator algorithm using epc_rk4""" + + op = dummy_operator.DummyOperator() + op.output_dir = "test_integrator_regression" + + # Perform simulation using the epc_rk4 algorithm + dt = [0.75, 0.75] + power = 1.0 + openmc.deplete.epc_rk4(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [2.01978516, 1.42038037] + s2 = [2.05246421, 3.06177191] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) diff --git a/tests/unit_tests/test_deplete_leqi.py b/tests/unit_tests/test_deplete_leqi.py new file mode 100644 index 000000000..a1a32e092 --- /dev/null +++ b/tests/unit_tests/test_deplete_leqi.py @@ -0,0 +1,37 @@ +"""Regression tests for openmc.deplete.integrator.leqi algorithm. + +These tests integrate a simple test problem described in dummy_geometry.py. +""" + +from pytest import approx +import openmc.deplete + +from tests import dummy_operator + + +def test_leqi(run_in_tmpdir): + """Integral regression test of integrator algorithm using leqi""" + + op = dummy_operator.DummyOperator() + op.output_dir = "test_integrator_regression" + + # Perform simulation using the leqi algorithm + dt = [0.75, 0.75] + power = 1.0 + openmc.deplete.leqi(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [1.82078767, 0.97122898] + s2 = [2.74526197, 0.23339915] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 745f2cce5..ec62064a4 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -166,3 +166,236 @@ def test_restart_cecm_predictor(run_in_tmpdir): assert y1[2] == approx(s2[0]) assert y2[2] == approx(s2[1]) + + +def test_restart_cf4(run_in_tmpdir): + """Integral regression test of integrator algorithm using CF4.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_cf4" + op.output_dir = output_dir + + # Perform simulation + dt = [0.75] + power = 1.0 + openmc.deplete.cf4(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation + openmc.deplete.cf4(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [2.06101629, 1.37783588] + s2 = [2.57241318, 2.63731630] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[3] == approx(s2[0]) + assert y2[3] == approx(s2[1]) + + +def test_restart_epc_rk4(run_in_tmpdir): + """Integral regression test of integrator algorithm using EPC-RK4.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_epc_rk4" + op.output_dir = output_dir + + # Perform simulation + dt = [0.75] + power = 1.0 + openmc.deplete.epc_rk4(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation + openmc.deplete.epc_rk4(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [2.01978516, 1.42038037] + s2 = [2.05246421, 3.06177191] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[3] == approx(s2[0]) + assert y2[3] == approx(s2[1]) + + +def test_restart_celi(run_in_tmpdir): + """Integral regression test of integrator algorithm using CELI.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_celi" + op.output_dir = output_dir + + # Perform simulation + dt = [0.75] + power = 1.0 + openmc.deplete.celi(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation + openmc.deplete.celi(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [1.82078767, 0.97122898] + s2 = [2.68441779, 0.05125966] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[3] == approx(s2[0]) + assert y2[3] == approx(s2[1]) + + +def test_restart_leqi(run_in_tmpdir): + """Integral regression test of integrator algorithm using LEQI.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_leqi" + op.output_dir = output_dir + + # Perform simulation + dt = [0.75] + power = 1.0 + openmc.deplete.leqi(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation + openmc.deplete.leqi(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [1.82078767, 0.97122898] + s2 = [2.74526197, 0.23339915] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[3] == approx(s2[0]) + assert y2[3] == approx(s2[1]) + +def test_restart_si_celi(run_in_tmpdir): + """Integral regression test of integrator algorithm using SI-CELI.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_si_celi" + op.output_dir = output_dir + + # Perform simulation + dt = [0.75] + power = 1.0 + openmc.deplete.si_celi(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation + openmc.deplete.si_celi(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [2.03325094, 1.16826254] + s2 = [2.69291933, 0.37907772] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[3] == approx(s2[0]) + assert y2[3] == approx(s2[1]) + + +def test_restart_si_leqi(run_in_tmpdir): + """Integral regression test of integrator algorithm using SI-LEQI.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_si_leqi" + op.output_dir = output_dir + + # Perform simulation + dt = [0.75] + power = 1.0 + openmc.deplete.si_leqi(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation + openmc.deplete.si_leqi(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [2.03325094, 1.16826254] + s2 = [2.92711288, 0.53753236] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[3] == approx(s2[0]) + assert y2[3] == approx(s2[1]) diff --git a/tests/unit_tests/test_deplete_si_celi.py b/tests/unit_tests/test_deplete_si_celi.py new file mode 100644 index 000000000..8e84bcba8 --- /dev/null +++ b/tests/unit_tests/test_deplete_si_celi.py @@ -0,0 +1,37 @@ +"""Regression tests for openmc.deplete.integrator.si_celi algorithm. + +These tests integrate a simple test problem described in dummy_geometry.py. +""" + +from pytest import approx +import openmc.deplete + +from tests import dummy_operator + + +def test_si_celi(run_in_tmpdir): + """Integral regression test of integrator algorithm using si_celi""" + + op = dummy_operator.DummyOperator() + op.output_dir = "test_integrator_regression" + + # Perform simulation using the si_celi algorithm + dt = [0.75, 0.75] + power = 1.0 + openmc.deplete.si_celi(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [2.03325094, 1.16826254] + s2 = [2.69291933, 0.37907772] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) diff --git a/tests/unit_tests/test_deplete_si_leqi.py b/tests/unit_tests/test_deplete_si_leqi.py new file mode 100644 index 000000000..4814d8ab8 --- /dev/null +++ b/tests/unit_tests/test_deplete_si_leqi.py @@ -0,0 +1,37 @@ +"""Regression tests for openmc.deplete.integrator.si_leqi algorithm. + +These tests integrate a simple test problem described in dummy_geometry.py. +""" + +from pytest import approx +import openmc.deplete + +from tests import dummy_operator + + +def test_si_leqi(run_in_tmpdir): + """Integral regression test of integrator algorithm using si_leqi""" + + op = dummy_operator.DummyOperator() + op.output_dir = "test_integrator_regression" + + # Perform simulation using the si_leqi algorithm + dt = [0.75, 0.75] + power = 1.0 + openmc.deplete.si_leqi(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Reference solution + s1 = [2.03325094, 1.16826254] + s2 = [2.92711288, 0.53753236] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1])