diff --git a/CMakeLists.txt b/CMakeLists.txt index 53aac68c3f..15c89142e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0 FATAL_ERROR) +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) project(openmc Fortran C CXX) # Setup output directories @@ -9,14 +9,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # Set module path set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) -#=============================================================================== -# Architecture specific definitions -#=============================================================================== - -if (${UNIX}) - add_definitions(-DUNIX) -endif() - #=============================================================================== # Command line options #=============================================================================== @@ -27,10 +19,7 @@ option(debug "Compile with debug flags" OFF) option(optimize "Turn on all compiler optimization flags" OFF) option(coverage "Compile with coverage analysis flags" OFF) option(mpif08 "Use Fortran 2008 MPI interface" OFF) - -# Maximum number of nested coordinates levels set(maxcoord 10 CACHE STRING "Maximum number of nested coordinate levels") -add_definitions(-DMAX_COORD=${maxcoord}) #=============================================================================== # MPI for distributed-memory parallelism @@ -39,17 +28,12 @@ add_definitions(-DMAX_COORD=${maxcoord}) set(MPI_ENABLED FALSE) if($ENV{FC} MATCHES "(mpi[^/]*|ftn)$") message("-- Detected MPI wrapper: $ENV{FC}") - add_definitions(-DOPENMC_MPI) set(MPI_ENABLED TRUE) - - # Get directory containing MPI wrapper - get_filename_component(MPI_DIR $ENV{FC} DIRECTORY) endif() # Check for Fortran 2008 MPI interface if(MPI_ENABLED AND mpif08) message("-- Using Fortran 2008 MPI bindings") - add_definitions(-DOPENMC_MPIF08) endif() #=============================================================================== @@ -77,7 +61,6 @@ if(HDF5_IS_PARALLEL) if(NOT MPI_ENABLED) message(FATAL_ERROR "Parallel HDF5 must be used with MPI.") endif() - add_definitions(-DPHDF5) message("-- Using parallel HDF5") endif() @@ -85,19 +68,15 @@ endif() # Set compile/link flags based on which compiler is being used #=============================================================================== -# Support for Fortran in FindOpenMP was added in CMake 3.1. To support lower -# versions, we manually add the flags. However, at some point in time, the -# manual logic can be removed in favor of the block below - -#if(NOT (CMAKE_VERSION VERSION_LESS 3.1)) -# if(openmp) -# find_package(OpenMP) -# if(OPENMP_FOUND) -# list(APPEND f90flags ${OpenMP_Fortran_FLAGS}) -# list(APPEND ldflags ${OpenMP_Fortran_FLAGS}) -# endif() -# endif() -#endif() +if(openmp) + # Requires CMake 3.1+ + find_package(OpenMP) + if(OPENMP_FOUND) + list(APPEND f90flags ${OpenMP_Fortran_FLAGS}) + list(APPEND cxxflags ${OpenMP_CXX_FLAGS}) + list(APPEND ldflags ${OpenMP_Fortran_FLAGS}) + endif() +endif() set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -125,10 +104,6 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU) list(REMOVE_ITEM f90flags -O2) list(APPEND f90flags -O3) endif() - if(openmp) - list(APPEND f90flags -fopenmp) - list(APPEND ldflags -fopenmp) - endif() if(coverage) list(APPEND f90flags -coverage) list(APPEND ldflags -coverage) @@ -149,10 +124,6 @@ elseif(CMAKE_Fortran_COMPILER_ID STREQUAL Intel) if(optimize) list(APPEND f90flags -O3) endif() - if(openmp) - list(APPEND f90flags -qopenmp) - list(APPEND ldflags -qopenmp) - endif() elseif(CMAKE_Fortran_COMPILER_ID STREQUAL PGI) # PGI Fortran compiler options @@ -187,10 +158,6 @@ elseif(CMAKE_Fortran_COMPILER_ID STREQUAL XL) list(REMOVE_ITEM f90flags -O2) list(APPEND f90flags -O3) endif() - if(openmp) - list(APPEND f90flags -qsmp=omp) - list(APPEND ldflags -qsmp=omp) - endif() elseif(CMAKE_Fortran_COMPILER_ID STREQUAL Cray) # Cray Fortran compiler options @@ -204,7 +171,7 @@ endif() if(CMAKE_C_COMPILER_ID STREQUAL GNU) # GCC compiler options - list(APPEND cflags -std=c99 -O2) + list(APPEND cflags -O2) if(debug) list(REMOVE_ITEM cflags -O2) list(APPEND cflags -g -Wall -pedantic -fbounds-check) @@ -222,7 +189,6 @@ if(CMAKE_C_COMPILER_ID STREQUAL GNU) elseif(CMAKE_C_COMPILER_ID STREQUAL Intel) # Intel compiler options - list(APPEND cflags -std=c99) if(debug) list(APPEND cflags -g -w3 -ftrapuv -fp-stack-check -O0) endif() @@ -235,7 +201,6 @@ elseif(CMAKE_C_COMPILER_ID STREQUAL Intel) elseif(CMAKE_C_COMPILER_ID MATCHES Clang) # Clang options - list(APPEND cflags -std=c99) if(debug) list(APPEND cflags -g -O0 -ftrapv) endif() @@ -257,9 +222,6 @@ if(optimize) list(REMOVE_ITEM cxxflags -O2) list(APPEND cxxflags -O3) endif() -if(openmp) - list(APPEND cxxflags -fopenmp) -endif() # Show flags being used message(STATUS "Fortran flags: ${f90flags}") @@ -267,26 +229,12 @@ message(STATUS "C flags: ${cflags}") message(STATUS "C++ flags: ${cxxflags}") message(STATUS "Linker flags: ${ldflags}") -#=============================================================================== -# git SHA1 hash -#=============================================================================== - -execute_process(COMMAND git rev-parse HEAD - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - RESULT_VARIABLE GIT_SHA1_SUCCESS - OUTPUT_VARIABLE GIT_SHA1 - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) -if(GIT_SHA1_SUCCESS EQUAL 0) - add_definitions(-DGIT_SHA1="${GIT_SHA1}") -endif() - #=============================================================================== # pugixml library #=============================================================================== -add_library(pugixml src/pugixml/pugixml_c.cpp src/pugixml/pugixml.cpp) -add_library(pugixml_fortran src/pugixml/pugixml_f.F90) -target_link_libraries(pugixml_fortran pugixml) +add_library(pugixml vendor/pugixml/pugixml.cpp) +target_include_directories(pugixml PUBLIC vendor/pugixml/) #=============================================================================== # RPATH information @@ -295,7 +243,7 @@ target_link_libraries(pugixml_fortran pugixml) # This block of code ensures that dynamic libraries can be found via the RPATH # whether the executable is the original one from the build directory or the # installed one in CMAKE_INSTALL_PREFIX. Ref: -# https://cmake.org/Wiki/CMake_RPATH_handling#Always_full_RPATH +# https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling # use, i.e. don't skip the full RPATH for the build tree set(CMAKE_SKIP_BUILD_RPATH FALSE) @@ -317,17 +265,20 @@ if("${isSystemDir}" STREQUAL "-1") endif() #=============================================================================== -# Build faddeeva library +# faddeeva library #=============================================================================== -add_library(faddeeva STATIC src/faddeeva/Faddeeva.c) +add_library(faddeeva STATIC vendor/faddeeva/Faddeeva.c) +target_compile_options(faddeeva PRIVATE ${cflags}) +set_target_properties(faddeeva PROPERTIES + C_STANDARD 99 + C_STANDARD_REQUIRED ON) #=============================================================================== -# List source files. Define the libopenmc and the OpenMC executable +# libopenmc #=============================================================================== -set(program "openmc") -set(LIBOPENMC_FORTRAN_SRC +add_library(libopenmc SHARED src/algorithm.F90 src/angle_distribution.F90 src/angleenergy_header.F90 @@ -344,7 +295,6 @@ set(LIBOPENMC_FORTRAN_SRC src/dict_header.F90 src/distribution_multivariate.F90 src/distribution_univariate.F90 - src/doppler.F90 src/eigenvalue.F90 src/endf.F90 src/endf_header.F90 @@ -363,7 +313,7 @@ set(LIBOPENMC_FORTRAN_SRC src/mesh_header.F90 src/message_passing.F90 src/mgxs_data.F90 - src/mgxs_header.F90 + src/mgxs_interface.F90 src/multipole_header.F90 src/nuclide_header.F90 src/output.F90 @@ -376,11 +326,11 @@ set(LIBOPENMC_FORTRAN_SRC src/plot_header.F90 src/product_header.F90 src/progress_header.F90 + src/pugixml/pugixml_f.F90 src/random_lcg.F90 src/reaction_header.F90 src/relaxng src/sab_header.F90 - src/scattdata_header.F90 src/secondary_correlated.F90 src/secondary_kalbach.F90 src/secondary_nbody.F90 @@ -430,8 +380,6 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/tally_header.F90 src/tallies/trigger.F90 src/tallies/trigger_header.F90 -) -set(LIBOPENMC_CXX_SRC src/cell.cpp src/initialize.cpp src/finalize.cpp @@ -440,53 +388,71 @@ set(LIBOPENMC_CXX_SRC src/lattice.cpp src/math_functions.cpp src/message_passing.cpp + src/mgxs.cpp + src/mgxs_interface.cpp src/plot.cpp + src/pugixml/pugixml_c.cpp src/random_lcg.cpp + src/scattdata.cpp src/simulation.cpp src/state_point.cpp + src/string_functions.cpp src/surface.cpp src/xml_interface.cpp - src/pugixml/pugixml.cpp) -add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) + src/xsdata.cpp) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc - PUBLIC_HEADER include/openmc.h) -add_executable(${program} src/main.cpp) + PUBLIC_HEADER include/openmc.h + LINKER_LANGUAGE Fortran) -#=============================================================================== -# Add compiler/linker flags -#=============================================================================== - -set_property(TARGET ${program} libopenmc pugixml_fortran - PROPERTY LINKER_LANGUAGE Fortran) - -target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) - -# The executable and the faddeeva package use only one language. They can be -# set via target_compile_options which accepts a list. -target_compile_options(${program} PUBLIC ${cxxflags}) -target_compile_options(faddeeva PRIVATE ${cflags}) +target_include_directories(libopenmc + PUBLIC include + PRIVATE ${HDF5_INCLUDE_DIRS}) # The libopenmc library has both F90 and C++ so the compile flags must be set -# file-by-file via set_source_file_properties. The compile flags must first be -# converted from lists to strings. -string(REPLACE ";" " " f90flags "${f90flags}") -string(REPLACE ";" " " cxxflags "${cxxflags}") -set_source_files_properties(${LIBOPENMC_FORTRAN_SRC} PROPERTIES COMPILE_FLAGS - ${f90flags}) -set_source_files_properties(${LIBOPENMC_CXX_SRC} PROPERTIES COMPILE_FLAGS - ${cxxflags}) +# differently depending on the language. The $ generator +# expression was added in CMake 3.3 +target_compile_options(libopenmc PRIVATE + $<$:${f90flags}> + $<$:${cxxflags}>) -# Add HDF5 library directories to link line with -L -foreach(LIBDIR ${HDF5_LIBRARY_DIRS}) - list(APPEND ldflags "-L${LIBDIR}") -endforeach() +target_compile_definitions(libopenmc PRIVATE -DMAX_COORD=${maxcoord}) +if (UNIX) + # Used in progress_header.F90 for calling check_isatty + target_compile_definitions(libopenmc PRIVATE -DUNIX) +endif() +if (HDF5_IS_PARALLEL) + target_compile_definitions(libopenmc PRIVATE -DPHDF5) +endif() +if (MPI_ENABLED) + target_compile_definitions(libopenmc PUBLIC -DOPENMC_MPI) + if (mpif08) + target_compile_definitions(libopenmc PRIVATE -DOPENMC_MPIF08) + endif() +endif() + +# Set git SHA1 hash as a compile definition +execute_process(COMMAND git rev-parse HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SHA1_SUCCESS + OUTPUT_VARIABLE GIT_SHA1 + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +if(GIT_SHA1_SUCCESS EQUAL 0) + target_compile_definitions(libopenmc PRIVATE -DGIT_SHA1="${GIT_SHA1}") +endif() # target_link_libraries treats any arguments starting with - but not -l as # linker flags. Thus, we can pass both linker flags and libraries together. -target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml_fortran +target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml faddeeva) -target_link_libraries(${program} ${ldflags} libopenmc) + +#=============================================================================== +# openmc executable +#=============================================================================== + +add_executable(openmc src/main.cpp) +target_compile_options(openmc PRIVATE ${cxxflags}) +target_link_libraries(openmc libopenmc) #=============================================================================== # Python package @@ -502,7 +468,7 @@ add_custom_command(TARGET libopenmc POST_BUILD # Install executable, scripts, manpage, license #=============================================================================== -install(TARGETS ${program} libopenmc +install(TARGETS openmc libopenmc RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib @@ -510,4 +476,4 @@ install(TARGETS ${program} libopenmc ) install(DIRECTORY src/relaxng DESTINATION share/openmc) install(FILES man/man1/openmc.1 DESTINATION share/man/man1) -install(FILES LICENSE DESTINATION "share/doc/${program}" RENAME copyright) +install(FILES LICENSE DESTINATION "share/doc/openmc" RENAME copyright) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..d29d42adc4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at openmc@anl.gov. All complaints will +be reviewed and investigated and will result in a response that is deemed +necessary and appropriate to the circumstances. The project team is obligated to +maintain confidentiality with regard to the reporter of an incident. However, +note that some project team members may have a legal obligation to report +certain forms of harassment because of their affiliation (for example, staff and +faculty at universities in the United States). Further details of specific +enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..b633bb9295 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing to OpenMC + +Welcome, and thank you for considering contributing to OpenMC! We look forward +to welcoming new members to the community and will do our best to help you get +up to speed. + +## Code of Conduct + +Participants in the OpenMC project are expected to follow and uphold the [Code +of Conduct](CODE_OF_CONDUCT.md). Please report any unacceptable behavior to +openmc@anl.gov. + +## Resources + +- [GitHub Repository](https://github.com/openmc-dev/openmc) +- [Documentation](http://openmc.readthedocs.io/en/latest) +- [User's Mailing List](openmc-users@googlegroups.com) +- [Developer's Mailing List](openmc-dev@googlegroups.com) +- [Slack Community](https://openmc.slack.com/signup) (If you don't see your + domain listed, contact openmc@anl.gov) + +## How to Report Bugs + +OpenMC is hosted on GitHub and all bugs are reported and tracked through the +[Issues](https://github.com/openmc-dev/openmc/issues) listed on GitHub. + +## How to Suggest Enhancements + +We welcome suggestions for new features or enhancements to the code and +encourage you to submit them as Issues on GitHub. However, it's important to +recognize that our development team is relatively small and does not have +unlimited time to devote to new feature suggestions. If you are interested in +working on the feature you are requesting, indicate so in the issue and the +development team will be happy to discuss it. + +## How to Submit Changes + +All changes to OpenMC happen through pull requests. For a full overview of the +process, see the developer's guide section on [Development +Workflow](http://openmc.readthedocs.io/en/latest/devguide/workflow.html). + +## Code Style + +Before you run off to make changes to the code, please have a look at our [style +guide](http://openmc.readthedocs.io/en/latest/devguide/styleguide.html), which +is used when reviewing new contributions. diff --git a/LICENSE b/LICENSE index a11dc44a27..cfa34033c2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2018 Massachusetts Institute of Technology +Copyright (c) 2011-2018 Massachusetts Institute of Technology and OpenMC contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md new file mode 100644 index 0000000000..57197c363e --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# OpenMC Monte Carlo Particle Transport Code + +[![License](https://img.shields.io/github/license/openmc-dev/openmc.svg)](http://openmc.readthedocs.io/en/latest/license.html) +[![Travis CI build status (Linux)](https://travis-ci.org/openmc-dev/openmc.svg?branch=develop)](https://travis-ci.org/openmc-dev/openmc) +[![Code Coverage](https://coveralls.io/repos/github/openmc-dev/openmc/badge.svg?branch=develop)](https://coveralls.io/github/openmc-dev/openmc?branch=develop) + +The OpenMC project aims to provide a fully-featured Monte Carlo particle +transport code based on modern methods. It is a constructive solid geometry, +continuous-energy transport code that uses HDF5 format cross sections. The +project started under the Computational Reactor Physics Group at MIT. + +Complete documentation on the usage of OpenMC is hosted on Read the Docs (both +for the [latest release](http://openmc.readthedocs.io/en/stable/) and +[developmental](http://openmc.readthedocs.io/en/latest/) version). If you are +interested in the project or would like to help and contribute, please send a +message to the OpenMC User's Group [mailing +list](https://groups.google.com/forum/?fromgroups=#!forum/openmc-users). + +## Installation + +Detailed [installation +instructions](http://openmc.readthedocs.io/en/stable/usersguide/install.html) +can be found in the User's Guide. + +## Citing + +If you use OpenMC in your research, please consider giving proper attribution by +citing the following publication: + +- Paul K. Romano, Nicholas E. Horelik, Bryan R. Herman, Adam G. Nelson, Benoit + Forget, and Kord Smith, "[OpenMC: A State-of-the-Art Monte Carlo Code for + Research and Development](https://doi.org/10.1016/j.anucene.2014.07.048)," + *Ann. Nucl. Energy*, **82**, 90--97 (2015). + +## Troubleshooting + +If you run into problems compiling, installing, or running OpenMC, first check +the [Troubleshooting +section](http://openmc.readthedocs.io/en/stable/usersguide/troubleshoot.html) in +the User's Guide. If you are not able to find a solution to your problem there, +please send a message to the User's Group [mailing +list](https://groups.google.com/forum/?fromgroups=#!forum/openmc-users). + +## Reporting Bugs + +OpenMC is hosted on GitHub and all bugs are reported and tracked through the +[Issues](https://github.com/openmc-dev/openmc/issues) feature on GitHub. However, +GitHub Issues should not be used for common troubleshooting purposes. If you are +having trouble installing the code or getting your model to run properly, you +should first send a message to the User's Group mailing list. If it turns out +your issue really is a bug in the code, an issue will then be created on +GitHub. If you want to request that a feature be added to the code, you may +create an Issue on github. + +## License + +OpenMC is distributed under the MIT/X +[license](http://openmc.readthedocs.io/en/stable/license.html). diff --git a/docs/source/conf.py b/docs/source/conf.py index eeecba23e4..be7e3a8d13 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -71,7 +71,7 @@ master_doc = 'index' # General information about the project. project = u'OpenMC' -copyright = u'2011-2018, Massachusetts Institute of Technology' +copyright = u'2011-2018, Massachusetts Institute of Technology and OpenMC contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -208,7 +208,7 @@ htmlhelp_basename = 'openmcdoc' # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'openmc.tex', u'OpenMC Documentation', - u'Massachusetts Institute of Technology', 'manual'), + u'OpenMC contributors', 'manual'), ] latex_elements = { diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index ccf6bf5217..e0e67a6d62 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -182,37 +182,83 @@ Always use C++-style comments (``//``) as opposed to C-style (``/**/``). (It is more difficult to comment out a large section of code that uses C-style comments.) -Header files should always use include guards with the following style (See -`SF.8 `_: - -.. code-block:: C++ - - #ifndef MODULE_NAME_H - #define MODULE_NAME_H - ... - content - ... - #endif // MODULE_NAME_H - Do not use C-style casting. Always use the C++-style casts ``static_cast``, ``const_cast``, or ``reinterpret_cast``. (See `ES.49 `_) +Source Files +------------ + +Use a ``.cpp`` suffix for code files and ``.h`` for header files. + +Header files should always use include guards with the following style (See +`SF.8 `_): + +.. code-block:: C++ + + #ifndef OPENMC_MODULE_NAME_H + #define OPENMC_MODULE_NAME_H + + namespace openmc { + ... + content + ... + } + + #endif // OPENMC_MODULE_NAME_H + +Avoid hidden dependencies by always including a related header file first, +followed by C/C++ library includes, other library includes, and then local +includes. For example: + +.. code-block:: C++ + + // foo.cpp + #include "foo.h" + + #include + #include + #include + + #include "hdf5.h" + #include "pugixml.hpp" + + #include "error.h" + #include "random_lcg.h" + Naming ------ -In general, write your code in lower-case. Having code in all caps does not -enhance code readability or otherwise. - Struct and class names should be CamelCase, e.g. ``HexLattice``. Functions (including member functions) should be lower-case with underscores, e.g. ``get_indices``. -Local variables, global variables, and struct/class attributes should be -lower-case with underscores (e.g. ``n_cells``) except for physics symbols that -are written differently by convention (e.g. ``E`` for energy). +Local variables, global variables, and struct/class member variables should be +lower-case with underscores (e.g., ``n_cells``) except for physics symbols that +are written differently by convention (e.g., ``E`` for energy). Data members of +classes (but not structs) additionally have trailing underscores (e.g., +``a_class_member_``). -Const variables should be in upper-case with underscores, e.g. ``SQRT_PI``. +Accessors and mutators (get and set functions) may be named like +variables. These often correspond to actual member variables, but this is not +required. For example, ``int count()`` and ``void set_count(int count)``. + +Variables declared constexpr or const that have static storage duration (exist +for the duration of the program) should be upper-case with underscores, +e.g., ``SQRT_PI``. + +Use C++-style declarator layout (see `NL.18 +`_): +pointer and reference operators in declarations should be placed adject to the +base type rather than the variable name. Avoid declaring multiple names in a +single declaration to avoid confusion: + +.. code-block:: C++ + + T* p; // good + T& p; // good + T *p; // bad + T* p, q; // misleading Curly braces ------------ @@ -280,6 +326,13 @@ the same line or omit the braces entirely. for (int i = 0; i < 5; i++) content(); +Documentation +------------- + +Classes, structs, and functions are to be annotated for the `Doxygen +`_ documentation generation tool. Use the +``\`` form of Doxygen commands, e.g., ``\brief`` instead of ``@brief``. + ------ Python ------ @@ -288,15 +341,17 @@ Style for Python code should follow PEP8_. Docstrings for functions and methods should follow numpydoc_ style. -Python code should work with both Python 2.7+ and Python 3.0+. +Python code should work with Python 3.4+. -Use of third-party Python packages should be limited to numpy_, scipy_, and -h5py_. Use of other third-party packages must be implemented as optional -dependencies rather than required dependencies. +Use of third-party Python packages should be limited to numpy_, scipy_, +matplotlib_, pandas_, and h5py_. Use of other third-party packages must be +implemented as optional dependencies rather than required dependencies. .. _C++ Core Guidelines: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines .. _PEP8: https://www.python.org/dev/peps/pep-0008/ .. _numpydoc: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt .. _numpy: http://www.numpy.org/ -.. _scipy: http://www.scipy.org/ +.. _scipy: https://www.scipy.org/ +.. _matplotlib: https://matplotlib.org/ +.. _pandas: https://pandas.pydata.org/ .. _h5py: http://www.h5py.org/ diff --git a/docs/source/devguide/user-input.rst b/docs/source/devguide/user-input.rst index 3e1ea1c380..3c41e74fcf 100644 --- a/docs/source/devguide/user-input.rst +++ b/docs/source/devguide/user-input.rst @@ -65,8 +65,8 @@ developer or send a message to the `developers mailing list`_. .. _property attribute: https://docs.python.org/3.6/library/functions.html#property .. _XML Schema Part 2: http://www.w3.org/TR/xmlschema-2/ .. _boolean: http://www.w3.org/TR/xmlschema-2/#boolean -.. _xml_interface module: https://github.com/mit-crpg/openmc/blob/develop/src/xml_interface.F90 -.. _input_xml module: https://github.com/mit-crpg/openmc/blob/develop/src/input_xml.F90 +.. _xml_interface module: https://github.com/openmc-dev/openmc/blob/develop/src/xml_interface.F90 +.. _input_xml module: https://github.com/openmc-dev/openmc/blob/develop/src/input_xml.F90 .. _RELAX NG: http://relaxng.org/ .. _compact syntax: http://relaxng.org/compact-tutorial-20030326.html .. _trang: http://www.thaiopensource.com/relaxng/trang.html diff --git a/docs/source/devguide/workflow.rst b/docs/source/devguide/workflow.rst index 9b27fd655f..6201f429a0 100644 --- a/docs/source/devguide/workflow.rst +++ b/docs/source/devguide/workflow.rst @@ -55,7 +55,7 @@ Now that you understand the basic development workflow, let's discuss how an individual to contribute to development. Note that this would apply to both new features and bug fixes. The general steps for contributing are as follows: -1. Fork the main openmc repository from `mit-crpg/openmc`_. This will create a +1. Fork the main openmc repository from `openmc-dev/openmc`_. This will create a repository with the same name under your personal account. As such, you can commit to it as you please without disrupting other developers. @@ -74,7 +74,7 @@ features and bug fixes. The general steps for contributing are as follows: ensure that those changes are made on a different branch. 4. Issue a pull request from GitHub and select the *develop* branch of - mit-crpg/openmc as the target. + openmc-dev/openmc as the target. .. image:: ../_images/pullrequest.png @@ -87,7 +87,7 @@ features and bug fixes. The general steps for contributing are as follows: request page itself. 6. After the pull request has been thoroughly vetted, it is merged back into the - *develop* branch of mit-crpg/openmc. + *develop* branch of openmc-dev/openmc. Private Development ------------------- @@ -99,7 +99,7 @@ create a complete copy of the OpenMC repository (not a fork from GitHub). The private repository can then either be stored just locally or in conjunction with a private repository on Github (this requires a `paid plan`_). Alternatively, `Bitbucket`_ offers private repositories for free. If you want to merge some -changes you've made in your private repository back to mit-crpg/openmc +changes you've made in your private repository back to openmc-dev/openmc repository, simply follow the steps above with an extra step of pulling a branch from your private repository into a public fork. @@ -128,9 +128,9 @@ can interfere with virtual environments. .. _GitHub: https://github.com/ .. _git flow: http://nvie.com/git-model .. _valgrind: http://valgrind.org/ -.. _style guide: http://mit-crpg.github.io/openmc/devguide/styleguide.html +.. _style guide: http://openmc.readthedocs.io/en/latest/devguide/styleguide.html .. _pull request: https://help.github.com/articles/using-pull-requests -.. _mit-crpg/openmc: https://github.com/mit-crpg/openmc +.. _openmc-dev/openmc: https://github.com/openmc-dev/openmc .. _paid plan: https://github.com/plans .. _Bitbucket: https://bitbucket.org .. _ctest: http://www.cmake.org/cmake/help/v2.8.12/ctest.html diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index 89a1f1fe0b..bcb1b1ad8f 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -24,6 +24,7 @@ Basic Usage triso candu nuclear-data + nuclear-data-resonance-covariance ------------------------------------ Multi-Group Cross Section Generation diff --git a/docs/source/examples/nuclear-data-resonance-covariance.rst b/docs/source/examples/nuclear-data-resonance-covariance.rst new file mode 100644 index 0000000000..4b505c9a58 --- /dev/null +++ b/docs/source/examples/nuclear-data-resonance-covariance.rst @@ -0,0 +1,13 @@ +.. _notebook_nuclear_data_resonance_covariance: + +================================== +Nuclear Data: Resonance Covariance +================================== + +.. only:: html + + .. notebook:: ../../../examples/jupyter/nuclear-data-resonance-covariance.ipynb + +.. only:: latex + + IPython notebooks must be viewed in the online HTML documentation. diff --git a/docs/source/license.rst b/docs/source/license.rst index e9cba3d8a8..c0728e1a8c 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -4,7 +4,7 @@ License Agreement ================= -Copyright © 2011-2018 Massachusetts Institute of Technology +Copyright © 2011-2018 Massachusetts Institute of Technology and OpenMC contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docs/source/pythonapi/data.rst b/docs/source/pythonapi/data.rst index 7feaa8d608..e7af5e273e 100644 --- a/docs/source/pythonapi/data.rst +++ b/docs/source/pythonapi/data.rst @@ -79,6 +79,11 @@ Resonance Data openmc.data.MultiLevelBreitWigner openmc.data.ReichMoore openmc.data.RMatrixLimited + openmc.data.ResonanceCovariances + openmc.data.ResonanceCovarianceRange + openmc.data.SingleLevelBreitWignerCovariance + openmc.data.MultiLevelBreitWignerCovariance + openmc.data.ReichMooreCovariance openmc.data.ParticlePair openmc.data.SpinGroup openmc.data.Unresolved diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 7176b67be0..731bffde70 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -83,7 +83,7 @@ Installing from Source on Linux or Mac OS X ------------------------------------------- All OpenMC source code is hosted on `GitHub -`_. If you have `git +`_. If you have `git `_, the `gcc `_ compiler suite, `CMake `_, and `HDF5 `_ installed, you can download and install OpenMC be entering the following @@ -91,7 +91,7 @@ commands in a terminal: .. code-block:: sh - git clone https://github.com/mit-crpg/openmc.git + git clone https://github.com/openmc-dev/openmc.git cd openmc mkdir build && cd build cmake .. diff --git a/docs/source/releasenotes.rst b/docs/source/releasenotes.rst index 28af52f5f4..d5ea6c58cd 100644 --- a/docs/source/releasenotes.rst +++ b/docs/source/releasenotes.rst @@ -71,14 +71,14 @@ Bug Fixes - 0c6915_: Bugfix for generating thermal scattering data - 61ecb4_: Fix bugs in Python multipole objects -.. _937469: https://github.com/mit-crpg/openmc/commit/937469 -.. _a149ef: https://github.com/mit-crpg/openmc/commit/a149ef -.. _2c9b21: https://github.com/mit-crpg/openmc/commit/2c9b21 -.. _8047f6: https://github.com/mit-crpg/openmc/commit/8047f6 -.. _0beb4c: https://github.com/mit-crpg/openmc/commit/0beb4c -.. _f124be: https://github.com/mit-crpg/openmc/commit/f124be -.. _0c6915: https://github.com/mit-crpg/openmc/commit/0c6915 -.. _61ecb4: https://github.com/mit-crpg/openmc/commit/61ecb4 +.. _937469: https://github.com/openmc-dev/openmc/commit/937469 +.. _a149ef: https://github.com/openmc-dev/openmc/commit/a149ef +.. _2c9b21: https://github.com/openmc-dev/openmc/commit/2c9b21 +.. _8047f6: https://github.com/openmc-dev/openmc/commit/8047f6 +.. _0beb4c: https://github.com/openmc-dev/openmc/commit/0beb4c +.. _f124be: https://github.com/openmc-dev/openmc/commit/f124be +.. _0c6915: https://github.com/openmc-dev/openmc/commit/0c6915 +.. _61ecb4: https://github.com/openmc-dev/openmc/commit/61ecb4 ------------ Contributors diff --git a/docs/source/usersguide/beginners.rst b/docs/source/usersguide/beginners.rst index ec37d0825a..93d52429f9 100644 --- a/docs/source/usersguide/beginners.rst +++ b/docs/source/usersguide/beginners.rst @@ -153,9 +153,9 @@ and `Volume II`_. You may also find it helpful to review the following terms: .. _Reactor Concepts Manual: http://www.tayloredge.com/periodic/trivia/ReactorConcepts.pdf .. _Volume I: https://www.standards.doe.gov/standards-documents/1000/1019-bhdbk-1993-v1 .. _Volume II: https://www.standards.doe.gov/standards-documents/1000/1019-bhdbk-1993-v2 -.. _OpenMC source code: https://github.com/mit-crpg/openmc +.. _OpenMC source code: https://github.com/openmc-dev/openmc .. _GitHub: https://github.com/ -.. _bug reports: https://github.com/mit-crpg/openmc/issues +.. _bug reports: https://github.com/openmc-dev/openmc/issues .. _Neutron cross section: http://en.wikipedia.org/wiki/Neutron_cross_section .. _Effective multiplication factor: https://en.wikipedia.org/wiki/Nuclear_chain_reaction#Effective_neutron_multiplication_factor .. _Flux: http://en.wikipedia.org/wiki/Neutron_flux diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index f5b277a840..48dcd8c782 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -181,7 +181,7 @@ with GitHub since this involves setting up ssh_ keys. With git installed and setup, the following command will download the full source code from the GitHub repository:: - git clone https://github.com/mit-crpg/openmc.git + git clone https://github.com/openmc-dev/openmc.git By default, the cloned repository will be set to the development branch. To switch to the source of the latest stable release, run the following commands:: @@ -189,7 +189,7 @@ switch to the source of the latest stable release, run the following commands:: cd openmc git checkout master -.. _GitHub: https://github.com/mit-crpg/openmc +.. _GitHub: https://github.com/openmc-dev/openmc .. _git: https://git-scm.com .. _ssh: https://en.wikipedia.org/wiki/Secure_Shell diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index bbec016423..1210bc2820 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -213,7 +213,7 @@ The following tables show all valid scores: +----------------------+---------------------------------------------------+ |nu-fission |Total production of neutrons due to fission. | +----------------------+---------------------------------------------------+ - |nu-scatter, |This score is similar in functionality to the | + |nu-scatter |This score is similar in functionality to the | | |``scatter`` score except the total production of | | |neutrons due to scattering is scored vice simply | | |the scattering rate. This accounts for | diff --git a/examples/jupyter/nuclear-data-resonance-covariance.ipynb b/examples/jupyter/nuclear-data-resonance-covariance.ipynb new file mode 100644 index 0000000000..2c44b6f7fa --- /dev/null +++ b/examples/jupyter/nuclear-data-resonance-covariance.ipynb @@ -0,0 +1,964 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this notebook we will explore features of the Python API that allow us to import and manipulate resonance covariance data. A full description of the ENDF-VI and ENDF-VII formats can be found in the [ENDF102 manual](https://www.oecd-nea.org/dbdata/data/manual-endf/endf102.pdf)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import os\n", + "from pprint import pprint\n", + "import shutil\n", + "import subprocess\n", + "import urllib.request\n", + "\n", + "import h5py\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import openmc.data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ENDF: Resonance Covariance Data\n", + "\n", + "Let's download the ENDF/B-VII.1 evaluation for $^{157}$Gd and load it in:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Download ENDF file\n", + "url = 'https://t2.lanl.gov/nis/data/data/ENDFB-VII.1-neutron/Gd/157'\n", + "filename, headers = urllib.request.urlretrieve(url, 'gd157.endf')\n", + "\n", + "# Load into memory\n", + "gd157_endf = openmc.data.IncidentNeutron.from_endf(filename, covariance=True)\n", + "gd157_endf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can access the parameters contained within File 32 in a similar manner to the File 2 parameters from before. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
energyJneutronWidthcaptureWidthfissionWidthAfissionWidthBL
00.03142.00.0004740.10720.00.00
12.82502.00.0003450.09700.00.00
216.24001.00.0004000.09100.00.00
316.77002.00.0128000.08050.00.00
420.56002.00.0113600.08800.00.00
\n", + "
" + ], + "text/plain": [ + " energy J neutronWidth captureWidth fissionWidthA fissionWidthB L\n", + "0 0.0314 2.0 0.000474 0.1072 0.0 0.0 0\n", + "1 2.8250 2.0 0.000345 0.0970 0.0 0.0 0\n", + "2 16.2400 1.0 0.000400 0.0910 0.0 0.0 0\n", + "3 16.7700 2.0 0.012800 0.0805 0.0 0.0 0\n", + "4 20.5600 2.0 0.011360 0.0880 0.0 0.0 0" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gd157_endf.resonance_covariance.ranges[0].parameters[:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The newly created object will contain multiple resonance regions within `gd157_endf.resonance_covariance.ranges`. We can access the full covariance matrix from File 32 for a given range by:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "covariance = gd157_endf.resonance_covariance.ranges[0].covariance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This covariance matrix currently only stores the upper triangular portion as covariance matrices are symmetric. Plotting the covariance matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(covariance,cmap='seismic',vmin=-0.08, vmax=0.08)\n", + "plt.colorbar()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The correlation matrix can be constructed using the covariance matrix and also give some insight into the relations among the parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "corr = np.zeros([len(covariance),len(covariance)])\n", + "for i in range(len(covariance)):\n", + " for j in range(len(covariance)):\n", + " corr[i, j]=covariance[i, j]/covariance[i, i]**(0.5)/covariance[j, j]**(0.5)\n", + "plt.imshow(corr, cmap='seismic',vmin=-1.0, vmax=1.0)\n", + "plt.colorbar()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sampling and Reconstruction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The covariance module also has the ability to sample a new set of parameters using the covariance matrix. Currently the sampling uses numpy.multivariate_normal(). Because parameters are assumed to have a multivariate normal distribution this method doesn't not currently guarantee that sampled parameters will be positive." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/icmeyer/openmc/openmc/data/resonance_covariance.py:233: UserWarning: Sampling routine does not guarantee positive values for parameters. This can lead to undefined behavior in the reconstruction routine.\n", + " warnings.warn(warn_str)\n" + ] + }, + { + "data": { + "text/plain": [ + "openmc.data.resonance.ReichMoore" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rm_resonance = gd157_endf.resonances.ranges[0]\n", + "n_samples = 5\n", + "samples = gd157_endf.resonance_covariance.ranges[0].sample(n_samples)\n", + "type(samples[0])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The sampling routine requires the incorporation of the `openmc.data.ResonanceRange` for the same resonance range object. This allows each sample itself to be its own `openmc.data.ResonanceRange` with a new set of parameters. Looking at some of the sampled parameters below:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sample 1\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
energyLJneutronWidthcaptureWidthfissionWidthAfissionWidthB
00.03115102.00.0004710.1070450.00.0
12.82092102.00.0003340.0988850.00.0
216.21740801.00.0004530.0683850.00.0
316.77102102.00.0136700.0712780.00.0
420.55968502.00.0106090.0975460.00.0
\n", + "
" + ], + "text/plain": [ + " energy L J neutronWidth captureWidth fissionWidthA fissionWidthB\n", + "0 0.031151 0 2.0 0.000471 0.107045 0.0 0.0\n", + "1 2.820921 0 2.0 0.000334 0.098885 0.0 0.0\n", + "2 16.217408 0 1.0 0.000453 0.068385 0.0 0.0\n", + "3 16.771021 0 2.0 0.013670 0.071278 0.0 0.0\n", + "4 20.559685 0 2.0 0.010609 0.097546 0.0 0.0" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print('Sample 1')\n", + "samples[0].parameters[:5]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sample 2\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
energyLJneutronWidthcaptureWidthfissionWidthAfissionWidthB
00.03383802.00.0004800.1033250.00.0
12.82237002.00.0003670.0915990.00.0
216.24396801.00.0003110.0896550.00.0
316.77599302.00.0130500.0844760.00.0
420.56169002.00.0111630.0868020.00.0
\n", + "
" + ], + "text/plain": [ + " energy L J neutronWidth captureWidth fissionWidthA fissionWidthB\n", + "0 0.033838 0 2.0 0.000480 0.103325 0.0 0.0\n", + "1 2.822370 0 2.0 0.000367 0.091599 0.0 0.0\n", + "2 16.243968 0 1.0 0.000311 0.089655 0.0 0.0\n", + "3 16.775993 0 2.0 0.013050 0.084476 0.0 0.0\n", + "4 20.561690 0 2.0 0.011163 0.086802 0.0 0.0" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print('Sample 2')\n", + "samples[1].parameters[:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can reconstruct the cross section from the sampled parameters using the reconstruct method of `openmc.data.ResonanceRange`. For more on reconstruction see the Nuclear Data example notebook. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[,\n", + " ]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gd157_endf.resonances.ranges" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0,0.5,'Cross section (b)')" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "energy_range = [rm_resonance.energy_min, rm_resonance.energy_max]\n", + "energies = np.logspace(np.log10(energy_range[0]),\n", + " np.log10(energy_range[1]), 10000)\n", + "for sample in samples:\n", + " xs = sample.reconstruct(energies)\n", + " elastic_xs = xs[2]\n", + " plt.loglog(energies, elastic_xs)\n", + "plt.xlabel('Energy (eV)')\n", + "plt.ylabel('Cross section (b)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Subset Selection" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Another capability of the covariance module is selecting a subset of the resonance parameters and the corresponding subset of the covariance matrix. We can do this by specifying the value we want to discriminate and the bounds within one energy region. Selecting only resonances with J=2:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
energyLJneutronWidthcaptureWidthfissionWidthAfissionWidthB
00.031402.00.0004740.10720.00.0
12.825002.00.0003450.09700.00.0
316.770002.00.0128000.08050.00.0
420.560002.00.0113600.08800.00.0
521.650002.00.0003760.11400.00.0
\n", + "
" + ], + "text/plain": [ + " energy L J neutronWidth captureWidth fissionWidthA fissionWidthB\n", + "0 0.0314 0 2.0 0.000474 0.1072 0.0 0.0\n", + "1 2.8250 0 2.0 0.000345 0.0970 0.0 0.0\n", + "3 16.7700 0 2.0 0.012800 0.0805 0.0 0.0\n", + "4 20.5600 0 2.0 0.011360 0.0880 0.0 0.0\n", + "5 21.6500 0 2.0 0.000376 0.1140 0.0 0.0" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lower_bound = 2; # inclusive\n", + "upper_bound = 2; # inclusive\n", + "rm_res_cov_sub = gd157_endf.resonance_covariance.ranges[0].subset('J',[lower_bound,upper_bound])\n", + "rm_res_cov_sub.file2res.parameters[:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The subset method will also store the corresponding subset of the covariance matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(180, 180)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rm_res_cov_sub.covariance\n", + "gd157_endf.resonance_covariance.ranges[0].covariance.shape\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Checking the size of the new covariance matrix to be sure it was sampled properly: " + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of parameters\n", + "Original: 60\n", + "Subet: 36\n", + "Covariance Size\n", + "Original: (180, 180)\n", + "Subset: (108, 108)\n" + ] + } + ], + "source": [ + "old_n_parameters = gd157_endf.resonance_covariance.ranges[0].parameters.shape[0]\n", + "old_shape = gd157_endf.resonance_covariance.ranges[0].covariance.shape\n", + "new_n_parameters = rm_res_cov_sub.file2res.parameters.shape[0]\n", + "new_shape = rm_res_cov_sub.covariance.shape\n", + "print('Number of parameters\\nOriginal: '+str(old_n_parameters)+'\\nSubet: '+str(new_n_parameters)+'\\nCovariance Size\\nOriginal: '+str(old_shape)+'\\nSubset: '+str(new_shape))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And finally, we can sample from the subset as well" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/icmeyer/openmc/openmc/data/resonance_covariance.py:233: UserWarning: Sampling routine does not guarantee positive values for parameters. This can lead to undefined behavior in the reconstruction routine.\n", + " warnings.warn(warn_str)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
energyLJneutronWidthcaptureWidthfissionWidthAfissionWidthB
00.03106502.00.0004740.1079540.00.0
12.82380902.00.0003340.0982180.00.0
216.76918602.00.0139870.0729100.00.0
320.55664902.00.0108140.0987800.00.0
421.65459102.00.0003660.1176790.00.0
\n", + "
" + ], + "text/plain": [ + " energy L J neutronWidth captureWidth fissionWidthA fissionWidthB\n", + "0 0.031065 0 2.0 0.000474 0.107954 0.0 0.0\n", + "1 2.823809 0 2.0 0.000334 0.098218 0.0 0.0\n", + "2 16.769186 0 2.0 0.013987 0.072910 0.0 0.0\n", + "3 20.556649 0 2.0 0.010814 0.098780 0.0 0.0\n", + "4 21.654591 0 2.0 0.000366 0.117679 0.0 0.0" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "samples_sub = rm_res_cov_sub.sample(n_samples)\n", + "samples_sub[0].parameters[:5]" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/include/openmc.h b/include/openmc.h index 2c0f10eae0..03597e5a10 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -134,6 +134,7 @@ extern "C" { extern int32_t n_lattices; extern int32_t n_materials; extern int32_t n_meshes; + extern int n_nuclides; extern int64_t n_particles; extern int32_t n_plots; extern int32_t n_realizations; diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index f49d80bad8..eb5c857f71 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -59,7 +59,8 @@ Indicates the default path to a directory containing windowed multipole data if the user has not specified the tag in .I materials.xml\fP. .SH LICENSE -Copyright \(co 2011-2018 Massachusetts Institute of Technology. +Copyright \(co 2011-2018 Massachusetts Institute of Technology and OpenMC +contributors. .PP Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -79,7 +80,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. .SH REPORTING BUGS The OpenMC source code is hosted on GitHub at -https://github.com/mit-crpg/openmc. With a github account, you can submit issues +https://github.com/openmc-dev/openmc. With a github account, you can submit issues directly on the github repository that will then be reviewed by OpenMC developers. Alternatively, you can send a bug report to .I openmc-users@googlegroups.com\fP. diff --git a/openmc/data/__init__.py b/openmc/data/__init__.py index 7158e9fe3f..44c59628c9 100644 --- a/openmc/data/__init__.py +++ b/openmc/data/__init__.py @@ -27,5 +27,6 @@ from .urr import * from .library import * from .fission_energy import * from .resonance import * +from .resonance_covariance import * from .multipole import * from .grid import * diff --git a/openmc/data/endf.py b/openmc/data/endf.py index 0d1f402c2f..8a118f1695 100644 --- a/openmc/data/endf.py +++ b/openmc/data/endf.py @@ -71,6 +71,24 @@ def float_endf(s): return float(_ENDF_FLOAT_RE.sub(r'\1e\2', s)) +def _int_endf(s): + """Convert string to int. Used for INTG records where blank entries + indicate a 0. + + Parameters + ---------- + s : str + Integer or spaces + + Returns + ------- + integer + The number or 0 + """ + s = s.strip() + return int(s) if s else 0 + + def get_text_record(file_obj): """Return data from a TEXT record in an ENDF-6 file. @@ -250,6 +268,50 @@ def get_tab2_record(file_obj): return params, Tabulated2D(breakpoints, interpolation) +def get_intg_record(file_obj): + """ + Return data from an INTG record in an ENDF-6 file. Used to store the + covariance matrix in a compact format. + + Parameters + ---------- + file_obj : file-like object + ENDF-6 file to read from + + Returns + ------- + numpy.ndarray + The correlation matrix described in the INTG record + """ + # determine how many items are in list and NDIGIT + items = get_cont_record(file_obj) + ndigit = int(items[2]) + npar = int(items[3]) # Number of parameters + nlines = int(items[4]) # Lines to read + NROW_RULES = {2: 18, 3: 12, 4: 11, 5: 9, 6: 8} + nrow = NROW_RULES[ndigit] + + # read lines and build correlation matrix + corr = np.identity(npar) + for i in range(nlines): + line = file_obj.readline() + ii = _int_endf(line[:5]) - 1 # -1 to account for 0 indexing + jj = _int_endf(line[5:10]) - 1 + factor = 10**ndigit + for j in range(nrow): + if jj+j >= ii: + break + element = _int_endf(line[11+(ndigit+1)*j:11+(ndigit+1)*(j+1)]) + if element > 0: + corr[ii, jj] = (element+0.5)/factor + elif element < 0: + corr[ii, jj] = (element-0.5)/factor + + # Symmetrize the correlation matrix + corr = corr + corr.T - np.diag(corr.diagonal()) + return corr + + def get_evaluations(filename): """Return a list of all evaluations within an ENDF file. @@ -288,7 +350,7 @@ class Evaluation(object): Attributes ---------- info : dict - Miscallaneous information about the evaluation. + Miscellaneous information about the evaluation. target : dict Information about the target material, such as its mass, isomeric state, whether it's stable, and whether it's fissionable. diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 1cc0e38879..3917496b8e 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -24,6 +24,7 @@ from .njoy import make_ace from .product import Product from .reaction import Reaction, _get_photon_products_ace from . import resonance as res +from . import resonance_covariance as res_cov from .urr import ProbabilityTables import openmc.checkvalue as cv from openmc.mixin import EqualityMixin @@ -148,6 +149,8 @@ class IncidentNeutron(EqualityMixin): and the values are Reaction objects. resonances : openmc.data.Resonances or None Resonance parameters + resonance_covariance : openmc.data.ResonanceCovariance or None + Covariance for resonance parameters summed_reactions : collections.OrderedDict Contains summed cross sections, e.g., the total cross section. The keys are the MT values and the values are Reaction objects. @@ -228,6 +231,10 @@ class IncidentNeutron(EqualityMixin): def resonances(self): return self._resonances + @property + def resonance_covariance(self): + return self._resonance_covariance + @property def summed_reactions(self): return self._summed_reactions @@ -289,6 +296,12 @@ class IncidentNeutron(EqualityMixin): cv.check_type('resonances', resonances, res.Resonances) self._resonances = resonances + @resonance_covariance.setter + def resonance_covariance(self, resonance_covariance): + cv.check_type('resonance covariance', resonance_covariance, + res_cov.ResonanceCovariances) + self._resonance_covariance = resonance_covariance + @summed_reactions.setter def summed_reactions(self, summed_reactions): cv.check_type('summed reactions', summed_reactions, Mapping) @@ -744,7 +757,7 @@ class IncidentNeutron(EqualityMixin): return data @classmethod - def from_endf(cls, ev_or_filename): + def from_endf(cls, ev_or_filename, covariance=False): """Generate incident neutron continuous-energy data from an ENDF evaluation Parameters @@ -753,6 +766,10 @@ class IncidentNeutron(EqualityMixin): ENDF evaluation to read from. If given as a string, it is assumed to be the filename for the ENDF file. + covariance : bool + Flag to indicate whether or not covariance data from File 32 should be + retrieved + Returns ------- openmc.data.IncidentNeutron @@ -784,6 +801,11 @@ class IncidentNeutron(EqualityMixin): if (2, 151) in ev.section: data.resonances = res.Resonances.from_endf(ev) + if (32, 151) in ev.section and covariance: + data.resonance_covariance = ( + res_cov.ResonanceCovariances.from_endf(ev, data.resonances) + ) + # Read each reaction for mf, mt, nc, mod in ev.reaction_list: if mf == 3: diff --git a/openmc/data/resonance.py b/openmc/data/resonance.py index d58f706eb9..5e4bd7129e 100644 --- a/openmc/data/resonance.py +++ b/openmc/data/resonance.py @@ -16,6 +16,7 @@ except ImportError: _reconstruct = False import openmc.checkvalue as cv + class Resonances(object): """Resolved and unresolved resonance data @@ -90,14 +91,14 @@ class Resonances(object): # Determine whether discrete or continuous representation items = get_head_record(file_obj) - n_isotope = items[4] # Number of isotopes + n_isotope = items[4] # Number of isotopes ranges = [] for iso in range(n_isotope): items = get_cont_record(file_obj) abundance = items[1] - fission_widths = (items[3] == 1) # fission widths are given? - n_ranges = items[4] # number of resonance energy ranges + fission_widths = (items[3] == 1) # fission widths are given? + n_ranges = items[4] # number of resonance energy ranges for j in range(n_ranges): items = get_cont_record(file_obj) @@ -112,7 +113,7 @@ class Resonances(object): # unresolved resonance region erange = Unresolved.from_endf(file_obj, items, fission_widths) - #erange.material = self + # erange.material = self ranges.append(erange) return cls(ranges) @@ -162,6 +163,13 @@ class ResonanceRange(object): self._prepared = False self._parameter_matrix = {} + def __copy__(self): + cls = type(self) + new_copy = cls.__new__(cls) + new_copy.__dict__.update(self.__dict__) + new_copy._prepared = False + return new_copy + @classmethod def from_endf(cls, ev, file_obj, items): """Create resonance range from an ENDF evaluation. @@ -437,7 +445,7 @@ class MultiLevelBreitWigner(ResonanceRange): self._l_values = np.array(l_values) self._competitive = np.array(competitive) for l in l_values: - self._parameter_matrix[l] = df[df.L == l].as_matrix() + self._parameter_matrix[l] = df[df.L == l].values self._prepared = True @@ -682,7 +690,7 @@ class ReichMoore(ResonanceRange): self._l_values = np.array(l_values) for (l, J) in lj_values: self._parameter_matrix[l, J] = df[(df.L == l) & - (abs(df.J) == J)].as_matrix() + (abs(df.J) == J)].values self._prepared = True diff --git a/openmc/data/resonance_covariance.py b/openmc/data/resonance_covariance.py new file mode 100644 index 0000000000..300e6dbf60 --- /dev/null +++ b/openmc/data/resonance_covariance.py @@ -0,0 +1,708 @@ +from collections import MutableSequence +import warnings +import io +import copy + +import numpy as np +import pandas as pd + +from . import endf +import openmc.checkvalue as cv +from .resonance import Resonances + + +def _add_file2_contributions(file32params, file2params): + """Function for aiding in adding resonance parameters from File 2 that are + not always present in File 32. Uses already imported resonance data. + + Paramaters + ---------- + file32params : pandas.Dataframe + Incomplete set of resonance parameters contained in File 32. + file2params : pandas.Dataframe + Resonance parameters from File 2. Ordered by energy. + + Returns + ------- + parameters : pandas.Dataframe + Complete set of parameters ordered by L-values and then energy + + """ + # Use l-values and competitiveWidth from File 2 data + # Re-sort File 2 by energy to match File 32 + file2params = file2params.sort_values(by=['energy']) + file2params.reset_index(drop=True, inplace=True) + # Sort File 32 parameters by energy as well (maintaining index) + file32params.sort_values(by=['energy'], inplace=True) + # Add in values (.values converts to array first to ignore index) + file32params['L'] = file2params['L'].values + if 'competitiveWidth' in file2params.columns: + file32params['competitiveWidth'] = file2params['competitiveWidth'].values + # Resort to File 32 order (by L then by E) for use with covariance + file32params.sort_index(inplace=True) + return file32params + + +class ResonanceCovariances(Resonances): + """Resolved resonance covariance data + + Parameters + ---------- + ranges : list of openmc.data.ResonanceCovarianceRange + Distinct energy ranges for resonance data + + Attributes + ---------- + ranges : list of openmc.data.ResonanceCovarianceRange + Distinct energy ranges for resonance data + + """ + + @property + def ranges(self): + return self._ranges + + @ranges.setter + def ranges(self, ranges): + cv.check_type('resonance ranges', ranges, MutableSequence) + self._ranges = cv.CheckedList(ResonanceCovarianceRange, + 'resonance range', ranges) + + @classmethod + def from_endf(cls, ev, resonances): + """Generate resonance covariance data from an ENDF evaluation. + + Parameters + ---------- + ev : openmc.data.endf.Evaluation + ENDF evaluation + resonances : openmc.data.Resonance object + openmc.data.Resonanance object generated from the same evaluation + used to import values not contained in File 32 + + Returns + ------- + openmc.data.ResonanceCovariances + Resonance covariance data + + """ + file_obj = io.StringIO(ev.section[32, 151]) + + # Determine whether discrete or continuous representation + items = endf.get_head_record(file_obj) + n_isotope = items[4] # Number of isotopes + + ranges = [] + for iso in range(n_isotope): + items = endf.get_cont_record(file_obj) + abundance = items[1] + fission_widths = (items[3] == 1) # Flag for fission widths + n_ranges = items[4] # Number of resonance energy ranges + + for j in range(n_ranges): + items = endf.get_cont_record(file_obj) + # Unresolved flags - 0: only scattering radius given + # 1: resolved parameters given + # 2: unresolved parameters given + unresolved_flag = items[2] + formalism = items[3] # resonance formalism + + # Throw error for unsupported formalisms + if formalism in [0, 7]: + error = 'LRF='+str(formalism)+' covariance not supported '\ + 'for this formalism' + raise NotImplementedError(error) + + if unresolved_flag in (0, 1): + # Resolved resonance region + resonance = resonances.ranges[j] + erange = _FORMALISMS[formalism].from_endf(ev, file_obj, + items, resonance) + ranges.append(erange) + + elif unresolved_flag == 2: + warn = 'Unresolved resonance not supported. Covariance '\ + 'values for the unresolved region not imported.' + warnings.warn(warn) + + return cls(ranges) + + +class ResonanceCovarianceRange: + """Resonace covariance range. Base class for different formalisms. + + Parameters + ---------- + energy_min : float + Minimum energy of the resolved resonance range in eV + energy_max : float + Maximum energy of the resolved resonance range in eV + + Attributes + ---------- + energy_min : float + Minimum energy of the resolved resonance range in eV + energy_max : float + Maximum energy of the resolved resonance range in eV + parameters : pandas.DataFrame + Resonance parameters + covariance : numpy.array + The covariance matrix contained within the ENDF evaluation + lcomp : int + Flag indicating format of the covariance matrix within the ENDF file + file2res : openmc.data.ResonanceRange object + Corresponding resonance range with File 2 data. + mpar : int + Number of parameters in covariance matrix for each individual resonance + formalism : str + String descriptor of formalism + """ + def __init__(self, energy_min, energy_max): + self.energy_min = energy_min + self.energy_max = energy_max + + def subset(self, parameter_str, bounds): + """Produce a subset of resonance parameters and the corresponding + covariance matrix to an IncidentNeutron object. + + Parameters + ---------- + parameter_str : str + parameter to be discriminated + (i.e. 'energy', 'captureWidth', 'fissionWidthA'...) + bounds : np.array + [low numerical bound, high numerical bound] + + Returns + ------- + res_cov_range : openmc.data.ResonanceCovarianceRange + ResonanceCovarianceRange object that contains a subset of the + covariance matrix (upper triangular) as well as a subset parameters + within self.file2params + + """ + # Copy range and prevent change of original + res_cov_range = copy.deepcopy(self) + + parameters = self.file2res.parameters + cov = res_cov_range.covariance + mpar = res_cov_range.mpar + # Create mask + mask1 = parameters[parameter_str] >= bounds[0] + mask2 = parameters[parameter_str] <= bounds[1] + mask = mask1 & mask2 + res_cov_range.parameters = parameters[mask] + indices = res_cov_range.parameters.index.values + # Build subset of covariance + sub_cov_dim = len(indices)*mpar + cov_subset_vals = [] + for index1 in indices: + for i in range(mpar): + for index2 in indices: + for j in range(mpar): + if index2*mpar+j >= index1*mpar+i: + cov_subset_vals.append(cov[index1*mpar+i, + index2*mpar+j]) + + cov_subset = np.zeros([sub_cov_dim, sub_cov_dim]) + tri_indices = np.triu_indices(sub_cov_dim) + cov_subset[tri_indices] = cov_subset_vals + + res_cov_range.file2res.parameters = parameters[mask] + res_cov_range.covariance = cov_subset + return res_cov_range + + def sample(self, n_samples): + """Sample resonance parameters based on the covariances provided + within an ENDF evaluation. + + Parameters + ---------- + n_samples : int + The number of samples to produce + + Returns + ------- + samples : list of openmc.data.ResonanceCovarianceRange objects + List of samples size `n_samples` + + """ + warn_str = 'Sampling routine does not guarantee positive values for '\ + 'parameters. This can lead to undefined behavior in the '\ + 'reconstruction routine.' + warnings.warn(warn_str) + parameters = self.parameters + cov = self.covariance + + # Symmetrizing covariance matrix + cov = cov + cov.T - np.diag(cov.diagonal()) + formalism = self.formalism + mpar = self.mpar + samples = [] + + # Handling MLBW/SLBW sampling + if formalism == 'mlbw' or formalism == 'slbw': + params = ['energy', 'neutronWidth', 'captureWidth', 'fissionWidth', + 'competitiveWidth'] + param_list = params[:mpar] + mean_array = parameters[param_list].values + mean = mean_array.flatten() + par_samples = np.random.multivariate_normal(mean, cov, + size=n_samples) + spin = parameters['J'].values + l_value = parameters['L'].values + for sample in par_samples: + energy = sample[0::mpar] + gn = sample[1::mpar] + gg = sample[2::mpar] + gf = sample[3::mpar] if mpar > 3 else parameters['fissionWidth'].values + gx = sample[4::mpar] if mpar > 4 else parameters['competitiveWidth'].values + gt = gn + gg + gf + gx + + records = [] + for j, E in enumerate(energy): + records.append([energy[j], l_value[j], spin[j], gt[j], + gn[j], gg[j], gf[j], gx[j]]) + columns = ['energy', 'L', 'J', 'totalWidth', 'neutronWidth', + 'captureWidth', 'fissionWidth', 'competitiveWidth'] + sample_params = pd.DataFrame.from_records(records, + columns=columns) + # Copy ResonanceRange object + res_range = copy.copy(self.file2res) + res_range.parameters = sample_params + samples.append(res_range) + + # Handling RM sampling + elif formalism == 'rm': + params = ['energy', 'neutronWidth', 'captureWidth', + 'fissionWidthA', 'fissionWidthB'] + param_list = params[:mpar] + mean_array = parameters[param_list].values + mean = mean_array.flatten() + par_samples = np.random.multivariate_normal(mean, cov, + size=n_samples) + spin = parameters['J'].values + l_value = parameters['L'].values + for sample in par_samples: + energy = sample[0::mpar] + gn = sample[1::mpar] + gg = sample[2::mpar] + gfa = sample[3::mpar] if mpar > 3 else parameters['fissionWidthA'].values + gfb = sample[4::mpar] if mpar > 3 else parameters['fissionWidthB'].values + + records = [] + for j, E in enumerate(energy): + records.append([energy[j], l_value[j], spin[j], gn[j], + gg[j], gfa[j], gfb[j]]) + columns = ['energy', 'L', 'J', 'neutronWidth', + 'captureWidth', 'fissionWidthA', 'fissionWidthB'] + sample_params = pd.DataFrame.from_records(records, + columns=columns) + # Copy ResonanceRange object + res_range = copy.copy(self.file2res) + res_range.parameters = sample_params + samples.append(res_range) + + return samples + + +class MultiLevelBreitWignerCovariance(ResonanceCovarianceRange): + """Multi-level Breit-Wigner resolved resonance formalism covariance data. + Parameters + ---------- + energy_min : float + Minimum energy of the resolved resonance range in eV + energy_max : float + Maximum energy of the resolved resonance range in eV + + Attributes + ---------- + energy_min : float + Minimum energy of the resolved resonance range in eV + energy_max : float + Maximum energy of the resolved resonance range in eV + parameters : pandas.DataFrame + Resonance parameters + covariance : numpy.array + The covariance matrix contained within the ENDF evaluation + mpar : int + Number of parameters in covariance matrix for each individual resonance + lcomp : int + Flag indicating format of the covariance matrix within the ENDF file + file2res : openmc.data.ResonanceRange object + Corresponding resonance range with File 2 data. + formalism : str + String descriptor of formalism + + """ + + def __init__(self, energy_min, energy_max, parameters, covariance, mpar, + lcomp, file2res): + super().__init__(energy_min, energy_max) + self.parameters = parameters + self.covariance = covariance + self.mpar = mpar + self.lcomp = lcomp + self.file2res = copy.copy(file2res) + self.formalism = 'mlbw' + + @classmethod + def from_endf(cls, ev, file_obj, items, resonance): + """Create MLBW covariance data from an ENDF evaluation. + + Parameters + ---------- + ev : openmc.data.endf.Evaluation + ENDF evaluation + file_obj : file-like object + ENDF file positioned at the second record of a resonance range + subsection in MF=32, MT=151 + items : list + Items from the CONT record at the start of the resonance range + subsection + resonance : openmc.data.ResonanceRange object + Corresponding resonance range with File 2 data. + + Returns + ------- + openmc.data.MultiLevelBreitWignerCovariance + Multi-level Breit-Wigner resonance covariance parameters + + """ + + # Read energy-dependent scattering radius if present + energy_min, energy_max = items[0:2] + nro, naps = items[4:6] + if nro != 0: + params, ape = endf.get_tab1_record(file_obj) + + # Other scatter radius parameters + items = endf.get_cont_record(file_obj) + target_spin = items[0] + lcomp = items[3] # Flag for compatibility 0, 1, 2 - 2 is compact form + nls = items[4] # number of l-values + + # Build covariance matrix for General Resolved Resonance Formats + if lcomp == 1: + items = endf.get_cont_record(file_obj) + # Number of short range type resonance covariances + num_short_range = items[4] + # Number of long range type resonance covariances + num_long_range = items[5] + + # Read resonance widths, J values, etc + records = [] + for i in range(num_short_range): + items, values = endf.get_list_record(file_obj) + mpar = items[2] + num_res = items[5] + num_par_vals = num_res*6 + res_values = values[:num_par_vals] + cov_values = values[num_par_vals:] + + energy = res_values[0::6] + spin = res_values[1::6] + gt = res_values[2::6] + gn = res_values[3::6] + gg = res_values[4::6] + gf = res_values[5::6] + + for i, E in enumerate(energy): + records.append([energy[i], spin[i], gt[i], gn[i], + gg[i], gf[i]]) + + # Build the upper-triangular covariance matrix + cov_dim = mpar*num_res + cov = np.zeros([cov_dim, cov_dim]) + indices = np.triu_indices(cov_dim) + cov[indices] = cov_values + + # Compact format - Resonances and individual uncertainties followed by + # compact correlations + elif lcomp == 2: + items, values = endf.get_list_record(file_obj) + mean = items + num_res = items[5] + energy = values[0::12] + spin = values[1::12] + gt = values[2::12] + gn = values[3::12] + gg = values[4::12] + gf = values[5::12] + par_unc = [] + for i in range(num_res): + res_unc = values[i*12+6 : i*12+12] + # Delete 0 values (not provided, no fission width) + # DAJ/DGT always zero, DGF sometimes nonzero [1, 2, 5] + res_unc_nonzero = [] + for j in range(6): + if j in [1, 2, 5] and res_unc[j] != 0.0: + res_unc_nonzero.append(res_unc[j]) + elif j in [0, 3, 4]: + res_unc_nonzero.append(res_unc[j]) + par_unc.extend(res_unc_nonzero) + + records = [] + for i, E in enumerate(energy): + records.append([energy[i], spin[i], gt[i], gn[i], + gg[i], gf[i]]) + + corr = endf.get_intg_record(file_obj) + cov = np.diag(par_unc).dot(corr).dot(np.diag(par_unc)) + + # Compatible resolved resonance format + elif lcomp == 0: + cov = np.zeros([4, 4]) + records = [] + cov_index = 0 + for i in range(nls): + items, values = endf.get_list_record(file_obj) + num_res = items[5] + for j in range(num_res): + one_res = values[18*j:18*(j+1)] + res_values = one_res[:6] + cov_values = one_res[6:] + records.append(list(res_values)) + + # Populate the coviariance matrix for this resonance + # There are no covariances between resonances in lcomp=0 + cov[cov_index, cov_index] = cov_values[0] + cov[cov_index+1, cov_index+1 : cov_index+2] = cov_values[1:2] + cov[cov_index+1, cov_index+3] = cov_values[4] + cov[cov_index+2, cov_index+2] = cov_values[3] + cov[cov_index+2, cov_index+3] = cov_values[5] + cov[cov_index+3, cov_index+3] = cov_values[6] + + cov_index += 4 + if j < num_res-1: # Pad matrix for additional values + cov = np.pad(cov, ((0, 4), (0, 4)), 'constant', + constant_values=0) + + # Create pandas DataFrame with resonance data, currently + # redundant with data.IncidentNeutron.resonance + columns = ['energy', 'J', 'totalWidth', 'neutronWidth', + 'captureWidth', 'fissionWidth'] + parameters = pd.DataFrame.from_records(records, columns=columns) + # Determine mpar (number of parameters for each resonance in + # covariance matrix) + nparams, params = parameters.shape + covsize = cov.shape[0] + mpar = int(covsize/nparams) + # Add parameters from File 2 + parameters = _add_file2_contributions(parameters, + resonance.parameters) + # Create instance of class + mlbw = cls(energy_min, energy_max, parameters, cov, mpar, lcomp, + resonance) + return mlbw + + +class SingleLevelBreitWignerCovariance(MultiLevelBreitWignerCovariance): + """Single-level Breit-Wigner resolved resonance formalism covariance data. + Single-level Breit-Wigner resolved resonance data is is identified by LRF=1 + in the ENDF-6 format. + + Parameters + ---------- + energy_min : float + Minimum energy of the resolved resonance range in eV + energy_max : float + Maximum energy of the resolved resonance range in eV + + Attributes + ---------- + energy_min : float + Minimum energy of the resolved resonance range in eV + energy_max : float + Maximum energy of the resolved resonance range in eV + parameters : pandas.DataFrame + Resonance parameters + covariance : numpy.array + The covariance matrix contained within the ENDF evaluation + mpar : int + Number of parameters in covariance matrix for each individual resonance + formalism : str + String descriptor of formalism + lcomp : int + Flag indicating format of the covariance matrix within the ENDF file + file2res : openmc.data.ResonanceRange object + Corresponding resonance range with File 2 data. + """ + + def __init__(self, energy_min, energy_max, parameters, covariance, mpar, + lcomp, file2res): + super().__init__(energy_min, energy_max, parameters, covariance, mpar, + lcomp, file2res) + self.formalism = 'slbw' + + +class ReichMooreCovariance(ResonanceCovarianceRange): + """Reich-Moore resolved resonance formalism covariance data. + + Reich-Moore resolved resonance data is identified by LRF=3 in the ENDF-6 + format. + + Parameters + ---------- + energy_min : float + Minimum energy of the resolved resonance range in eV + energy_max : float + Maximum energy of the resolved resonance range in eV + + Attributes + ---------- + energy_min : float + Minimum energy of the resolved resonance range in eV + energy_max : float + Maximum energy of the resolved resonance range in eV + parameters : pandas.DataFrame + Resonance parameters + covariance : numpy.array + The covariance matrix contained within the ENDF evaluation + lcomp : int + Flag indicating format of the covariance matrix within the ENDF file + mpar : int + Number of parameters in covariance matrix for each individual resonance + file2res : openmc.data.ResonanceRange object + Corresponding resonance range with File 2 data. + formalism : str + String descriptor of formalism + """ + + def __init__(self, energy_min, energy_max, parameters, covariance, mpar, + lcomp, file2res): + super().__init__(energy_min, energy_max) + self.parameters = parameters + self.covariance = covariance + self.mpar = mpar + self.lcomp = lcomp + self.file2res = copy.copy(file2res) + self.formalism = 'rm' + + @classmethod + def from_endf(cls, ev, file_obj, items, resonance): + """Create Reich-Moore resonance covariance data from an ENDF + evaluation. Includes the resonance parameters contained separately in + File 32. + + Parameters + ---------- + ev : openmc.data.endf.Evaluation + ENDF evaluation + file_obj : file-like object + ENDF file positioned at the second record of a resonance range + subsection in MF=2, MT=151 + items : list + Items from the CONT record at the start of the resonance range + subsection + resonance : openmc.data.Resonance object + openmc.data.Resonanance object generated from the same evaluation + used to import values not contained in File 32 + + Returns + ------- + openmc.data.ReichMooreCovariance + Reich-Moore resonance covariance parameters + + """ + # Read energy-dependent scattering radius if present + energy_min, energy_max = items[0:2] + nro, naps = items[4:6] + if nro != 0: + params, ape = endf.get_tab1_record(file_obj) + + # Other scatter radius parameters + items = endf.get_cont_record(file_obj) + target_spin = items[0] + lcomp = items[3] # Flag for compatibility 0, 1, 2 - 2 is compact form + nls = items[4] # Number of l-values + + # Build covariance matrix for General Resolved Resonance Formats + if lcomp == 1: + items = endf.get_cont_record(file_obj) + # Number of short range type resonance covariances + num_short_range = items[4] + # Number of long range type resonance covariances + num_long_range = items[5] + # Read resonance widths, J values, etc + channel_radius = {} + scattering_radius = {} + records = [] + for i in range(num_short_range): + items, values = endf.get_list_record(file_obj) + mpar = items[2] + num_res = items[5] + num_par_vals = num_res*6 + res_values = values[:num_par_vals] + cov_values = values[num_par_vals:] + + energy = res_values[0::6] + spin = res_values[1::6] + gn = res_values[2::6] + gg = res_values[3::6] + gfa = res_values[4::6] + gfb = res_values[5::6] + + for i, E in enumerate(energy): + records.append([energy[i], spin[i], gn[i], gg[i], + gfa[i], gfb[i]]) + + # Build the upper-triangular covariance matrix + cov_dim = mpar*num_res + cov = np.zeros([cov_dim, cov_dim]) + indices = np.triu_indices(cov_dim) + cov[indices] = cov_values + + # Compact format - Resonances and individual uncertainties followed by + # compact correlations + elif lcomp == 2: + items, values = endf.get_list_record(file_obj) + num_res = items[5] + energy = values[0::12] + spin = values[1::12] + gn = values[2::12] + gg = values[3::12] + gfa = values[4::12] + gfb = values[5::12] + par_unc = [] + for i in range(num_res): + res_unc = values[i*12+6 : i*12+12] + # Delete 0 values (not provided in evaluation) + res_unc = [x for x in res_unc if x != 0.0] + par_unc.extend(res_unc) + + records = [] + for i, E in enumerate(energy): + records.append([energy[i], spin[i], gn[i], gg[i], + gfa[i], gfb[i]]) + + corr = endf.get_intg_record(file_obj) + cov = np.diag(par_unc).dot(corr).dot(np.diag(par_unc)) + + # Create pandas DataFrame with resonacne data + columns = ['energy', 'J', 'neutronWidth', 'captureWidth', + 'fissionWidthA', 'fissionWidthB'] + parameters = pd.DataFrame.from_records(records, columns=columns) + + # Determine mpar (number of parameters for each resonance in + # covariance matrix) + nparams, params = parameters.shape + covsize = cov.shape[0] + mpar = int(covsize/nparams) + + # Add parameters from File 2 + parameters = _add_file2_contributions(parameters, + resonance.parameters) + # Create instance of ReichMooreCovariance + rmc = cls(energy_min, energy_max, parameters, cov, mpar, lcomp, + resonance) + return rmc + + +_FORMALISMS = { + 0: ResonanceCovarianceRange, + 1: SingleLevelBreitWignerCovariance, + 2: MultiLevelBreitWignerCovariance, + 3: ReichMooreCovariance + # 7: RMatrixLimitedCovariance +} diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index a036a2680c..18ba56bc56 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -24,40 +24,44 @@ from openmc.stats import Discrete, Tabular _THERMAL_NAMES = { - 'c_Al27': ('al', 'al27'), - 'c_Be': ('be', 'be-metal'), - 'c_BeO': ('beo'), - 'c_Be_in_BeO': ('bebeo', 'be-o', 'be/o'), + 'c_Al27': ('al', 'al27', 'al-27'), + 'c_Be': ('be', 'be-metal', 'be-met'), + 'c_BeO': ('beo',), + 'c_Be_in_BeO': ('bebeo', 'be-beo', 'be-o', 'be/o'), 'c_C6H6': ('benz', 'c6h6'), - 'c_C_in_SiC': ('csic',), - 'c_Ca_in_CaH2': ('cah'), - 'c_D_in_D2O': ('dd2o', 'hwtr', 'hw'), - 'c_Fe56': ('fe', 'fe56'), + 'c_C_in_SiC': ('csic', 'c-sic'), + 'c_Ca_in_CaH2': ('cah',), + 'c_D_in_D2O': ('dd2o', 'd-d2o', 'hwtr', 'hw'), + 'c_Fe56': ('fe', 'fe56', 'fe-56'), 'c_Graphite': ('graph', 'grph', 'gr'), - 'c_H_in_CaH2': ('hcah2'), - 'c_H_in_CH2': ('hch2', 'poly', 'pol'), + 'c_Graphite_10p': ('grph10',), + 'c_Graphite_30p': ('grph30',), + 'c_H_in_CaH2': ('hcah2',), + 'c_H_in_CH2': ('hch2', 'poly', 'pol', 'h-poly'), 'c_H_in_CH4_liquid': ('lch4', 'lmeth'), 'c_H_in_CH4_solid': ('sch4', 'smeth'), - 'c_H_in_H2O': ('hh2o', 'lwtr', 'lw'), - 'c_H_in_H2O_solid': ('hice',), - 'c_H_in_C5O2H8': ('lucite', 'c5o2h8'), - 'c_H_in_YH2': ('hyh2'), - 'c_H_in_ZrH': ('hzrh', 'h-zr', 'h/zr', 'hzr'), + 'c_H_in_H2O': ('hh2o', 'h-h2o', 'lwtr', 'lw'), + 'c_H_in_H2O_solid': ('hice', 'h-ice'), + 'c_H_in_C5O2H8': ('lucite', 'c5o2h8', 'h-luci'), + 'c_H_in_YH2': ('hyh2', 'h-yh2'), + 'c_H_in_ZrH': ('hzrh', 'h-zrh', 'h-zr', 'h/zr', 'hzr'), 'c_Mg24': ('mg', 'mg24'), - 'c_O_in_BeO': ('obeo', 'o-be', 'o/be'), - 'c_O_in_D2O': ('od2o'), - 'c_O_in_H2O_ice': ('oice'), - 'c_O_in_UO2': ('ouo2', 'o2-u', 'o2/u'), - 'c_ortho_D': ('orthod', 'dortho'), - 'c_ortho_H': ('orthoh', 'hortho'), - 'c_Si_in_SiC': ('sisic'), + 'c_O_in_BeO': ('obeo', 'o-beo', 'o-be', 'o/be'), + 'c_O_in_D2O': ('od2o', 'o-d2o'), + 'c_O_in_H2O_ice': ('oice', 'o-ice'), + 'c_O_in_UO2': ('ouo2', 'o-uo2', 'o2-u', 'o2/u'), + 'c_N_in_UN': ('n-un',), + 'c_ortho_D': ('orthod', 'orthoD', 'dortho'), + 'c_ortho_H': ('orthoh', 'orthoH', 'hortho'), + 'c_Si_in_SiC': ('sisic', 'si-sic'), 'c_SiO2_alpha': ('sio2', 'sio2a'), - 'c_SiO2_beta': ('sio2b'), - 'c_para_D': ('parad', 'dpara'), - 'c_para_H': ('parah', 'hpara'), - 'c_U_in_UO2': ('uuo2', 'u-o2', 'u/o2'), - 'c_Y_in_YH2': ('yyh2'), - 'c_Zr_in_ZrH': ('zrzrh', 'zr-h', 'zr/h') + 'c_SiO2_beta': ('sio2b',), + 'c_para_D': ('parad', 'paraD', 'dpara'), + 'c_para_H': ('parah', 'paraH', 'hpara'), + 'c_U_in_UN': ('u-un',), + 'c_U_in_UO2': ('uuo2', 'u-uo2', 'u-o2', 'u/o2'), + 'c_Y_in_YH2': ('yyh2', 'y-yh2'), + 'c_Zr_in_ZrH': ('zrzrh', 'zr-zrh', 'zr-h', 'zr/h') } diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index 5159cfd473..1970c07710 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -54,7 +54,7 @@ class LegendreFilter(ExpansionFilter): r"""Score Legendre expansion moments up to specified order. This filter allows scores to be multiplied by Legendre polynomials of the - change in particle angle ($\mu$) up to a user-specified order. + change in particle angle (:math:`\mu`) up to a user-specified order. Parameters ---------- diff --git a/openmc/mesh.py b/openmc/mesh.py index 3f53582d5e..f7cba22f8d 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -208,12 +208,12 @@ class Mesh(IDManagerMixin): shape = np.array(lattice.shape) width = lattice.pitch*shape - + mesh = cls(mesh_id, name) mesh.lower_left = lattice.lower_left mesh.upper_right = lattice.lower_left + width mesh.dimension = shape*division - + return mesh def to_xml_element(self): @@ -333,14 +333,16 @@ class Mesh(IDManagerMixin): if n_dim == 1: universe_array = np.array([universes]) elif n_dim == 2: - universe_array = np.empty(self.dimension, dtype=openmc.Universe) + universe_array = np.empty(self.dimension[::-1], + dtype=openmc.Universe) i = 0 for y in range(self.dimension[1] - 1, -1, -1): for x in range(self.dimension[0]): universe_array[y][x] = universes[i] i += 1 else: - universe_array = np.empty(self.dimension, dtype=openmc.Universe) + universe_array = np.empty(self.dimension[::-1], + dtype=openmc.Universe) i = 0 for z in range(self.dimension[2]): for y in range(self.dimension[1] - 1, -1, -1): diff --git a/readme.rst b/readme.rst deleted file mode 100644 index 32cbb34e14..0000000000 --- a/readme.rst +++ /dev/null @@ -1,80 +0,0 @@ -========================================== -OpenMC Monte Carlo Particle Transport Code -========================================== - -|licensebadge| |travisbadge| |coverallsbadge| - -The OpenMC project aims to provide a fully-featured Monte Carlo particle -transport code based on modern methods. It is a constructive solid geometry, -continuous-energy transport code that uses HDF5 format cross sections. The -project started under the Computational Reactor Physics Group at MIT. - -Complete documentation on the usage of OpenMC is hosted on Read the Docs (both -for the `latest release`_ and developmental_ version). If you are interested in -the project or would like to help and contribute, please send a message to the -OpenMC User's Group `mailing list`_. - ------------- -Installation ------------- - -Detailed `installation instructions`_ can be found in the User's Guide. - ------- -Citing ------- - -If you use OpenMC in your research, please consider giving proper attribution by -citing the following publication: - -- Paul K. Romano, Nicholas E. Horelik, Bryan R. Herman, Adam G. Nelson, Benoit - Forget, and Kord Smith, "`OpenMC: A State-of-the-Art Monte Carlo Code for - Research and Development `_," - *Ann. Nucl. Energy*, **82**, 90--97 (2015). - ---------------- -Troubleshooting ---------------- - -If you run into problems compiling, installing, or running OpenMC, first check -the `Troubleshooting section`_ in the User's Guide. If you are not able to find -a solution to your problem there, please send a message to the User's Group -`mailing list`_. - --------------- -Reporting Bugs --------------- - -OpenMC is hosted on GitHub and all bugs are reported and tracked through the -Issues_ feature on GitHub. However, GitHub Issues should not be used for common -troubleshooting purposes. If you are having trouble installing the code or -getting your model to run properly, you should first send a message to the -User's Group `mailing list`_. If it turns out your issue really is a bug in the -code, an issue will then be created on GitHub. If you want to request that a -feature be added to the code, you may create an Issue on github. - -------- -License -------- - -OpenMC is distributed under the MIT/X license_. - -.. _latest release: http://openmc.readthedocs.io/en/stable/ -.. _developmental: http://openmc.readthedocs.io/en/latest/ -.. _mailing list: https://groups.google.com/forum/?fromgroups=#!forum/openmc-users -.. _installation instructions: http://openmc.readthedocs.io/en/stable/usersguide/install.html -.. _Troubleshooting section: http://openmc.readthedocs.io/en/stable/usersguide/troubleshoot.html -.. _Issues: https://github.com/mit-crpg/openmc/issues -.. _license: http://openmc.readthedocs.io/en/stable/license.html - -.. |licensebadge| image:: https://img.shields.io/github/license/mit-crpg/openmc.svg - :target: http://openmc.readthedocs.io/en/latest/license.html - :alt: License - -.. |travisbadge| image:: https://travis-ci.org/mit-crpg/openmc.svg?branch=develop - :target: https://travis-ci.org/mit-crpg/openmc - :alt: Travis CI build status (Linux) - -.. |coverallsbadge| image:: https://coveralls.io/repos/github/mit-crpg/openmc/badge.svg?branch=develop - :target: https://coveralls.io/github/mit-crpg/openmc?branch=develop - :alt: Code Coverage diff --git a/setup.py b/setup.py index 2a42dd65dd..33987095d3 100755 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ kwargs = { 'author': 'The OpenMC Development Team', 'author_email': 'openmc-dev@googlegroups.com', 'description': 'OpenMC', - 'url': 'https://github.com/mit-crpg/openmc', + 'url': 'https://github.com/openmc-dev/openmc', 'classifiers': [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', diff --git a/src/api.F90 b/src/api.F90 index 03b19cee3c..8afc6061cd 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -129,7 +129,7 @@ contains index_ufs_mesh = -1 keff = ONE legendre_to_tabular = .true. - legendre_to_tabular_points = 33 + legendre_to_tabular_points = C_NONE n_batch_interval = 1 n_lost_particles = 0 n_particles = 0 @@ -310,7 +310,6 @@ contains subroutine free_memory() use cmfd_header - use mgxs_header use plot_header use sab_header use settings @@ -328,7 +327,6 @@ contains call free_memory_simulation() call free_memory_nuclide() call free_memory_settings() - call free_memory_mgxs() call free_memory_sab() call free_memory_source() call free_memory_mesh() diff --git a/src/cell.h b/src/cell.h index a2f72c8337..24413b4fd4 100644 --- a/src/cell.h +++ b/src/cell.h @@ -7,7 +7,7 @@ #include #include "hdf5.h" -#include "pugixml/pugixml.hpp" +#include "pugixml.hpp" namespace openmc { @@ -50,7 +50,7 @@ public: //! A geometry primitive that links surfaces, universes, and materials //============================================================================== -class Cell +class Cell { public: int32_t id; //!< Unique ID diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index 549cff4191..1e4b8f3742 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -3,8 +3,8 @@ module cmfd_input use, intrinsic :: ISO_C_BINDING use cmfd_header - use mesh_header, only: mesh_dict - use mgxs_header, only: energy_bins + use mesh_header, only: mesh_dict + use mgxs_interface, only: energy_bins, num_energy_groups use tally use tally_header use timer_header diff --git a/src/constants.F90 b/src/constants.F90 index 4c1e3013c2..ab6887610e 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -405,6 +405,25 @@ module constants DIFF_NUCLIDE_DENSITY = 2, & DIFF_TEMPERATURE = 3 + + ! Mgxs::get_xs enumerated types + integer(C_INT), parameter :: & + MG_GET_XS_TOTAL = 0, & + MG_GET_XS_ABSORPTION = 1, & + MG_GET_XS_INVERSE_VELOCITY = 2, & + MG_GET_XS_DECAY_RATE = 3, & + MG_GET_XS_SCATTER = 4, & + MG_GET_XS_SCATTER_MULT = 5, & + MG_GET_XS_SCATTER_FMU_MULT = 6, & + MG_GET_XS_SCATTER_FMU = 7, & + MG_GET_XS_FISSION = 8, & + MG_GET_XS_KAPPA_FISSION = 9, & + MG_GET_XS_PROMPT_NU_FISSION = 10, & + MG_GET_XS_DELAYED_NU_FISSION = 11, & + MG_GET_XS_NU_FISSION = 12, & + MG_GET_XS_CHI_PROMPT = 13, & + MG_GET_XS_CHI_DELAYED = 14 + ! ============================================================================ ! RANDOM NUMBER STREAM CONSTANTS diff --git a/src/constants.h b/src/constants.h index 685fbcfd72..016a6b3484 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,10 +1,85 @@ +//! \file constants.h +//! A collection of constants + #ifndef CONSTANTS_H #define CONSTANTS_H +#include +#include #include +#include +namespace openmc { -namespace openmc{ +typedef std::vector double_1dvec; +typedef std::vector > double_2dvec; +typedef std::vector > > double_3dvec; +typedef std::vector > > > double_4dvec; +typedef std::vector > > > > double_5dvec; +typedef std::vector > > > > > double_6dvec; +typedef std::vector int_1dvec; +typedef std::vector > int_2dvec; +typedef std::vector > > int_3dvec; + +constexpr int MAX_SAMPLE {10000}; + +constexpr std::array VERSION {0, 10, 0}; +constexpr std::array VERSION_PARTICLE_RESTART {2, 0}; + +// Maximum number of words in a single line, length of line, and length of +// single word +constexpr int MAX_WORDS {500}; +constexpr int MAX_LINE_LEN {250}; +constexpr int MAX_WORD_LEN {150}; +constexpr int MAX_FILE_LEN {255}; + +// Physical Constants +constexpr double K_BOLTZMANN {8.6173303e-5}; // Boltzmann constant in eV/K + +// Angular distribution type +constexpr int ANGLE_ISOTROPIC {1}; +constexpr int ANGLE_32_EQUI {2}; +constexpr int ANGLE_TABULAR {3}; +constexpr int ANGLE_LEGENDRE {4}; +constexpr int ANGLE_HISTOGRAM {5}; + +// MGXS Table Types +constexpr int MGXS_ISOTROPIC {1}; // Isotroically weighted data +constexpr int MGXS_ANGLE {2}; // Data by angular bins + +// Flag to denote this was a macroscopic data object +constexpr double MACROSCOPIC_AWR {-2.}; + +// Number of mu bins to use when converting Legendres to tabular type +constexpr int DEFAULT_NMU {33}; + +// Temperature treatment method +constexpr int TEMPERATURE_NEAREST {1}; +constexpr int TEMPERATURE_INTERPOLATION {2}; + +// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we +// use so for now we will reuse the Fortran constant until we are OK with +// modifying test results +constexpr double PI {3.1415926535898}; + +const double SQRT_PI {std::sqrt(PI)}; + +// Mgxs::get_xs enumerated types +constexpr int MG_GET_XS_TOTAL {0}; +constexpr int MG_GET_XS_ABSORPTION {1}; +constexpr int MG_GET_XS_INVERSE_VELOCITY {2}; +constexpr int MG_GET_XS_DECAY_RATE {3}; +constexpr int MG_GET_XS_SCATTER {4}; +constexpr int MG_GET_XS_SCATTER_MULT {5}; +constexpr int MG_GET_XS_SCATTER_FMU_MULT {6}; +constexpr int MG_GET_XS_SCATTER_FMU {7}; +constexpr int MG_GET_XS_FISSION {8}; +constexpr int MG_GET_XS_KAPPA_FISSION {9}; +constexpr int MG_GET_XS_PROMPT_NU_FISSION {10}; +constexpr int MG_GET_XS_DELAYED_NU_FISSION {11}; +constexpr int MG_GET_XS_NU_FISSION {12}; +constexpr int MG_GET_XS_CHI_PROMPT {13}; +constexpr int MG_GET_XS_CHI_DELAYED {14}; extern "C" double FP_COINCIDENT; extern "C" double FP_PRECISION; diff --git a/src/doppler.F90 b/src/doppler.F90 deleted file mode 100644 index 56c07c50eb..0000000000 --- a/src/doppler.F90 +++ /dev/null @@ -1,216 +0,0 @@ -module doppler - - use constants, only: ZERO, ONE, PI, K_BOLTZMANN - - implicit none - - real(8), parameter :: sqrt_pi_inv = ONE / sqrt(PI) - -contains - -!=============================================================================== -! BROADEN takes a microscopic cross section at a temperature T_1 and Doppler -! broadens it to a higher temperature T_2 based on a method originally developed -! by Cullen and Weisbin (see "Exact Doppler Broadening of Tabulated Cross -! Sections," Nucl. Sci. Eng. 60, 199-229 (1976)). The only difference here is -! the F functions are evaluated based on complementary error functions rather -! than error functions as is done in the BROADR module of NJOY. -!=============================================================================== - - subroutine broaden(energy, xs, A_target, T, sigmaNew) - - real(8), intent(in) :: energy(:) ! energy grid - real(8), intent(in) :: xs(:) ! unbroadened cross section - integer, intent(in) :: A_target ! mass number of target - real(8), intent(in) :: T ! temperature (difference) - real(8), intent(out) :: sigmaNew(:) ! broadened cross section - - integer :: i, k ! loop indices - integer :: n ! number of energy points - real(8) :: F_a(0:4) ! F(a) functions as per C&W - real(8) :: F_b(0:4) ! F(b) functions as per C&W - real(8) :: H(0:4) ! H functions as per C&W - real(8), allocatable :: x(:) ! proportional to relative velocity - real(8) :: y ! proportional to neutron velocity - real(8) :: y_sq ! y**2 - real(8) :: y_inv ! 1/y - real(8) :: y_inv_sq ! 1/y**2 - real(8) :: alpha ! constant equal to A/kT - real(8) :: slope ! slope of xs between adjacent points - real(8) :: Ak, Bk ! coefficients at each point - real(8) :: a, b ! values of x(k)-y and x(k+1)-y - real(8) :: sigma ! broadened cross section at one point - - ! Determine alpha parameter -- have to convert k to eV/K - alpha = A_target/(K_BOLTZMANN * T) - - ! Allocate memory for x and assign values - n = size(energy) - allocate(x(n)) - x = sqrt(alpha * energy) - - ! Loop over incoming neutron energies - ENERGY_NEUTRON: do i = 1, n - - sigma = ZERO - y = x(i) - y_sq = y*y - y_inv = ONE / y - y_inv_sq = y_inv / y - - ! ======================================================================= - ! EVALUATE FIRST TERM FROM x(k) - y = 0 to -4 - - k = i - a = ZERO - call calculate_F(F_a, a) - - do while (a >= -4.0 .and. k > 1) - ! Move to next point - F_b = F_a - k = k - 1 - a = x(k) - y - - ! Calculate F and H functions - call calculate_F(F_a, a) - H = F_a - F_b - - ! Calculate A(k), B(k), and slope terms - Ak = y_inv_sq*H(2) + 2.0*y_inv*H(1) + H(0) - Bk = y_inv_sq*H(4) + 4.0*y_inv*H(3) + 6.0*H(2) + 4.0*y*H(1) + y_sq*H(0) - slope = (xs(k+1) - xs(k)) / (x(k+1)**2 - x(k)**2) - - ! Add contribution to broadened cross section - sigma = sigma + Ak*(xs(k) - slope*x(k)**2) + slope*Bk - end do - - ! ======================================================================= - ! EXTEND CROSS SECTION TO 0 ASSUMING 1/V SHAPE - - if (k == 1 .and. a >= -4.0) then - ! Since x = 0, this implies that a = -y - F_b = F_a - a = -y - - ! Calculate F and H functions - call calculate_F(F_a, a) - H = F_a - F_b - - ! Add contribution to broadened cross section - sigma = sigma + xs(k)*x(k)*(y_inv_sq*H(1) + y_inv*H(0)) - end if - - ! ======================================================================= - ! EVALUATE FIRST TERM FROM x(k) - y = 0 to 4 - - k = i - b = ZERO - call calculate_F(F_b, b) - - do while (b <= 4.0 .and. k < n) - ! Move to next point - F_a = F_b - k = k + 1 - b = x(k) - y - - ! Calculate F and H functions - call calculate_F(F_b, b) - H = F_a - F_b - - ! Calculate A(k), B(k), and slope terms - Ak = y_inv_sq*H(2) + 2.0*y_inv*H(1) + H(0) - Bk = y_inv_sq*H(4) + 4.0*y_inv*H(3) + 6.0*H(2) + 4.0*y*H(1) + y_sq*H(0) - slope = (xs(k) - xs(k-1)) / (x(k)**2 - x(k-1)**2) - - ! Add contribution to broadened cross section - sigma = sigma + Ak*(xs(k) - slope*x(k)**2) + slope*Bk - end do - - ! ======================================================================= - ! EXTEND CROSS SECTION TO INFINITY ASSUMING CONSTANT SHAPE - - if (k == n .and. b <= 4.0) then - ! Calculate F function at last energy point - a = x(k) - y - call calculate_F(F_a, a) - - ! Add contribution to broadened cross section - sigma = sigma + xs(k) * (y_inv_sq*F_a(2) + 2.0*y_inv*F_a(1) + F_a(0)) - end if - - ! ======================================================================= - ! EVALUATE SECOND TERM FROM x(k) + y = 0 to +4 - - if (y <= 4.0) then - ! Swap signs on y - y = -y - y_inv = -y_inv - k = 1 - - ! Calculate a and b based on 0 and x(1) - a = -y - b = x(k) - y - - ! Calculate F and H functions - call calculate_F(F_a, a) - call calculate_F(F_b, b) - H = F_a - F_b - - ! Add contribution to broadened cross section - sigma = sigma - xs(k) * x(k) * (y_inv_sq*H(1) + y_inv*H(0)) - - ! Now progress forward doing the remainder of the second term - do while (b <= 4.0) - ! Move to next point - F_a = F_b - k = k + 1 - b = x(k) - y - - ! Calculate F and H functions - call calculate_F(F_b, b) - H = F_a - F_b - - ! Calculate A(k), B(k), and slope terms - Ak = y_inv_sq*H(2) + 2.0*y_inv*H(1) + H(0) - Bk = y_inv_sq*H(4) + 4.0*y_inv*H(3) + 6.0*H(2) + 4.0*y*H(1) & - + y_sq*H(0) - slope = (xs(k) - xs(k-1)) / (x(k)**2 - x(k-1)**2) - - ! Add contribution to broadened cross section - sigma = sigma - Ak*(xs(k) - slope*x(k)**2) - slope*Bk - end do - end if - - ! Set broadened cross section - sigmaNew(i) = sigma - - end do ENERGY_NEUTRON - - end subroutine broaden - -!=============================================================================== -! CALCULATE_F evaluates the function: -! -! F(n,a) = 1/sqrt(pi)*int(z^n*exp(-z^2), z = a to infinity) -! -! The five values returned in a vector correspond to the integral for n = 0 -! through 4. These functions are called over and over during the Doppler -! broadening routine. -!=============================================================================== - - subroutine calculate_F(F, a) - - real(8), intent(inout) :: F(0:4) - real(8), intent(in) :: a - -#ifndef NO_F2008 - F(0) = 0.5*erfc(a) -#endif - F(1) = 0.5*sqrt_pi_inv*exp(-a*a) - F(2) = 0.5*F(0) + a*F(1) - F(3) = F(1)*(1.0 + a*a) - F(4) = 0.75*F(0) + F(1)*a*(1.5 + a*a) - - end subroutine calculate_F - -end module doppler diff --git a/src/endf_header.F90 b/src/endf_header.F90 index 9443efe2be..d45fbe68f3 100644 --- a/src/endf_header.F90 +++ b/src/endf_header.F90 @@ -37,7 +37,6 @@ module endf_header contains procedure :: from_hdf5 => polynomial_from_hdf5 procedure :: evaluate => polynomial_evaluate - procedure :: from_ace => polynomial_from_ace end type Polynomial !=============================================================================== @@ -52,7 +51,6 @@ module endf_header real(8), allocatable :: x(:) ! values of abscissa real(8), allocatable :: y(:) ! values of ordinate contains - procedure :: from_ace => tabulated1d_from_ace procedure :: from_hdf5 => tabulated1d_from_hdf5 procedure :: evaluate => tabulated1d_evaluate end type Tabulated1D @@ -63,24 +61,6 @@ contains ! Polynomial implementation !=============================================================================== - subroutine polynomial_from_ace(this, xss, idx) - class(Polynomial), intent(inout) :: this - real(8), intent(in) :: xss(:) - integer, intent(in) :: idx - - integer :: nc ! number of coefficients (order - 1) - - ! Clear space - if (allocated(this % coef)) deallocate(this % coef) - - ! Determine number of coefficients - nc = nint(xss(idx)) - - ! Allocate space for and read coefficients - allocate(this % coef(nc)) - this % coef(:) = xss(idx + 1 : idx + nc) - end subroutine polynomial_from_ace - subroutine polynomial_from_hdf5(this, dset_id) class(Polynomial), intent(inout) :: this integer(HID_T), intent(in) :: dset_id @@ -111,42 +91,6 @@ contains ! Tabulated1D implementation !=============================================================================== - subroutine tabulated1d_from_ace(this, xss, idx) - class(Tabulated1D), intent(inout) :: this - real(8), intent(in) :: xss(:) - integer, intent(in) :: idx - - integer :: nr, ne - - ! Clear space - if (allocated(this % nbt)) deallocate(this % nbt) - if (allocated(this % int)) deallocate(this % int) - if (allocated(this % x)) deallocate(this % x) - if (allocated(this % y)) deallocate(this % y) - - ! Determine number of regions - nr = nint(xss(idx)) - this % n_regions = nr - - ! Read interpolation region data - if (nr > 0) then - allocate(this % nbt(nr)) - allocate(this % int(nr)) - this % nbt(:) = nint(xss(idx + 1 : idx + nr)) - this % int(:) = nint(xss(idx + nr + 1 : idx + 2*nr)) - end if - - ! Determine number of pairs - ne = int(XSS(idx + 2*nr + 1)) - this % n_pairs = ne - - ! Read (x,y) pairs - allocate(this % x(ne)) - allocate(this % y(ne)) - this % x(:) = xss(idx + 2*nr + 2 : idx + 2*nr + 1 + ne) - this % y(:) = xss(idx + 2*nr + 2 + ne : idx + 2*nr + 1 + 2*ne) - end subroutine tabulated1d_from_ace - subroutine tabulated1d_from_hdf5(this, dset_id) class(Tabulated1D), intent(inout) :: this integer(HID_T), intent(in) :: dset_id diff --git a/src/error.F90 b/src/error.F90 index d041051d29..82b142b51e 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -265,4 +265,13 @@ contains end subroutine write_message + subroutine write_message_from_c(message, message_len, level) bind(C) + integer(C_INT), intent(in), value :: message_len + character(kind=C_CHAR), intent(in) :: message(message_len) + integer(C_INT), intent(in), value :: level + character(message_len+1) :: message_out + write(message_out, *) message + call write_message(message_out, level) + end subroutine write_message_from_c + end module error diff --git a/src/error.h b/src/error.h index 45d8bcdede..356dfd8010 100644 --- a/src/error.h +++ b/src/error.h @@ -11,7 +11,8 @@ namespace openmc { extern "C" void fatal_error_from_c(const char* message, int message_len); extern "C" void warning_from_c(const char* message, int message_len); - +extern "C" void write_message_from_c(const char* message, int message_len, + int level); inline void fatal_error(const char *message) @@ -43,5 +44,23 @@ void warning(const std::stringstream& message) warning(message.str()); } +inline +void write_message(const char* message, int level) +{ + write_message_from_c(message, strlen(message), level); +} + +inline +void write_message(const std::string& message, int level) +{ + write_message_from_c(message.c_str(), message.length(), level); +} + +inline +void write_message(const std::stringstream& message, int level) +{ + write_message(message.str(), level); +} + } // namespace openmc #endif // ERROR_H diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 758fc54da7..2c943706d7 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -736,7 +736,7 @@ contains ! find which material is associated with this cell (material_index ! is the index into the materials array) if (present(instance)) then - material_index = cells(index) % material(instance) + material_index = cells(index) % material(instance + 1) else material_index = cells(index) % material(1) end if diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 8a8391bb54..7133ad51ac 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -447,6 +447,172 @@ read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep } +void +read_nd_vector(hid_t obj_id, const char* name, std::vector& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + read_double(obj_id, name, result.data(), true); + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector >& result, bool must_have) +{ + if (object_exists(obj_id, name)) { + int dim1 = result.size(); + int dim2 = result[0].size(); + double temp_arr[dim1 * dim2]; + read_double(obj_id, name, temp_arr, true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + result[i][j] = temp_arr[temp_idx++]; + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector >& result, bool must_have) +{ + if (object_exists(obj_id, name)) { + int dim1 = result.size(); + int dim2 = result[0].size(); + int temp_arr[dim1 * dim2]; + read_int(obj_id, name, temp_arr, true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + result[i][j] = temp_arr[temp_idx++]; + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > >& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + int dim1 = result.size(); + int dim2 = result[0].size(); + int dim3 = result[0][0].size(); + double temp_arr[dim1 * dim2 * dim3]; + read_double(obj_id, name, temp_arr, true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + for (int k = 0; k < dim3; k++) { + result[i][j][k] = temp_arr[temp_idx++]; + } + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > >& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + int dim1 = result.size(); + int dim2 = result[0].size(); + int dim3 = result[0][0].size(); + int temp_arr[dim1 * dim2 * dim3]; + read_int(obj_id, name, temp_arr, true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + for (int k = 0; k < dim3; k++) { + result[i][j][k] = temp_arr[temp_idx++]; + } + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > > >& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + int dim1 = result.size(); + int dim2 = result[0].size(); + int dim3 = result[0][0].size(); + int dim4 = result[0][0][0].size(); + double temp_arr[dim1 * dim2 * dim3 * dim4]; + read_double(obj_id, name, temp_arr, true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + for (int k = 0; k < dim3; k++) { + for (int l = 0; l < dim4; l++) { + result[i][j][k][l] = temp_arr[temp_idx++]; + } + } + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > > > >& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + int dim1 = result.size(); + int dim2 = result[0].size(); + int dim3 = result[0][0].size(); + int dim4 = result[0][0][0].size(); + int dim5 = result[0][0][0][0].size(); + double temp_arr[dim1 * dim2 * dim3 * dim4 * dim5]; + read_double(obj_id, name, temp_arr, true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + for (int k = 0; k < dim3; k++) { + for (int l = 0; l < dim4; l++) { + for (int m = 0; m < dim5; m++) { + result[i][j][k][l][m] = temp_arr[temp_idx++]; + } + } + } + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + + void read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results) { diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 353d0137af..b00fe7c879 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -55,6 +56,39 @@ extern "C" void read_string(hid_t obj_id, const char* name, size_t slen, extern "C" void read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep); +void +read_nd_vector(hid_t obj_id, const char* name, std::vector& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector >& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector >& result, bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > >& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > >& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > > >& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > > > >& result, + bool must_have = false); + extern "C" void read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results); diff --git a/src/input_xml.F90 b/src/input_xml.F90 index e24f3fe253..37ddee8956 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -19,7 +19,7 @@ module input_xml use mesh_header use message_passing use mgxs_data, only: create_macro_xs, read_mgxs - use mgxs_header + use mgxs_interface use nuclide_header use output, only: title, header, print_plot use plot_header @@ -228,7 +228,7 @@ contains ¬ exist! In order to run OpenMC, you first need a set of input & &files; at a minimum, this includes settings.xml, geometry.xml, & &and materials.xml. Please consult the user's guide at & - &http://mit-crpg.github.io/openmc for further information.") + &http://openmc.readthedocs.io for further information.") else ! The settings.xml file is optional if we just want to make a plot. return @@ -1369,7 +1369,7 @@ contains &materials.xml, settings.xml, or in the OPENMC_CROSS_SECTIONS& & environment variable. OpenMC needs such a file to identify & &where to find ACE cross section libraries. Please consult the& - & user's guide at http://mit-crpg.github.io/openmc for & + & user's guide at http://openmc.readthedocs.io for & &information on how to set up ACE cross section libraries.") else call warning("The CROSS_SECTIONS environment variable is & @@ -1387,7 +1387,7 @@ contains &materials.xml or in the OPENMC_MG_CROSS_SECTIONS environment & &variable. OpenMC needs such a file to identify where to & &find MG cross section libraries. Please consult the user's & - &guide at http://mit-crpg.github.io/openmc for information on & + &guide at http://openmc.readthedocs.io for information on & &how to set up MG cross section libraries.") else if (len_trim(env_variable) /= 0) then path_cross_sections = trim(env_variable) @@ -3458,7 +3458,7 @@ contains if (run_CE) then awr = nuclides(mat % nuclide(j)) % awr else - awr = nuclides_MG(mat % nuclide(j)) % obj % awr + awr = get_awr_c(mat % nuclide(j)) end if ! if given weight percent, convert all values so that they are divided @@ -3483,7 +3483,7 @@ contains if (run_CE) then awr = nuclides(mat % nuclide(j)) % awr else - awr = nuclides_MG(mat % nuclide(j)) % obj % awr + awr = get_awr_c(mat % nuclide(j)) end if x = mat % atom_density(j) sum_percent = sum_percent + x*awr diff --git a/src/lattice.h b/src/lattice.h index 19749ce4f0..bc706a5a3f 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -9,7 +9,7 @@ #include "constants.h" #include "hdf5.h" -#include "pugixml/pugixml.hpp" +#include "pugixml.hpp" namespace openmc { diff --git a/src/material_header.F90 b/src/material_header.F90 index 038e529bee..7fbfe553b3 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -34,7 +34,7 @@ module material_header integer :: n_nuclides = 0 ! number of nuclides integer, allocatable :: nuclide(:) ! index in nuclides array real(8) :: density ! total atom density in atom/b-cm - real(8), allocatable :: atom_density(:) ! nuclide 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 diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 159e0a4e23..31f86ea5f6 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -97,7 +97,7 @@ void calc_pn_c(int n, double x, double pnx[]) { } // Use recursion relation to build the higher orders - for (int l = 1; l < n; l ++) { + for (int l = 1; l < n; l++) { pnx[l + 1] = ((2 * l + 1) * x * pnx[l] - l * pnx[l - 1]) / (l + 1); } } diff --git a/src/math_functions.h b/src/math_functions.h index 68e89251e3..2ceea98d87 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -7,22 +7,12 @@ #include #include +#include "constants.h" #include "random_lcg.h" namespace openmc { -//============================================================================== -// Module constants. -//============================================================================== - -// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we -// use so for now we will reuse the Fortran constant until we are OK with -// modifying test results -extern "C" constexpr double PI {3.1415926535898}; - -extern "C" const double SQRT_PI {std::sqrt(PI)}; - //============================================================================== //! Calculate the percentile of the standard normal distribution with a //! specified probability level. diff --git a/src/mgxs.cpp b/src/mgxs.cpp new file mode 100644 index 0000000000..3e84f63072 --- /dev/null +++ b/src/mgxs.cpp @@ -0,0 +1,712 @@ +#include +#include +#include +#include + + #ifdef _OPENMP + # include + #endif + +#include "error.h" +#include "math_functions.h" +#include "random_lcg.h" +#include "string_functions.h" +#include "mgxs.h" + + +namespace openmc { + +// Storage for the MGXS data +std::vector nuclides_MG; +std::vector macro_xs; + + +//============================================================================== +// Mgxs base-class methods +//============================================================================== + +void +Mgxs::init(const std::string& in_name, double in_awr, + const double_1dvec& in_kTs, bool in_fissionable, int in_scatter_format, + int in_num_groups, int in_num_delayed_groups, bool in_is_isotropic, + const double_1dvec& in_polar, const double_1dvec& in_azimuthal) +{ + // Set the metadata + name = in_name; + awr = in_awr; + kTs = in_kTs; + fissionable = in_fissionable; + scatter_format = in_scatter_format; + num_groups = in_num_groups; + num_delayed_groups = in_num_delayed_groups; + xs.resize(in_kTs.size()); + is_isotropic = in_is_isotropic; + n_pol = in_polar.size(); + n_azi = in_azimuthal.size(); + polar = in_polar; + azimuthal = in_azimuthal; + + // Set the cross section index cache +#ifdef _OPENMP + int n_threads = omp_get_max_threads(); +#else + int n_threads = 1; +#endif + cache.resize(n_threads); + // std::vector.resize() will value-initialize the members of cache[:] +} + +//============================================================================== + +void +Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups, + int in_num_delayed_groups, const double_1dvec& temperature, + double tolerance, int_1dvec& temps_to_read, int& order_dim, int& method) +{ + // get name + char char_name[MAX_WORD_LEN]; + get_name(xs_id, char_name); + std::string in_name {char_name}; + // remove the leading '/' + in_name = in_name.substr(1); + + // Get the AWR + double in_awr; + if (attribute_exists(xs_id, "atomic_weight_ratio")) { + read_attr_double(xs_id, "atomic_weight_ratio", &in_awr); + } else { + in_awr = MACROSCOPIC_AWR; + } + + // Determine the available temperatures + hid_t kT_group = open_group(xs_id, "kTs"); + int num_temps = get_num_datasets(kT_group); + char* dset_names[num_temps]; + for (int i = 0; i < num_temps; i++) { + dset_names[i] = new char[151]; + } + get_datasets(kT_group, dset_names); + double_1dvec available_temps(num_temps); + for (int i = 0; i < num_temps; i++) { + read_double(kT_group, dset_names[i], &available_temps[i], true); + + // convert eV to Kelvin + available_temps[i] /= K_BOLTZMANN; + + // Done with dset_names, so delete it + delete[] dset_names[i]; + } + std::sort(available_temps.begin(), available_temps.end()); + + // If only one temperature is available, lets just use nearest temperature + // interpolation + if ((num_temps == 1) && (method == TEMPERATURE_INTERPOLATION)) { + warning("Cross sections for " + strtrim(name) + " are only available " + + "at one temperature. Reverting to the nearest temperature " + + "method."); + method = TEMPERATURE_NEAREST; + } + + switch(method) { + case TEMPERATURE_NEAREST: + // Find the minimum difference + for (int i = 0; i < temperature.size(); i++) { + std::valarray temp_diff(available_temps.data(), + available_temps.size()); + temp_diff = std::abs(temp_diff - temperature[i]); + int i_closest = std::min_element(std::begin(temp_diff), std::end(temp_diff)) - + std::begin(temp_diff); + double temp_actual = available_temps[i_closest]; + + if (std::abs(temp_actual - temperature[i]) < 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)); + } else { + fatal_error("MGXS Library does not contain cross section for " + + in_name + " at or near " + + std::to_string(std::round(temperature[i])) + " K."); + } + } + } + break; + + case TEMPERATURE_INTERPOLATION: + for (int i = 0; i < temperature.size(); i++) { + for (int j = 0; j < num_temps - 1; j++) { + if ((available_temps[j] <= temperature[i]) && + (temperature[i] < available_temps[j + 1])) { + if (std::find(temps_to_read.begin(), + temps_to_read.end(), + std::round(available_temps[j])) == temps_to_read.end()) { + temps_to_read.push_back(std::round((int)available_temps[j])); + } + + if (std::find(temps_to_read.begin(), temps_to_read.end(), + std::round(available_temps[j + 1])) == temps_to_read.end()) { + temps_to_read.push_back(std::round((int) available_temps[j + 1])); + } + continue; + } + } + + fatal_error("MGXS Library does not contain cross sections for " + + in_name + " at temperatures that bound " + + std::to_string(std::round(temperature[i]))); + } + } + std::sort(temps_to_read.begin(), temps_to_read.end()); + + // Get the library's temperatures + int n_temperature = temps_to_read.size(); + double_1dvec in_kTs(n_temperature); + for (int i = 0; i < n_temperature; i++) { + std::string temp_str(std::to_string(temps_to_read[i]) + "K"); + + //read exact temperature value + read_double(kT_group, temp_str.c_str(), &in_kTs[i], true); + } + close_group(kT_group); + + // Load the remaining metadata + int in_scatter_format; + if (attribute_exists(xs_id, "scatter_format")) { + std::string temp_str(MAX_WORD_LEN, ' '); + read_attr_string(xs_id, "scatter_format", MAX_WORD_LEN, &temp_str[0]); + to_lower(strtrim(temp_str)); + if (temp_str.compare(0, 8, "legendre") == 0) { + in_scatter_format = ANGLE_LEGENDRE; + } else if (temp_str.compare(0, 9, "histogram") == 0) { + in_scatter_format = ANGLE_HISTOGRAM; + } else if (temp_str.compare(0, 7, "tabular") == 0) { + in_scatter_format = ANGLE_TABULAR; + } else { + fatal_error("Invalid scatter_format option!"); + } + } else { + in_scatter_format = ANGLE_LEGENDRE; + } + + if (attribute_exists(xs_id, "scatter_shape")) { + std::string temp_str(MAX_WORD_LEN, ' '); + read_attr_string(xs_id, "scatter_shape", MAX_WORD_LEN, &temp_str[0]); + to_lower(strtrim(temp_str)); + if (temp_str.compare(0, 14, "[g][g\'][order]") != 0) { + fatal_error("Invalid scatter_shape option!"); + } + } + + bool in_fissionable = false; + if (attribute_exists(xs_id, "fissionable")) { + int int_fiss; + read_attr_int(xs_id, "fissionable", &int_fiss); + in_fissionable = int_fiss; + } else { + fatal_error("Fissionable element must be set!"); + } + + // Get the library's value for the order + if (attribute_exists(xs_id, "order")) { + read_attr_int(xs_id, "order", &order_dim); + } else { + fatal_error("Order must be provided!"); + } + + // Store the dimensionality of the data in order_dim. + // For Legendre data, we usually refer to it as Pn where n is the order. + // However Pn has n+1 sets of points (since you need to count the P0 + // moment). Adjust for that. Histogram and Tabular formats dont need this + // adjustment. + if (in_scatter_format == ANGLE_LEGENDRE) { + ++order_dim; + } + + // Get the angular information + int in_n_pol; + int in_n_azi; + bool in_is_isotropic = true; + if (attribute_exists(xs_id, "representation")) { + std::string temp_str(MAX_WORD_LEN, ' '); + read_attr_string(xs_id, "representation", MAX_WORD_LEN, &temp_str[0]); + to_lower(strtrim(temp_str)); + if (temp_str.compare(0, 5, "angle") == 0) { + in_is_isotropic = false; + } else if (temp_str.compare(0, 9, "isotropic") != 0) { + fatal_error("Invalid Data Representation!"); + } + } + + if (!in_is_isotropic) { + if (attribute_exists(xs_id, "num_polar")) { + read_attr_int(xs_id, "num_polar", &in_n_pol); + } else { + fatal_error("num_polar must be provided!"); + } + if (attribute_exists(xs_id, "num_azimuthal")) { + read_attr_int(xs_id, "num_azimuthal", &in_n_azi); + } else { + fatal_error("num_azimuthal must be provided!"); + } + } else { + in_n_pol = 1; + in_n_azi = 1; + } + + // Set the angular bins to use equally-spaced bins + double_1dvec in_polar(in_n_pol); + double dangle = PI / in_n_pol; + for (int p = 0; p < in_n_pol; p++) { + in_polar[p] = (p + 0.5) * dangle; + } + double_1dvec in_azimuthal(in_n_azi); + dangle = 2. * PI / in_n_azi; + for (int a = 0; a < in_n_azi; a++) { + in_azimuthal[a] = (a + 0.5) * dangle - PI; + } + + // 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_azimuthal); +} + +//============================================================================== + +Mgxs::Mgxs(hid_t xs_id, int energy_groups, int delayed_groups, + const double_1dvec& temperature, double tolerance, int max_order, + bool legendre_to_tabular, int legendre_to_tabular_points, int& method) +{ + // Call generic data gathering routine (will populate the metadata) + int order_data; + int_1dvec temps_to_read; + metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature, + tolerance, temps_to_read, order_data, method); + + // Set number of energy and delayed groups + int final_scatter_format = scatter_format; + if (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); + // 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); + close_group(xsdata_grp); + + } // end temperature loop + + // Make sure the scattering format is updated to the final case + scatter_format = final_scatter_format; +} + +//============================================================================== + +Mgxs::Mgxs(const std::string& in_name, const double_1dvec& mat_kTs, + const std::vector& micros, const double_1dvec& atom_densities, + double tolerance, int& method) +{ + // Get the minimum data needed to initialize: + // Dont need awr, but lets just initialize it anyways + double in_awr = -1.; + // start with the assumption it is not fissionable + bool in_fissionable = false; + for (int m = 0; m < micros.size(); m++) { + if (micros[m]->fissionable) in_fissionable = true; + } + // Force all of the following data to be the same; these will be verified + // to be true later + int in_scatter_format = micros[0]->scatter_format; + int in_num_groups = micros[0]->num_groups; + int in_num_delayed_groups = micros[0]->num_delayed_groups; + bool in_is_isotropic = micros[0]->is_isotropic; + double_1dvec in_polar = micros[0]->polar; + double_1dvec 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); + + // 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()); + + // Find the right temperature index to use + double temp_desired = mat_kTs[t]; + + // Create the list of temperature indices and interpolation factors for + // each microscopic data at the material temperature + int_1dvec micro_t(micros.size(), 0); + double_1dvec micro_t_interp(micros.size(), 0.); + for (int m = 0; m < micros.size(); m++) { + switch(method) { + case TEMPERATURE_NEAREST: + { + // Find the nearest temperature + std::valarray temp_diff(micros[m]->kTs.data(), + micros[m]->kTs.size()); + temp_diff = std::abs(temp_diff - temp_desired); + micro_t[m] = std::min_element(std::begin(temp_diff), + std::end(temp_diff)) - + std::begin(temp_diff); + double temp_actual = micros[m]->kTs[micro_t[m]]; + + if (std::abs(temp_actual - temp_desired) >= K_BOLTZMANN * tolerance) { + fatal_error("MGXS Library does not contain cross section for " + + name + " at or near " + + std::to_string(std::round(temp_desired / K_BOLTZMANN)) + + " K."); + } + } + break; + case TEMPERATURE_INTERPOLATION: + // Get a list of bounding temperatures for each actual temperature + // present in the model + for (int k = 0; k < micros[m]->kTs.size() - 1; k++) { + if ((micros[m]->kTs[k] <= temp_desired) && + (temp_desired < micros[m]->kTs[k + 1])) { + micro_t[m] = k; + if (k == 0) { + micro_t_interp[m] = (temp_desired - micros[m]->kTs[k]) / + (micros[m]->kTs[k + 1] - micros[m]->kTs[k]); + } else { + micro_t_interp[m] = 1.; + } + } + } + } // end switch + } // end microscopic temperature loop + + // We are about to loop through each of the microscopic objects + // and incorporate the contribution of each microscopic data at + // one of the two temperature interpolants to this macroscopic quantity. + // 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; + for (int interp_point = 0; interp_point < num_interp_points; interp_point++) { + double_1dvec interp(micros.size()); + double_1dvec temp_indices(micros.size()); + for (int m = 0; m < micros.size(); m++) { + interp[m] = (1. - micro_t_interp[m]) * atom_densities[m]; + temp_indices[m] = micro_t[m] + interp_point; + } + + combine(micros, interp, micro_t, t); + } // end loop to sum all micros across the temperatures + } // end temperature (t) loop +} + +//============================================================================== + +void +Mgxs::combine(const std::vector& micros, const double_1dvec& scalars, + const int_1dvec& micro_ts, int this_t) +{ + // Build the vector of pointers to the xs objects within micros + std::vector those_xs(micros.size()); + for (int i = 0; i < micros.size(); i++) { + if (!xs[this_t].equiv(micros[i]->xs[micro_ts[i]])) { + fatal_error("Cannot combine the Mgxs objects!"); + } + those_xs[i] = &(micros[i]->xs[micro_ts[i]]); + } + + xs[this_t].combine(those_xs, scalars); +} + +//============================================================================== + +double +Mgxs::get_xs(int xstype, int gin, int* gout, double* mu, int* dg) +{ + // This method assumes that the temperature and angle indices are set +#ifdef _OPENMP + int tid = omp_get_thread_num(); + XsData* xs_t = &xs[cache[tid].t]; + int a = cache[tid].a; +#else + XsData* xs_t = &xs[cache[0].t]; + int a = cache[0].a; +#endif + double val; + switch(xstype) { + case MG_GET_XS_TOTAL: + val = xs_t->total[a][gin]; + break; + case MG_GET_XS_NU_FISSION: + val = fissionable ? xs_t->nu_fission[a][gin] : 0.; + break; + case MG_GET_XS_ABSORPTION: + val = xs_t->absorption[a][gin]; + break; + case MG_GET_XS_FISSION: + val = fissionable ? xs_t->fission[a][gin] : 0.; + break; + case MG_GET_XS_KAPPA_FISSION: + val = fissionable ? xs_t->kappa_fission[a][gin] : 0.; + break; + case MG_GET_XS_SCATTER: + case MG_GET_XS_SCATTER_MULT: + case MG_GET_XS_SCATTER_FMU_MULT: + case MG_GET_XS_SCATTER_FMU: + val = xs_t->scatter[a]->get_xs(xstype, gin, gout, mu); + break; + case MG_GET_XS_PROMPT_NU_FISSION: + val = fissionable ? xs_t->prompt_nu_fission[a][gin] : 0.; + break; + case MG_GET_XS_DELAYED_NU_FISSION: + if (fissionable) { + if (dg != nullptr) { + val = xs_t->delayed_nu_fission[a][gin][*dg]; + } else { + val = 0.; + for (auto& num : xs_t->delayed_nu_fission[a][gin]) { + val += num; + } + } + } else { + val = 0.; + } + break; + case MG_GET_XS_CHI_PROMPT: + if (fissionable) { + if (gout != nullptr) { + val = xs_t->chi_prompt[a][gin][*gout]; + } else { + // provide an outgoing group-wise sum + val = 0.; + for (auto& num : xs_t->chi_prompt[a][gin]) { + val += num; + } + } + } else { + val = 0.; + } + break; + case MG_GET_XS_CHI_DELAYED: + if (fissionable) { + if (gout != nullptr) { + if (dg != nullptr) { + val = xs_t->chi_delayed[a][gin][*gout][*dg]; + } else { + val = xs_t->chi_delayed[a][gin][*gout][0]; + } + } else { + if (dg != nullptr) { + val = 0.; + for (int i = 0; i < xs_t->chi_delayed[a][gin].size(); i++) { + val += xs_t->chi_delayed[a][gin][i][*dg]; + } + } else { + val = 0.; + for (int i = 0; i < xs_t->chi_delayed[a][gin].size(); i++) { + for (auto& num : xs_t->chi_delayed[a][gin][i]) { + val += num; + } + } + } + } + } else { + val = 0.; + } + break; + case MG_GET_XS_INVERSE_VELOCITY: + val = xs_t->inverse_velocity[a][gin]; + break; + case MG_GET_XS_DECAY_RATE: + if (dg != nullptr) { + val = xs_t->decay_rate[a][*dg + 1]; + } else { + val = xs_t->decay_rate[a][0]; + } + break; + default: + val = 0.; + } + return val; +} + +//============================================================================== + +void +Mgxs::sample_fission_energy(int gin, int& dg, int& gout) +{ + // This method assumes that the temperature and angle indices are set +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif + XsData* xs_t = &xs[cache[tid].t]; + double nu_fission = xs_t->nu_fission[cache[tid].a][gin]; + + // Find the probability of having a prompt neutron + double prob_prompt = + xs_t->prompt_nu_fission[cache[tid].a][gin]; + + // sample random numbers + double xi_pd = prn() * nu_fission; + double xi_gout = prn(); + + // Select whether the neutron is prompt or delayed + if (xi_pd <= prob_prompt) { + // the neutron is prompt + + // set the delayed group for the particle to be -1, indicating prompt + dg = -1; + + // sample the outgoing energy group + gout = 0; + double prob_gout = + xs_t->chi_prompt[cache[tid].a][gin][gout]; + while (prob_gout < xi_gout) { + gout++; + prob_gout += xs_t->chi_prompt[cache[tid].a][gin][gout]; + } + + } else { + // the neutron is delayed + + // get the delayed group + dg = 0; + while (xi_pd >= prob_prompt) { + dg++; + prob_prompt += + xs_t->delayed_nu_fission[cache[tid].a][gin][dg]; + } + + // adjust dg in case of round-off error + dg = std::min(dg, num_delayed_groups - 1); + + // sample the outgoing energy group + gout = 0; + double prob_gout = + xs_t->chi_delayed[cache[tid].a][gin][gout][dg]; + while (prob_gout < xi_gout) { + gout++; + prob_gout += + xs_t->chi_delayed[cache[tid].a][gin][gout][dg]; + } + } +} + +//============================================================================== + +void +Mgxs::sample_scatter(int gin, int& gout, double& mu, double& wgt) +{ + // This method assumes that the temperature and angle indices are set + // Sample the data +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif + xs[cache[tid].t].scatter[cache[tid].a]->sample(gin, gout, mu, wgt); +} + +//============================================================================== + +void +Mgxs::calculate_xs(int gin, double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs) +{ + // Set our indices +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif + set_temperature_index(sqrtkT); + set_angle_index(uvw); + XsData* xs_t = &xs[cache[tid].t]; + total_xs = xs_t->total[cache[tid].a][gin]; + abs_xs = xs_t->absorption[cache[tid].a][gin]; + + nu_fiss_xs = fissionable ? xs_t->nu_fission[cache[tid].a][gin] : 0.; +} + +//============================================================================== + +bool +Mgxs::equiv(const Mgxs& that) +{ + return ((num_delayed_groups == that.num_delayed_groups) && + (num_groups == that.num_groups) && + (n_pol == that.n_pol) && + (n_azi == that.n_azi) && + (std::equal(polar.begin(), polar.end(), that.polar.begin())) && + (std::equal(azimuthal.begin(), azimuthal.end(), that.azimuthal.begin())) && + (scatter_format == that.scatter_format)); +} + +//============================================================================== + +void +Mgxs::set_temperature_index(double sqrtkT) +{ + // See if we need to find the new index +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif + if (sqrtkT != cache[tid].sqrtkT) { + double kT = sqrtkT * sqrtkT; + + // initialize vector for storage of the differences + std::valarray temp_diff(kTs.data(), kTs.size()); + + // Find the minimum difference of kT and kTs + temp_diff = std::abs(temp_diff - kT); + cache[tid].t = std::min_element(std::begin(temp_diff), std::end(temp_diff)) - + std::begin(temp_diff); + + // store this temperature as the last one used + cache[tid].sqrtkT = sqrtkT; + } +} + +//============================================================================== + +void +Mgxs::set_angle_index(const double uvw[3]) +{ + // See if we need to find the new index +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif + if (!is_isotropic && + ((uvw[0] != cache[tid].u) || (uvw[1] != cache[tid].v) || + (uvw[2] != cache[tid].w))) { + // convert uvw to polar and azimuthal angles + double my_pol = std::acos(uvw[2]); + double my_azi = std::atan2(uvw[1], uvw[0]); + + // Find the location, assuming equal-bin angles + double delta_angle = PI / n_pol; + int p = std::floor(my_pol / delta_angle); + delta_angle = 2. * PI / n_azi; + int a = std::floor((my_azi + PI) / delta_angle); + + cache[tid].a = n_azi * p + a; + + // store this direction as the last one used + cache[tid].u = uvw[0]; + cache[tid].v = uvw[1]; + cache[tid].w = uvw[2]; + } +} + +} // namespace openmc \ No newline at end of file diff --git a/src/mgxs.h b/src/mgxs.h new file mode 100644 index 0000000000..0434bd55d5 --- /dev/null +++ b/src/mgxs.h @@ -0,0 +1,205 @@ +//! \file mgxs.h +//! A collection of classes for Multi-Group Cross Section data + +#ifndef MGXS_H +#define MGXS_H + +#include +#include + +#include "constants.h" +#include "hdf5_interface.h" +#include "xsdata.h" + + +namespace openmc { + +//============================================================================== +// Cache contains the cached data for an MGXS object +//============================================================================== + +struct CacheData { + double sqrtkT; // last temperature corresponding to t + int t; // temperature index + int a; // angle index + // last angle that corresponds to a + double u; + double v; + double w; +}; + +//============================================================================== +// MGXS contains the mgxs data for a nuclide/material +//============================================================================== + +class Mgxs { + private: + + double_1dvec kTs; // temperature in eV (k * T) + int scatter_format; // flag for if this is legendre, histogram, or tabular + int num_delayed_groups; // number of delayed neutron groups + int num_groups; // number of energy groups + std::vector xs; // Cross section data + // MGXS Incoming Flux Angular grid information + bool is_isotropic; // used to skip search for angle indices if isotropic + int n_pol; + int n_azi; + double_1dvec polar; + double_1dvec azimuthal; + + //! \brief Initializes the Mgxs object metadata + //! + //! @param in_name Name of the object. + //! @param in_awr atomic-weight ratio. + //! @param in_kTs temperatures (in units of eV) that data is available. + //! @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 double_1dvec& in_kTs, + bool in_fissionable, int in_scatter_format, int in_num_groups, + int in_num_delayed_groups, bool in_is_isotropic, + const double_1dvec& in_polar, const double_1dvec& 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 double_1dvec& temperature, + double tolerance, int_1dvec& temps_to_read, int& order_dim, + int& method); + + //! \brief Performs the actual act of combining the microscopic data for a + //! single temperature. + //! + //! @param micros Microscopic objects to combine. + //! @param scalars Scalars to multiply the microscopic data by. + //! @param micro_ts The temperature index of the microscopic objects that + //! corresponds to the temperature of interest. + //! @param this_t The temperature index of the macroscopic object. + void + combine(const std::vector& micros, const double_1dvec& scalars, + const int_1dvec& micro_ts, int this_t); + + //! \brief Checks to see if this and that are able to be combined + //! + //! This comparison is used when building macroscopic cross sections + //! from microscopic cross sections. + //! @param that The other Mgxs to compare to this one. + //! @return True if they can be combined, False otherwise. + bool equiv(const Mgxs& that); + + public: + + std::string name; // name of dataset, e.g., UO2 + double awr; // atomic weight ratio + bool fissionable; // Is this fissionable + std::vector cache; // index and data cache + + Mgxs() = default; + + //! \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 double_1dvec& temperature, double tolerance, + int max_order, bool legendre_to_tabular, + int legendre_to_tabular_points, int& method); + + //! \brief Constructor that initializes and populates all data to build a + //! macroscopic cross section from microscopic cross section. + //! + //! @param in_name Name of the object. + //! @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 double_1dvec& mat_kTs, + const std::vector& micros, const double_1dvec& atom_densities, + double tolerance, int& method); + + //! \brief Provides a cross section value given certain parameters + //! + //! @param xstype Type of cross section requested, according to the + //! enumerated constants. + //! @param gin Incoming energy group. + //! @param gout Outgoing energy group; use nullptr if irrelevant, or if a + //! sum is requested. + //! @param mu Cosine of the change-in-angle, for scattering quantities; + //! use nullptr if irrelevant. + //! @param dg delayed group index; use nullptr if irrelevant. + //! @return Requested cross section value. + double + get_xs(int xstype, int gin, int* gout, double* mu, int* dg); + + //! \brief Samples the fission neutron energy and if prompt or delayed. + //! + //! @param gin Incoming energy group. + //! @param dg Sampled delayed group index. + //! @param gout Sampled outgoing energy group. + void + sample_fission_energy(int gin, int& dg, int& gout); + + //! \brief Samples the outgoing energy and angle from a scatter event. + //! + //! @param gin Incoming energy group. + //! @param gout Sampled outgoing energy group. + //! @param mu Sampled cosine of the change-in-angle. + //! @param wgt Weight of the particle to be adjusted. + void + sample_scatter(int gin, int& gout, double& mu, double& wgt); + + //! \brief Calculates cross section quantities needed for tracking. + //! + //! @param gin Incoming energy group. + //! @param sqrtkT Temperature of the material. + //! @param uvw Incoming particle direction. + //! @param total_xs Resultant total cross section. + //! @param abs_xs Resultant absorption cross section. + //! @param nu_fiss_xs Resultant nu-fission cross section. + void + calculate_xs(int gin, double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs); + + //! \brief Sets the temperature index in cache given a temperature + //! + //! @param sqrtkT Temperature of the material. + void + set_temperature_index(double sqrtkT); + + //! \brief Sets the angle index in cache given a direction + //! + //! @param uvw Incoming particle direction. + void + set_angle_index(const double uvw[3]); +}; + +} // namespace openmc +#endif // MGXS_H \ No newline at end of file diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index e0631d16c5..1b3ed35113 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -1,5 +1,7 @@ module mgxs_data + use, intrinsic :: ISO_C_BINDING + use constants use algorithm, only: find use dict_header, only: DictCharInt @@ -7,11 +9,11 @@ module mgxs_data use geometry_header, only: get_temperatures, cells use hdf5_interface use material_header, only: Material, materials, n_materials - use mgxs_header + use mgxs_interface use nuclide_header, only: n_nuclides use set_header, only: SetChar use settings - use stl_vector, only: VectorReal + use stl_vector, only: VectorReal, VectorChar use string, only: to_lower implicit none @@ -27,14 +29,11 @@ contains integer :: j ! index over nuclides in material integer :: i_nuclide ! index in nuclides array character(20) :: name ! name of library to load - integer :: representation ! Data representation - character(MAX_LINE_LEN) :: temp_str type(Material), pointer :: mat type(SetChar) :: already_read integer(HID_T) :: file_id - integer(HID_T) :: xsdata_group logical :: file_exists - type(VectorReal), allocatable :: temps(:) + type(VectorReal), allocatable, target :: temps(:) character(MAX_WORD_LEN) :: word integer, allocatable :: array(:) @@ -69,9 +68,6 @@ contains &version supported by OpenMC.") end if - ! allocate arrays for MGXS storage and cross section cache - allocate(nuclides_MG(n_nuclides)) - ! ========================================================================== ! READ ALL MGXS CROSS SECTION TABLES @@ -80,89 +76,29 @@ contains mat => materials(i) NUCLIDE_LOOP: do j = 1, mat % n_nuclides - name = mat % names(j) + name = trim(mat % names(j)) // C_NULL_CHAR + i_nuclide = mat % nuclide(j) if (.not. already_read % contains(name)) then - i_nuclide = mat % nuclide(j) + 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 write_message("Loading " // trim(name) // " data...", 6) - - ! Check to make sure cross section set exists in the library - if (object_exists(file_id, trim(name))) then - xsdata_group = open_group(file_id, trim(name)) - else - call fatal_error("Data for '" // trim(name) // "' does not exist in "& - &// trim(path_cross_sections)) - end if - - ! First find out the data representation - if (attribute_exists(xsdata_group, "representation")) then - - call read_attribute(temp_str, xsdata_group, "representation") - - if (trim(temp_str) == 'isotropic') then - representation = MGXS_ISOTROPIC - else if (trim(temp_str) == 'angle') then - representation = MGXS_ANGLE - else - call fatal_error("Invalid Data Representation!") - end if - else - ! Default to isotropic representation - representation = MGXS_ISOTROPIC - end if - - ! Now allocate accordingly - select case(representation) - - case(MGXS_ISOTROPIC) - allocate(MgxsIso :: nuclides_MG(i_nuclide) % obj) - - case(MGXS_ANGLE) - allocate(MgxsAngle :: nuclides_MG(i_nuclide) % obj) - - end select - - ! Now read in the data specific to the type we just declared - call nuclides_MG(i_nuclide) % obj % from_hdf5(xsdata_group, & - num_energy_groups, num_delayed_groups, temps(i_nuclide), & - temperature_method, temperature_tolerance, max_order, & - legendre_to_tabular, legendre_to_tabular_points) - - ! Add name to dictionary call already_read % add(name) - - call close_group(xsdata_group) - end if end do NUCLIDE_LOOP + + mat % fissionable = 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() - ! Loop around material - MATERIAL_LOOP3: do i = 1, n_materials - - ! Get material - mat => materials(i) - - ! Loop around nuclides in material - NUCLIDE_LOOP2: do j = 1, mat % n_nuclides - - ! Is this fissionable? - if (nuclides_MG(mat % nuclide(j)) % obj % fissionable) then - mat % fissionable = .true. - end if - if (mat % fissionable) then - exit NUCLIDE_LOOP2 - end if - - end do NUCLIDE_LOOP2 - end do MATERIAL_LOOP3 - - call file_close(file_id) - end subroutine read_mgxs !=============================================================================== @@ -173,8 +109,7 @@ contains integer :: i_mat ! index in materials array type(Material), pointer :: mat ! current material type(VectorReal), allocatable :: kTs(:) - - allocate(macro_xs(n_materials)) + character(MAX_WORD_LEN) :: name ! name of material ! Get temperatures to read for each material call get_mat_kTs(kTs) @@ -188,19 +123,13 @@ contains ! Get the material mat => materials(i_mat) - ! Get the scattering type for the first nuclide - select type(nuc => nuclides_MG(mat % nuclide(1)) % obj) - type is (MgxsIso) - allocate(MgxsIso :: macro_xs(i_mat) % obj) - type is (MgxsAngle) - allocate(MgxsAngle :: macro_xs(i_mat) % obj) - end select + name = trim(mat % name) // C_NULL_CHAR ! Do not read materials which we do not actually use in the problem to ! reduce storage if (allocated(kTs(i_mat) % data)) then - call macro_xs(i_mat) % obj % combine(kTs(i_mat), mat, nuclides_MG, & - num_energy_groups, num_delayed_groups, max_order, & + 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 if end do diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 deleted file mode 100644 index 5abeccbcfa..0000000000 --- a/src/mgxs_header.F90 +++ /dev/null @@ -1,3593 +0,0 @@ -module mgxs_header - - use, intrinsic :: ISO_FORTRAN_ENV - use, intrinsic :: ISO_C_BINDING - - use algorithm, only: find, sort - use constants, only: MAX_WORD_LEN, ZERO, ONE, TWO, PI, MACROSCOPIC_AWR - use error, only: fatal_error - use hdf5_interface - use material_header, only: material - use math, only: evaluate_legendre - use nuclide_header, only: MaterialMacroXS - use random_lcg, only: prn - use scattdata_header - use string - use stl_vector, only: VectorInt, VectorReal - -!=============================================================================== -! XS* contains the temperature-dependent cross section data for an MGXS -!=============================================================================== - - type :: XsDataIso - ! Microscopic cross sections - real(8), allocatable :: total(:) ! total cross section - real(8), allocatable :: absorption(:) ! absorption cross section - class(ScattData), allocatable :: scatter ! scattering info - real(8), allocatable :: delayed_nu_fission(:,:) ! Delayed fission matrix (Dg x Gin) - real(8), allocatable :: prompt_nu_fission(:) ! Prompt fission vector (Gin) - real(8), allocatable :: kappa_fission(:) ! Kappa fission - real(8), allocatable :: fission(:) ! Neutron production - real(8), allocatable :: decay_rate(:) ! Delayed neutron precursor decay rate - real(8), allocatable :: inverse_velocity(:) ! Inverse neutron velocity - real(8), allocatable :: chi_delayed(:, :, :) ! Delayed fission spectra - real(8), allocatable :: chi_prompt(:, :) ! Prompt fission spectra - end type XsDataIso - - type :: XsDataAngle - ! Microscopic cross sections - ! In all cases, right-most indices are theta, phi - real(8), allocatable :: total(:, :, :) ! total cross section - real(8), allocatable :: absorption(:, :, :) ! absorption cross section - type(ScattDataContainer), allocatable :: scatter(:, :) ! scattering info - real(8), allocatable :: delayed_nu_fission(:, :, :, :) ! Delayed fission matrix (Gout x Gin) - real(8), allocatable :: prompt_nu_fission(:, :, :) ! Prompt fission matrix (Gout x Gin) - real(8), allocatable :: kappa_fission(:, :, :) ! Kappa fission - real(8), allocatable :: fission(:, :, :) ! Neutron production - real(8), allocatable :: decay_rate(:, :, :) ! Delayed neutron precursor decay rate - real(8), allocatable :: inverse_velocity(:, :, :) ! Inverse neutron velocity - real(8), allocatable :: chi_delayed(:, :, :, :, :) ! Delayed fission spectra - real(8), allocatable :: chi_prompt(:, :, :, :) ! Prompt fission spectra - end type XsDataAngle - -!=============================================================================== -! MGXS contains the base mgxs data for a nuclide/material -!=============================================================================== - - type, abstract :: Mgxs - character(len=MAX_WORD_LEN) :: name ! name of dataset, e.g. UO2 - real(8) :: awr ! Atomic Weight Ratio - real(8), allocatable :: kTs(:) ! temperature in eV (k*T) - - ! Fission information - logical :: fissionable ! mgxs object is fissionable? - integer :: scatter_format ! either legendre, histogram, or tabular. - integer :: num_delayed_groups ! Num delayed groups - - ! Caching information - integer :: index_temp ! temperature index for nuclide - - contains - procedure(mgxs_from_hdf5_), deferred :: from_hdf5 ! Load the data - procedure(mgxs_combine_), deferred :: combine ! initializes object - procedure(mgxs_get_xs_), deferred :: get_xs ! Get the requested xs - - ! Sample the outgoing energy from a fission event - procedure(mgxs_sample_fission_), deferred :: sample_fission_energy - - ! Sample the outgoing energy and angle from a scatter event - procedure(mgxs_sample_scatter_), deferred :: sample_scatter - - ! Calculate the material specific MGXS data from the nuclides - procedure(mgxs_calculate_xs_), deferred :: calculate_xs - - ! Find the temperature - procedure :: find_temperature => mgxs_find_temperature - end type Mgxs - -!=============================================================================== -! MGXSCONTAINER pointer array for storing Nuclides -!=============================================================================== - - type MgxsContainer - class(Mgxs), pointer :: obj - end type MgxsContainer - -!=============================================================================== -! Interfaces for MGXS -!=============================================================================== - - abstract interface - subroutine mgxs_from_hdf5_(this, xs_id, energy_groups, delayed_groups, & - temperature, method, tolerance, max_order, legendre_to_tabular, & - legendre_to_tabular_points) - import Mgxs, HID_T, VectorReal - class(Mgxs), intent(inout) :: this ! Working Object - integer(HID_T), intent(in) :: xs_id ! Library data - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - type(VectorReal), intent(in) :: temperature ! list of desired temperatures - integer, intent(inout) :: method ! Type of temperature access - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: max_order ! Maximum requested order - logical, intent(in) :: legendre_to_tabular ! Convert Legendres to Tabular? - integer, intent(in) :: legendre_to_tabular_points ! Number of points to use - ! in that conversion - end subroutine mgxs_from_hdf5_ - - subroutine mgxs_combine_(this, temps, mat, nuclides, energy_groups, & - delayed_groups, max_order, tolerance, method) - import Mgxs, Material, MgxsContainer, VectorReal - class(Mgxs), intent(inout) :: this ! The Mgxs to initialize - type(VectorReal), intent(in) :: temps ! Temperatures to obtain - type(Material), pointer, intent(in) :: mat ! base material - type(MgxsContainer), intent(in) :: nuclides(:) ! List of nuclides to harvest from - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - integer, intent(in) :: max_order ! Maximum requested order - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: method ! Type of temperature access - end subroutine mgxs_combine_ - - pure function mgxs_get_xs_(this, xstype, gin, gout, uvw, mu, dg) result(xs_val) - import Mgxs - class(Mgxs), intent(in) :: this - character(*), intent(in) :: xstype ! Cross Section Type - integer, intent(in) :: gin ! Incoming Energy group - integer, optional, intent(in) :: gout ! Outgoing Group - real(8), optional, intent(in) :: uvw(3) ! Requested Angle - real(8), optional, intent(in) :: mu ! Change in angle - integer, optional, intent(in) :: dg ! Delayed group - real(8) :: xs_val ! Resultant xs - end function mgxs_get_xs_ - - subroutine mgxs_sample_fission_(this, gin, uvw, dg, gout) - import Mgxs - class(Mgxs), intent(in) :: this - integer, intent(in) :: gin ! Incoming energy group - real(8), intent(in) :: uvw(3) ! Particle Direction - integer, intent(out) :: dg ! Delayed group - integer, intent(out) :: gout ! Sampled outgoing group - - end subroutine mgxs_sample_fission_ - - subroutine mgxs_sample_scatter_(this, uvw, gin, gout, mu, wgt) - import Mgxs - class(Mgxs), intent(in) :: this - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - end subroutine mgxs_sample_scatter_ - - subroutine mgxs_calculate_xs_(this, gin, sqrtkT, uvw, xs) - import Mgxs, MaterialMacroXS - class(Mgxs), intent(inout) :: this - integer, intent(in) :: gin ! Incoming neutron group - real(8), intent(in) :: sqrtkT ! Material temperature - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - type(MaterialMacroXS), intent(inout) :: xs ! Resultant Mgxs Data - end subroutine mgxs_calculate_xs_ - end interface - -!=============================================================================== -! MGXSISO contains the base MGXS data specifically for -! isotropically weighted MGXS -!=============================================================================== - - type, extends(Mgxs) :: MgxsIso - type(XsDataIso), allocatable :: xs(:) ! One for every temperature - contains - procedure :: from_hdf5 => mgxsiso_from_hdf5 ! Initialize Nuclidic MGXS Data - procedure :: get_xs => mgxsiso_get_xs ! Gets Size of Data w/in Object - procedure :: combine => mgxsiso_combine ! inits object - procedure :: sample_fission_energy => mgxsiso_sample_fission_energy - procedure :: sample_scatter => mgxsiso_sample_scatter - procedure :: calculate_xs => mgxsiso_calculate_xs - end type MgxsIso - -!=============================================================================== -! MGXSANGLE contains the base MGXS data specifically for -! angular flux weighted MGXS -!=============================================================================== - - type, extends(Mgxs) :: MgxsAngle - type(XsDataAngle), allocatable :: xs(:) ! One for every temperature - integer :: n_pol ! Number of polar angles - integer :: n_azi ! Number of azimuthal angles - real(8), allocatable :: polar(:) ! polar angles - real(8), allocatable :: azimuthal(:) ! azimuthal angles - - contains - procedure :: from_hdf5 => mgxsang_from_hdf5 ! Initialize Nuclidic MGXS Data - procedure :: get_xs => mgxsang_get_xs ! Gets Size of Data w/in Object - procedure :: combine => mgxsang_combine ! inits object - procedure :: sample_fission_energy => mgxsang_sample_fission_energy - procedure :: sample_scatter => mgxsang_sample_scatter - procedure :: calculate_xs => mgxsang_calculate_xs - end type MgxsAngle - - ! Cross section arrays - type(MgxsContainer), allocatable, target :: nuclides_MG(:) - - ! Cross section caches - type(MgxsContainer), target, allocatable :: macro_xs(:) - - ! Number of energy groups - integer :: num_energy_groups - - ! Number of delayed groups - integer :: num_delayed_groups - - ! Energy group structure with decreasing energy - real(8), allocatable :: energy_bins(:) - - ! Midpoint of the energy group structure - real(8), allocatable :: energy_bin_avg(:) - - ! Energy group structure with increasing energy - real(8), allocatable :: rev_energy_bins(:) - -contains - -!=============================================================================== -! MGXS*_FROM_HDF5 reads in the data from the HDF5 Library. At the point of entry -! the file would have been opened and metadata read. -!=============================================================================== - - subroutine mgxs_from_hdf5(this, xs_id, temperature, method, tolerance, & - temps_to_read, order_dim) - class(Mgxs), intent(inout) :: this ! Working Object - integer(HID_T), intent(in) :: xs_id ! Group in H5 file - type(VectorReal), intent(in) :: temperature ! list of desired temperatures - integer, intent(inout) :: method ! Type of temperature access - real(8), intent(in) :: tolerance ! Tolerance on method - type(VectorInt), intent(out) :: temps_to_read ! Temperatures to read - integer, intent(out) :: order_dim ! Scattering data order size - - integer(HID_T) :: kT_group - character(MAX_WORD_LEN), allocatable :: dset_names(:) - real(8), allocatable :: temps_available(:) ! temperatures available - real(8) :: temp_desired - real(8) :: temp_actual - character(MAX_WORD_LEN) :: temp_str - real(8) :: dangle - integer :: ipol, iazi - - ! Get name of dataset from group - this % name = get_name(xs_id) - - ! Get rid of leading '/' - this % name = trim(this % name(2:)) - - if (attribute_exists(xs_id, "atomic_weight_ratio")) then - call read_attribute(this % awr, xs_id, "atomic_weight_ratio") - else - this % awr = MACROSCOPIC_AWR - end if - - ! Determine temperatures available - kT_group = open_group(xs_id, 'kTs') - call get_datasets(kT_group, dset_names) - allocate(temps_available(size(dset_names))) - do i = 1, size(dset_names) - ! Read temperature value - call read_dataset(temps_available(i), kT_group, trim(dset_names(i))) - ! Convert eV to Kelvin - temps_available(i) = temps_available(i) / K_BOLTZMANN - end do - call sort(temps_available) - - ! If only one temperature is available, revert to nearest temperature - if (size(temps_available) == 1 .and. & - method == TEMPERATURE_INTERPOLATION) then - call warning("Cross sections for " // trim(this % name) // " are only & - &available at one temperature. Reverting to nearest temperature & - &method.") - method = TEMPERATURE_NEAREST - end if - - select case (method) - case (TEMPERATURE_NEAREST) - ! Determine actual temperatures to read - TEMP_LOOP: do i = 1, temperature % size() - temp_desired = temperature % data(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 (find(temps_to_read, nint(temp_actual)) == -1) then - call temps_to_read % push_back(nint(temp_actual)) - end if - else - call fatal_error("MGXS library does not contain cross sections & - &for " // trim(this % name) // " at or near " // & - trim(to_str(nint(temp_desired))) // " K.") - end if - end do TEMP_LOOP - - case (TEMPERATURE_INTERPOLATION) - ! If temperature interpolation or multipole is selected, get a list of - ! bounding temperatures for each actual temperature present in the model - TEMPS_LOOP: do i = 1, temperature % size() - temp_desired = temperature % data(i) - - do j = 1, size(temps_available) - 1 - if (temps_available(j) <= temp_desired .and. & - temp_desired < temps_available(j + 1)) then - if (find(temps_to_read, nint(temps_available(j))) == -1) then - call temps_to_read % push_back(nint(temps_available(j))) - end if - if (find(temps_to_read, nint(temps_available(j + 1))) == -1) then - call temps_to_read % push_back(nint(temps_available(j + 1))) - end if - cycle TEMPS_LOOP - end if - end do - - call fatal_error("MGXS library does not contain cross sections & - &for " // trim(this % name) // " at temperatures that bound " // & - trim(to_str(nint(temp_desired))) // " K.") - end do TEMPS_LOOP - end select - - ! Sort temperatures to read - call sort(temps_to_read) - - ! Get temperatures - n_temperature = temps_to_read % size() - allocate(this % kTs(n_temperature)) - do i = 1, n_temperature - ! Get temperature as a string - temp_str = trim(to_str(temps_to_read % data(i))) // "K" - - ! Read exact temperature value - call read_dataset(this % kTs(i), kT_group, trim(temp_str)) - end do - call close_group(kT_group) - - ! Allocate the XS object for the number of temperatures - select type(this) - type is (MgxsIso) - allocate(this % xs(n_temperature)) - type is (MgxsAngle) - allocate(this % xs(n_temperature)) - end select - - ! Load the remaining metadata - if (attribute_exists(xs_id, "scatter_format")) then - call read_attribute(temp_str, xs_id, "scatter_format") - temp_str = trim(temp_str) - if (to_lower(temp_str) == 'legendre') then - this % scatter_format = ANGLE_LEGENDRE - else if (to_lower(temp_str) == 'histogram') then - this % scatter_format = ANGLE_HISTOGRAM - else if (to_lower(temp_str) == 'tabular') then - this % scatter_format = ANGLE_TABULAR - else - call fatal_error("Invalid scatter_format option!") - end if - else - this % scatter_format = ANGLE_LEGENDRE - end if - if (attribute_exists(xs_id, "scatter_shape")) then - call read_attribute(temp_str, xs_id, "scatter_shape") - temp_str = trim(temp_str) - if (to_lower(temp_str) /= "[g][g'][order]") then - call fatal_error("Invalid scatter_shape option!") - end if - end if - if (attribute_exists(xs_id, "fissionable")) then - call read_attribute(this % fissionable, xs_id, "fissionable") - else - call fatal_error("Fissionable element must be set!") - end if - - ! Get the library's value for the order - if (attribute_exists(xs_id, "order")) then - call read_attribute(order_dim, xs_id, "order") - else - call fatal_error("Order must be provided!") - end if - - ! Store the dimensionality of the data in order_dim. - ! For Legendre data, we usually refer to it as Pn where n is the order. - ! However Pn has n+1 sets of points (since you need to count the P0 - ! moment). Adjust for that. Histogram and Tabular formats dont need this - ! adjustment. - if (this % scatter_format == ANGLE_LEGENDRE) then - order_dim = order_dim + 1 - else - order_dim = order_dim - end if - - ! Get angular meta-data and allocate as needed based off of the - ! information therein - select type(this) - type is (MgxsAngle) - if (attribute_exists(xs_id, "num_polar")) then - call read_attribute(this % n_pol, xs_id, "num_polar") - else - call fatal_error("num_polar must be provided!") - end if - - if (attribute_exists(xs_id, "num_azimuthal")) then - call read_attribute(this % n_azi, xs_id, "num_azimuthal") - else - call fatal_error("num_azimuthal must be provided!") - end if - - ! Set angle data to use equally-spaced bins - allocate(this % polar(this % n_pol)) - dangle = PI / real(this % n_pol, 8) - do ipol = 1, this % n_pol - this % polar(ipol) = (real(ipol, 8) - HALF) * dangle - end do - allocate(this % azimuthal(this % n_azi)) - dangle = TWO * PI / real(this % n_azi, 8) - do iazi = 1, this % n_azi - this % azimuthal(iazi) = -PI + (real(iazi, 8) - HALF) * dangle - end do - end select - - end subroutine mgxs_from_hdf5 - - subroutine mgxsiso_from_hdf5(this, xs_id, energy_groups, delayed_groups, & - temperature, method, tolerance, max_order, & - legendre_to_tabular, legendre_to_tabular_points) - class(MgxsIso), intent(inout) :: this ! Working Object - integer(HID_T), intent(in) :: xs_id ! Group in H5 file - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - type(VectorReal), intent(in) :: temperature ! list of desired temperatures - integer, intent(inout) :: method ! Type of temperature access - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: max_order ! Maximum requested order - logical, intent(in) :: legendre_to_tabular ! Convert Legendres to Tabular? - integer, intent(in) :: legendre_to_tabular_points ! Number of points to use - ! in that conversion - - character(MAX_LINE_LEN) :: temp_str - integer(HID_T) :: xsdata, xsdata_grp, scatt_grp - integer :: ndims - integer(HSIZE_T) :: dims(2) - real(8), allocatable :: temp_arr(:), temp_2d(:, :) - real(8), allocatable :: temp_beta(:, :), temp_3d(:, :, :) - real(8) :: dmu, mu, norm, chi_sum - integer :: order, order_dim, gin, gout, l, imu, length - type(VectorInt) :: temps_to_read - integer :: t, dg, order_data - type(Jagged2D), allocatable :: input_scatt(:), scatt_coeffs(:) - type(Jagged1D), allocatable :: temp_mult(:) - integer, allocatable :: gmin(:), gmax(:) - - ! Call generic data gathering routine (will populate the metadata) - call mgxs_from_hdf5(this, xs_id, temperature, method, tolerance, & - temps_to_read, order_data) - - ! Set the number of delayed groups - this % num_delayed_groups = delayed_groups - - ! Load the more specific data - do t = 1, temps_to_read % size() - associate(xs => this % xs(t)) - - ! Get temperature as a string - temp_str = trim(to_str(temps_to_read % data(t))) // "K" - xsdata_grp = open_group(xs_id, trim(temp_str)) - - ! Allocate data for all the cross sections - allocate(xs % total(energy_groups)) - allocate(xs % absorption(energy_groups)) - allocate(xs % delayed_nu_fission(delayed_groups, energy_groups)) - allocate(xs % prompt_nu_fission(energy_groups)) - allocate(xs % fission(energy_groups)) - allocate(xs % kappa_fission(energy_groups)) - allocate(xs % decay_rate(delayed_groups)) - allocate(xs % inverse_velocity(energy_groups)) - allocate(xs % chi_delayed(delayed_groups, energy_groups, & - energy_groups)) - allocate(xs % chi_prompt(energy_groups, energy_groups)) - - ! Set all fissionable terms to zero - xs % delayed_nu_fission = ZERO - xs % prompt_nu_fission = ZERO - xs % fission = ZERO - xs % kappa_fission = ZERO - xs % chi_delayed = ZERO - xs % chi_prompt = ZERO - xs % decay_rate = ZERO - xs % inverse_velocity = ZERO - - if (this % fissionable) then - - ! Allocate temporary array for beta - allocate(temp_beta(delayed_groups, energy_groups)) - - ! Set beta - if (object_exists(xsdata_grp, "beta")) then - - ! Get the dimensions of the beta dataset - xsdata = open_dataset(xsdata_grp, "beta") - call get_ndims(xsdata, ndims) - - ! Beta is input as (delayed_groups) - if (ndims == 1) then - - ! Allocate temporary array for beta - allocate(temp_arr(delayed_groups)) - - ! Read beta - call read_dataset(temp_arr, xsdata_grp, "beta") - - do dg = 1, delayed_groups - do gin = 1, energy_groups - temp_beta(dg, gin) = temp_arr(dg) - end do - end do - - ! Deallocate temporary beta array - deallocate(temp_arr) - - ! Beta is input as (delayed_groups, energy_groups) - else if (ndims == 2) then - - ! Allocate temporary array for beta - allocate(temp_arr(delayed_groups * energy_groups)) - - ! Read beta - call read_dataset(temp_arr, xsdata_grp, "beta") - - ! Reshape array and set to dedicated beta array - temp_beta = reshape(temp_arr, (/delayed_groups, energy_groups/)) - - ! Deallocate temporary beta array - deallocate(temp_arr) - - else - call fatal_error("beta must be provided as a 1D or 2D array") - end if - - call close_dataset(xsdata) - else - temp_beta = ZERO - end if - - ! If chi provided, set chi-prompt and chi-delayed - if (object_exists(xsdata_grp, "chi")) then - - ! Allocate temporary array for chi - allocate(temp_arr(energy_groups)) - - ! Read chi - call read_dataset(temp_arr, xsdata_grp, "chi") - - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_prompt(gout, gin) = temp_arr(gout) - end do - - ! Normalize chi-prompt so its CDF goes to 1 - chi_sum =sum(xs % chi_prompt(:, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi for a group that sums to & - &zero") - else - xs % chi_prompt(:, gin) = xs % chi_prompt(:, gin) / chi_sum - end if - end do - - ! Set chi-delayed to chi-prompt - do dg = 1, delayed_groups - xs % chi_delayed(dg, :, :) = xs % chi_prompt(:, :) - end do - - ! Deallocate temporary chi array - deallocate(temp_arr) - end if - - ! If nu-fission provided, set prompt-nu_-ission and - ! delayed-nu-fission. If nu fission is a matrix, set chi-prompt and - ! chi-delayed. - if (object_exists(xsdata_grp, "nu-fission")) then - - ! Get the dimensions of the nu-fission dataset - xsdata = open_dataset(xsdata_grp, "nu-fission") - call get_ndims(xsdata, ndims) - - ! If nu-fission is a vector - if (ndims == 1) then - - ! Get nu-fission - call read_dataset(xs % prompt_nu_fission, xsdata_grp, & - "nu-fission") - - ! Set delayed-nu-fission and correct prompt-nu-fission with - ! beta - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin) = temp_beta(dg, gin) * & - xs % prompt_nu_fission(gin) - end do - - ! Correct prompt-nu-fission using delayed neutron fraction - if (delayed_groups > 0) then - xs % prompt_nu_fission(gin) = (1 - sum(temp_beta(:, gin))) & - * xs % prompt_nu_fission(gin) - end if - end do - - ! If nu-fission is a matrix, set prompt-nu-fission, - ! delayed-nu-fission, chi-prompt, and chi-delayed. - else if (ndims == 2) then - - ! chi is embedded in nu-fission -> extract chi - allocate(temp_arr(energy_groups * energy_groups)) - call read_dataset(temp_arr, xsdata_grp, "nu-fission") - allocate(temp_2d(energy_groups, energy_groups)) - temp_2d = reshape(temp_arr, (/energy_groups, energy_groups/)) - - ! Deallocate temporary 1D array for nu-fission matrix - deallocate(temp_arr) - - ! Set the vector nu-fission from the matrix nu-fission - do gin = 1, energy_groups - xs % prompt_nu_fission(gin) = sum(temp_2d(:, gin)) - end do - - ! Set delayed-nu-fission and correct prompt-nu-fission with - ! beta - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin) = temp_beta(dg, gin) * & - xs % prompt_nu_fission(gin) - end do - - ! Correct prompt-nu-fission using delayed neutron fraction - if (delayed_groups > 0) then - xs % prompt_nu_fission(gin) = (1 - sum(temp_beta(:, gin))) & - * xs % prompt_nu_fission(gin) - end if - end do - - ! Now pull out information needed for chi - xs % chi_prompt(:, :) = temp_2d - - ! Deallocate temporary 2D array for nu-fission matrix - deallocate(temp_2d) - - ! Normalize chi so its CDF goes to 1 - do gin = 1, energy_groups - chi_sum = sum(xs % chi_prompt(:, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi for a group that sums to & - &zero") - else - xs % chi_prompt(:, gin) = xs % chi_prompt(:, gin) / chi_sum - end if - end do - - ! Set chi-delayed to chi-prompt - do dg = 1, delayed_groups - xs % chi_delayed(dg, :, :) = xs % chi_prompt(:, :) - end do - else - call fatal_error("nu-fission must be provided as a 1D or 2D & - &array") - end if - - call close_dataset(xsdata) - end if - - ! If chi-prompt provided, set chi-prompt - if (object_exists(xsdata_grp, "chi-prompt")) then - - ! Allocate temporary array for chi-prompt - allocate(temp_arr(energy_groups)) - - ! Get array with chi-prompt - call read_dataset(temp_arr, xsdata_grp, "chi-prompt") - - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_prompt(gout, gin) = temp_arr(gout) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_prompt(:, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi prompt for a group that & - &sums to zero") - else - xs % chi_prompt(:, gin) = xs % chi_prompt(:, gin) / chi_sum - end if - end do - - ! Deallocate temporary array for chi-prompt - deallocate(temp_arr) - end if - - ! If chi-delayed provided, set chi-delayed - if (object_exists(xsdata_grp, "chi-delayed")) then - - ! Get the dimensions of the chi-delayed dataset - xsdata = open_dataset(xsdata_grp, "chi-delayed") - call get_ndims(xsdata, ndims) - - ! If chi-delayed is a vector - if (ndims == 1) then - - ! Allocate temporary array for chi-delayed - allocate(temp_arr(energy_groups)) - - ! Get chi-delayed - call read_dataset(temp_arr, xsdata_grp, "chi-delayed") - - do dg = 1, delayed_groups - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin) = temp_arr(gout) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_delayed(dg, :, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group & - &that sums to zero") - else - xs % chi_delayed(dg, :, gin) = & - xs % chi_delayed(dg, :, gin) / chi_sum - end if - end do - end do - - ! Deallocate temporary array for chi-delayed - deallocate(temp_arr) - - else if (ndims == 2) then - - ! Allocate temporary array for chi-delayed - allocate(temp_arr(delayed_groups * energy_groups)) - - ! Get chi-delayed - call read_dataset(temp_arr, xsdata_grp, "chi-delayed") - allocate(temp_2d(delayed_groups, energy_groups)) - temp_2d = reshape(temp_arr, (/delayed_groups, energy_groups/)) - - do dg = 1, delayed_groups - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin) = temp_2d(dg, gout) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_delayed(dg, :, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group & - &that sums to zero") - else - xs % chi_delayed(dg, :, gin) = & - xs % chi_delayed(dg, :, gin) / chi_sum - end if - end do - end do - - ! Deallocate temporary arrays for chi-delayed - deallocate(temp_arr) - deallocate(temp_2d) - - else - call fatal_error("chi-delayed must be provided as a 1D or 2D & - &array") - end if - - call close_dataset(xsdata) - end if - - ! If prompt-nu-fission present, set prompt-nu-fission - if (object_exists(xsdata_grp, "prompt-nu-fission")) then - - ! Get the dimensions of the prompt-nu-fission dataset - xsdata = open_dataset(xsdata_grp, "prompt-nu-fission") - call get_ndims(xsdata, ndims) - - ! If prompt-nu-fission is a vector - if (ndims == 1) then - - ! Set prompt_nu_fission - call read_dataset(xs % prompt_nu_fission, xsdata_grp, & - "prompt-nu-fission") - - ! If prompt-nu-fission is a matrix, set prompt_nu_fission and - ! chi_prompt. - else if (ndims == 2) then - - ! chi_prompt is embedded in prompt_nu_fission -> extract - ! chi_prompt - allocate(temp_arr(energy_groups * energy_groups)) - call read_dataset(temp_arr, xsdata_grp, "prompt-nu-fission") - allocate(temp_2d(energy_groups, energy_groups)) - temp_2d = reshape(temp_arr, (/energy_groups, energy_groups/)) - - ! Deallocate temporary 1D array for prompt_nu_fission matrix - deallocate(temp_arr) - - ! Set the vector prompt-nu-fission from the matrix - ! prompt-nu-fission - do gin = 1, energy_groups - xs % prompt_nu_fission(gin) = sum(temp_2d(:, gin)) - end do - - ! Now pull out information needed for chi - xs % chi_prompt(:, :) = temp_2d - - ! Deallocate temporary 2D array for nu_fission matrix - deallocate(temp_2d) - - ! Normalize chi so its CDF goes to 1 - do gin = 1, energy_groups - chi_sum = sum(xs % chi_prompt(:, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi prompt for a group & - &that sums to zero") - else - xs % chi_prompt(:, gin) = xs % chi_prompt(:, gin) / chi_sum - end if - end do - else - call fatal_error("prompt-nu-fission must be provided as a 1D & - &or 2D array") - end if - - call close_dataset(xsdata) - end if - - ! If delayed-nu-fission provided, set delayed-nu-fission. If - ! delayed-nu-fission is a matrix, set chi-delayed. - if (object_exists(xsdata_grp, "delayed-nu-fission")) then - - ! Get the dimensions of the delayed-nu-fission dataset - xsdata = open_dataset(xsdata_grp, "delayed-nu-fission") - call get_ndims(xsdata, ndims) - - ! If delayed-nu-fission is a vector - if (ndims == 1) then - - ! If beta is zeros, raise error - if (temp_beta(1,1) == ZERO) then - call fatal_error("cannot set delayed-nu-fission with a 1D & - &array if beta not provided") - end if - - ! Allocate temporary array for delayed-nu-fission - allocate(temp_arr(energy_groups)) - - ! Get delayed-nu-fission - call read_dataset(temp_arr, xsdata_grp, "delayed-nu-fission") - - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin) = temp_beta(dg, gin) * & - temp_arr(gin) - end do - end do - - ! Deallocate temporary delayed-nu-fission array - deallocate(temp_arr) - - ! If delayed-nu-fission is a (delayed_group, energy_group) - ! matrix, set delayed-nu-fission separately for each delayed - ! group. - else if (ndims == 2) then - - ! Get the shape of delayed-nu-fission - call get_shape(xsdata, dims) - - ! Issue error if 1st dimension not correct - if (dims(1) /= delayed_groups) then - call fatal_error("The delayed-nu-fission matrix was input & - &with a 1st dimension not equal to the number of & - &delayed groups.") - end if - - ! Issue error if 2nd dimension not correct - if (dims(2) /= energy_groups) then - call fatal_error("The delayed-nu-fission matrix was input & - &with a 2nd dimension not equal to the number of & - &energy groups.") - end if - - ! Issue warning if delayed_groups == energy_groups - if (delayed_groups == energy_groups) then - call warning("delayed-nu-fission was input as a dimension & - &2 matrix with the same number of delayed groups and & - &groups. It is important to know that OpenMC assumes & - &the dimensions in the matrix are (delayed_groups, & - &energy_groups). Currently, delayed-nu-fission cannot & - &be set as a group by group matrix.") - end if - - ! Get delayed-nu-fission - allocate(temp_arr(delayed_groups * energy_groups)) - call read_dataset(temp_arr, xsdata_grp, "delayed-nu-fission") - xs % delayed_nu_fission = reshape(temp_arr, (/delayed_groups, & - energy_groups/)) - - ! Deallocate temporary array for delayed-nu-fission matrix - deallocate(temp_arr) - - ! If delayed nu-fission is a 3D matrix, set delayed_nu_fission - ! and chi_delayed. - else if (ndims == 3) then - - ! chi_delayed is embedded in delayed_nu_fission -> extract - ! chi_delayed - allocate(temp_arr(delayed_groups * energy_groups * & - energy_groups)) - call read_dataset(temp_arr, xsdata_grp, "delayed-nu-fission") - allocate(temp_3d(delayed_groups, energy_groups, energy_groups)) - temp_3d = reshape(temp_arr, (/delayed_groups, energy_groups, & - energy_groups/)) - - ! Deallocate temporary 1D array for delayed_nu_fission matrix - deallocate(temp_arr) - - ! Set the 2D delayed-nu-fission matrix and 3D chi_dealyed matrix - ! from the 3D delayed-nu-fission matrix - do dg = 1, delayed_groups - do gin = 1, energy_groups - xs % delayed_nu_fission(dg, gin) = sum(temp_3d(dg, :, gin)) - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin) = temp_3d(dg, gout, gin) - end do - end do - end do - - ! Normalize chi_delayed so its CDF goes to 1 - do dg = 1, delayed_groups - do gin = 1, energy_groups - chi_sum = sum(xs % chi_delayed(dg, :, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group & - &that sums to zero") - else - xs % chi_delayed(dg, :, gin) = & - xs % chi_delayed(dg, :, gin) / chi_sum - end if - end do - end do - - ! Deallocate temporary 3D matrix for delayed_nu_fission - deallocate(temp_3d) - else - call fatal_error("delayed-nu-fission must be provided as a & - &1D, 2D, or 3D array") - end if - - call close_dataset(xsdata) - end if - - ! Deallocate temporary beta array - deallocate(temp_beta) - - ! chi-prompt, chi-delayed, prompt-nu-fission, and delayed-nu-fission - ! have been set; Now we will check for the rest of the XS that are - ! unique to fissionable isotopes - - ! Get fission xs - if (object_exists(xsdata_grp, "fission")) then - call read_dataset(xs % fission, xsdata_grp, "fission") - end if - - ! Get kappa-fission xs - if (object_exists(xsdata_grp, "kappa-fission")) then - call read_dataset(xs % kappa_fission, xsdata_grp, "kappa-fission") - end if - - ! Get decay rate xs - if (object_exists(xsdata_grp, "decay rate")) then - call read_dataset(xs % decay_rate, xsdata_grp, "decay rate") - end if - end if - - ! All the XS unique to fissionable isotopes have been set; Now set all - ! the generation XS - - if (object_exists(xsdata_grp, "absorption")) then - call read_dataset(xs % absorption, xsdata_grp, "absorption") - else - call fatal_error("Must provide absorption!") - end if - - ! Get inverse velocity - if (object_exists(xsdata_grp, "inverse-velocity")) then - call read_dataset(xs % inverse_velocity, xsdata_grp, & - "inverse-velocity") - end if - - ! Get scattering data - if (.not. object_exists(xsdata_grp, "scatter_data")) & - call fatal_error("Must provide 'scatter_data'") - - scatt_grp = open_group(xsdata_grp, 'scatter_data') - - ! First get the outgoing group boundary indices - if (object_exists(scatt_grp, "g_min")) then - allocate(gmin(energy_groups)) - call read_dataset(gmin, scatt_grp, "g_min") - else - call fatal_error("'g_min' for the scatter_data must be provided") - end if - - if (object_exists(scatt_grp, "g_max")) then - allocate(gmax(energy_groups)) - call read_dataset(gmax, scatt_grp, "g_max") - else - call fatal_error("'g_max' for the scatter_data must be provided") - end if - - ! Now use this information to find the length of a container array - ! to hold the flattened data - length = 0 - - do gin = 1, energy_groups - length = length + order_data * (gmax(gin) - gmin(gin) + 1) - end do - - ! Allocate flattened array - allocate(temp_arr(length)) - - if (.not. object_exists(scatt_grp, 'scatter_matrix')) & - call fatal_error("'scatter_matrix' must be provided") - call read_dataset(temp_arr, scatt_grp, "scatter_matrix") - - ! Compare the number of orders given with the maximum order of the - ! problem. Strip off the supefluous orders if needed. - if (this % scatter_format == ANGLE_LEGENDRE) then - order = min(order_data - 1, max_order) - order_dim = order + 1 - else - order_dim = order_data - end if - - ! Convert temp_arr to a jagged array ((gin) % data(l, gout)) for - ! passing to ScattData - allocate(input_scatt(energy_groups)) - - index = 1 - do gin = 1, energy_groups - allocate(input_scatt(gin) % data(order_dim, gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - do l = 1, order_dim - input_scatt(gin) % data(l, gout) = temp_arr(index) - index = index + 1 - end do - ! Adjust index for the orders we didnt take - index = index + (order_data - order_dim) - end do - end do - - deallocate(temp_arr) - - ! Finally convert the legendre to tabular if needed - allocate(scatt_coeffs(energy_groups)) - - if (this % scatter_format == ANGLE_LEGENDRE .and. & - legendre_to_tabular) then - - this % scatter_format = ANGLE_TABULAR - order_dim = legendre_to_tabular_points - order = order_dim - dmu = TWO / real(order - 1, 8) - - do gin = 1, energy_groups - allocate(scatt_coeffs(gin) % data(order_dim, gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - - norm = ZERO - - do imu = 1, order_dim - - if (imu == 1) then - mu = -ONE - else if (imu == order_dim) then - mu = ONE - else - mu = -ONE + real(imu - 1, 8) * dmu - end if - - scatt_coeffs(gin) % data(imu, gout) = & - evaluate_legendre(input_scatt(gin) % data(:, gout), mu) - - ! Ensure positivity of distribution - if (scatt_coeffs(gin) % data(imu, gout) < ZERO) & - scatt_coeffs(gin) % data(imu, gout) = ZERO - - ! And accrue the integral - if (imu > 1) then - norm = norm + HALF * dmu * & - (scatt_coeffs(gin) % data(imu - 1, gout) + & - scatt_coeffs(gin) % data(imu, gout)) - end if - end do ! mu - - ! Now that we have the integral, lets ensure that the - ! distribution is normalized such that it preserves the original - ! scattering xs - if (norm > ZERO) then - scatt_coeffs(gin) % data(:, gout) = & - scatt_coeffs(gin) % data(:, gout) * & - input_scatt(gin) % data(1, gout) / norm - end if - end do ! gout - end do ! gin - else - - ! Sticking with current representation - do gin = 1, energy_groups - allocate(scatt_coeffs(gin) % data(order_dim, gmin(gin):gmax(gin))) - scatt_coeffs(gin) % data(:, :) = & - input_scatt(gin) % data(1:order_dim, :) - end do - end if - - deallocate(input_scatt) - - ! Now get the multiplication matrix - if (object_exists(scatt_grp, 'multiplicity_matrix')) then - - ! Now use this information to find the length of a container array - ! to hold the flattened data - length = 0 - - do gin = 1, energy_groups - length = length + (gmax(gin) - gmin(gin) + 1) - end do - - ! Allocate flattened array - allocate(temp_arr(length)) - call read_dataset(temp_arr, scatt_grp, "multiplicity_matrix") - - ! Convert temp_arr to a jagged array ((gin) % data(gout)) for - ! passing to ScattData - allocate(temp_mult(energy_groups)) - - index = 1 - do gin = 1, energy_groups - - allocate(temp_mult(gin) % data(gmin(gin):gmax(gin))) - - do gout = gmin(gin), gmax(gin) - temp_mult(gin) % data(gout) = temp_arr(index) - index = index + 1 - end do - end do - deallocate(temp_arr) - else - - ! Default to multiplicities of 1.0 - allocate(temp_mult(energy_groups)) - - do gin = 1, energy_groups - allocate(temp_mult(gin) % data(gmin(gin):gmax(gin))) - temp_mult(gin) % data = ONE - end do - end if - - ! Allocate and initialize our ScattData Object. - if (this % scatter_format == ANGLE_HISTOGRAM) then - allocate(ScattDataHistogram :: xs % scatter) - else if (this % scatter_format == ANGLE_TABULAR) then - allocate(ScattDataTabular :: xs % scatter) - else if (this % scatter_format == ANGLE_LEGENDRE) then - allocate(ScattDataLegendre :: xs % scatter) - end if - - ! Initialize the ScattData Object - call xs % scatter % init(gmin, gmax, temp_mult, scatt_coeffs) - - ! Check sigA to ensure it is not 0 since it is - ! often divided by in the tally routines - ! (This may happen with Helium data) - do gin = 1, energy_groups - if (xs % absorption(gin) == ZERO) xs % absorption(gin) = 1E-10_8 - end do - - ! Get, or infer, total xs data. - if (object_exists(xsdata_grp, "total")) then - call read_dataset(xs % total, xsdata_grp, "total") - else - xs % total(:) = xs % absorption(:) + xs % scatter % scattxs(:) - end if - - ! Check sigT to ensure it is not 0 since it is - ! often divided by in the tally routines - do gin = 1, energy_groups - if (xs % total(gin) == ZERO) xs % total(gin) = 1E-10_8 - end do - - ! Close the groups we have opened and deallocate - call close_group(xsdata_grp) - call close_group(scatt_grp) - deallocate(scatt_coeffs, temp_mult) - end associate ! xs - end do ! Temperatures - - end subroutine mgxsiso_from_hdf5 - - subroutine mgxsang_from_hdf5(this, xs_id, energy_groups, delayed_groups, & - temperature, method, tolerance, max_order, legendre_to_tabular, & - legendre_to_tabular_points) - class(MgxsAngle), intent(inout) :: this ! Working Object - integer(HID_T), intent(in) :: xs_id ! Group in H5 file - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of energy groups - type(VectorReal), intent(in) :: temperature ! list of desired temperatures - integer, intent(inout) :: method ! Type of temperature access - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: max_order ! Maximum requested order - logical, intent(in) :: legendre_to_tabular ! Convert Legendres to Tabular? - integer, intent(in) :: legendre_to_tabular_points ! Number of points to use - ! in that conversion - - character(MAX_LINE_LEN) :: temp_str - integer(HID_T) :: xsdata, xsdata_grp, scatt_grp - integer :: ndims - integer(HSIZE_T) :: dims(4) - integer, allocatable :: int_arr(:) - real(8), allocatable :: temp_1d(:), temp_3d(:, :, :) - real(8), allocatable :: temp_4d(:, :, :, :), temp_5d(:, :, :, :, :) - real(8), allocatable :: temp_beta(:, :, :, :) - real(8) :: dmu, mu, norm, chi_sum - integer :: order, order_dim, gin, gout, l, imu, dg - type(VectorInt) :: temps_to_read - integer :: t, length, ipol, iazi, order_data - type(Jagged2D), allocatable :: input_scatt(:, :, :), scatt_coeffs(:, :, :) - type(Jagged1D), allocatable :: temp_mult(:, :, :) - integer, allocatable :: gmin(:, :, :), gmax(:, :, :) - - ! Call generic data gathering routine (will populate the metadata) - call mgxs_from_hdf5(this, xs_id, temperature, method, tolerance, & - temps_to_read, order_data) - - ! Set the number of delayed groups - this % num_delayed_groups = delayed_groups - - ! Load the more specific data - do t = 1, temps_to_read % size() - associate(xs => this % xs(t)) - - ! Get temperature as a string - temp_str = trim(to_str(temps_to_read % data(t))) // "K" - xsdata_grp = open_group(xs_id, trim(temp_str)) - - ! Load the more specific data - allocate(xs % prompt_nu_fission(energy_groups, this % n_azi, & - this % n_pol)) - allocate(xs % delayed_nu_fission(delayed_groups, energy_groups, & - this % n_azi, this % n_pol)) - allocate(xs % chi_prompt(energy_groups, energy_groups, this % n_azi, & - this % n_pol)) - allocate(xs % chi_delayed(delayed_groups, energy_groups, & - energy_groups, this % n_azi, this % n_pol)) - allocate(xs % total(energy_groups, this % n_azi, this % n_pol)) - allocate(xs % absorption(energy_groups, this % n_azi, this % n_pol)) - allocate(xs % fission(energy_groups, this % n_azi, this % n_pol)) - allocate(xs % kappa_fission(energy_groups, this % n_azi, & - this % n_pol)) - allocate(xs % decay_rate(delayed_groups, this % n_azi, this % n_pol)) - allocate(xs % inverse_velocity(energy_groups, this % n_azi, & - this % n_pol)) - - ! Set all fissionable terms to zero - xs % delayed_nu_fission = ZERO - xs % prompt_nu_fission = ZERO - xs % fission = ZERO - xs % kappa_fission = ZERO - xs % chi_delayed = ZERO - xs % chi_prompt = ZERO - xs % decay_rate = ZERO - xs % inverse_velocity = ZERO - - if (this % fissionable) then - - ! Allocate temporary array for beta - allocate(temp_beta(delayed_groups, energy_groups, this % n_azi, & - this % n_pol)) - - ! Set beta - if (object_exists(xsdata_grp, "beta")) then - - ! Get the dimensions of the beta dataset - xsdata = open_dataset(xsdata_grp, "beta") - call get_ndims(xsdata, ndims) - - ! Beta is input as (delayed_groups, n_azi, n_pol) - if (ndims == 3) then - - ! Allocate temporary arrays for beta - allocate(temp_1d(delayed_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(delayed_groups, this % n_azi, this % n_pol)) - - ! Read beta - call read_dataset(temp_1d, xsdata_grp, "beta") - temp_3d = reshape(temp_1d, (/delayed_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - temp_beta(dg, gin, iazi, ipol) = temp_3d(dg, iazi, ipol) - end do - end do - end do - end do - - ! Deallocate temporary beta arrays - deallocate(temp_1d) - deallocate(temp_3d) - - ! Beta is input as (delayed_groups, energy_groups, n_azi, n_pol) - else if (ndims == 4) then - - ! Allocate temporary array for beta - allocate(temp_1d(delayed_groups * energy_groups * this % n_azi & - * this % n_pol)) - - ! Read beta - call read_dataset(temp_1d, xsdata_grp, "beta") - - ! Reshape array and set to dedicated beta array - temp_beta = reshape(temp_1d, (/delayed_groups, & - energy_groups, this % n_azi, this % n_pol/)) - - ! Deallocate temporary beta array - deallocate(temp_1d) - - else - call fatal_error("beta must be provided as a 3D or 4D array") - end if - - call close_dataset(xsdata) - else - temp_beta = ZERO - end if - - ! If chi provided, set chi-prompt and chi-delayed - if (object_exists(xsdata_grp, "chi")) then - - ! Allocate temporary array for chi - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(energy_groups, this % n_azi, this % n_pol)) - - ! Read chi - call read_dataset(temp_1d, xsdata_grp, "chi") - temp_3d = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_prompt(gout, gin, iazi, ipol) = & - temp_3d(gout, iazi, ipol) - end do - - ! Normalize chi-prompt so its CDF goes to 1 - chi_sum = sum(xs % chi_prompt(:, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi for a group that sums& - & to zero") - else - xs % chi_prompt(:, gin, iazi, ipol) = & - xs % chi_prompt(:, gin, iazi, ipol) / chi_sum - end if - end do - end do - end do - - ! Set chi-delayed to chi-prompt - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - xs % chi_delayed(dg, :, :, iazi, ipol) = & - xs % chi_prompt(:, :, iazi, ipol) - end do - end do - end do - - ! Deallocate temporary chi arrays - deallocate(temp_1d) - deallocate(temp_3d) - end if - - ! If nu-fission provided, set prompt-nu_-ission and - ! delayed-nu-fission. If nu fission is a matrix, set chi-prompt and - ! chi-delayed. - if (object_exists(xsdata_grp, "nu-fission")) then - - ! Get the dimensions of the nu-fission dataset - xsdata = open_dataset(xsdata_grp, "nu-fission") - call get_ndims(xsdata, ndims) - - ! If nu-fission is a 3D array - if (ndims == 3) then - - ! Get nu-fission - call read_dataset(xs % prompt_nu_fission, xsdata_grp, & - "nu-fission") - - ! Set delayed-nu-fission and correct prompt-nu-fission with - ! beta - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin, iazi, ipol) = & - temp_beta(dg, gin, iazi, ipol) * & - xs % prompt_nu_fission(gin, iazi, ipol) - end do - - ! Correct prompt-nu-fission using delayed neutron fraction - if (delayed_groups > 0) then - xs % prompt_nu_fission(gin, iazi, ipol) = & - (1 - sum(temp_beta(:, gin, iazi, ipol))) * & - xs % prompt_nu_fission(gin, iazi, ipol) - end if - end do - end do - end do - - ! If nu-fission is a matrix, set prompt-nu-fission, - ! delayed-nu-fission, chi-prompt, and chi-delayed. - else if (ndims == 4) then - - ! chi is embedded in nu-fission -> extract chi - allocate(temp_1d(energy_groups * energy_groups * & - this % n_azi * this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "nu-fission") - allocate(temp_4d(energy_groups, energy_groups, this % n_azi, & - this % n_pol)) - temp_4d = reshape(temp_1d, (/energy_groups, energy_groups, & - this % n_azi, this % n_pol /)) - - ! Deallocate temporary 1D array for nu-fission matrix - deallocate(temp_1d) - - ! Set the vector nu-fission from the matrix nu-fission - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - xs % prompt_nu_fission(gin, iazi, ipol) = & - sum(temp_4d(:, gin, iazi, ipol)) - end do - end do - end do - - ! Set delayed-nu-fission and correct prompt-nu-fission with - ! beta - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin, iazi, ipol) = & - temp_beta(dg, gin, iazi, ipol) * & - xs % prompt_nu_fission(gin, iazi, ipol) - end do - - ! Correct prompt-nu-fission using delayed neutron fraction - if (delayed_groups > 0) then - xs % prompt_nu_fission(gin, iazi, ipol) = & - (1 - sum(temp_beta(:, gin, iazi, ipol))) * & - xs % prompt_nu_fission(gin, iazi, ipol) - end if - end do - end do - end do - - ! Now pull out information needed for chi - xs % chi_prompt(:, :, :, :) = temp_4d - - ! Deallocate temporary 4D array for nu-fission matrix - deallocate(temp_4d) - - ! Normalize chi so its CDF goes to 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - chi_sum = sum(xs % chi_prompt(:, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi for a group that & - &sums to zero") - else - xs % chi_prompt(:, gin, iazi, ipol) = & - xs % chi_prompt(:, gin, iazi, ipol) / chi_sum - end if - end do - - ! Set chi-delayed to chi-prompt - do dg = 1, delayed_groups - xs % chi_delayed(dg, :, :, iazi, ipol) = & - xs % chi_prompt(:, :, iazi, ipol) - end do - end do - end do - else - call fatal_error("nu-fission must be provided as a 3D or & - &4D array") - end if - - call close_dataset(xsdata) - end if - - ! If chi-prompt provided, set chi-prompt - if (object_exists(xsdata_grp, "chi-prompt")) then - - ! Allocate temporary array for chi-prompt - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(energy_groups, this % n_azi, this % n_pol)) - - ! Get array with chi-prompt - call read_dataset(temp_1d, xsdata_grp, "chi-prompt") - temp_3d = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_prompt(gout, gin, iazi, ipol) = & - temp_3d(gout, iazi, ipol) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_prompt(:, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi prompt for a group that& - & sums to zero") - else - xs % chi_prompt(:, gin, iazi, ipol) = & - xs % chi_prompt(:, gin, iazi, ipol) / chi_sum - end if - end do - end do - end do - - ! Deallocate temporary arrays for chi-prompt - deallocate(temp_1d) - deallocate(temp_3d) - end if - - ! If chi-delayed provided, set chi-delayed - if (object_exists(xsdata_grp, "chi-delayed")) then - - ! Get the dimensions of the chi-delayed dataset - xsdata = open_dataset(xsdata_grp, "chi-delayed") - call get_ndims(xsdata, ndims) - - ! chi-delayed is input as (energy_groups, n_azi, n_pol) - if (ndims == 3) then - - ! Allocate temporary array for chi-prompt - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(energy_groups, this % n_azi, this % n_pol)) - - ! Get array with chi-prompt - call read_dataset(temp_1d, xsdata_grp, "chi-delayed") - temp_3d = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin, iazi, ipol) = & - temp_3d(gout, iazi, ipol) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_delayed(dg, :, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group& - & that sums to zero") - else - xs % chi_delayed(dg, :, gin, iazi, ipol) = & - xs % chi_delayed(dg, :, gin, iazi, ipol) / & - chi_sum - end if - end do - end do - end do - end do - - ! Deallocate temporary arrays for chi-delayed - deallocate(temp_1d) - deallocate(temp_3d) - - ! chi-delayed is input as (delayed_groups, energy_groups, n_azi, - ! n_pol) - else if (ndims == 4) then - - ! Allocate temporary array for chi-delayed - allocate(temp_1d(delayed_groups * energy_groups * this % n_azi & - * this % n_pol)) - allocate(temp_4d(delayed_groups, energy_groups, this % n_azi, & - this % n_pol)) - - ! Get chi-delayed - call read_dataset(temp_1d, xsdata_grp, "chi-delayed") - temp_4d = reshape(temp_1d, (/delayed_groups, energy_groups, & - this % n_azi, this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin, iazi, ipol) = & - temp_4d(dg, gout, iazi, ipol) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_delayed(dg, :, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group& - & that sums to zero") - else - xs % chi_delayed(dg, :, gin, iazi, ipol) = & - xs % chi_delayed(dg, :, gin, iazi, ipol) / & - chi_sum - end if - end do - end do - end do - end do - - ! Deallocate temporary arrays for chi-delayed - deallocate(temp_1d) - deallocate(temp_4d) - - else - call fatal_error("chi-delayed must be provided as a 3D or 4D & - &array") - end if - - call close_dataset(xsdata) - end if - - ! If prompt-nu-fission present, set prompt-nu-fission - if (object_exists(xsdata_grp, "prompt-nu-fission")) then - - ! Get the dimensions of the prompt-nu-fission dataset - xsdata = open_dataset(xsdata_grp, "prompt-nu-fission") - call get_ndims(xsdata, ndims) - - ! If prompt-nu-fission is a vector for each azi and pol - if (ndims == 3) then - - ! Set prompt_nu_fission - call read_dataset(xs % prompt_nu_fission, xsdata_grp, & - "prompt-nu-fission") - - ! If prompt-nu-fission is a matrix for each azi and pol, - ! set prompt_nu_fission and chi_prompt. - else if (ndims == 4) then - - ! chi_prompt is embedded in prompt_nu_fission -> extract - ! chi_prompt - allocate(temp_1d(energy_groups * energy_groups & - * this % n_azi * this % n_pol)) - allocate(temp_4d(energy_groups, energy_groups, this % n_azi, & - this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "prompt-nu-fission") - temp_4d = reshape(temp_1d, (/energy_groups, energy_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary 1D array for prompt_nu_fission matrix - deallocate(temp_1d) - - ! Set the vector prompt-nu-fission from the matrix - ! prompt-nu-fission - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - xs % prompt_nu_fission(gin, iazi, ipol) = & - sum(temp_4d(:, gin, iazi, ipol)) - end do - end do - end do - - ! Now pull out information needed for chi - xs % chi_prompt(:, :, :, :) = temp_4d - - ! Deallocate temporary 4D array for nu_fission matrix - deallocate(temp_4d) - - ! Normalize chi so its CDF goes to 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - chi_sum = sum(xs % chi_prompt(:, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi prompt for a group & - &that sums to zero") - else - xs % chi_prompt(:, gin, iazi, ipol) = & - xs % chi_prompt(:, gin, iazi, ipol) / chi_sum - end if - end do - end do - end do - else - call fatal_error("prompt-nu-fission must be provided as a 3D & - &or 4D array") - end if - - call close_dataset(xsdata) - end if - - ! If delayed-nu-fission provided, set delayed-nu-fission. If - ! delayed-nu-fission is a matrix, set chi-delayed. - if (object_exists(xsdata_grp, "delayed-nu-fission")) then - - ! Get the dimensions of the delayed-nu-fission dataset - xsdata = open_dataset(xsdata_grp, "delayed-nu-fission") - call get_ndims(xsdata, ndims) - - ! delayed-nu-fission is input as (energy_groups, n_azi, n_pol) - if (ndims == 3) then - - ! If beta is zeros, raise error - if (temp_beta(1,1,1,1) == ZERO) then - call fatal_error("cannot set delayed-nu-fission with a 3D & - &array if beta not provided") - end if - - ! Allocate temporary arrays for delayed-nu-fission - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(energy_groups, this % n_azi, this % n_pol)) - - ! Get delayed-nu-fission - call read_dataset(temp_1d, xsdata_grp, "delayed-nu-fission") - temp_3d = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin, iazi, ipol) = & - temp_beta(dg, gin, iazi, ipol) * & - temp_3d(gin, iazi, ipol) - end do - end do - end do - end do - - ! Deallocate temporary delayed-nu-fission arrays - deallocate(temp_1d) - deallocate(temp_3d) - - ! If delayed-nu-fission is a (delayed_group, energy_group, - ! n_azi, n_pol) matrix, set delayed-nu-fission separately for - ! each delayed group. - else if (ndims == 4) then - - ! Get the shape of delayed-nu-fission - call get_shape(xsdata, dims) - - ! Issue error if 1st dimension not correct - if (dims(1) /= delayed_groups) then - call fatal_error("The delayed-nu-fission matrix was input & - &with a 1st dimension not equal to the number of & - &delayed groups.") - end if - - ! Issue error if 2nd dimension not correct - if (dims(2) /= energy_groups) then - call fatal_error("The delayed-nu-fission matrix was input & - &with a 2nd dimension not equal to the number of & - &energy groups.") - end if - - ! Issue warning if delayed_groups == energy_groups - if (delayed_groups == energy_groups) then - call warning("delayed-nu-fission was input as a dimension & - &4 matrix with the same number of delayed groups and & - &groups. It is important to know that OpenMC assumes & - &the dimensions in the matrix are (delayed_groups, & - &energy_groups, n_azi, n_pol). Currently, & - &delayed-nu-fission cannot be set as a group by group & - &matrix.") - end if - - ! Get delayed-nu-fission - allocate(temp_1d(delayed_groups * energy_groups * this % n_azi & - * this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "delayed-nu-fission") - xs % delayed_nu_fission = reshape(temp_1d, (/delayed_groups, & - energy_groups, this % n_azi, this % n_pol /)) - - ! Deallocate temporary array for delayed-nu-fission matrix - deallocate(temp_1d) - - ! If delayed nu-fission is a 5D matrix, set delayed_nu_fission - ! and chi_delayed. - else if (ndims == 5) then - - ! chi_delayed is embedded in delayed_nu_fission -> extract - ! chi_delayed - allocate(temp_1d(delayed_groups * energy_groups * & - energy_groups * this % n_azi * this % n_pol)) - allocate(temp_5d(delayed_groups, energy_groups, energy_groups, & - this % n_azi, this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "delayed-nu-fission") - temp_5d = reshape(temp_1d, (/delayed_groups, energy_groups, & - energy_groups, this % n_azi, this % n_pol/)) - - ! Deallocate temporary 1D array for delayed_nu_fission matrix - deallocate(temp_1d) - - ! Set the 4D delayed-nu-fission matrix and 5D chi_delayed matrix - ! from the 5D delayed-nu-fission matrix - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - xs % delayed_nu_fission(dg, gin, iazi, ipol) = & - sum(temp_5d(dg, :, gin, iazi, ipol)) - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin, iazi, ipol) = & - temp_5d(dg, gout, gin, iazi, ipol) - end do - end do - end do - end do - end do - - ! Normalize chi_delayed so its CDF goes to 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - chi_sum = sum(xs % chi_delayed(dg, :, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group& - & that sums to zero") - else - xs % chi_delayed(dg, :, gin, iazi, ipol) = & - xs % chi_delayed(dg, :, gin, iazi, ipol) / & - chi_sum - end if - end do - end do - end do - end do - - ! Deallocate temporary 5D matrix for delayed_nu_fission - deallocate(temp_5d) - else - call fatal_error("delayed-nu-fission must be provided as a & - &3D, 4D, or 5D array") - end if - - call close_dataset(xsdata) - end if - - ! Deallocate temporary beta array - deallocate(temp_beta) - - ! chi-prompt, chi-delayed, prompt-nu-fission, and delayed-nu-fission - ! have been set; Now we will check for the rest of the XS that are - ! unique to fissionable isotopes - - ! Set fission xs - if (object_exists(xsdata_grp, "fission")) then - - ! Allocate temporary array for fission - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - - ! Get fission array - call read_dataset(temp_1d, xsdata_grp, "fission") - xs % fission(:, :, :) = reshape(temp_1d, (/energy_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary array for fission - deallocate(temp_1d) - end if - - ! Set kappa-fission xs - if (object_exists(xsdata_grp, "kappa-fission")) then - - ! Allocate temporary array for kappa-fission - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - - ! Get kappa-fission array - call read_dataset(temp_1d, xsdata_grp, "kappa-fission") - xs % kappa_fission(:, :, :) = reshape(temp_1d, (/energy_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary array for kappa-fission - deallocate(temp_1d) - end if - - ! Set decay rate - if (object_exists(xsdata_grp, "decay rate")) then - - ! Allocate temporary array for decay rate - allocate(temp_1d(this % n_azi * this % n_pol * delayed_groups)) - - ! Get decay rate array - call read_dataset(temp_1d, xsdata_grp, "decay rate") - xs % decay_rate(:, :, :) = reshape(temp_1d, (/delayed_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary array for decay rate - deallocate(temp_1d) - end if - end if - - ! All the XS unique to fissionable isotopes have been set; Now set all - ! the generation XS - - if (object_exists(xsdata_grp, "absorption")) then - - ! Allocate temporary array for absorption xs - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - - ! Read in absorption xs - call read_dataset(temp_1d, xsdata_grp, "absorption") - - xs % absorption = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - ! Deallocate temporary array for absorption xs - deallocate(temp_1d) - else - call fatal_error("Must provide absorption!") - end if - - if (object_exists(xsdata_grp, "inverse-velocity")) then - - ! Allocate temporary array for inverse velocity - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - - ! Read in inverse velocity - call read_dataset(temp_1d, xsdata_grp, "inverse-velocity") - - xs % inverse_velocity = reshape(temp_1d, (/energy_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary array for inverse velocity - deallocate(temp_1d) - end if - - ! Get scattering data - if (.not. object_exists(xsdata_grp, "scatter_data")) & - call fatal_error("Must provide 'scatter_data'") - - scatt_grp = open_group(xsdata_grp, 'scatter_data') - - ! First get the outgoing group boundary indices - if (object_exists(scatt_grp, "g_min")) then - - allocate(int_arr(energy_groups * this % n_azi * this % n_pol)) - - call read_dataset(int_arr, scatt_grp, "g_min") - allocate(gmin(energy_groups, this % n_azi, this % n_pol)) - gmin = reshape(int_arr, (/energy_groups, this % n_azi, & - this % n_pol/)) - - deallocate(int_arr) - else - call fatal_error("'g_min' for the scatter_data must be provided") - end if - - if (object_exists(scatt_grp, "g_max")) then - - allocate(int_arr(energy_groups * this % n_azi * this % n_pol)) - - call read_dataset(int_arr, scatt_grp, "g_max") - allocate(gmax(energy_groups, this % n_azi, this % n_pol)) - gmax = reshape(int_arr, (/energy_groups, this % n_azi, & - this % n_pol/)) - - deallocate(int_arr) - else - call fatal_error("'g_max' for the scatter_data must be provided") - end if - - ! Now use this information to find the length of a container array - ! to hold the flattened data - length = 0 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - length = length + order_data * (gmax(gin, iazi, ipol) - & - gmin(gin, iazi, ipol) + 1) - end do - end do - end do - - ! Allocate flattened array - allocate(temp_1d(length)) - - if (.not. object_exists(scatt_grp, 'scatter_matrix')) & - call fatal_error("'scatter_matrix' must be provided") - call read_dataset(temp_1d, scatt_grp, "scatter_matrix") - - ! Compare the number of orders given with the maximum order of the - ! problem. Strip off the superfluous orders if needed. - if (this % scatter_format == ANGLE_LEGENDRE) then - order = min(order_data - 1, max_order) - order_dim = order + 1 - else - order_dim = order_data - end if - - ! Convert temp_1d to a jagged array ((gin) % data(l, gout)) for - ! passing to ScattData - allocate(input_scatt(energy_groups, this % n_azi, this % n_pol)) - - index = 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(input_scatt(gin, iazi, ipol) % data(order_dim, & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - do gout = gmin(gin, iazi, ipol), gmax(gin, iazi, ipol) - do l = 1, order_dim - input_scatt(gin, iazi, ipol) % data(l, gout) = & - temp_1d(index) - index = index + 1 - end do ! gout - ! Adjust index for the orders we didnt take - index = index + (order_data - order_dim) - end do ! order - end do ! gin - end do ! iazi - end do ! ipol - - deallocate(temp_1d) - - ! Finally convert the legendre to tabular if needed - allocate(scatt_coeffs(energy_groups, this % n_azi, this % n_pol)) - - if (this % scatter_format == ANGLE_LEGENDRE .and. & - legendre_to_tabular) then - - this % scatter_format = ANGLE_TABULAR - order_dim = legendre_to_tabular_points - order = order_dim - dmu = TWO / real(order - 1, 8) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(scatt_coeffs(gin, iazi, ipol) % data(& - order_dim, & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - - do gout = gmin(gin, iazi, ipol), gmax(gin, iazi, ipol) - norm = ZERO - do imu = 1, order_dim - if (imu == 1) then - mu = -ONE - else if (imu == order_dim) then - mu = ONE - else - mu = -ONE + real(imu - 1, 8) * dmu - end if - - scatt_coeffs(gin, iazi, ipol) % data(imu, gout) = & - evaluate_legendre(& - input_scatt(gin, iazi, ipol) % data(:, gout), mu) - - ! Ensure positivity of distribution - if (scatt_coeffs(gin, iazi, ipol) % data(imu, gout) < ZERO) & - scatt_coeffs(gin, iazi, ipol) % data(imu, gout) = ZERO - - ! And accrue the integral - if (imu > 1) then - norm = norm + HALF * dmu * & - (scatt_coeffs(gin, iazi, ipol) % data(imu - 1, gout) + & - scatt_coeffs(gin, iazi, ipol) % data(imu, gout)) - end if - end do ! mu - - ! Now that we have the integral, lets ensure that the distribution - ! is normalized such that it preserves the original scattering xs - if (norm > ZERO) then - scatt_coeffs(gin, iazi, ipol) % data(:, gout) = & - scatt_coeffs(gin, iazi, ipol) % data(:, gout) * & - input_scatt(gin, iazi, ipol) % data(1, gout) / & - norm - end if - end do ! gout - end do ! gin - end do ! iazi - end do ! ipol - else - ! Sticking with current representation, carry forward - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(scatt_coeffs(gin, iazi, ipol) % data(order_dim, & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - scatt_coeffs(gin, iazi, ipol) % data(:, :) = & - input_scatt(gin, iazi, ipol) % data(1:order_dim, :) - end do - end do - end do - end if - - deallocate(input_scatt) - - ! Now get the multiplication matrix - if (object_exists(scatt_grp, 'multiplicity_matrix')) then - - ! Now use this information to find the length of a container array - ! to hold the flattened data - length = 0 - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - length = length + (gmax(gin, iazi, ipol) - gmin(gin, iazi, ipol) + 1) - end do - end do - end do - - ! Allocate flattened array - allocate(temp_1d(length)) - call read_dataset(temp_1d, scatt_grp, "multiplicity_matrix") - - ! Convert temp_1d to a jagged array ((gin) % data(gout)) for passing - ! to ScattData - allocate(temp_mult(energy_groups, this % n_azi, this % n_pol)) - - index = 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(temp_mult(gin, iazi, ipol) % data( & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - do gout = gmin(gin, iazi, ipol), gmax(gin, iazi, ipol) - temp_mult(gin, iazi, ipol) % data(gout) = temp_1d(index) - index = index + 1 - end do - end do - end do - end do - deallocate(temp_1d) - else - - allocate(temp_mult(energy_groups, this % n_azi, this % n_pol)) - - ! Default to multiplicities of 1.0 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(temp_mult(gin, iazi, ipol) % data( & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - temp_mult(gin, iazi, ipol) % data = ONE - end do - end do - end do - end if - - ! Allocate and initialize our ScattData Object. - allocate(xs % scatter(this % n_azi, this % n_pol)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - - ! Allocate and initialize our ScattData Object. - if (this % scatter_format == ANGLE_HISTOGRAM) then - allocate(ScattDataHistogram :: xs % scatter(iazi, ipol) % obj) - else if (this % scatter_format == ANGLE_TABULAR) then - allocate(ScattDataTabular :: xs % scatter(iazi, ipol) % obj) - else if (this % scatter_format == ANGLE_LEGENDRE) then - allocate(ScattDataLegendre :: xs % scatter(iazi, ipol) % obj) - end if - - ! Initialize the ScattData Object - call xs % scatter(iazi, ipol) % obj % init(gmin(:, iazi, ipol), & - gmax(:, iazi, ipol), temp_mult(:, iazi, ipol), & - scatt_coeffs(:, iazi, ipol)) - end do - end do - - ! Check sigA to ensure it is not 0 since it is - ! often divided by in the tally routines - ! (This may happen with Helium data) - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - if (xs % absorption(gin, iazi, ipol) == ZERO) then - xs % absorption(gin, iazi, ipol) = 1E-10_8 - end if - end do - end do - end do - - if (object_exists(xsdata_grp, "total")) then - - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "total") - xs % total = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - deallocate(temp_1d) - else - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - xs % total(:, iazi, ipol) = xs % absorption(:, iazi, ipol) + & - xs % scatter(iazi, ipol) % obj % scattxs(:) - end do - end do - end if - - ! Check sigT to ensure it is not 0 since it is often divided by in - ! the tally routines - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - if (xs % total(gin, iazi, ipol) == ZERO) then - xs % total(gin, iazi, ipol) = 1E-10_8 - end if - end do - end do - end do - - ! Close the groups we have opened and deallocate - call close_group(xsdata_grp) - call close_group(scatt_grp) - deallocate(scatt_coeffs, temp_mult) - - end associate ! xs - end do ! Temperatures - end subroutine mgxsang_from_hdf5 - -!=============================================================================== -! MGXS*_COMBINE Builds a macroscopic Mgxs object from microscopic Mgxs objects -!=============================================================================== - - subroutine mgxs_combine(this, temps, mat, nuclides, max_order, & - scatter_format, order_dim) - class(Mgxs), intent(inout) :: this ! The Mgxs to initialize - type(VectorReal), intent(in) :: temps ! Temperatures to obtain - type(Material), pointer, intent(in) :: mat ! base material - type(MgxsContainer), intent(in) :: nuclides(:) ! List of nuclides to harvest from - integer, intent(in) :: max_order ! Maximum requested order - integer, intent(out) :: scatter_format ! Type of scatter - integer, intent(out) :: order_dim ! Scattering data order size - - integer :: t, mat_max_order, order - - ! Fill in meta-data from material information - if (mat % name == "") then - this % name = trim(to_str(mat % id)) - else - this % name = trim(mat % name) - end if - - ! Set whether this material is fissionable - this % fissionable = mat % fissionable - - ! The following info we should initialize, but we dont need it nor - ! does it have guaranteed meaning. - this % awr = -ONE - - allocate(this % kTs(temps % size())) - - do t = 1, temps % size() - this % kTs(t) = temps % data(t) - end do - - ! Allocate the XS object for the number of temperatures - select type(this) - type is (MgxsIso) - allocate(this % xs(temps % size())) - type is (MgxsAngle) - allocate(this % xs(temps % size())) - end select - - ! Determine the scattering type of our data and ensure all scattering orders - ! are the same. - scatter_format = nuclides(mat % nuclide(1)) % obj % scatter_format - - select type(nuc => nuclides(mat % nuclide(1)) % obj) - type is (MgxsIso) - order = size(nuc % xs(1) % scatter % dist(1) % data, dim=1) - type is (MgxsAngle) - order = size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, dim=1) - end select - - ! If we have tabular only data, then make sure all datasets have same size - if (scatter_format == ANGLE_HISTOGRAM) then - ! Check all scattering data to ensure it is the same size - do i = 2, mat % n_nuclides - select type(nuc => nuclides(mat % nuclide(i)) % obj) - type is (MgxsIso) - if (order /= size(nuc % xs(1) % scatter % dist(1) % data, dim=1)) & - call fatal_error("All histogram scattering entries must be& - & same length!") - type is (MgxsAngle) - if (order /= size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, dim=1)) & - call fatal_error("All histogram scattering entries must be& - & same length!") - end select - end do - - ! Ok, got our order, store the dimensionality - order_dim = order - - else if (scatter_format == ANGLE_TABULAR) then - ! Check all scattering data to ensure it is the same size - do i = 2, mat % n_nuclides - select type(nuc => nuclides(mat % nuclide(i)) % obj) - type is (MgxsIso) - if (order /= size(nuc % xs(1) % scatter % dist(1) % data, dim=1)) & - call fatal_error("All tabular scattering entries must be& - & same length!") - type is (MgxsAngle) - if (order /= size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, dim=1)) & - call fatal_error("All tabular scattering entries must be& - & same length!") - end select - end do - - ! Ok, got our order, store the dimensionality - order_dim = order - - else if (scatter_format == ANGLE_LEGENDRE) then - - ! Need to determine the maximum scattering order of all data in this material - mat_max_order = 0 - - do i = 1, mat % n_nuclides - select type(nuc => nuclides(mat % nuclide(i)) % obj) - type is (MgxsIso) - if (size(nuc % xs(1) % scatter % dist(1) % data, & - dim=1) > mat_max_order) & - mat_max_order = size(nuc % xs(1) % scatter % dist(1) % data, & - dim=1) - type is (MgxsAngle) - if (size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, & - dim=1) > mat_max_order) & - mat_max_order = & - size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, & - dim=1) - end select - end do - - ! Now need to compare this material maximum scattering order with - ! the problem wide max scatt order and use whichever is lower - order = min(mat_max_order, max_order + 1) - - ! Ok, got our order, store the dimensionality - order_dim = order - end if - - end subroutine mgxs_combine - - subroutine mgxsiso_combine(this, temps, mat, nuclides, energy_groups, & - delayed_groups, max_order, tolerance, method) - class(MgxsIso), intent(inout) :: this ! The Mgxs to initialize - type(VectorReal), intent(in) :: temps ! Temperatures to obtain [MeV] - type(Material), pointer, intent(in) :: mat ! base material - type(MgxsContainer), intent(in) :: nuclides(:) ! List of nuclides to harvest from - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - integer, intent(in) :: max_order ! Maximum requested order - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: method ! Type of temperature access - - integer :: i ! loop index over nuclides - integer :: t ! Index in to temps - integer :: gin, gout ! group indices - integer :: dg ! delayed group index - real(8) :: atom_density ! atom density of a nuclide - real(8) :: norm, nuscatt - integer :: order_dim, nuc_order_dim - real(8), allocatable :: temp_mult(:, :), mult_num(:, :), mult_denom(:, :) - real(8), allocatable :: scatt_coeffs(:, :, :) - integer :: nuc_t - integer, allocatable :: nuc_ts(:) - real(8) :: temp_actual, temp_desired, interp - integer :: scatter_format - type(Jagged2D), allocatable :: nuc_matrix(:) - integer, allocatable :: gmin(:), gmax(:) - type(Jagged2D), allocatable :: jagged_scatt(:) - type(Jagged1D), allocatable :: jagged_mult(:) - - ! Set the meta-data - call mgxs_combine(this, temps, mat, nuclides, max_order, scatter_format, & - order_dim) - - ! Set the number of delayed groups - this % num_delayed_groups = delayed_groups - - ! Create the Xs Data for each temperature - TEMP_LOOP: do t = 1, temps % size() - - ! Allocate and initialize the data needed for macro_xs(i_mat) object - allocate(this % xs(t) % total(energy_groups)) - this % xs(t) % total(:) = ZERO - - allocate(this % xs(t) % absorption(energy_groups)) - this % xs(t) % absorption(:) = ZERO - - allocate(this % xs(t) % fission(energy_groups)) - this % xs(t) % fission(:) = ZERO - - allocate(this % xs(t) % kappa_fission(energy_groups)) - this % xs(t) % kappa_fission(:) = ZERO - - allocate(this % xs(t) % prompt_nu_fission(energy_groups)) - this % xs(t) % prompt_nu_fission(:) = ZERO - - allocate(this % xs(t) % delayed_nu_fission(delayed_groups, & - energy_groups)) - this % xs(t) % delayed_nu_fission(:, :) = ZERO - - allocate(this % xs(t) % chi_prompt(energy_groups, energy_groups)) - this % xs(t) % chi_prompt(:, :) = ZERO - - allocate(this % xs(t) % chi_delayed(delayed_groups, energy_groups, & - energy_groups)) - this % xs(t) % chi_delayed(:, :, :) = ZERO - - allocate(this % xs(t) % inverse_velocity(energy_groups)) - this % xs(t) % inverse_velocity(:) = ZERO - - allocate(this % xs(t) % decay_rate(delayed_groups)) - this % xs(t) % decay_rate(:) = ZERO - - allocate(temp_mult(energy_groups, energy_groups)) - temp_mult(:, :) = ZERO - - allocate(mult_num(energy_groups, energy_groups)) - mult_num(:, :) = ZERO - - allocate(mult_denom(energy_groups, energy_groups)) - mult_denom(:, :) = ZERO - - allocate(scatt_coeffs(order_dim, energy_groups, energy_groups)) - scatt_coeffs(:, :, :) = ZERO - - this % scatter_format = scatter_format - - if (scatter_format == ANGLE_LEGENDRE) then - allocate(ScattDataLegendre :: this % xs(t) % scatter) - else if (scatter_format == ANGLE_TABULAR) then - allocate(ScattDataTabular :: this % xs(t) % scatter) - else if (scatter_format == ANGLE_HISTOGRAM) then - allocate(ScattDataHistogram :: this % xs(t) % scatter) - end if - - ! Add contribution from each nuclide in material - NUC_LOOP: do i = 1, mat % n_nuclides - associate(nuc => nuclides(mat % nuclide(i)) % obj) - - ! Copy atom density of nuclide in material - atom_density = mat % atom_density(i) - - select case (method) - case (TEMPERATURE_NEAREST) - - ! Determine actual temperatures to read - temp_desired = temps % data(i) - allocate(nuc_ts(1)) - - nuc_ts(1) = minloc(abs(nuc % kTs - temp_desired), dim=1) - temp_actual = nuc % kTs(nuc_ts(1)) - - if (abs(temp_actual - temp_desired) >= K_BOLTZMANN * tolerance) then - call fatal_error("MGXS library does not contain cross sections & - &for " // trim(this % name) // " at or near " // & - trim(to_str(nint(temp_desired / K_BOLTZMANN))) // " K.") - end if - - 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_desired = temps % data(i) - allocate(nuc_ts(2)) - - do j = 1, size(nuc % kTs) - 1 - if (nuc % kTs(j) <= temp_desired .and. & - temp_desired < nuc % kTs(j + 1)) then - nuc_ts(1) = j - nuc_ts(2) = j + 1 - end if - end do - - call fatal_error("Nuclear data library does not contain cross sections & - &for " // trim(this % name) // " at temperatures that bound " // & - trim(to_str(nint(temp_desired / K_BOLTZMANN))) // " K.") - end select - - select type(nuc) - type is (MgxsIso) - do j = 1, size(nuc_ts) - - nuc_t = nuc_ts(j) - - if (size(nuc_ts) == 1) then - interp = ONE - else if (j == 1) then - interp = (ONE - (temp_desired - nuc % kTs(nuc_ts(1))) / & - (nuc % kTs(nuc_ts(2)) - nuc % kTs(nuc_ts(1)))) - else - interp = ONE - interp - end if - - ! Perform our operations which depend upon the type - ! Add contributions to total, absorption, and fission data (if necessary) - this % xs(t) % total = this % xs(t) % total + & - atom_density * nuc % xs(nuc_t) % total * interp - - this % xs(t) % absorption = this % xs(t) % absorption + & - atom_density * nuc % xs(nuc_t) % absorption * interp - - this % xs(t) % decay_rate = this % xs(t) % decay_rate + & - atom_density * nuc % xs(nuc_t) % decay_rate * interp - - this % xs(t) % inverse_velocity = & - this % xs(t) % inverse_velocity + & - atom_density * nuc % xs(nuc_t) % inverse_velocity * interp - - if (nuc % fissionable) then - - this % xs(t) % chi_prompt = this % xs(t) % chi_prompt + & - atom_density * nuc % xs(nuc_t) % chi_prompt * interp - - this % xs(t) % chi_delayed = this % xs(t) % chi_delayed + & - atom_density * nuc % xs(nuc_t) % chi_delayed * interp - - this % xs(t) % prompt_nu_fission = this % xs(t) % & - prompt_nu_fission + atom_density * nuc % xs(nuc_t) % & - prompt_nu_fission * interp - - this % xs(t) % delayed_nu_fission = this % xs(t) % & - delayed_nu_fission + atom_density * nuc % xs(nuc_t) % & - delayed_nu_fission * interp - - this % xs(t) % fission = this % xs(t) % fission + & - atom_density * nuc % xs(nuc_t) % fission * interp - - this % xs(t) % kappa_fission = this % xs(t) % kappa_fission +& - atom_density * nuc % xs(nuc_t) % kappa_fission * interp - end if - - ! We will next gather the multiplicity and scattering matrices. - ! To avoid multiple re-allocations as we resize the storage - ! matrix (and/or to avoidlots of duplicate code), we will use a - ! dense matrix for this storage, with a reduction to the sparse - ! format at the end. - - ! Get the multiplicity_matrix - ! To combine from nuclidic data we need to use the final relationship - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - ! sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - ! Developed as follows: - ! mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - ! sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - ! nuscatt_{i,g,g'} can be reconstructed from scatter % energy and - ! scatter % scattxs - do gin = 1, energy_groups - do gout = nuc % xs(nuc_t) % scatter % gmin(gin), nuc % xs(nuc_t) % scatter % gmax(gin) - - nuscatt = nuc % xs(nuc_t) % scatter % scattxs(gin) * & - nuc % xs(nuc_t) % scatter % energy(gin) % data(gout) - - mult_num(gout, gin) = mult_num(gout, gin) + atom_density * & - nuscatt * interp - - if (nuc % xs(nuc_t) % scatter % mult(gin) % data(gout) > ZERO) then - mult_denom(gout, gin) = mult_denom(gout,gin) + atom_density * & - nuscatt / nuc % xs(nuc_t) % scatter % mult(gin) % data(gout) * & - interp - else - ! Avoid division by zero - mult_denom(gout, gin) = mult_denom(gout,gin) + atom_density * & - interp - end if - end do - end do - - ! Get the complete scattering matrix - nuc_order_dim = size(nuc % xs(nuc_t) % scatter % dist(1) % data, dim=1) - nuc_order_dim = min(nuc_order_dim, order_dim) - - call nuc % xs(nuc_t) % scatter % get_matrix(nuc_order_dim, & - nuc_matrix) - - do gin = 1, energy_groups - do gout = nuc % xs(nuc_t) % scatter % gmin(gin), & - nuc % xs(nuc_t) % scatter % gmax(gin) - scatt_coeffs(1:nuc_order_dim, gout, gin) = & - scatt_coeffs(1: nuc_order_dim, gout, gin) + & - atom_density * interp * & - nuc_matrix(gin) % data(1:nuc_order_dim, gout) - end do - end do - end do - - type is (MgxsAngle) - call fatal_error("Invalid passing of MgxsAngle to MgxsIso object") - end select - - ! Obtain temp_mult - do gin = 1, energy_groups - do gout = 1, energy_groups - if (mult_denom(gout, gin) > ZERO) then - temp_mult(gout, gin) = mult_num(gout, gin) / mult_denom(gout, gin) - else - temp_mult(gout, gin) = ONE - end if - end do - end do - - ! Now create our jagged data from the dense data - call jagged_from_dense_2D(scatt_coeffs, jagged_scatt, gmin, gmax) - call jagged_from_dense_1D(temp_mult, jagged_mult) - - ! Initialize the ScattData Object - call this % xs(t) % scatter % init(gmin, gmax, jagged_mult, & - jagged_scatt) - - ! Now normalize chi - if (mat % fissionable) then - do gin = 1, energy_groups - norm = sum(this % xs(t) % chi_prompt(:, gin)) - if (norm > ZERO) then - this % xs(t) % chi_prompt(:, gin) = & - this % xs(t) % chi_prompt(:, gin) / norm - end if - end do - - do dg = 1, delayed_groups - do gin = 1, energy_groups - norm = sum(this % xs(t) % chi_delayed(dg, :, gin)) - if (norm > ZERO) then - this % xs(t) % chi_delayed(dg, :, gin) = & - this % xs(t) % chi_delayed(dg, :, gin) / norm - end if - end do - end do - end if - - ! Deallocate temporaries - deallocate(jagged_mult, jagged_scatt, gmin, gmax, scatt_coeffs, & - temp_mult, mult_num, mult_denom) - end associate ! nuc - end do NUC_LOOP - end do TEMP_LOOP - - end subroutine mgxsiso_combine - - subroutine mgxsang_combine(this, temps, mat, nuclides, energy_groups, & - delayed_groups, max_order, tolerance, method) - class(MgxsAngle), intent(inout) :: this ! The Mgxs to initialize - type(VectorReal), intent(in) :: temps ! Temperatures to obtain - type(Material), pointer, intent(in) :: mat ! base material - type(MgxsContainer), intent(in) :: nuclides(:) ! List of nuclides to harvest from - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - integer, intent(in) :: max_order ! Maximum requested order - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: method ! Type of temperature access - - integer :: i ! loop index over nuclides - integer :: t ! temperature loop index - integer :: gin, gout ! group indices - integer :: dg ! delayed group index - real(8) :: atom_density ! atom density of a nuclide - integer :: ipol, iazi, n_pol, n_azi - real(8) :: norm, nuscatt - integer :: order_dim, nuc_order_dim - real(8), allocatable :: temp_mult(:, :, :, :), mult_num(:, :, :, :) - real(8), allocatable :: mult_denom(:, :, :, :), scatt_coeffs(:, :, :, :, :) - integer :: nuc_t - integer, allocatable :: nuc_ts(:) - real(8) :: temp_actual, temp_desired, interp - integer :: scatter_format - type(Jagged2D), allocatable :: nuc_matrix(:) - integer, allocatable :: gmin(:), gmax(:) - type(Jagged2D), allocatable :: jagged_scatt(:) - type(Jagged1D), allocatable :: jagged_mult(:) - - ! Set the meta-data - call mgxs_combine(this, temps, mat, nuclides, max_order, scatter_format, & - order_dim) - - ! Set the number of delayed groups - this % num_delayed_groups = delayed_groups - - ! Get the number of each polar and azi angles and make sure all the - ! NuclideAngle types have the same number of these angles - n_pol = -1 - n_azi = -1 - - do i = 1, mat % n_nuclides - select type(nuc => nuclides(mat % nuclide(i)) % obj) - type is (MgxsAngle) - - if (n_pol == -1) then - n_pol = nuc % n_pol - n_azi = nuc % n_azi - - allocate(this % polar(n_pol)) - this % polar(:) = nuc % polar(:) - - allocate(this % azimuthal(n_azi)) - this % azimuthal(:) = nuc % azimuthal(:) - else - if ((n_pol /= nuc % n_pol) .or. (n_azi /= nuc % n_azi)) then - call fatal_error("All angular data must be same length!") - end if - end if - end select - end do - - ! Create the Xs Data for each temperature - TEMP_LOOP: do t = 1, temps % size() - - ! Allocate and initialize the data needed for macro_xs(i_mat) object - allocate(this % xs(t) % total(energy_groups, n_azi, n_pol)) - this % xs(t) % total = ZERO - - allocate(this % xs(t) % absorption(energy_groups, n_azi, n_pol)) - this % xs(t) % absorption = ZERO - - allocate(this % xs(t) % fission(energy_groups, n_azi, n_pol)) - this % xs(t) % fission = ZERO - - allocate(this % xs(t) % decay_rate(delayed_groups, n_azi, n_pol)) - this % xs(t) % decay_rate = ZERO - - allocate(this % xs(t) % inverse_velocity(energy_groups, n_azi, n_pol)) - this % xs(t) % inverse_velocity = ZERO - - allocate(this % xs(t) % kappa_fission(energy_groups, n_azi, n_pol)) - this % xs(t) % kappa_fission = ZERO - - allocate(this % xs(t) % prompt_nu_fission(energy_groups, n_azi, n_pol)) - this % xs(t) % prompt_nu_fission = ZERO - - allocate(this % xs(t) % delayed_nu_fission(delayed_groups, & - energy_groups, n_azi, n_pol)) - this % xs(t) % delayed_nu_fission = ZERO - - allocate(this % xs(t) % chi_prompt(energy_groups, energy_groups, & - n_azi, n_pol)) - this % xs(t) % chi_prompt = ZERO - - allocate(this % xs(t) % chi_delayed(delayed_groups, energy_groups, & - energy_groups, n_azi, n_pol)) - this % xs(t) % chi_delayed = ZERO - - allocate(temp_mult(energy_groups, energy_groups, n_azi, n_pol)) - temp_mult = ZERO - - allocate(mult_num(energy_groups, energy_groups, n_azi, n_pol)) - mult_num = ZERO - - allocate(mult_denom(energy_groups, energy_groups, n_azi, n_pol)) - mult_denom = ZERO - - allocate(scatt_coeffs(order_dim, energy_groups, energy_groups, n_azi, n_pol)) - scatt_coeffs = ZERO - - allocate(this % xs(t) % scatter(n_azi, n_pol)) - - do ipol = 1, n_pol - do iazi = 1, n_azi - if (scatter_format == ANGLE_LEGENDRE) then - allocate(ScattDataLegendre :: & - this % xs(t) % scatter(iazi, ipol) % obj) - else if (scatter_format == ANGLE_TABULAR) then - allocate(ScattDataTabular :: & - this % xs(t) % scatter(iazi, ipol) % obj) - else if (scatter_format == ANGLE_HISTOGRAM) then - allocate(ScattDataHistogram :: & - this % xs(t) % scatter(iazi, ipol) % obj) - end if - end do - end do - - ! Add contribution from each nuclide in material - NUC_LOOP: do i = 1, mat % n_nuclides - associate(nuc => nuclides(mat % nuclide(i)) % obj) - - select case (method) - case (TEMPERATURE_NEAREST) - - ! Determine actual temperatures to read - temp_desired = temps % data(i) - allocate(nuc_ts(1)) - - nuc_ts(1) = minloc(abs(nuc % kTs - temp_desired), dim=1) - temp_actual = nuc % kTs(nuc_ts(1)) - - if (abs(temp_actual - temp_desired) >= K_BOLTZMANN * tolerance) then - call fatal_error("MGXS library does not contain cross sections & - &for " // trim(this % name) // " at or near " // & - trim(to_str(nint(temp_desired / K_BOLTZMANN))) // " K.") - end if - - 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_desired = temps % data(i) - allocate(nuc_ts(2)) - - do j = 1, size(nuc % kTs) - 1 - if (nuc % kTs(j) <= temp_desired .and. & - temp_desired < nuc % kTs(j + 1)) then - nuc_ts(1) = j - nuc_ts(2) = j + 1 - end if - end do - - call fatal_error("Nuclear data library does not contain cross sections & - &for " // trim(this % name) // " at temperatures that bound " // & - trim(to_str(nint(temp_desired / K_BOLTZMANN))) // " K.") - end select - - ! Copy atom density of nuclide in material - atom_density = mat % atom_density(i) - - select type(nuc) - type is (MgxsAngle) - do j = 1, size(nuc_ts) - - nuc_t = nuc_ts(j) - - if (size(nuc_ts) == 1) then - interp = ONE - else if (j == 1) then - interp = (ONE - (temp_desired - nuc % kTs(nuc_ts(1))) / & - (nuc % kTs(nuc_ts(2)) - nuc % kTs(nuc_ts(1)))) - else - interp = ONE - interp - end if - - ! Perform our operations which depend upon the type - ! Add contributions to total, absorption, and fission data - ! (if necessary) - this % xs(t) % total = this % xs(t) % total + & - atom_density * nuc % xs(nuc_t) % total * interp - - this % xs(t) % absorption = this % xs(t) % absorption + & - atom_density * nuc % xs(nuc_t) % absorption * interp - - this % xs(t) % decay_rate = this % xs(t) % decay_rate + & - atom_density * nuc % xs(nuc_t) % decay_rate * interp - - this % xs(t) % inverse_velocity = & - this % xs(t) % inverse_velocity + & - atom_density * nuc % xs(nuc_t) % inverse_velocity * interp - - if (nuc % fissionable) then - - this % xs(t) % chi_prompt = this % xs(t) % chi_prompt + & - atom_density * nuc % xs(nuc_t) % chi_prompt * interp - - this % xs(t) % chi_delayed = this % xs(t) % chi_delayed + & - atom_density * nuc % xs(nuc_t) % chi_delayed * interp - - this % xs(t) % prompt_nu_fission = & - this % xs(t) % prompt_nu_fission + atom_density * & - nuc % xs(nuc_t) % prompt_nu_fission * interp - - this % xs(t) % delayed_nu_fission = & - this % xs(t) % delayed_nu_fission + atom_density * & - nuc % xs(nuc_t) % delayed_nu_fission * interp - - this % xs(t) % fission = this % xs(t) % fission + & - atom_density * nuc % xs(nuc_t) % fission * interp - - this % xs(t) % kappa_fission = this % xs(t) % kappa_fission & - + atom_density * nuc % xs(nuc_t) % kappa_fission * interp - - end if - - ! We will next gather the multiplicity and scattering matrices. - ! To avoid multiple re-allocations as we resize the storage - ! matrix (and/or to avoidlots of duplicate code), we will use a - ! dense matrix for this storage, with a reduction to the sparse - ! format at the end. - - ! Get the multiplicity_matrix - ! To combine from nuclidic data we need to use the final relationship - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - ! sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - ! Developed as follows: - ! mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - ! sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - ! nuscatt_{i,g,g'} can be reconstructed from scatter % energy and - ! scatter % scattxs - do ipol = 1, n_pol - do iazi = 1, n_azi - do gin = 1, energy_groups - do gout = nuc % xs(nuc_t) % scatter(iazi, ipol) %obj % gmin(gin), & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % gmax(gin) - - nuscatt = nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % scattxs(gin) * & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % energy(gin) % data(gout) - - mult_num(gout, gin, iazi, ipol) = & - mult_num(gout, gin, iazi, ipol) + atom_density * & - nuscatt * interp - - if (nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % mult(gin) % data(gout) > ZERO) then - mult_denom(gout, gin, iazi, ipol) = & - mult_denom(gout, gin, iazi, ipol) + atom_density * & - interp * nuscatt / & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % mult(gin) % data(gout) - else - ! Avoid division by zero - mult_denom(gout, gin, iazi, ipol) = & - mult_denom(gout, gin, iazi, ipol) + atom_density * & - interp - end if - end do - end do - - ! Get the complete scattering matrix - nuc_order_dim = & - size(nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % & - dist(1) % data, dim=1) - nuc_order_dim = min(nuc_order_dim, order_dim) - - call nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % & - get_matrix(nuc_order_dim, nuc_matrix) - - do gin = 1, energy_groups - do gout = & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % gmin(gin), & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % gmax(gin) - scatt_coeffs(1:nuc_order_dim, gout, gin, iazi, ipol) = & - scatt_coeffs(1: nuc_order_dim, gout, gin, iazi, ipol) + & - atom_density * interp * & - nuc_matrix(gin) % data(1:nuc_order_dim, gout) - end do ! gout - end do ! gin - end do ! iazi - end do ! ipol - end do - type is (MgxsIso) - call fatal_error("Invalid passing of MgxsIso to MgxsAngle object") - end select - - ! Obtain temp_mult, create jaged arrays and initialize the - ! ScattData object. - do ipol = 1, n_pol - do iazi = 1, n_azi - ! Obtain temp_mult - do gin = 1, energy_groups - do gout = 1, energy_groups - if (mult_denom(gout, gin, iazi, ipol) > ZERO) then - temp_mult(gout, gin, iazi, ipol) = & - mult_num(gout, gin, iazi, ipol) / & - mult_denom(gout, gin, iazi, ipol) - else - temp_mult(gout, gin, iazi, ipol) = ONE - end if - end do - end do - - ! Now create our jagged data from the dense data - call jagged_from_dense_2D(scatt_coeffs(:, :, :, iazi, ipol), & - jagged_scatt, gmin, gmax) - call jagged_from_dense_1D(temp_mult(:, :, iazi, ipol), & - jagged_mult) - - ! Initialize the ScattData Object - call this % xs(t) % scatter(iazi, ipol) % obj % init(gmin, & - gmax, jagged_mult, jagged_scatt) - deallocate(jagged_scatt, jagged_mult, gmin, gmax) - end do - end do - - ! Now normalize chi - if (mat % fissionable) then - do ipol = 1, n_pol - do iazi = 1, n_azi - do gin = 1, energy_groups - norm = sum(this % xs(t) % chi_prompt(:, gin, iazi, ipol)) - if (norm > ZERO) then - this % xs(t) % chi_prompt(:, gin, iazi, ipol) = & - this % xs(t) % chi_prompt(:, gin, iazi, ipol) / norm - end if - end do - end do - end do - - do dg = 1, delayed_groups - do ipol = 1, n_pol - do iazi = 1, n_azi - do gin = 1, energy_groups - norm = sum(this % xs(t) % chi_delayed(dg, :, gin, iazi, ipol)) - if (norm > ZERO) then - this % xs(t) % chi_delayed(dg, :, gin, iazi, ipol) = & - this % xs(t) % chi_delayed(dg, :, gin, iazi, ipol)& - / norm - end if - end do - end do - end do - end do - - end if - - ! Deallocate temporaries - deallocate(scatt_coeffs, temp_mult, mult_num, mult_denom) - end associate ! nuc - end do NUC_LOOP - - end do TEMP_LOOP - - end subroutine mgxsang_combine - -!=============================================================================== -! MGXS*_GET_XS returns the requested data cross section data -!=============================================================================== - - pure function mgxsiso_get_xs(this, xstype, gin, gout, uvw, mu, dg) result(xs) - class(MgxsIso), intent(in) :: this ! The Xs to get data from - character(*) , intent(in) :: xstype ! Type of xs requested - integer, intent(in) :: gin ! Incoming Energy group - integer, optional, intent(in) :: gout ! Outgoing Energy group - real(8), optional, intent(in) :: uvw(3) ! Requested Angle - real(8), optional, intent(in) :: mu ! Change in angle - integer, optional, intent(in) :: dg ! Delayed group - real(8) :: xs ! Requested x/s - integer :: t ! temperature index - - t = this % index_temp - - select case(xstype) - - case('total') - xs = this % xs(t) % total(gin) - - case('absorption') - xs = this % xs(t) % absorption(gin) - - case('fission') - xs = this % xs(t) % fission(gin) - - case('kappa-fission') - xs = this % xs(t) % kappa_fission(gin) - - case('inverse-velocity') - xs = this % xs(t) % inverse_velocity(gin) - - case('decay rate') - if (present(dg)) then - xs = this % xs(t) % decay_rate(dg) - else - xs = this % xs(t) % decay_rate(1) - end if - - case('prompt-nu-fission') - xs = this % xs(t) % prompt_nu_fission(gin) - - case('delayed-nu-fission') - if (present(dg)) then - xs = this % xs(t) % delayed_nu_fission(dg, gin) - else - xs = sum(this % xs(t) % delayed_nu_fission(:, gin)) - end if - - case('nu-fission') - xs = this % xs(t) % prompt_nu_fission(gin) + & - sum(this % xs(t) % delayed_nu_fission(:, gin)) - - case('chi-prompt') - if (present(gout)) then - xs = this % xs(t) % chi_prompt(gout,gin) - else - ! Not sure youd want a 1 or a 0, but here you go! - xs = sum(this % xs(t) % chi_prompt(:, gin)) - end if - - case('chi-delayed') - if (present(gout)) then - if (present(dg)) then - xs = this % xs(t) % chi_delayed(dg, gout, gin) - else - xs = this % xs(t) % chi_delayed(1, gout, gin) - end if - else - if (present(dg)) then - xs = sum(this % xs(t) % chi_delayed(dg, :, gin)) - else - xs = sum(this % xs(t) % chi_delayed(dg, :, gin)) - end if - end if - - case('scatter') - if (present(gout)) then - if (gout < this % xs(t) % scatter % gmin(gin) .or. & - gout > this % xs(t) % scatter % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter % scattxs(gin) * & - this % xs(t) % scatter % energy(gin) % data(gout) - end if - else - xs = this % xs(t) % scatter % scattxs(gin) - end if - - case('scatter/mult') - if (present(gout)) then - if (gout < this % xs(t) % scatter % gmin(gin) .or. & - gout > this % xs(t) % scatter % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter % scattxs(gin) * & - this % xs(t) % scatter % energy(gin) % data(gout) / & - this % xs(t) % scatter % mult(gin) % data(gout) - end if - else - xs = this % xs(t) % scatter % scattxs(gin) / & - (dot_product(this % xs(t) % scatter % mult(gin) % data, & - this % xs(t) % scatter % energy(gin) % data)) - end if - - case('scatter*f_mu/mult','scatter*f_mu') - if (present(gout)) then - if (gout < this % xs(t) % scatter % gmin(gin) .or. & - gout > this % xs(t) % scatter % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter % scattxs(gin) * & - this % xs(t) % scatter % energy(gin) % data(gout) * & - this % xs(t) % scatter % calc_f(gin, gout, mu) - if (xstype == 'scatter*f_mu/mult') then - xs = xs / this % xs(t) % scatter % mult(gin) % data(gout) - end if - end if - else - xs = ZERO - ! TODO (Not likely needed) - ! (asking for f_mu without asking for a group or mu would mean the - ! user of this code wants the complete 1-outgoing group distribution - ! which Im not sure what they would do with that. - end if - - case default - xs = ZERO - end select - - end function mgxsiso_get_xs - - pure function mgxsang_get_xs(this, xstype, gin, gout, uvw, mu, dg) result(xs) - class(MgxsAngle), intent(in) :: this ! The Mgxs to initialize - character(*) , intent(in) :: xstype ! Type of xs requested - integer, intent(in) :: gin ! Incoming Energy group - integer, optional, intent(in) :: gout ! Outgoing Energy group - real(8), optional, intent(in) :: uvw(3) ! Requested Angle - real(8), optional, intent(in) :: mu ! Change in angle - integer, optional, intent(in) :: dg ! Delayed group - real(8) :: xs ! Requested x/s - - integer :: iazi, ipol, t - - t = this % index_temp - - if (present(uvw)) then - - call find_angle(this % polar, this % azimuthal, uvw, iazi, ipol) - - select case(xstype) - - case('total') - xs = this % xs(t) % total(gin, iazi, ipol) - - case('absorption') - xs = this % xs(t) % absorption(gin, iazi, ipol) - - case('fission') - xs = this % xs(t) % fission(gin, iazi, ipol) - - case('kappa-fission') - xs = this % xs(t) % kappa_fission(gin, iazi, ipol) - - case('prompt-nu-fission') - xs = this % xs(t) % prompt_nu_fission(gin, iazi, ipol) - - case('delayed-nu-fission') - if (present(dg)) then - xs = this % xs(t) % delayed_nu_fission(dg, gin, iazi, ipol) - else - xs = sum(this % xs(t) % delayed_nu_fission(:, gin, iazi, ipol)) - end if - - case('nu-fission') - xs = this % xs(t) % prompt_nu_fission(gin, iazi, ipol) + & - sum(this % xs(t) % delayed_nu_fission(:, gin, iazi, ipol)) - - case('chi-prompt') - if (present(gout)) then - xs = this % xs(t) % chi_prompt(gout, gin, iazi, ipol) - else - ! Not sure you would want a 1 or a 0, but here you go! - xs = sum(this % xs(t) % chi_prompt(:, gin, iazi, ipol)) - end if - - case('chi-delayed') - if (present(gout)) then - if (present(dg)) then - xs = this % xs(t) % chi_delayed(dg, gout, gin, iazi, ipol) - else - xs = this % xs(t) % chi_delayed(1, gout, gin, iazi, ipol) - end if - else - if (present(dg)) then - xs = sum(this % xs(t) % chi_delayed(dg, :, gin, iazi, ipol)) - else - xs = sum(this % xs(t) % chi_delayed(1, :, gin, iazi, ipol)) - end if - end if - - case('decay rate') - if (present(dg)) then - xs = this % xs(t) % decay_rate(iazi, ipol, dg) - else - xs = this % xs(t) % decay_rate(iazi, ipol, 1) - end if - - case('inverse-velocity') - xs = this % xs(t) % inverse_velocity(gin, iazi, ipol) - - case('scatter') - if (present(gout)) then - if (gout < this % xs(t) % scatter(iazi, ipol) % obj % gmin(gin) .or. & - gout > this % xs(t) % scatter(iazi, ipol) % obj % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) * & - this % xs(t) % scatter(iazi, ipol) % obj % energy(gin) % data(gout) - end if - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) - end if - - case('scatter/mult') - if (present(gout)) then - if (gout < this % xs(t) % scatter(iazi, ipol) % obj % gmin(gin) .or. & - gout > this % xs(t) % scatter(iazi, ipol) % obj % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) * & - this % xs(t) % scatter(iazi, ipol) % obj % energy(gin) % data(gout) / & - this % xs(t) % scatter(iazi, ipol) % obj % mult(gin) % data(gout) - end if - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) / & - (dot_product(this % xs(t) % scatter(iazi, ipol) % obj % mult(gin) % data, & - this % xs(t) % scatter(iazi, ipol) % obj % energy(gin) % data)) - end if - - case('scatter*f_mu/mult','scatter*f_mu') - if (present(gout)) then - if (gout < this % xs(t) % scatter(iazi, ipol) % obj % gmin(gin) .or. & - gout > this % xs(t) % scatter(iazi, ipol) % obj % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) * & - this % xs(t) % scatter(iazi, ipol) % obj % energy(gin) % data(gout) - xs = xs * this % xs(t) % scatter(iazi, ipol) % obj % calc_f(gin, gout, mu) - if (xstype == 'scatter*f_mu/mult') then - xs = xs / & - this % xs(t) % scatter(iazi, ipol) % obj % mult(gin) % data(gout) - end if - end if - else - xs = ZERO - ! TODO (Not likely needed) - ! (asking for f_mu without asking for a group or mu would mean the - ! user of this code wants the complete 1-outgoing group distribution - ! which Im not sure what they would do with that. - end if - - case default - xs = ZERO - - end select - - else - xs = ZERO - end if - - end function mgxsang_get_xs - -!=============================================================================== -! MGXS*_SAMPLE_FISSION_ENERGY samples the outgoing energy from a fission event -!=============================================================================== - - subroutine mgxsiso_sample_fission_energy(this, gin, uvw, dg, gout) - - class(MgxsIso), intent(in) :: this ! Data to work with - integer, intent(in) :: gin ! Incoming energy group - real(8), intent(in) :: uvw(3) ! Particle Direction - integer, intent(out) :: dg ! Delayed group - integer, intent(out) :: gout ! Sampled outgoing group - real(8) :: xi_pd ! Our random number for prompt/delayed - real(8) :: xi_gout ! Our random number for gout - real(8) :: prob_gout ! Running probability for gout - - ! Get nu and nu_prompt - real(8) :: prob_prompt - - prob_prompt = this % get_xs('prompt-nu-fission', gin) / & - this % get_xs('nu-fission', gin) - - ! Sample random numbers - xi_pd = prn() - xi_gout = prn() - - ! Neutron is born prompt - if (xi_pd <= prob_prompt) then - - ! set the delayed group for the particle born from fission to 0 - dg = 0 - - gout = 1 - prob_gout = this % get_xs('chi-prompt', gin, gout) - - do while (prob_gout < xi_gout) - gout = gout + 1 - prob_gout = prob_gout + this % get_xs('chi-prompt', gin, gout) - end do - - ! Neutron is born delayed - else - - ! Get the delayed group - dg = 0 - - do while (xi_pd >= prob_prompt) - dg = dg + 1 - prob_prompt = prob_prompt + & - this % get_xs('delayed-nu-fission', gin, dg=dg) & - / this % get_xs('nu-fission', gin) - end do - - ! Adjust dg in case of round off error - dg = min(dg, this % num_delayed_groups) - - ! Get the outgoing group - gout = 1 - prob_gout = this % get_xs('chi-delayed', gin, gout, dg=dg) - - do while (prob_gout < xi_gout) - gout = gout + 1 - prob_gout = prob_gout + this % get_xs('chi-delayed', gin, gout, dg=dg) - end do - end if - - end subroutine mgxsiso_sample_fission_energy - - subroutine mgxsang_sample_fission_energy(this, gin, uvw, dg, gout) - class(MgxsAngle), intent(in) :: this ! Data to work with - integer, intent(in) :: gin ! Incoming energy group - real(8), intent(in) :: uvw(3) ! Direction vector - integer, intent(out) :: dg ! Delayed group - integer, intent(out) :: gout ! Sampled outgoing group - real(8) :: xi_pd ! Our random number for prompt/delayed - real(8) :: xi_gout ! Our random number for gout - real(8) :: prob_gout ! Running probability for gout - real(8) :: prob_prompt - - ! Get nu and nu_prompt - prob_prompt = this % get_xs('prompt-nu-fission', gin, uvw=uvw) / & - this % get_xs('nu-fission', gin, uvw=uvw) - - ! Sample random numbers - xi_pd = prn() - xi_gout = prn() - - ! Neutron is born prompt - if (xi_pd <= prob_prompt) then - - ! set the delayed group for the particle born from fission to 0 - dg = 0 - - gout = 1 - prob_gout = this % get_xs('chi-prompt', gin, gout, uvw=uvw) - - do while (prob_gout < xi_gout) - gout = gout + 1 - prob_gout = prob_gout + & - this % get_xs('chi-prompt', gin, gout, uvw=uvw) - end do - - ! Neutron is born delayed - else - - ! Get the delayed group - dg = 0 - - do while (xi_pd >= prob_prompt) - dg = dg + 1 - prob_prompt = prob_prompt + & - this % get_xs('delayed-nu-fission', gin, uvw=uvw, dg=dg) / & - this % get_xs('nu-fission', gin, uvw=uvw) - end do - - ! Adjust dg in case of round off error - dg = min(dg, this % num_delayed_groups) - - ! Get the outgoing group - gout = 1 - prob_gout = this % get_xs('chi-delayed', gin, gout, uvw=uvw, dg=dg) - - do while (prob_gout < xi_gout) - gout = gout + 1 - prob_gout = prob_gout + & - this % get_xs('chi-delayed', gin, gout, uvw=uvw, dg=dg) - end do - end if - - end subroutine mgxsang_sample_fission_energy - -!=============================================================================== -! MGXS*_SAMPLE_SCATTER Selects outgoing energy and angle after a scatter event -!=============================================================================== - - subroutine mgxsiso_sample_scatter(this, uvw, gin, gout, mu, wgt) - class(MgxsIso), intent(in) :: this - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - call this % xs(this % index_temp) % scatter % sample(gin, gout, mu, wgt) - - end subroutine mgxsiso_sample_scatter - - subroutine mgxsang_sample_scatter(this, uvw, gin, gout, mu, wgt) - class(MgxsAngle), intent(in) :: this - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - integer :: iazi, ipol ! Angular indices - - call find_angle(this % polar, this % azimuthal, uvw, iazi, ipol) - call this % xs(this % index_temp) % scatter(iazi, ipol) % obj % sample( & - gin, gout, mu, wgt) - - end subroutine mgxsang_sample_scatter - -!=============================================================================== -! MGXS*_CALCULATE_XS determines the multi-group cross sections -! for the material the particle is currently traveling through. -!=============================================================================== - - subroutine mgxsiso_calculate_xs(this, gin, sqrtkT, uvw, xs) - class(MgxsIso), intent(inout) :: this - integer, intent(in) :: gin ! Incoming neutron group - real(8), intent(in) :: sqrtkT ! Material temperature - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - type(MaterialMacroXS), intent(inout) :: xs ! Resultant Mgxs Data - - ! Update the temperature index - call this % find_temperature(sqrtkT) - - xs % total = this % xs(this % index_temp) % total(gin) - xs % absorption = this % xs(this % index_temp) % absorption(gin) - xs % nu_fission = & - this % xs(this % index_temp) % prompt_nu_fission(gin) + & - sum(this % xs(this % index_temp) % delayed_nu_fission(:, gin)) - - end subroutine mgxsiso_calculate_xs - - subroutine mgxsang_calculate_xs(this, gin, sqrtkT, uvw, xs) - class(MgxsAngle), intent(inout) :: this - integer, intent(in) :: gin ! Incoming neutron group - real(8), intent(in) :: sqrtkT ! Material temperature - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - type(MaterialMacroXS), intent(inout) :: xs ! Resultant Mgxs Data - - integer :: iazi, ipol - - ! Update the temperature and angle indices - call this % find_temperature(sqrtkT) - call find_angle(this % polar, this % azimuthal, uvw, iazi, ipol) - - xs % total = this % xs(this % index_temp) % & - total(gin, iazi, ipol) - xs % absorption = this % xs(this % index_temp) % & - absorption(gin, iazi, ipol) - xs % nu_fission = this % xs(this % index_temp) % & - prompt_nu_fission(gin, iazi, ipol) + & - sum(this % xs(this % index_temp) % & - delayed_nu_fission(:, gin, iazi, ipol)) - - end subroutine mgxsang_calculate_xs - -!=============================================================================== -! MGXS_FIND_TEMPERATURE sets the temperature index for the given -! sqrt(temperature), (with temperature in units of eV) -!=============================================================================== - - subroutine mgxs_find_temperature(this, sqrtkT) - class(Mgxs), intent(inout) :: this - real(8), intent(in) :: sqrtkT ! Temperature (in units of eV) - - this % index_temp = minloc(abs(this % kTs - (sqrtkT * sqrtkT)), dim=1) - - end subroutine mgxs_find_temperature - -!=============================================================================== -! FIND_ANGLE finds the closest angle on the data grid and returns that index -!=============================================================================== - - pure subroutine find_angle(polar, azimuthal, uvw, i_azi, i_pol) - real(8), intent(in) :: polar(:) ! Polar angles [0,pi] - real(8), intent(in) :: azimuthal(:) ! Azi. angles [-pi,pi] - real(8), intent(in) :: uvw(3) ! Direction of motion - integer, intent(inout) :: i_pol ! Closest polar bin - integer, intent(inout) :: i_azi ! Closest azi bin - - real(8) :: my_pol, my_azi, dangle - - ! Convert uvw to polar and azi - - my_pol = acos(uvw(3)) - my_azi = atan2(uvw(2), uvw(1)) - - ! Search for equi-binned angles - dangle = PI / real(size(polar),8) - i_pol = floor(my_pol / dangle + ONE) - dangle = TWO * PI / real(size(azimuthal),8) - i_azi = floor((my_azi + PI) / dangle + ONE) - - end subroutine find_angle - -!=============================================================================== -! FREE_MEMORY_MGXS deallocates global arrays defined in this module -!=============================================================================== - - subroutine free_memory_mgxs() - if (allocated(nuclides_MG)) deallocate(nuclides_MG) - if (allocated(macro_xs)) deallocate(macro_xs) - if (allocated(energy_bins)) deallocate(energy_bins) - if (allocated(energy_bin_avg)) deallocate(energy_bin_avg) - end subroutine free_memory_mgxs - -end module mgxs_header diff --git a/src/mgxs_interface.F90 b/src/mgxs_interface.F90 new file mode 100644 index 0000000000..2a579e930a --- /dev/null +++ b/src/mgxs_interface.F90 @@ -0,0 +1,164 @@ +module mgxs_interface + + use, intrinsic :: ISO_C_BINDING + + use hdf5_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 + implicit none + integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: gin + real(C_DOUBLE), value, intent(in) :: sqrtkT + real(C_DOUBLE), intent(in) :: uvw(1:3) + real(C_DOUBLE), intent(inout) :: total_xs + real(C_DOUBLE), intent(inout) :: abs_xs + real(C_DOUBLE), intent(inout) :: nu_fiss_xs + end subroutine calculate_xs_c + + subroutine sample_scatter_c(i_mat, gin, gout, mu, wgt, uvw) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: gin + integer(C_INT), intent(inout) :: gout + real(C_DOUBLE), intent(inout) :: mu + real(C_DOUBLE), intent(inout) :: wgt + real(C_DOUBLE), intent(inout) :: uvw(1:3) + end subroutine sample_scatter_c + + subroutine sample_fission_energy_c(i_mat, gin, dg, gout) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: gin + integer(C_INT), intent(inout) :: dg + integer(C_INT), intent(inout) :: gout + end subroutine sample_fission_energy_c + + subroutine get_name_c(index, name_len, name) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: name_len + character(kind=C_CHAR), intent(inout) :: name(name_len) + end subroutine get_name_c + + function get_awr_c(index) result(awr) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + real(C_DOUBLE) :: awr + end function get_awr_c + + function get_nuclide_xs_c(index, xstype, gin, gout, mu, dg) result(val) & + bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: xstype + integer(C_INT), value, intent(in) :: gin + integer(C_INT), optional, intent(in) :: gout + real(C_DOUBLE), optional, intent(in) :: mu + integer(C_INT), optional, intent(in) :: dg + real(C_DOUBLE) :: val + end function get_nuclide_xs_c + + function get_macro_xs_c(index, xstype, gin, gout, mu, dg) result(val) & + bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: xstype + integer(C_INT), value, intent(in) :: gin + integer(C_INT), optional, intent(in) :: gout + real(C_DOUBLE), optional, intent(in) :: mu + integer(C_INT), optional, intent(in) :: dg + real(C_DOUBLE) :: val + end function get_macro_xs_c + + subroutine set_nuclide_angle_index_c(index, uvw) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + real(C_DOUBLE), intent(in) :: uvw(1:3) + end subroutine set_nuclide_angle_index_c + + subroutine set_macro_angle_index_c(index, uvw) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + real(C_DOUBLE), intent(in) :: uvw(1:3) + end subroutine set_macro_angle_index_c + + subroutine set_nuclide_temperature_index_c(index, sqrtkT) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + real(C_DOUBLE), value, intent(in) :: sqrtkT + end subroutine set_nuclide_temperature_index_c + + end interface + + ! Number of energy groups + integer(C_INT) :: num_energy_groups + + ! Number of delayed groups + integer(C_INT) :: num_delayed_groups + + ! Energy group structure with decreasing energy + real(8), allocatable :: energy_bins(:) + + ! Midpoint of the energy group structure + real(8), allocatable :: energy_bin_avg(:) + + ! Energy group structure with increasing energy + real(8), allocatable :: rev_energy_bins(:) + +end module mgxs_interface \ No newline at end of file diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp new file mode 100644 index 0000000000..d875987791 --- /dev/null +++ b/src/mgxs_interface.cpp @@ -0,0 +1,229 @@ +#include + +#include "error.h" +#include "math_functions.h" +#include "mgxs_interface.h" + + +namespace openmc { + +//============================================================================== +// 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) +{ + // Convert temps to a vector for the from_hdf5 function + double_1dvec temperature(temps, temps + n_temps); + + 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); + } 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); + + nuclides_MG.push_back(mg); + close_group(xs_grp); +} + +//============================================================================== + +bool +query_fissionable_c(int n_nuclides, const int i_nuclides[]) +{ + bool result = false; + for (int n = 0; n < n_nuclides; n++) { + if (nuclides_MG[i_nuclides[n] - 1].fissionable) result = true; + } + 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) +{ + if (n_temps > 0) { + // // Convert temps to a vector + double_1dvec temperature(temps, temps + n_temps); + + // Convert atom_densities to a vector + double_1dvec atom_densities_vec(atom_densities, + atom_densities + n_nuclides); + + // 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] = &nuclides_MG[i_nuclides[n] - 1]; + } + + Mgxs macro(mat_name, temperature, mgxs_ptr, atom_densities_vec, + tolerance, method); + macro_xs.emplace_back(macro); + } else { + // Preserve the ordering of materials by including a blank entry + Mgxs macro; + macro_xs.emplace_back(macro); + } +} + +//============================================================================== +// Mgxs tracking/transport/tallying interface methods +//============================================================================== + +void +calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs) +{ + macro_xs[i_mat - 1].calculate_xs(gin - 1, sqrtkT, uvw, total_xs, abs_xs, + nu_fiss_xs); +} + +//============================================================================== + +void +sample_scatter_c(int i_mat, int gin, int& gout, double& mu, double& wgt, + double uvw[3]) +{ + int gout_c = gout - 1; + macro_xs[i_mat - 1].sample_scatter(gin - 1, gout_c, mu, wgt); + + // adjust return value for fortran indexing + gout = gout_c + 1; + + // Rotate the angle + rotate_angle_c(uvw, mu, nullptr); +} + +//============================================================================== + +void +sample_fission_energy_c(int i_mat, int gin, int& dg, int& gout) +{ + int dg_c = 0; + int gout_c = 0; + macro_xs[i_mat - 1].sample_fission_energy(gin - 1, dg_c, gout_c); + + // adjust return values for fortran indexing + dg = dg_c + 1; + gout = gout_c + 1; +} + +//============================================================================== + +double +get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) +{ + int gout_c; + int* gout_c_p; + int dg_c; + int* dg_c_p; + if (gout != nullptr) { + gout_c = *gout - 1; + gout_c_p = &gout_c; + } else { + gout_c_p = gout; + } + if (dg != nullptr) { + dg_c = *dg - 1; + dg_c_p = &dg_c; + } else { + dg_c_p = dg; + } + return nuclides_MG[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); +} + +//============================================================================== + +double +get_macro_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) +{ + int gout_c; + int* gout_c_p; + int dg_c; + int* dg_c_p; + if (gout != nullptr) { + gout_c = *gout - 1; + gout_c_p = &gout_c; + } else { + gout_c_p = gout; + } + if (dg != nullptr) { + dg_c = *dg - 1; + dg_c_p = &dg_c; + } else { + dg_c_p = dg; + } + return macro_xs[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); +} + +//============================================================================== + +void +set_nuclide_angle_index_c(int index, const double uvw[3]) +{ + // Update the values + nuclides_MG[index - 1].set_angle_index(uvw); +} + +//============================================================================== + +void +set_macro_angle_index_c(int index, const double uvw[3]) +{ + // Update the values + macro_xs[index - 1].set_angle_index(uvw); +} + +//============================================================================== + +void +set_nuclide_temperature_index_c(int index, double sqrtkT) +{ + // Update the values + nuclides_MG[index - 1].set_temperature_index(sqrtkT); +} + +//============================================================================== +// General Mgxs methods +//============================================================================== + +void +get_name_c(int index, int name_len, char* name) +{ + // First blank out our input string + std::string str(name_len, ' '); + std::strcpy(name, str.c_str()); + + // Now get the data and copy to the C-string + str = nuclides_MG[index - 1].name; + std::strcpy(name, str.c_str()); + + // Finally, remove the null terminator + name[std::strlen(name)] = ' '; +} + +//============================================================================== + +double +get_awr_c(int index) +{ + return nuclides_MG[index - 1].awr; +} + +} // namespace openmc \ No newline at end of file diff --git a/src/mgxs_interface.h b/src/mgxs_interface.h new file mode 100644 index 0000000000..65afd20f9c --- /dev/null +++ b/src/mgxs_interface.h @@ -0,0 +1,79 @@ +//! \file mgxs_interface.h +//! A collection of C interfaces to the C++ Mgxs class + +#ifndef MGXS_INTERFACE_H +#define MGXS_INTERFACE_H + +#include "hdf5_interface.h" +#include "mgxs.h" + + +namespace openmc { + +//============================================================================== +// Global variables +//============================================================================== + +extern std::vector nuclides_MG; +extern std::vector macro_xs; + +//============================================================================== +// 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); + +extern "C" bool +query_fissionable_c(int n_nuclides, const int i_nuclides[]); + +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); + +//============================================================================== +// Mgxs tracking/transport/tallying interface methods +//============================================================================== + +extern "C" void +calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs); + +extern "C" void +sample_scatter_c(int i_mat, int gin, int& gout, double& mu, double& wgt, + double uvw[3]); + +extern "C" void +sample_fission_energy_c(int i_mat, int gin, int& dg, int& gout); + +extern "C" double +get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg); + +extern "C" double +get_macro_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg); + +extern "C" void +set_nuclide_angle_index_c(int index, const double uvw[3]); + +extern "C" void +set_macro_angle_index_c(int index, const double uvw[3]); + +extern "C" void +set_nuclide_temperature_index_c(int index, double sqrtkT); + +//============================================================================== +// General Mgxs methods +//============================================================================== + +extern "C" void +get_name_c(int index, int name_len, char* name); + +extern "C" double +get_awr_c(int index); + +} // namespace openmc +#endif // MGXS_INTERFACE_H \ No newline at end of file diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 7a6b2444c7..2ccee51aea 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -165,10 +165,10 @@ module nuclide_header !=============================================================================== type MaterialMacroXS - real(8) :: total ! macroscopic total xs - real(8) :: absorption ! macroscopic absorption xs - real(8) :: fission ! macroscopic fission xs - real(8) :: nu_fission ! macroscopic production xs + real(C_DOUBLE) :: total ! macroscopic total xs + real(C_DOUBLE) :: absorption ! macroscopic absorption xs + real(C_DOUBLE) :: fission ! macroscopic fission xs + real(C_DOUBLE) :: nu_fission ! macroscopic production xs end type MaterialMacroXS !=============================================================================== diff --git a/src/output.F90 b/src/output.F90 index b1671df984..0d50d54853 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -12,7 +12,7 @@ module output use math, only: t_percentile use mesh_header, only: RegularMesh, meshes use message_passing, only: master, n_procs - use mgxs_header, only: nuclides_MG + use mgxs_interface use nuclide_header use particle_header, only: LocalCoord, Particle use plot_header @@ -76,7 +76,7 @@ contains write(UNIT=OUTPUT_UNIT, FMT=*) & ' | The OpenMC Monte Carlo Code' write(UNIT=OUTPUT_UNIT, FMT=*) & - ' Copyright | 2011-2018 Massachusetts Institute of Technology' + ' Copyright | 2011-2018 MIT and OpenMC contributors' write(UNIT=OUTPUT_UNIT, FMT=*) & ' License | http://openmc.readthedocs.io/en/latest/license.html' write(UNIT=OUTPUT_UNIT, FMT='(11X,"Version | ",I1,".",I2,".",I1)') & @@ -171,7 +171,7 @@ contains write(UNIT=OUTPUT_UNIT, FMT='(1X,A,A)') "Git SHA1: ", GIT_SHA1 #endif write(UNIT=OUTPUT_UNIT, FMT=*) "Copyright (c) 2011-2018 & - &Massachusetts Institute of Technology" + &Massachusetts Institute of Technology and OpenMC contributors" write(UNIT=OUTPUT_UNIT, FMT=*) "MIT/X license at & &" end if @@ -680,6 +680,7 @@ contains character(36) :: score_name ! names of scoring function ! to be applied at write-time type(TallyFilterMatch), allocatable :: matches(:) + character(MAX_WORD_LEN) :: temp_name ! Skip if there are no tallies if (n_tallies == 0) return @@ -843,8 +844,9 @@ contains write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & trim(nuclides(i_nuclide) % name) else + call get_name_c(i_nuclide, len(temp_name), temp_name) write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & - trim(nuclides_MG(i_nuclide) % obj % name) + trim(temp_name) end if end if diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index c3d25151b6..200773c256 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -6,7 +6,7 @@ module particle_restart use constants use error, only: write_message use hdf5_interface, only: file_open, file_close, read_dataset, HID_T - use mgxs_header, only: energy_bin_avg + use mgxs_interface, only: energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides use output, only: print_particle use particle_header, only: Particle diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index eb82085941..a7a1832956 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -8,13 +8,12 @@ module physics_mg use material_header, only: Material, materials use math, only: rotate_angle use mesh_header, only: meshes - use mgxs_header + use mgxs_interface use message_passing use nuclide_header, only: material_xs use particle_header, only: Particle use physics_common use random_lcg, only: prn - use scattdata_header use settings use simulation_header use string, only: to_str @@ -143,16 +142,12 @@ contains type(Particle), intent(inout) :: p - call macro_xs(p % material) % obj % sample_scatter(p % coord(1) % uvw, & - p % last_g, p % g, & - p % mu, p % wgt) + call sample_scatter_c(p % material, p % last_g, p % g, p % mu, & + p % wgt, p % coord(1) % uvw) ! Update energy value for downstream compatability (in tallying) p % E = energy_bin_avg(p % g) - ! Convert change in angle (mu) to new direction - p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, p % mu) - ! Set event component p % event = EVENT_SCATTER @@ -178,10 +173,6 @@ contains real(8) :: mu ! fission neutron angular cosine real(8) :: phi ! fission neutron azimuthal angle real(8) :: weight ! weight adjustment for ufs method - class(Mgxs), pointer :: xs - - ! Get Pointers - xs => macro_xs(p % material) % obj ! TODO: Heat generation from fission @@ -261,7 +252,7 @@ contains ! Sample secondary energy distribution for fission reaction and set energy ! in fission bank - call xs % sample_fission_energy(p % g, bank_array(i) % uvw, dg, gout) + call sample_fission_energy_c(p % material, p % g, dg, gout) bank_array(i) % E = real(gout, 8) bank_array(i) % delayed_group = dg diff --git a/src/scattdata.cpp b/src/scattdata.cpp new file mode 100644 index 0000000000..a316eba46d --- /dev/null +++ b/src/scattdata.cpp @@ -0,0 +1,936 @@ +#include +#include +#include + +#include "constants.h" +#include "math_functions.h" +#include "random_lcg.h" +#include "error.h" +#include "scattdata.h" + +namespace openmc { + +//============================================================================== +// ScattData base-class methods +//============================================================================== + +void +ScattData::base_init(int order, const int_1dvec& in_gmin, + const int_1dvec& in_gmax, const double_2dvec& in_energy, + const double_2dvec& in_mult) +{ + int groups = in_energy.size(); + + gmin = in_gmin; + gmax = in_gmax; + energy.resize(groups); + mult.resize(groups); + dist.resize(groups); + + for (int gin = 0; gin < groups; gin++) { + // Store the inputted data + energy[gin] = in_energy[gin]; + mult[gin] = in_mult[gin]; + + // Make sure the energy is normalized + double norm = std::accumulate(energy[gin].begin(), energy[gin].end(), 0.); + + if (norm != 0.) { + for (auto& n : energy[gin]) n /= norm; + } + + // Initialize the distribution data + dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1); + for (auto& v : dist[gin]) { + v.resize(order); + } + } +} + +//============================================================================== + +void +ScattData::base_combine(int max_order, + const std::vector& those_scatts, const double_1dvec& scalars, + int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& sparse_mult, + double_3dvec& sparse_scatter) +{ + int groups = those_scatts[0] -> energy.size(); + + // Now allocate and zero our storage spaces + double_3dvec this_matrix = double_3dvec(groups, double_2dvec(groups, + double_1dvec(max_order, 0.))); + double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); + double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); + + // Build the dense scattering and multiplicity matrices + // Get the multiplicity_matrix + // To combine from nuclidic data we need to use the final relationship + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // Developed as follows: + // mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // nuscatt_{i,g,g'} can be reconstructed from the energy and scattxs member + // variables + for (int i = 0; i < those_scatts.size(); i++) { + ScattData* that = those_scatts[i]; + + // Build the dense matrix for that object + double_3dvec that_matrix = that->get_matrix(max_order); + + // Now add that to this for the scattering and multiplicity + for (int gin = 0; gin < groups; gin++) { + // Only spend time adding that's gmin to gmax data since the rest will + // be zeros + int i_gout = 0; + for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { + // Do the scattering matrix + for (int l = 0; l < max_order; l++) { + this_matrix[gin][gout][l] += scalars[i] * that_matrix[gin][gout][l]; + } + + // Incorporate that's contribution to the multiplicity matrix data + double nuscatt = that->scattxs[gin] * that->energy[gin][i_gout]; + mult_numer[gin][gout] += scalars[i] * nuscatt; + if (that->mult[gin][i_gout] > 0.) { + mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][i_gout]; + } else { + mult_denom[gin][gout] += scalars[i]; + } + i_gout++; + } + } + } + + // Combine mult_numer and mult_denom into the combined multiplicity matrix + double_2dvec this_mult(groups, double_1dvec(groups, 1.)); + for (int gin = 0; gin < groups; gin++) { + for (int gout = 0; gout < groups; gout++) { + if (mult_denom[gin][gout] > 0.) { + this_mult[gin][gout] = mult_numer[gin][gout] / mult_denom[gin][gout]; + } + } + } + mult_numer.clear(); + mult_denom.clear(); + + // We have the data, now we need to convert to a jagged array and then use + // the initialize function to store it on the object. + for (int gin = 0; gin < groups; gin++) { + // Find the minimum and maximum group boundaries + int gmin_; + for (gmin_ = 0; gmin_ < groups; gmin_++) { + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmin_].size(); l++) { + if (this_matrix[gin][gmin_][l] != 0.) { + non_zero = true; + break; + } + } + if (non_zero) break; + } + int gmax_; + for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmax_].size(); l++) { + if (this_matrix[gin][gmax_][l] != 0.) { + non_zero = true; + break; + } + } + if (non_zero) break; + } + + // treat the case of all values being 0 + if (gmin_ > gmax_) { + gmin_ = gin; + gmax_ = gin; + } + + // Store the group bounds + in_gmin[gin] = gmin_; + in_gmax[gin] = gmax_; + + // Store the data in the compressed format + sparse_scatter[gin].resize(gmax_ - gmin_ + 1); + sparse_mult[gin].resize(gmax_ - gmin_ + 1); + int i_gout = 0; + for (int gout = gmin_; gout <= gmax_; gout++) { + sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; + sparse_mult[gin][i_gout] = this_mult[gin][gout]; + i_gout++; + } + } +} + +//============================================================================== + +void +ScattData::sample_energy(int gin, int& gout, int& i_gout) +{ + // Sample the outgoing group + double xi = prn(); + + i_gout = 0; + gout = gmin[gin]; + double prob = energy[gin][i_gout]; + while((prob < xi) && (gout < gmax[gin])) { + gout++; + i_gout++; + prob += energy[gin][i_gout]; + } +} + +//============================================================================== + +double +ScattData::get_xs(int xstype, int gin, const int* gout, const double* mu) +{ + // Set the outgoing group offset index as needed + int i_gout = 0; + if (gout != nullptr) { + // short circuit the function if gout is from a zero portion of the + // scattering matrix + if ((*gout < gmin[gin]) || (*gout > gmax[gin])) { // > gmax? + return 0.; + } + i_gout = *gout - gmin[gin]; + } + + double val = scattxs[gin]; + switch(xstype) { + case MG_GET_XS_SCATTER: + if (gout != nullptr) val *= energy[gin][i_gout]; + break; + case MG_GET_XS_SCATTER_MULT: + if (gout != nullptr) { + val *= energy[gin][i_gout] / mult[gin][i_gout]; + } else { + val /= std::inner_product(mult[gin].begin(), mult[gin].end(), + energy[gin].begin(), 0.0); + } + break; + case MG_GET_XS_SCATTER_FMU_MULT: + if ((gout != nullptr) && (mu != nullptr)) { + val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu); + } else { + // This is not an expected path (asking for f_mu without asking for a + // group or mu is not useful + fatal_error("Invalid call to get_xs"); + } + break; + case MG_GET_XS_SCATTER_FMU: + if ((gout != nullptr) && (mu != nullptr)) { + val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu) / mult[gin][i_gout]; + } else { + // This is not an expected path (asking for f_mu without asking for a + // group or mu is not useful + fatal_error("Invalid call to get_xs"); + } + break; + } + return val; +} + +//============================================================================== +// ScattDataLegendre methods +//============================================================================== + +void +ScattDataLegendre::init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs) +{ + int groups = coeffs.size(); + int order = coeffs[0][0].size(); + + // make a copy of coeffs that we can use to both extract data and normalize + double_3dvec matrix = coeffs; + + // Get the scattering cross section value by summing the un-normalized P0 + // coefficient in the variable matrix over all outgoing groups. + scattxs.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = in_gmax[gin] - in_gmin[gin] + 1; + scattxs[gin] = 0.; + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + scattxs[gin] += matrix[gin][i_gout][0]; + } + } + + // Build the energy transfer matrix from data in the variable matrix while + // also normalizing the variable matrix itself + // (forcing the CDF of f(mu=1) == 1) + double_2dvec in_energy; + in_energy.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = in_gmax[gin] - in_gmin[gin] + 1; + in_energy[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + double norm = matrix[gin][i_gout][0]; + in_energy[gin][i_gout] = norm; + if (norm != 0.) { + for (auto& n : matrix[gin][i_gout]) n /= norm; + } + } + } + + // Initialize the base class attributes + ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult); + + // Set the distribution (sdata.dist) values and initialize max_val + max_val.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + dist[gin][i_gout] = matrix[gin][i_gout]; + } + max_val[gin].resize(num_groups); + for (auto& n : max_val[gin]) n = 0.; + } + + // Now update the maximum value + update_max_val(); +} + +//============================================================================== + +void +ScattDataLegendre::update_max_val() +{ + int groups = max_val.size(); + // Step through the polynomial with fixed number of points to identify the + // maximal value + int Nmu = 1001; + double dmu = 2. / (Nmu - 1); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + for (int imu = 0; imu < Nmu; imu++) { + double mu; + if (imu == 0) { + mu = -1.; + } else if (imu == (Nmu - 1)) { + mu = 1.; + } else { + mu = -1. + (imu - 1) * dmu; + } + + // Calculate probability + double f = evaluate_legendre_c(dist[gin][i_gout].size() - 1, + dist[gin][i_gout].data(), mu); + + // if this is a new maximum, store it + if (f > max_val[gin][i_gout]) max_val[gin][i_gout] = f; + } // end imu loop + + // Since we may not have caught the true max, add 10% margin + max_val[gin][i_gout] *= 1.1; + } + } +} + +//============================================================================== + +double +ScattDataLegendre::calc_f(int gin, int gout, double mu) +{ + double f; + if ((gout < gmin[gin]) || (gout > gmax[gin])) { + f = 0.; + } else { + int i_gout = gout - gmin[gin]; + f = evaluate_legendre_c(dist[gin][i_gout].size() - 1, + dist[gin][i_gout].data(), mu); + } + return f; +} + +//============================================================================== + +void +ScattDataLegendre::sample(int gin, int& gout, double& mu, double& wgt) +{ + // Sample the outgoing energy using the base-class method + int i_gout; + sample_energy(gin, gout, i_gout); + + // Now we can sample mu using the scattering kernel using rejection + // sampling from a rectangular bounding box + double M = max_val[gin][i_gout]; + int samples = 0; + + while(true) { + mu = 2. * prn() - 1.; + double f = calc_f(gin, gout, mu); + if (f > 0.) { + double u = prn() * M; + if (u <= f) break; + } + samples++; + if (samples > MAX_SAMPLE) { + fatal_error("Maximum number of Legendre expansion samples reached"); + } + }; + + // Update the weight to reflect neutron multiplicity + wgt *= mult[gin][i_gout]; +} + +//============================================================================== + +void +ScattDataLegendre::combine(const std::vector& those_scatts, + const double_1dvec& scalars) +{ + // Find the max order in the data set and make sure we can combine the sets + int max_order = 0; + for (int i = 0; i < those_scatts.size(); i++) { + // Lets also make sure these items are combineable + ScattDataLegendre* that = dynamic_cast(those_scatts[i]); + if (!that) { + fatal_error("Cannot combine the ScattData objects!"); + } + int that_order = that->get_order(); + if (that_order > max_order) max_order = that_order; + } + max_order++; // Add one since this is a Legendre + + int groups = those_scatts[0] -> energy.size(); + + int_1dvec in_gmin(groups); + int_1dvec in_gmax(groups); + double_3dvec sparse_scatter(groups); + double_2dvec sparse_mult(groups); + + // The rest of the steps do not depend on the type of angular representation + // so we use a base class method to sum up xs and create new energy and mult + // matrices + ScattData::base_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + sparse_mult, sparse_scatter); + + // Got everything we need, store it. + init(in_gmin, in_gmax, sparse_mult, sparse_scatter); +} + +//============================================================================== + +double_3dvec +ScattDataLegendre::get_matrix(int max_order) +{ + // Get the sizes and initialize the data to 0 + int groups = energy.size(); + int order_dim = max_order + 1; + double_3dvec matrix = double_3dvec(groups, double_2dvec(groups, + double_1dvec(order_dim, 0.))); + + for (int gin = 0; gin < groups; gin++) { + for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) { + int gout = i_gout + gmin[gin]; + for (int l = 0; l < order_dim; l++) { + matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * + dist[gin][i_gout][l]; + } + } + } + return matrix; +} + +//============================================================================== +// ScattDataHistogram methods +//============================================================================== + +void +ScattDataHistogram::init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs) +{ + int groups = coeffs.size(); + int order = coeffs[0][0].size(); + + // make a copy of coeffs that we can use to both extract data and normalize + double_3dvec matrix = coeffs; + + // Get the scattering cross section value by summing the distribution + // over all the histogram bins in angle and outgoing energy groups + scattxs.resize(groups); + for (int gin = 0; gin < groups; gin++) { + scattxs[gin] = 0.; + for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) { + scattxs[gin] += std::accumulate(matrix[gin][i_gout].begin(), + matrix[gin][i_gout].end(), 0.); + } + } + + // Build the energy transfer matrix from data in the variable matrix + double_2dvec in_energy; + in_energy.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = in_gmax[gin] - in_gmin[gin] + 1; + in_energy[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + double norm = std::accumulate(matrix[gin][i_gout].begin(), + matrix[gin][i_gout].end(), 0.); + in_energy[gin][i_gout] = norm; + if (norm != 0.) { + for (auto& n : matrix[gin][i_gout]) n /= norm; + } + } + } + + // Initialize the base class attributes + ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult); + + // Build the angular distribution mu values + mu = double_1dvec(order); + dmu = 2. / order; + mu[0] = -1.; + for (int imu = 1; imu < order; imu++) { + mu[imu] = -1. + imu * dmu; + } + + // Calculate f(mu) and integrate it so we can avoid rejection sampling + fmu.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + fmu[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + fmu[gin][i_gout].resize(order); + // The variable matrix contains f(mu); so directly assign it + fmu[gin][i_gout] = matrix[gin][i_gout]; + + // Integrate the histogram + dist[gin][i_gout][0] = dmu * matrix[gin][i_gout][0]; + for (int imu = 1; imu < order; imu++) { + dist[gin][i_gout][imu] = dmu * matrix[gin][i_gout][imu] + + dist[gin][i_gout][imu - 1]; + } + + // Now re-normalize for integral to unity + double norm = dist[gin][i_gout][order - 1]; + if (norm > 0.) { + for (int imu = 0; imu < order; imu++) { + fmu[gin][i_gout][imu] /= norm; + dist[gin][i_gout][imu] /= norm; + } + } + } + } +} + +//============================================================================== + +double +ScattDataHistogram::calc_f(int gin, int gout, double mu) +{ + double f; + if ((gout < gmin[gin]) || (gout > gmax[gin])) { + f = 0.; + } else { + // Find mu bin + int i_gout = gout - gmin[gin]; + int imu; + if (mu == 1.) { + // use size -2 to have the index one before the end + imu = this->mu.size() - 2; + } else { + imu = std::floor((mu + 1.) / dmu + 1.) - 1; + } + + f = fmu[gin][i_gout][imu]; + } + return f; +} + +//============================================================================== + +void +ScattDataHistogram::sample(int gin, int& gout, double& mu, double& wgt) +{ + // Sample the outgoing energy using the base-class method + int i_gout; + sample_energy(gin, gout, i_gout); + + // Determine the outgoing cosine bin + double xi = prn(); + + int imu; + if (xi < dist[gin][i_gout][0]) { + imu = 0; + } else { + // TODO lower_bound? + 1? + imu = std::upper_bound(dist[gin][i_gout].begin(), + dist[gin][i_gout].end(), xi) - + dist[gin][i_gout].begin(); + } + + // Randomly select mu within the imu bin + mu = prn() * dmu + this->mu[imu]; + + if (mu < -1.) { + mu = -1.; + } else if (mu > 1.) { + mu = 1.; + } + + // Update the weight to reflect neutron multiplicity + wgt *= mult[gin][i_gout]; +} + +//============================================================================== + +double_3dvec +ScattDataHistogram::get_matrix(int max_order) +{ + // Get the sizes and initialize the data to 0 + int groups = energy.size(); + // We ignore the requested order for Histogram and Tabular representations + int order_dim = get_order(); + double_3dvec matrix = double_3dvec(groups, double_2dvec(groups, + double_1dvec(order_dim, 0.))); + + for (int gin = 0; gin < groups; gin++) { + for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) { + int gout = i_gout + gmin[gin]; + for (int l = 0; l < order_dim; l++) { + matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * + fmu[gin][i_gout][l]; + } + } + } + return matrix; +} + +//============================================================================== + +void +ScattDataHistogram::combine(const std::vector& those_scatts, + const double_1dvec& scalars) +{ + // Find the max order in the data set and make sure we can combine the sets + int max_order = those_scatts[0]->get_order(); + for (int i = 0; i < those_scatts.size(); i++) { + // Lets also make sure these items are combineable + ScattDataHistogram* that = dynamic_cast(those_scatts[i]); + if (!that) { + fatal_error("Cannot combine the ScattData objects!"); + } + if (max_order != that->get_order()) { + fatal_error("Cannot combine the ScattData objects!"); + } + } + + int groups = those_scatts[0] -> energy.size(); + + int_1dvec in_gmin(groups); + int_1dvec in_gmax(groups); + double_3dvec sparse_scatter(groups); + double_2dvec sparse_mult(groups); + + // The rest of the steps do not depend on the type of angular representation + // so we use a base class method to sum up xs and create new energy and mult + // matrices + ScattData::base_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + sparse_mult, sparse_scatter); + + // Got everything we need, store it. + init(in_gmin, in_gmax, sparse_mult, sparse_scatter); +} + +//============================================================================== +// ScattDataTabular methods +//============================================================================== + +void +ScattDataTabular::init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs) +{ + int groups = coeffs.size(); + int order = coeffs[0][0].size(); + + // make a copy of coeffs that we can use to both extract data and normalize + double_3dvec matrix = coeffs; + + // Build the angular distribution mu values + mu = double_1dvec(order); + dmu = 2. / (order - 1); + mu[0] = -1.; + for (int imu = 1; imu < order - 1; imu++) { + mu[imu] = -1. + imu * dmu; + } + mu[order - 1] = 1.; + + // Get the scattering cross section value by integrating the distribution + // over all mu points and then combining over all outgoing groups + scattxs.resize(groups); + for (int gin = 0; gin < groups; gin++) { + scattxs[gin] = 0.; + for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) { + for (int imu = 1; imu < order; imu++) { + scattxs[gin] += 0.5 * dmu * (matrix[gin][i_gout][imu - 1] + + matrix[gin][i_gout][imu]); + } + } + } + + // Build the energy transfer matrix from data in the variable matrix + double_2dvec in_energy(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = in_gmax[gin] - in_gmin[gin] + 1; + in_energy[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + double norm = 0.; + for (int imu = 1; imu < order; imu++) { + norm += 0.5 * dmu * (matrix[gin][i_gout][imu - 1] + + matrix[gin][i_gout][imu]); + } + in_energy[gin][i_gout] = norm; + } + } + + // Initialize the base class attributes + ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult); + + // Calculate f(mu) and integrate it so we can avoid rejection sampling + fmu.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + fmu[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + fmu[gin][i_gout].resize(order); + // The variable matrix contains f(mu); so directly assign it + fmu[gin][i_gout] = matrix[gin][i_gout]; + + // Ensure positivity + for (auto& val : fmu[gin][i_gout]) { + if (val < 0.) val = 0.; + } + + // Now re-normalize for numerical integration issues and to take care of + // the above negative fix-up. Also accrue the CDF + double norm = 0.; + for (int imu = 1; imu < order; imu++) { + norm += 0.5 * dmu * (fmu[gin][i_gout][imu - 1] + + fmu[gin][i_gout][imu]); + // incorporate to the CDF + dist[gin][i_gout][imu] = norm; + } + + // now do the normalization + if (norm > 0.) { + for (int imu = 0; imu < order; imu++) { + fmu[gin][i_gout][imu] /= norm; + dist[gin][i_gout][imu] /= norm; + } + } + } + } +} + +//============================================================================== + +double +ScattDataTabular::calc_f(int gin, int gout, double mu) +{ + double f; + if ((gout < gmin[gin]) || (gout > gmax[gin])) { + f = 0.; + } else { + // Find mu bin + int i_gout = gout - gmin[gin]; + int imu; + if (mu == 1.) { + // use size -2 to have the index one before the end + imu = this->mu.size() - 2; + } else { + imu = std::floor((mu + 1.) / dmu + 1.) - 1; + } + + double r = (mu - this->mu[imu]) / (this->mu[imu + 1] - this->mu[imu]); + f = (1. - r) * fmu[gin][i_gout][imu] + r * fmu[gin][i_gout][imu + 1]; + } + return f; +} + +//============================================================================== + +void +ScattDataTabular::sample(int gin, int& gout, double& mu, double& wgt) +{ + // Sample the outgoing energy using the base-class method + int i_gout; + sample_energy(gin, gout, i_gout); + + // Determine the outgoing cosine bin + int NP = this->mu.size(); + double xi = prn(); + + double c_k = dist[gin][i_gout][0]; + int k; + for (k = 0; k < NP - 1; k++) { + double c_k1 = dist[gin][i_gout][k + 1]; + if (xi < c_k1) break; + c_k = c_k1; + } + + // Check to make sure k is <= NP - 1 + k = std::min(k, NP - 2); + + // Find the pdf values we want + double p0 = fmu[gin][i_gout][k]; + double mu0 = this -> mu[k]; + double p1 = fmu[gin][i_gout][k + 1]; + double mu1 = this -> mu[k + 1]; + + if (p0 == p1) { + mu = mu0 + (xi - c_k) / p0; + } else { + double frac = (p1 - p0) / (mu1 - mu0); + mu = mu0 + (std::sqrt(std::max(0., p0 * p0 + 2. * frac * (xi - c_k))) + - p0) / frac; + } + + if (mu < -1.) { + mu = -1.; + } else if (mu > 1.) { + mu = 1.; + } + + // Update the weight to reflect neutron multiplicity + wgt *= mult[gin][i_gout]; +} + +//============================================================================== + +double_3dvec +ScattDataTabular::get_matrix(int max_order) +{ + // Get the sizes and initialize the data to 0 + int groups = energy.size(); + // We ignore the requested order for Histogram and Tabular representations + int order_dim = get_order(); + double_3dvec matrix = double_3dvec(groups, double_2dvec(groups, + double_1dvec(order_dim, 0.))); + + for (int gin = 0; gin < groups; gin++) { + for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) { + int gout = i_gout + gmin[gin]; + for (int l = 0; l < order_dim; l++) { + matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * + fmu[gin][i_gout][l]; + } + } + } + return matrix; +} + +//============================================================================== + +void +ScattDataTabular::combine(const std::vector& those_scatts, + const double_1dvec& scalars) +{ + // Find the max order in the data set and make sure we can combine the sets + int max_order = those_scatts[0]->get_order(); + for (int i = 0; i < those_scatts.size(); i++) { + // Lets also make sure these items are combineable + ScattDataTabular* that = dynamic_cast(those_scatts[i]); + if (!that) { + fatal_error("Cannot combine the ScattData objects!"); + } + if (max_order != that->get_order()) { + fatal_error("Cannot combine the ScattData objects!"); + } + } + + int groups = those_scatts[0] -> energy.size(); + + int_1dvec in_gmin(groups); + int_1dvec in_gmax(groups); + double_3dvec sparse_scatter(groups); + double_2dvec sparse_mult(groups); + + // The rest of the steps do not depend on the type of angular representation + // so we use a base class method to sum up xs and create new energy and mult + // matrices + ScattData::base_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + sparse_mult, sparse_scatter); + + // Got everything we need, store it. + init(in_gmin, in_gmax, sparse_mult, sparse_scatter); +} + +//============================================================================== +// module-level methods +//============================================================================== + +void +convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, + int n_mu) +{ + // See if the user wants us to figure out how many points to use + 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 + if (leg.get_order() == 0) { + n_mu = 2; + } else { + n_mu = DEFAULT_NMU; + } + } + + tab.base_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult); + tab.scattxs = leg.scattxs; + + // Build mu and dmu + tab.mu = double_1dvec(n_mu); + tab.dmu = 2. / (n_mu - 1); + tab.mu[0] = -1.; + for (int imu = 1; imu < n_mu - 1; imu++) { + tab.mu[imu] = -1. + imu * tab.dmu; + } + tab.mu[n_mu - 1] = 1.; + + // Calculate f(mu) and integrate it so we can avoid rejection sampling + int groups = tab.energy.size(); + tab.fmu.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = tab.gmax[gin] - tab.gmin[gin] + 1; + tab.fmu[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + tab.fmu[gin][i_gout].resize(n_mu); + for (int imu = 0; imu < n_mu; imu++) { + tab.fmu[gin][i_gout][imu] = + evaluate_legendre_c(leg.dist[gin][i_gout].size() - 1, + leg.dist[gin][i_gout].data(), tab.mu[imu]); + } + + // Ensure positivity + for (auto& val : tab.fmu[gin][i_gout]) { + if (val < 0.) val = 0.; + } + + // Now re-normalize for numerical integration issues and to take care of + // the above negative fix-up. Also accrue the CDF + double norm = 0.; + tab.dist[gin][i_gout][0] = 0.; + for (int imu = 1; imu < n_mu; imu++) { + norm += 0.5 * tab.dmu * (tab.fmu[gin][i_gout][imu - 1] + + tab.fmu[gin][i_gout][imu]); + // incorporate to the CDF + tab.dist[gin][i_gout][imu] = norm; + } + + // now do the normalization + if (norm > 0.) { + for (int imu = 0; imu < n_mu; imu++) { + tab.fmu[gin][i_gout][imu] /= norm; + tab.dist[gin][i_gout][imu] /= norm; + } + } + } + } +} + +} // namespace openmc diff --git a/src/scattdata.h b/src/scattdata.h new file mode 100644 index 0000000000..72ebaf6771 --- /dev/null +++ b/src/scattdata.h @@ -0,0 +1,258 @@ +//! \file scattdata.h +//! A collection of multi-group scattering data classes + +#ifndef SCATTDATA_H +#define SCATTDATA_H + +#include + +namespace openmc { + +// forward declarations so we can name our friend functions +class ScattDataLegendre; +class ScattDataTabular; + +//============================================================================== +// SCATTDATA contains all the data needed to describe the scattering energy and +// angular distribution data +//============================================================================== + +class ScattData { + protected: + //! \brief Initializes the attributes of the base class. + void + base_init(int order, const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_energy, const double_2dvec& in_mult); + + //! \brief Combines microscopic ScattDatas into a macroscopic one. + void + base_combine(int max_order, const std::vector& those_scatts, + const double_1dvec& scalars, int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& sparse_mult, double_3dvec& sparse_scatter); + + public: + + double_2dvec energy; // Normalized p0 matrix for sampling Eout + double_2dvec mult; // nu-scatter multiplication (nu-scatt/scatt) + double_3dvec dist; // Angular distribution + int_1dvec gmin; // minimum outgoing group + int_1dvec gmax; // maximum outgoing group + double_1dvec scattxs; // Isotropic Sigma_{s,g_{in}} + + //! \brief Calculates the value of normalized f(mu). + //! + //! The value of f(mu) is normalized as in the integral of f(mu)dmu across + //! [-1,1] is 1. + //! + //! @param gin Incoming energy group of interest. + //! @param gout Outgoing energy group of interest. + //! @param mu Cosine of the change-in-angle of interest. + //! @return The value of f(mu). + virtual double + calc_f(int gin, int gout, double mu) = 0; + + //! \brief Samples the outgoing energy and angle from the ScattData info. + //! + //! @param gin Incoming energy group. + //! @param gout Sampled outgoing energy group. + //! @param mu Sampled cosine of the change-in-angle. + //! @param wgt Weight of the particle to be adjusted. + virtual void + sample(int gin, int& gout, double& mu, double& wgt) = 0; + + //! \brief Initializes the ScattData object from a given scatter and + //! multiplicity matrix. + //! + //! @param in_gmin List of minimum outgoing groups for every incoming group + //! @param in_gmax List of maximum outgoing groups for every incoming group + //! @param in_mult Input sparse multiplicity matrix + //! @param coeffs Input sparse scattering matrix + virtual void + init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs) = 0; + + //! \brief Combines the microscopic data. + //! + //! @param those_scatts Microscopic objects to combine. + //! @param scalars Scalars to multiply the microscopic data by. + virtual void + combine(const std::vector& those_scatts, + const double_1dvec& scalars) = 0; + + //! \brief Getter for the dimensionality of the scattering order. + //! + //! If Legendre this is the "n" in "Pn"; for Tabular, this is the number + //! of points, and for Histogram this is the number of bins. + //! + //! @return The order. + virtual int + get_order() = 0; + + //! \brief Builds a dense scattering matrix from the constituent parts + //! + //! @param max_order If Legendre this is the maximum value of "n" in "Pn" + //! requested; ignored otherwise. + //! @return The dense scattering matrix. + virtual double_3dvec + get_matrix(int max_order) = 0; + + //! \brief Samples the outgoing energy from the ScattData info. + //! + //! @param gin Incoming energy group. + //! @param gout Sampled outgoing energy group. + //! @param i_gout Sampled outgoing energy group index. + void + sample_energy(int gin, int& gout, int& i_gout); + + //! \brief Provides a cross section value given certain parameters + //! + //! @param xstype Type of cross section requested, according to the + //! enumerated constants. + //! @param gin Incoming energy group. + //! @param gout Outgoing energy group; use nullptr if irrelevant, or if a + //! sum is requested. + //! @param mu Cosine of the change-in-angle, for scattering quantities; + //! use nullptr if irrelevant. + //! @return Requested cross section value. + double + get_xs(int xstype, int gin, const int* gout, const double* mu); +}; + +//============================================================================== +// ScattDataLegendre represents the angular distributions as Legendre kernels +//============================================================================== + +class ScattDataLegendre: public ScattData { + + protected: + + // Maximal value for rejection sampling from a rectangle + double_2dvec max_val; + + // 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); + + public: + + void + init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs); + + void + combine(const std::vector& those_scatts, + const double_1dvec& scalars); + + //! \brief Find the maximal value of the angular distribution to use as a + // bounding box with rejection sampling. + void + update_max_val(); + + double + calc_f(int gin, int gout, double mu); + + void + sample(int gin, int& gout, double& mu, double& wgt); + + int + get_order() {return dist[0][0].size() - 1;}; + + double_3dvec + get_matrix(int max_order); +}; + +//============================================================================== +// ScattDataHistogram represents the angular distributions as a histogram, as it +// would be if it came from a "mu" tally in OpenMC +//============================================================================== + +class ScattDataHistogram: public ScattData { + + protected: + + double_1dvec mu; // Angle distribution mu bin boundaries + double dmu; // Quick storage of the spacing between the mu bin points + double_3dvec fmu; // The angular distribution histogram + + public: + + void + init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs); + + void + combine(const std::vector& those_scatts, + const double_1dvec& scalars); + + double + calc_f(int gin, int gout, double mu); + + void + sample(int gin, int& gout, double& mu, double& wgt); + + int + get_order() {return dist[0][0].size();}; + + double_3dvec + get_matrix(int max_order); +}; + +//============================================================================== +// ScattDataTabular represents the angular distributions as a table of mu and +// f(mu) +//============================================================================== + +class ScattDataTabular: public ScattData { + + protected: + + double_1dvec mu; // Angle distribution mu grid points + double dmu; // Quick storage of the spacing between the mu points + double_3dvec fmu; // The angular distribution function + + // 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); + + public: + + void + init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs); + + void + combine(const std::vector& those_scatts, + const double_1dvec& scalars); + + double + calc_f(int gin, int gout, double mu); + + void + sample(int gin, int& gout, double& mu, double& wgt); + + int + get_order() {return dist[0][0].size();}; + + double_3dvec get_matrix(int max_order); +}; + +//============================================================================== +// Function to convert Legendre functions to tabular +//============================================================================== + +//! \brief Converts a ScattDatalegendre to a ScattDataHistogram +//! +//! @param leg The initial ScattDataLegendre object. +//! @param leg The resultant ScattDataTabular object. +//! @param n_mu The number of mu points to use when building the +//! ScattDataTabular object. +void +convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, + int n_mu); + +} // namespace openmc +#endif // SCATTDATA_H \ No newline at end of file diff --git a/src/scattdata_header.F90 b/src/scattdata_header.F90 deleted file mode 100644 index 511e2a2376..0000000000 --- a/src/scattdata_header.F90 +++ /dev/null @@ -1,852 +0,0 @@ -module scattdata_header - - use algorithm, only: binary_search - use constants - use error, only: fatal_error - use math - use random_lcg, only: prn - - implicit none - - -!=============================================================================== -! JAGGED1D and JAGGED2D is a type which allows for jagged 1-D or 2-D array. -!=============================================================================== - - type :: Jagged2D - real(8), allocatable :: data(:, :) - end type Jagged2D - - type :: Jagged1D - real(8), allocatable :: data(:) - end type Jagged1D - -!=============================================================================== -! SCATTDATA contains all the data to describe the scattering energy and -! angular distribution -!=============================================================================== - - type, abstract :: ScattData - ! The data attribute of the energy, mult, and dist arrays - ! are not necessarily 1-indexed as they instead will be allocated - ! from a minimum outgoing group to an outgoing minimum group. - ! Normalized p0 matrix on its own for sampling energy - type(Jagged1D), allocatable :: energy(:) ! (Gin % data(Gout)) - ! Nu-scatter multiplication (i.e. nu-scatt/scatt) - type(Jagged1D), allocatable :: mult(:) ! (Gin % data(Gout)) - ! Angular distribution - type(Jagged2D), allocatable :: dist(:) ! (Gin % data(Order/Nmu, Gout) - integer, allocatable :: gmin(:) ! Minimum outgoing group - integer, allocatable :: gmax(:) ! Maximum outgoing group - real(8), allocatable :: scattxs(:) ! Isotropic Sigma_{s,g_{in}} - - contains - procedure(scattdata_init_), deferred :: init ! Initializes ScattData - procedure(scattdata_calc_f_), deferred :: calc_f ! Calculates f, given mu - procedure(scattdata_sample_), deferred :: sample ! sample the scatter event - procedure :: get_matrix => scattdata_get_matrix ! Rebuild scattering matrix - end type ScattData - - abstract interface - subroutine scattdata_init_(this, gmin, gmax, mult, coeffs) - import ScattData, Jagged1D, Jagged2D - class(ScattData), intent(inout) :: this ! Object to work with - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - type(Jagged2D), intent(in) :: coeffs(:) ! Coefficients to use - end subroutine scattdata_init_ - - pure function scattdata_calc_f_(this, gin, gout, mu) result(f) - import ScattData - class(ScattData), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: gin ! Incoming Energy Group - integer, intent(in) :: gout ! Outgoing Energy Group - real(8), intent(in) :: mu ! Angle of interest - real(8) :: f ! Return value of f(mu) - - end function scattdata_calc_f_ - - subroutine scattdata_sample_(this, gin, gout, mu, wgt) - import ScattData - class(ScattData), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - end subroutine scattdata_sample_ - end interface - - type, extends(ScattData) :: ScattDataLegendre - ! Maximal value for rejection sampling from rectangle - type(Jagged1D), allocatable :: max_val(:) ! (Gin % data(Gout)) - contains - procedure :: init => scattdatalegendre_init - procedure :: calc_f => scattdatalegendre_calc_f - procedure :: sample => scattdatalegendre_sample - end type ScattDataLegendre - - type, extends(ScattData) :: ScattDataHistogram - real(8), allocatable :: mu(:) ! Mu bins - real(8) :: dmu ! Mu spacing - ! Histogram of f(mu) (dist has CDF) - type(Jagged2D), allocatable :: fmu(:) ! (Gin % data(Order/Nmu x Gout) - contains - procedure :: init => scattdatahistogram_init - procedure :: calc_f => scattdatahistogram_calc_f - procedure :: sample => scattdatahistogram_sample - procedure :: get_matrix => scattdatahistogram_get_matrix - end type ScattDataHistogram - - type, extends(ScattData) :: ScattDataTabular - real(8), allocatable :: mu(:) ! Mu bins - real(8) :: dmu ! Mu spacing - ! PDF of f(mu) (dist has CDF) - type(Jagged2D), allocatable :: fmu(:) ! (Gin % data(Order/Nmu x Gout) - contains - procedure :: init => scattdatatabular_init - procedure :: calc_f => scattdatatabular_calc_f - procedure :: sample => scattdatatabular_sample - procedure :: get_matrix => scattdatatabular_get_matrix - end type ScattDataTabular - -!=============================================================================== -! SCATTDATACONTAINER allocatable array for storing ScattData Objects (for angle) -!=============================================================================== - - type ScattDataContainer - class(ScattData), allocatable :: obj - end type ScattDataContainer - -contains - -!=============================================================================== -! SCATTDATA*_INIT builds the scattdata object -!=============================================================================== - - subroutine scattdata_init(this, order, gmin, gmax, energy, mult) - class(ScattData), intent(inout) :: this ! Object to work on - integer, intent(in) :: order ! Data Order - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(inout) :: energy(:) ! Energy Transfer Matrix - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - - integer :: groups, gin - real(8) :: norm - - groups = size(energy, dim=1) - - allocate(this % gmin(groups)) - allocate(this % gmax(groups)) - allocate(this % energy(groups)) - allocate(this % mult(groups)) - allocate(this % dist(groups)) - - this % gmin = gmin - this % gmax = gmax - - ! Set the outgoing energy PDF values - do gin = 1, groups - ! Make sure energy is normalized (i.e., CDF is 1) - norm = sum(energy(gin) % data(:)) - if (norm /= ZERO) energy(gin) % data(:) = energy(gin) % data(:) / norm - ! Set the values - allocate(this % energy(gin) % data(gmin(gin):gmax(gin))) - this % energy(gin) % data(:) = energy(gin) % data(:) - allocate(this % mult(gin) % data(gmin(gin):gmax(gin))) - this % mult(gin) % data(gmin(gin):gmax(gin)) = & - mult(gin) % data(gmin(gin):gmax(gin)) - allocate(this % dist(gin) % data(order, gmin(gin):gmax(gin))) - this % dist(gin) % data = ZERO - end do - end subroutine scattdata_init - - subroutine scattdatalegendre_init(this, gmin, gmax, mult, coeffs) - class(ScattDataLegendre), intent(inout) :: this ! Object to work on - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - type(Jagged2D), intent(in) :: coeffs(:) ! Coefficients to use - - real(8) :: dmu, mu, f, norm - integer :: imu, Nmu, gout, gin, groups, order - type(Jagged1D), allocatable :: energy(:) - type(Jagged2D), allocatable :: matrix(:) - - groups = size(coeffs) - order = size(coeffs(1) % data, dim=1) - - ! make a copy of coeffs that we can use to extract data and normalize - allocate(matrix(groups)) - do gin = 1, groups - allocate(matrix(gin) % data(order, gmin(gin):gmax(gin))) - matrix(gin) % data = coeffs(gin) % data - end do - - ! Get scattxs value - allocate(this % scattxs(groups)) - ! Get this by summing the un-normalized P0 coefficient in matrix - ! over all outgoing groups - do gin = 1, groups - this % scattxs(gin) = sum(matrix(gin) % data(1, :), dim=1) - end do - - allocate(energy(groups)) - ! Build energy transfer probability matrix from data in matrix - ! while also normalizing matrix itself (making CDF of f(mu=1)=1) - do gin = 1, groups - allocate(energy(gin) % data(gmin(gin):gmax(gin))) - energy(gin) % data = ZERO - do gout = gmin(gin), gmax(gin) - norm = matrix(gin) % data(1, gout) - energy(gin) % data(gout) = norm - if (norm /= ZERO) then - matrix(gin) % data(:, gout) = matrix(gin) % data(:, gout) / norm - end if - end do - end do - - call scattdata_init(this, order, gmin, gmax, energy, mult) - - allocate(this % max_val(groups)) - ! Set dist values from matrix and initialize max_val - do gin = 1, groups - do gout = gmin(gin), gmax(gin) - this % dist(gin) % data(:, gout) = matrix(gin) % data(:, gout) - end do - allocate(this % max_val(gin) % data(gmin(gin):gmax(gin))) - this % max_val(gin) % data(:) = ZERO - end do - - ! Step through the polynomial with fixed number of points to identify - ! the maximal value. - Nmu = 1001 - dmu = TWO / real(Nmu - 1, 8) - do gin = 1, groups - do gout = gmin(gin), gmax(gin) - do imu = 1, Nmu - ! Update mu. Do first and last seperate to avoid float errors - if (imu == 1) then - mu = -ONE - else if (imu == Nmu) then - mu = ONE - else - mu = -ONE + real(imu - 1, 8) * dmu - end if - ! Calculate probability - f = this % calc_f(gin,gout,mu) - ! If this is a new max, store it. - if (f > this % max_val(gin) % data(gout)) & - this % max_val(gin) % data(gout) = f - end do - ! Finally, since we may not have caught the exact max, add 10% margin - this % max_val(gin) % data(gout) = & - this % max_val(gin) % data(gout) * 1.1_8 - end do - end do - end subroutine scattdatalegendre_init - - subroutine scattdatahistogram_init(this, gmin, gmax, mult, coeffs) - class(ScattDataHistogram), intent(inout) :: this ! Object to work on - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - type(Jagged2D), intent(in) :: coeffs(:) ! Coefficients to use - - integer :: imu, gin, gout, groups, order - real(8) :: norm - type(Jagged1D), allocatable :: energy(:) - type(Jagged2D), allocatable :: matrix(:) - - groups = size(coeffs) - order = size(coeffs(1) % data, dim=1) - - ! make a copy of coeffs that we can use to extract data and normalize - allocate(matrix(groups)) - do gin = 1, groups - allocate(matrix(gin) % data(order, gmin(gin):gmax(gin))) - matrix(gin) % data(:, :) = coeffs(gin) % data(:, :) - end do - - ! Get scattxs value - allocate(this % scattxs(groups)) - ! Get this by summing the un-normalized angular distribution in matrix - ! over all outgoing groups - do gin = 1, groups - this % scattxs(gin) = sum(matrix(gin) % data(:, :)) - end do - - allocate(energy(groups)) - ! Build energy transfer probability matrix from data in matrix - ! while also normalizing matrix itself (making CDF of f(mu=1)=1) - do gin = 1, groups - allocate(energy(gin) % data(gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - norm = sum(matrix(gin) % data(:, gout)) - energy(gin) % data(gout) = norm - if (norm /= ZERO) then - matrix(gin) % data(:, gout) = matrix(gin) % data(:, gout) / norm - end if - end do - end do - - call scattdata_init(this, order, gmin, gmax, energy, mult) - - allocate(this % mu(order)) - this % dmu = TWO / real(order, 8) - this % mu(1) = -ONE - do imu = 2, order - this % mu(imu) = -ONE + real(imu - 1, 8) * this % dmu - end do - - ! Integrate this histogram so we can avoid rejection sampling while - ! also saving the original histogram in fmu - allocate(this % fmu(groups)) - do gin = 1, groups - allocate(this % fmu(gin) % data(order, gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - ! Store the histogram - this % fmu(gin) % data(:, gout) = matrix(gin) % data(:, gout) - ! Integrate the histogram - this % dist(gin) % data(1, gout) = & - this % dmu * matrix(gin) % data(1, gout) - do imu = 2, order - this % dist(gin) % data(imu, gout) = & - this % dmu * matrix(gin) % data(imu, gout) + & - this % dist(gin) % data(imu - 1, gout) - end do - - ! Normalize the integral to unity - norm = this % dist(gin) % data(order, gout) - if (norm > ZERO) then - this % fmu(gin) % data(:, gout) = & - this % fmu(gin) % data(:, gout) / norm - this % dist(gin) % data(:, gout) = & - this % dist(gin) % data(:, gout) / norm - end if - end do - end do - - end subroutine scattdatahistogram_init - - subroutine scattdatatabular_init(this, gmin, gmax, mult, coeffs) - class(ScattDataTabular), intent(inout) :: this ! Object to work on - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - type(Jagged2D), intent(in) :: coeffs(:) ! Coefficients to use - - integer :: imu, gin, gout, groups, order - real(8) :: norm - type(Jagged1D), allocatable :: energy(:) - type(Jagged2D), allocatable :: matrix(:) - - groups = size(coeffs) - order = size(coeffs(1) % data, dim=1) - - ! make a copy of coeffs that we can use to extract data and normalize - allocate(matrix(groups)) - do gin = 1, groups - allocate(matrix(gin) % data(order, gmin(gin):gmax(gin))) - matrix(gin) % data = coeffs(gin) % data - end do - - ! Build the angular distribution mu values - allocate(this % mu(order)) - this % dmu = TWO / real(order - 1, 8) - this % mu(1) = -ONE - do imu = 2, order - 1 - this % mu(imu) = -ONE + real(imu - 1, 8) * this % dmu - end do - this % mu(order) = ONE - - ! Get scattxs - allocate(this % scattxs(groups)) - ! Get this by integrating the scattering distribution over all mu points - ! and then combining over all outgoing groups - ! over all outgoing groups - do gin = 1, groups - norm = ZERO - do gout = gmin(gin), gmax(gin) - do imu = 2, order - norm = norm + HALF * this % dmu * & - (matrix(gin) % data(imu - 1, gout) + & - matrix(gin) % data(imu, gout)) - end do - end do - this % scattxs(gin) = norm - end do - - allocate(energy(groups)) - ! Build energy transfer probability matrix from data in matrix - do gin = 1, groups - allocate(energy(gin) % data(gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - norm = ZERO - do imu = 2, order - norm = norm + HALF * this % dmu * & - (matrix(gin) % data(imu - 1, gout) + & - matrix(gin) % data(imu, gout)) - end do - energy(gin) % data(gout) = norm - end do - end do - call scattdata_init(this, order, gmin, gmax, energy, mult) - - ! Calculate f(mu) and integrate it so we can avoid rejection sampling - allocate(this % fmu(groups)) - do gin = 1, groups - allocate(this % fmu(gin) % data(order, gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - ! Coeffs contain f(mu), put in f(mu) as that is where the - ! PDF lives - this % fmu(gin) % data(:, gout) = matrix(gin) % data(:, gout) - - ! Force positivity - do imu = 1, order - if (this % fmu(gin) % data(imu, gout) < ZERO) then - this % fmu(gin) % data(imu, gout) = ZERO - end if - end do - - ! Re-normalize fmu for numerical integration issues and in case - ! the negative fix-up introduced un-normalized data while - ! accruing the CDF - norm = ZERO - do imu = 2, order - norm = norm + HALF * this % dmu * & - (this % fmu(gin) % data(imu - 1, gout) + & - this % fmu(gin) % data(imu, gout)) - this % dist(gin) % data(imu, gout) = norm - end do - if (norm > ZERO) then - this % fmu(gin) % data(:, gout) = & - this % fmu(gin) % data(:, gout) / norm - this % dist(gin) % data(:, gout) = & - this % dist(gin) % data(:, gout) / norm - end if - end do - end do - end subroutine scattdatatabular_init - -!=============================================================================== -! SCATTDATA_*_CALC_F Calculates the value of f given mu (and gin,gout pair) -!=============================================================================== - - pure function scattdatalegendre_calc_f(this, gin, gout, mu) result(f) - class(ScattDataLegendre), intent(in) :: this ! The ScattData to evaluate - integer, intent(in) :: gin ! Incoming Energy Group - integer, intent(in) :: gout ! Outgoing Energy Group - real(8), intent(in) :: mu ! Angle of interest - real(8) :: f ! Return value of f(mu) - - ! Plug mu in to the legendre expansion and go from there - if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then - f = ZERO - else - f = evaluate_legendre(this % dist(gin) % data(:, gout), mu) - end if - - end function scattdatalegendre_calc_f - - pure function scattdatahistogram_calc_f(this, gin, gout, mu) result(f) - class(ScattDataHistogram), intent(in) :: this ! The ScattData to evaluate - integer, intent(in) :: gin ! Incoming Energy Group - integer, intent(in) :: gout ! Outgoing Energy Group - real(8), intent(in) :: mu ! Angle of interest - real(8) :: f ! Return value of f(mu) - - integer :: imu - - if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then - f = ZERO - else - ! Find mu bin - if (mu == ONE) then - imu = size(this % fmu(gin) % data, dim=1) - else - imu = floor((mu + ONE) / this % dmu + ONE) - end if - - f = this % fmu(gin) % data(imu, gout) - end if - - end function scattdatahistogram_calc_f - - pure function scattdatatabular_calc_f(this, gin, gout, mu) result(f) - class(ScattDataTabular), intent(in) :: this ! The ScattData to evaluate - integer, intent(in) :: gin ! Incoming Energy Group - integer, intent(in) :: gout ! Outgoing Energy Group - real(8), intent(in) :: mu ! Angle of interest - real(8) :: f ! Return value of f(mu) - - integer :: imu - real(8) :: r - - if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then - f = ZERO - else - ! Find mu bin - if (mu == ONE) then - imu = size(this % fmu(gin) % data, dim=1) - 1 - else - imu = floor((mu + ONE) / this % dmu + ONE) - end if - - ! Now interpolate to find f(mu) - r = (mu - this % mu(imu)) / (this % mu(imu + 1) - this % mu(imu)) - f = (ONE - r) * this % fmu(gin) % data(imu, gout) + & - r * this % fmu(gin) % data(imu + 1, gout) - end if - - end function scattdatatabular_calc_f - -!=============================================================================== -! SCATTDATA*_SCATTER Samples the outgoing energy and change in angle. -!=============================================================================== - - subroutine scattdatalegendre_sample(this, gin, gout, mu, wgt) - class(ScattDataLegendre), intent(in) :: this ! Scattering object to use - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - real(8) :: xi ! Our random number - real(8) :: prob ! Running probability - real(8) :: u, f, M - integer :: samples - - xi = prn() - gout = this % gmin(gin) - prob = this % energy(gin) % data(gout) - - do while ((prob < xi) .and. (gout < this % gmax(gin))) - gout = gout + 1 - prob = prob + this % energy(gin) % data(gout) - end do - - ! Now we can sample mu using the legendre representation of the scattering - ! kernel in data(1:this % order) - - ! Do with rejection sampling from a rectangular bounding box - ! Set maximal value - M = this % max_val(gin) % data(gout) - samples = 0 - do - mu = TWO * prn() - ONE - f = this % calc_f(gin, gout, mu) - if (f > ZERO) then - u = prn() * M - if (u <= f) then - exit - end if - end if - samples = samples + 1 - if (samples > MAX_SAMPLE) then - call fatal_error("Maximum number of Legendre expansion samples reached!") - end if - end do - - wgt = wgt * this % mult(gin) % data(gout) - - end subroutine scattdatalegendre_sample - - subroutine scattdatahistogram_sample(this, gin, gout, mu, wgt) - class(ScattDataHistogram), intent(in) :: this ! Scattering object to use - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - real(8) :: xi ! Our random number - real(8) :: prob ! Running probability - integer :: imu - - xi = prn() - gout = this % gmin(gin) - prob = this % energy(gin) % data(gout) - - do while ((prob < xi) .and. (gout < this % gmax(gin))) - gout = gout + 1 - prob = prob + this % energy(gin) % data(gout) - end do - - xi = prn() - if (xi < this % dist(gin) % data(1, gout)) then - imu = 1 - else - imu = binary_search(this % dist(gin) % data(:, gout), & - size(this % dist(gin) % data(:, gout)), xi) + 1 - end if - - ! Randomly select a mu in this bin. - mu = prn() * this % dmu + this % mu(imu) - - wgt = wgt * this % mult(gin) % data(gout) - - end subroutine scattdatahistogram_sample - - subroutine scattdatatabular_sample(this, gin, gout, mu, wgt) - class(ScattDataTabular), intent(in) :: this ! Scattering object to use - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - real(8) :: xi ! Our random number - real(8) :: prob ! Running probability - real(8) :: mu0, frac, mu1 - real(8) :: c_k, c_k1, p0, p1 - integer :: k, NP - - xi = prn() - gout = this % gmin(gin) - prob = this % energy(gin) % data(gout) - - do while ((prob < xi) .and. (gout < this % gmax(gin))) - gout = gout + 1 - prob = prob + this % energy(gin) % data(gout) - end do - - ! determine outgoing cosine bin - NP = size(this % dist(gin) % data(:, gout)) - xi = prn() - - c_k = this % dist(gin) % data(1, gout) - do k = 1, NP - 1 - c_k1 = this % dist(gin) % data(k + 1, gout) - if (xi < c_k1) exit - c_k = c_k1 - end do - - ! check to make sure k is <= NP - 1 - k = min(k, NP - 1) - - p0 = this % fmu(gin) % data(k, gout) - mu0 = this % mu(k) - ! Linear-linear interpolation to find mu value w/in bin. - p1 = this % fmu(gin) % data(k + 1, gout) - mu1 = this % mu(k + 1) - - if (p0 == p1) then - mu = mu0 + (xi - c_k) / p0 - else - frac = (p1 - p0) / (mu1 - mu0) - mu = mu0 + & - (sqrt(max(ZERO, p0 * p0 + TWO * frac * (xi - c_k))) - p0) / frac - end if - - if (mu <= -ONE) then - mu = -ONE - else if (mu >= ONE) then - mu = ONE - end if - - wgt = wgt * this % mult(gin) % data(gout) - - end subroutine scattdatatabular_sample - -!=============================================================================== -! SCATTDATA*_GET_MATRIX Reproduces the original scattering matrix (densely) -! using ScattData's information of fmu/dist, energy, and scattxs -!=============================================================================== - - subroutine scattdata_get_matrix(this, req_order, matrix) - class(ScattData), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: req_order ! Requested order of matrix - type(Jagged2D), allocatable, intent(inout) :: matrix(:) ! Resultant matrix just built - - integer :: order, groups, gin, gout - - groups = size(this % energy) - ! Set gin and gout for getting the order - order = min(req_order, size(this % dist(1) % data, dim=1)) - - if (allocated(matrix)) deallocate(matrix) - allocate(matrix(groups)) - ! Initialize to 0; this way the zero entries in the dense matrix dont - ! need to be explicitly set, requiring a significant increase in the - ! lines of code. - do gin = 1, groups - allocate(matrix(gin) % data(order, groups)) - do gout = this % gmin(gin), this % gmax(gin) - matrix(gin) % data(:, gout) = this % scattxs(gin) * & - this % energy(gin) % data(gout) * & - this % dist(gin) % data(1:order, gout) - end do - end do - end subroutine scattdata_get_matrix - - subroutine scattdatahistogram_get_matrix(this, req_order, matrix) - class(ScattDataHistogram), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: req_order ! Requested order of matrix - type(Jagged2D), allocatable, intent(inout) :: matrix(:) ! Resultant matrix just built - - integer :: order, groups, gin, gout - - groups = size(this % energy) - order = min(req_order, size(this % dist(1) % data, dim=1)) - - if (allocated(matrix)) deallocate(matrix) - allocate(matrix(groups)) - ! Initialize to 0; this way the zero entries in the dense matrix dont - ! need to be explicitly set, requiring a significant increase in the - ! lines of code. - do gin = 1, groups - allocate(matrix(gin) % data(order, groups)) - do gout = this % gmin(gin), this % gmax(gin) - matrix(gin) % data(:, gout) = this % scattxs(gin) * & - this % energy(gin) % data(gout) * & - this % fmu(gin) % data(1:order, gout) - end do - end do - end subroutine scattdatahistogram_get_matrix - - subroutine scattdatatabular_get_matrix(this, req_order, matrix) - class(ScattDataTabular), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: req_order ! Requested order of matrix - type(Jagged2D), allocatable, intent(inout) :: matrix(:) ! Resultant matrix just built - - integer :: order, groups, gin, gout - - groups = size(this % energy) - order = min(req_order, size(this % dist(1) % data, dim=1)) - - if (allocated(matrix)) deallocate(matrix) - allocate(matrix(groups)) - ! Initialize to 0; this way the zero entries in the dense matrix dont - ! need to be explicitly set, requiring a significant increase in the - ! lines of code. - do gin = 1, groups - allocate(matrix(gin) % data(order, groups)) - do gout = this % gmin(gin), this % gmax(gin) - matrix(gin) % data(:, gout) = this % scattxs(gin) * & - this % energy(gin) % data(gout) * & - this % fmu(gin) % data(1:order, gout) - end do - end do - end subroutine scattdatatabular_get_matrix - -!=============================================================================== -! JAGGED_FROM_DENSE_*D Creates a jagged array from a sparse dense matrix. -! The user can supply a key which indicates the values to remove, but the -! default is ZERO -!=============================================================================== - - subroutine jagged_from_dense_1D(dense, jagged, lo_bounds_, hi_bounds_, key_) - real(8), intent(in) :: dense(:, :) - type(Jagged1D), allocatable, intent(inout) :: jagged(:) - real(8), intent(in), optional :: key_ - integer, intent(inout), allocatable, optional :: lo_bounds_(:) - integer, intent(inout), allocatable, optional :: hi_bounds_(:) - - real(8) :: key - integer :: i, jmin, jmax - integer, allocatable :: lo_bounds(:), hi_bounds(:) - - if (present(key_)) then - key = key_ - else - key = ZERO - end if - - allocate(lo_bounds(size(dense, dim=2))) - allocate(hi_bounds(size(dense, dim=2))) - - if (allocated(jagged)) deallocate(jagged) - allocate(jagged(size(dense, dim=2))) - do i = 1, size(dense, dim=2) - ! Find the min and max j values - do jmin = 1, size(dense, dim=1) - if (dense(jmin, i) /= key) exit - end do - do jmax = size(dense, dim=1), 1, -1 - if (dense(jmax, i) /= key) exit - end do - ! Treat the case of all values matching the key - if (jmin > jmax) then - jmin = i - jmax = i - end if - - ! Now store the jagged row - allocate(jagged(i) % data(jmin:jmax)) - jagged(i) % data(jmin:jmax) = dense(jmin:jmax, i) - - lo_bounds(i) = jmin - hi_bounds(i) = jmax - end do - - if (present(lo_bounds_)) then - if (allocated(lo_bounds_)) deallocate(lo_bounds_) - allocate(lo_bounds_(size(dense, dim=2))) - lo_bounds_ = lo_bounds - end if - if (present(hi_bounds_)) then - if (allocated(hi_bounds_)) deallocate(hi_bounds_) - allocate(hi_bounds_(size(dense, dim=2))) - hi_bounds_ = hi_bounds - end if - - end subroutine jagged_from_dense_1D - - subroutine jagged_from_dense_2D(dense, jagged, lo_bounds_, hi_bounds_, key_) - real(8), intent(in) :: dense(:, :, :) - type(Jagged2D), allocatable, intent(inout) :: jagged(:) - real(8), intent(in), optional :: key_ - integer, intent(inout), allocatable, optional :: lo_bounds_(:) - integer, intent(inout), allocatable, optional :: hi_bounds_(:) - - real(8) :: key - integer :: i, jmin, jmax - integer, allocatable :: lo_bounds(:), hi_bounds(:) - - if (present(key_)) then - key = key_ - else - key = ZERO - end if - - allocate(lo_bounds(size(dense, dim=3))) - allocate(hi_bounds(size(dense, dim=3))) - - if (allocated(jagged)) deallocate(jagged) - allocate(jagged(size(dense, dim=3))) - do i = 1, size(dense, dim=3) - ! Find the min and max j values - do jmin = 1, size(dense, dim=2) - if (any(dense(:, jmin, i) /= key)) exit - end do - do jmax = size(dense, dim=2), 1, -1 - if (any(dense(:, jmax, i) /= key)) exit - end do - ! Treat the case of all values matching the key - if (jmin > jmax) then - jmin = i - jmax = i - end if - - ! Now store the jagged row - allocate(jagged(i) % data(size(dense, dim=1), jmin:jmax)) - jagged(i) % data(:, jmin:jmax) = dense(:, jmin:jmax, i) - - lo_bounds(i) = jmin - hi_bounds(i) = jmax - end do - - if (present(lo_bounds_)) then - if (allocated(lo_bounds_)) deallocate(lo_bounds_) - allocate(lo_bounds_(size(dense, dim=3))) - lo_bounds_ = lo_bounds - end if - if (present(hi_bounds_)) then - if (allocated(hi_bounds_)) deallocate(hi_bounds_) - allocate(hi_bounds_(size(dense, dim=3))) - hi_bounds_ = hi_bounds - end if - - end subroutine jagged_from_dense_2D - -end module scattdata_header diff --git a/src/settings.F90 b/src/settings.F90 index aef1c011be..5994651608 100644 --- a/src/settings.F90 +++ b/src/settings.F90 @@ -18,9 +18,9 @@ module settings logical :: urr_ptables_on = .true. ! Default temperature and method for choosing temperatures - integer :: temperature_method = TEMPERATURE_NEAREST + integer(C_INT) :: temperature_method = TEMPERATURE_NEAREST logical :: temperature_multipole = .false. - real(8) :: temperature_tolerance = 10.0_8 + real(C_DOUBLE) :: temperature_tolerance = 10.0_8 real(8) :: temperature_default = 293.6_8 real(8) :: temperature_range(2) = [ZERO, ZERO] @@ -30,13 +30,16 @@ module settings ! MULTI-GROUP CROSS SECTION RELATED VARIABLES ! Maximum Data Order - integer :: max_order + integer(C_INT) :: max_order ! Whether or not to convert Legendres to tabulars logical :: legendre_to_tabular = .true. ! Number of points to use in the Legendre to tabular conversion - integer :: legendre_to_tabular_points = 33 + integer(C_INT) :: legendre_to_tabular_points = C_NONE + + ! ============================================================================ + ! SIMULATION VARIABLES ! Assume all tallies are spatially distinct logical :: assume_separate = .false. @@ -44,9 +47,6 @@ module settings ! Use confidence intervals for results instead of standard deviations logical :: confidence_intervals = .false. - ! ============================================================================ - ! SIMULATION VARIABLES - integer(C_INT64_T), bind(C) :: n_particles = 0 ! # of particles per generation integer(C_INT32_T), bind(C) :: n_batches ! # of batches integer(C_INT32_T), bind(C) :: n_inactive ! # of inactive batches diff --git a/src/simulation.F90 b/src/simulation.F90 index 4f6163429e..ffcfb8bab8 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -20,7 +20,7 @@ module simulation use geometry_header, only: n_cells use material_header, only: n_materials, materials use message_passing - use mgxs_header, only: energy_bins, energy_bin_avg + use mgxs_interface, only: energy_bins, energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides use output, only: header, print_columns, & print_batch_keff, print_generation, print_runtime, & diff --git a/src/source.F90 b/src/source.F90 index 3683611315..a6b6a92ac2 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -14,7 +14,7 @@ module source use hdf5_interface use math use message_passing, only: rank - use mgxs_header, only: rev_energy_bins, num_energy_groups + use mgxs_interface, only: rev_energy_bins, num_energy_groups use output, only: write_message use particle_header, only: Particle use random_lcg, only: prn, set_particle_seed, prn_set_stream diff --git a/src/state_point.F90 b/src/state_point.F90 index 27fdd969b5..ebb473d496 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -22,7 +22,7 @@ module state_point use hdf5_interface use mesh_header, only: RegularMesh, meshes, n_meshes use message_passing - use mgxs_header, only: nuclides_MG + use mgxs_interface use nuclide_header, only: nuclides use output, only: time_stamp use random_lcg, only: openmc_get_seed, openmc_set_seed @@ -73,6 +73,7 @@ contains character(MAX_WORD_LEN), allocatable :: str_array(:) character(C_CHAR), pointer :: string(:) character(len=:, kind=C_CHAR), allocatable :: filename_ + character(MAX_WORD_LEN, kind=C_CHAR) :: temp_name err = 0 if (present(filename)) then @@ -307,11 +308,13 @@ contains str_array(j) = nuclides(tally % nuclide_bins(j)) % name end if else - i_xs = index(nuclides_MG(tally % nuclide_bins(j)) % obj % name, '.') + call get_name_c(tally % nuclide_bins(j), len(temp_name), & + temp_name) + i_xs = index(temp_name, '.') if (i_xs > 0) then - str_array(j) = nuclides_MG(tally % nuclide_bins(j)) % obj % name(1 : i_xs-1) + str_array(j) = trim(temp_name(1 : i_xs-1)) else - str_array(j) = nuclides_MG(tally % nuclide_bins(j)) % obj % name + str_array(j) = trim(temp_name) end if end if else diff --git a/src/string_functions.cpp b/src/string_functions.cpp new file mode 100644 index 0000000000..f41ec40c1e --- /dev/null +++ b/src/string_functions.cpp @@ -0,0 +1,30 @@ +#include "string_functions.h" + +namespace openmc { + +std::string& strtrim(std::string& s) +{ + const char* t = " \t\n\r\f\v"; + s.erase(s.find_last_not_of(t) + 1); + s.erase(0, s.find_first_not_of(t)); + return s; +} + + +char* strtrim(char* c_str) +{ + std::string std_str; + std_str.assign(c_str); + strtrim(std_str); + int length = std_str.copy(c_str, std_str.size()); + c_str[length] = '\0'; + return c_str; +} + + +void to_lower(std::string& str) +{ + for (int i = 0; i < str.size(); i++) str[i] = std::tolower(str[i]); +} + +} // namespace openmc \ No newline at end of file diff --git a/src/string_functions.h b/src/string_functions.h new file mode 100644 index 0000000000..bf30612fe8 --- /dev/null +++ b/src/string_functions.h @@ -0,0 +1,18 @@ +//! \file string_functions.h +//! A collection of helper routines for C-strings and STL strings + +#ifndef STRING_FUNCTIONS_H +#define STRING_FUNCTIONS_H + +#include + +namespace openmc { + +std::string& strtrim(std::string& s); + +char* strtrim(char* c_str); + +void to_lower(std::string& str); + +} // namespace openmc +#endif // STRING_FUNCTIONS_H diff --git a/src/summary.F90 b/src/summary.F90 index 940e3128ab..062b4efc24 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -8,7 +8,7 @@ module summary use material_header, only: Material, n_materials use mesh_header, only: RegularMesh use message_passing - use mgxs_header, only: nuclides_MG + use mgxs_interface use nuclide_header use output, only: time_stamp use settings, only: run_CE @@ -93,7 +93,7 @@ contains num_nuclides = 0 num_macros = 0 do i = 1, n_nuclides - if (nuclides_MG(i) % obj % awr /= MACROSCOPIC_AWR) then + if (get_awr_c(i) /= MACROSCOPIC_AWR) then num_nuclides = num_nuclides + 1 else num_macros = num_macros + 1 @@ -118,12 +118,14 @@ contains nuc_names(i) = nuclides(i) % name awrs(i) = nuclides(i) % awr else - if (nuclides_MG(i) % obj % awr /= MACROSCOPIC_AWR) then - nuc_names(j) = nuclides_MG(i) % obj % name - awrs(j) = nuclides_MG(i) % obj % awr + 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 - macro_names(k) = nuclides_MG(i) % obj % name + 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 @@ -353,7 +355,7 @@ contains num_nuclides = 0 num_macros = 0 do j = 1, m % n_nuclides - if (nuclides_MG(m % nuclide(j)) % obj % awr /= MACROSCOPIC_AWR) then + if (get_awr_c(m % nuclide(j)) /= MACROSCOPIC_AWR) then num_nuclides = num_nuclides + 1 else num_macros = num_macros + 1 @@ -379,12 +381,14 @@ contains k = 1 n = 1 do j = 1, m % n_nuclides - if (nuclides_MG(m % nuclide(j)) % obj % awr /= MACROSCOPIC_AWR) then - nuc_names(k) = nuclides_MG(m % nuclide(j)) % obj % name + 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 - macro_names(n) = nuclides_MG(m % nuclide(j)) % obj % name + 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 diff --git a/src/surface.h b/src/surface.h index 1d8cc6ac7b..666fc0dbae 100644 --- a/src/surface.h +++ b/src/surface.h @@ -6,7 +6,7 @@ #include #include "hdf5.h" -#include "pugixml/pugixml.hpp" +#include "pugixml.hpp" #include "constants.h" diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index b245bf5249..ed0963a883 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -10,7 +10,7 @@ module tally use math, only: t_percentile use mesh_header, only: RegularMesh, meshes use message_passing - use mgxs_header + use mgxs_interface use nuclide_header use output, only: header use particle_header, only: LocalCoord, Particle @@ -1229,8 +1229,6 @@ contains real(8) :: p_uvw(3) ! Particle's current uvw integer :: p_g ! Particle group to use for getting info ! to tally with. - class(Mgxs), pointer :: matxs - class(Mgxs), pointer :: nucxs ! Set the direction and group to use with get_xs if (t % estimator == ESTIMATOR_ANALOG .or. & @@ -1268,13 +1266,13 @@ contains ! To significantly reduce de-referencing, point matxs to the ! macroscopic Mgxs for the material of interest - matxs => macro_xs(p % material) % obj + call set_macro_angle_index_c(p % material, p_uvw) ! Do same for nucxs, point it to the microscopic nuclide data of interest if (i_nuclide > 0) then - nucxs => nuclides_MG(i_nuclide) % obj ! And since we haven't calculated this temperature index yet, do so now - call nucxs % find_temperature(p % sqrtkT) + call set_nuclide_temperature_index_c(i_nuclide, p % sqrtkT) + call set_nuclide_angle_index_c(i_nuclide, p_uvw) end if i = 0 @@ -1328,14 +1326,14 @@ contains end if if (i_nuclide > 0) then - score = score * atom_density * & - nucxs % get_xs('total', p_g, UVW=p_uvw) / & - matxs % get_xs('total', p_g, UVW=p_uvw) * flux + score = score * flux * atom_density * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_TOTAL, p_g) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('total', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) * & atom_density * flux else score = material_xs % total * flux @@ -1358,19 +1356,23 @@ contains end if if (i_nuclide > 0) then - score = score * nucxs % get_xs('inverse-velocity', p_g, UVW=p_uvw) & - / matxs % get_xs('absorption', p_g, UVW=p_uvw) * flux + score = score * flux * get_nuclide_xs_c(i_nuclide, & + MG_GET_XS_INVERSE_VELOCITY, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score * matxs % get_xs('inverse-velocity', p_g, UVW=p_uvw) & - / matxs % get_xs('absorption', p_g, UVW=p_uvw) * flux + score = score * flux * get_macro_xs_c(p % material, & + MG_GET_XS_INVERSE_VELOCITY, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = flux * nucxs % get_xs('inverse-velocity', p_g, UVW=p_uvw) + score = flux * get_nuclide_xs_c(i_nuclide, & + MG_GET_XS_INVERSE_VELOCITY, p_g) else - score = flux * matxs % get_xs('inverse-velocity', p_g, UVW=p_uvw) + score = flux * get_macro_xs_c(p % material, & + MG_GET_XS_INVERSE_VELOCITY, p_g) end if end if @@ -1392,21 +1394,23 @@ contains ! adjust the score by the actual probability for that nuclide. if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('scatter*f_mu/mult', p % last_g, p % g, & - UVW=p_uvw, MU=p % mu) / & - matxs % get_xs('scatter*f_mu/mult', p % last_g, p % g, & - UVW=p_uvw, MU=p % mu) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU_MULT, & + p % last_g, p % g, MU=p % mu) / & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU_MULT, & + p % last_g, p % g, MU=p % mu) end if else if (i_nuclide > 0) then score = atom_density * flux * & - nucxs % get_xs('scatter/mult', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_MULT, & + p_g, MU=p % mu) else ! Get the scattering x/s and take away ! the multiplication baked in to sigS score = flux * & - matxs % get_xs('scatter/mult', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_MULT, & + p_g, MU=p % mu) end if end if @@ -1428,19 +1432,20 @@ contains ! adjust the score by the actual probability for that nuclide. if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('scatter*f_mu', p % last_g, p % g, & - UVW=p_uvw, MU=p % mu) / & - matxs % get_xs('scatter*f_mu', p % last_g, p % g, & - UVW=p_uvw, MU=p % mu) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU, & + p % last_g, p % g, MU=p % mu) / & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU, & + p % last_g, p % g, MU=p % mu) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('scatter', p_g, UVW=p_uvw) * & - atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER, p_g) else ! Get the scattering x/s, which includes multiplication - score = matxs % get_xs('scatter', p_g, UVW=p_uvw) * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER, p_g) end if end if @@ -1460,13 +1465,13 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('absorption', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('absorption', p_g, UVW=p_uvw) * & - atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) else score = material_xs % absorption * flux end if @@ -1491,19 +1496,19 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - matxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('fission', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) * & atom_density * flux else - score = matxs % get_xs('fission', p_g, UVW=p_uvw) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux end if end if @@ -1529,12 +1534,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('nu-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - matxs % get_xs('nu-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else ! Skip any non-fission events @@ -1547,17 +1552,17 @@ contains score = keff * p % wgt_bank * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if else if (i_nuclide > 0) then - score = nucxs % get_xs('nu-fission', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) * & atom_density * flux else - score = matxs % get_xs('nu-fission', p_g, UVW=p_uvw) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) * flux end if end if @@ -1583,12 +1588,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('prompt-nu-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - matxs % get_xs('prompt-nu-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else ! Skip any non-fission events @@ -1602,17 +1607,17 @@ contains / real(p % n_bank, 8)) * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if else if (i_nuclide > 0) then - score = nucxs % get_xs('prompt-nu-fission', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & atom_density * flux else - score = matxs % get_xs('prompt-nu-fission', p_g, UVW=p_uvw) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) * flux end if end if @@ -1638,7 +1643,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - if (matxs % get_xs('absorption', p_g, UVW=p_uvw) > ZERO) then + if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then select type(filt => filters(t % filter(dg_filter)) % obj) @@ -1653,13 +1658,13 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then - score = score * nucxs % get_xs('delayed-nu-fission', & - p_g, UVW=p_uvw, dg=d) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + score = score * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score * matxs % get_xs('delayed-nu-fission', & - p_g, UVW=p_uvw, dg=d) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + score = score * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1669,11 +1674,13 @@ contains else score = p % absorb_wgt * flux if (i_nuclide > 0) then - score = score * nucxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw) / matxs % get_xs('absorption', p_g, UVW=p_uvw) + score = score * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score * matxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw) / matxs % get_xs('absorption', p_g, UVW=p_uvw) + score = score * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if end if end if @@ -1703,8 +1710,8 @@ contains if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1715,8 +1722,8 @@ contains score = keff * p % wgt_bank / p % n_bank * sum(p % n_delayed_bank) * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if end if @@ -1735,11 +1742,11 @@ contains d = filt % groups(d_bin) if (i_nuclide > 0) then - score = nucxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw, dg=d) * atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else - score = matxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw, dg=d) * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1748,11 +1755,12 @@ contains end select else if (i_nuclide > 0) then - score = nucxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw) & - * atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) + else - score = matxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw) & - * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) end if end if end if @@ -1767,7 +1775,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - if (matxs % get_xs('absorption', p_g, UVW=p_uvw) > ZERO) then + if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then select type(filt => filters(t % filter(dg_filter)) % obj) @@ -1782,17 +1790,15 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then - score = score * nucxs % get_xs('decay rate', p_g, & - UVW=p_uvw, dg=d) * & - nucxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw, dg=d) / matxs % get_xs('absorption', & - p_g, UVW=p_uvw) + score = score * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score * matxs % get_xs('decay rate', p_g, & - UVW=p_uvw, dg=d) * & - matxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw, dg=d) / matxs % get_xs('absorption', & - p_g, UVW=p_uvw) + score = score * & + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1809,15 +1815,15 @@ contains ! for all delayed groups. do d = 1, num_delayed_groups if (i_nuclide > 0) then - score = score + p % absorb_wgt * & - nucxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - nucxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, & - dg=d) / matxs % get_xs('absorption', p_g, UVW=p_uvw) * flux + score = score + p % absorb_wgt * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score + p % absorb_wgt * & - matxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - matxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, & - dg=d) / matxs % get_xs('absorption', p_g, UVW=p_uvw) * flux + score = score + p % absorb_wgt * flux * & + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if end do end if @@ -1846,13 +1852,13 @@ contains if (i_nuclide > 0) then score = score + keff * atom_density * & fission_bank(n_bank - p % n_bank + k) % wgt * & - nucxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=g) * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) * flux + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux else score = score + keff * & fission_bank(n_bank - p % n_bank + k) % wgt * & - matxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=g) * flux + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * flux end if ! if the delayed group filter is present, tally to corresponding @@ -1904,13 +1910,13 @@ contains d = filt % groups(d_bin) if (i_nuclide > 0) then - score = nucxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - nucxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, & - dg=d) * atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else - score = matxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - matxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, & - dg=d) * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1927,12 +1933,12 @@ contains do d = 1, num_delayed_groups if (i_nuclide > 0) then score = score + atom_density * flux * & - nucxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - nucxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, dg=d) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = score + flux * & - matxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - matxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, dg=d) + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if end do end if @@ -1957,19 +1963,20 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('kappa-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - matxs % get_xs('kappa-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('kappa-fission', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) * & atom_density * flux else - score = matxs % get_xs('kappa-fission', p_g, UVW=p_uvw) * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) end if end if @@ -1988,8 +1995,6 @@ contains t % results(RESULT_VALUE, score_index, filter_index) + score end do SCORE_LOOP - - nullify(matxs, nucxs) end subroutine score_general_mg !=============================================================================== diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 index 93da69edda..f230d438e8 100644 --- a/src/tallies/tally_filter_energy.F90 +++ b/src/tallies/tally_filter_energy.F90 @@ -6,7 +6,7 @@ module tally_filter_energy use constants use error use hdf5_interface - use mgxs_header, only: num_energy_groups, rev_energy_bins + use mgxs_interface, only: num_energy_groups, rev_energy_bins use particle_header, only: Particle use settings, only: run_CE use string, only: to_str diff --git a/src/tracking.F90 b/src/tracking.F90 index 25121d7f44..8fe05e3c26 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -1,5 +1,7 @@ module tracking + use, intrinsic :: ISO_C_BINDING + use constants use error, only: warning, write_message use geometry_header, only: cells @@ -7,7 +9,7 @@ module tracking check_cell_overlap use material_header, only: materials, Material use message_passing - use mgxs_header + use mgxs_interface use nuclide_header use particle_header, only: LocalCoord, Particle use physics, only: collision @@ -112,8 +114,10 @@ contains end if else ! Get the MG data - call macro_xs(p % material) % obj % calculate_xs(p % g, p % sqrtkT, & - p % coord(p % n_coord) % uvw, material_xs) + call calculate_xs_c(p % material, p % g, p % sqrtkT, & + p % coord(p % n_coord) % uvw, material_xs % total, & + material_xs % absorption, material_xs % nu_fission) + ! Finally, update the particle group while we have already checked ! for if multi-group diff --git a/src/xml_interface.h b/src/xml_interface.h index b6f9e3246d..de89018efe 100644 --- a/src/xml_interface.h +++ b/src/xml_interface.h @@ -4,7 +4,7 @@ #include #include -#include "pugixml/pugixml.hpp" +#include "pugixml.hpp" namespace openmc { diff --git a/src/xsdata.cpp b/src/xsdata.cpp new file mode 100644 index 0000000000..3482a316a2 --- /dev/null +++ b/src/xsdata.cpp @@ -0,0 +1,713 @@ +#include +#include +#include +#include + +#include "constants.h" +#include "error.h" +#include "math_functions.h" +#include "random_lcg.h" +#include "xsdata.h" + + +namespace openmc { + +//============================================================================== +// XsData class methods +//============================================================================== + +XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, + int scatter_format, int n_pol, int n_azi) +{ + int n_ang = n_pol * n_azi; + + // check to make sure scatter format is OK before we allocate + if (scatter_format != ANGLE_HISTOGRAM && scatter_format != ANGLE_TABULAR && + scatter_format != ANGLE_LEGENDRE) { + fatal_error("Invalid scatter_format!"); + } + // allocate all [temperature][phi][theta][in group] quantities + total = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + absorption = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + inverse_velocity = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + if (fissionable) { + fission = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + nu_fission = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + prompt_nu_fission = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + kappa_fission = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + } + + // allocate decay_rate; [temperature][phi][theta][delayed group] + decay_rate = double_2dvec(n_ang, double_1dvec(num_delayed_groups, 0.)); + + if (fissionable) { + // allocate delayed_nu_fission; [temperature][phi][theta][in group][delay group] + delayed_nu_fission = double_3dvec(n_ang, double_2dvec(energy_groups, + double_1dvec(num_delayed_groups, 0.))); + + // chi_prompt; [temperature][phi][theta][in group][delayed group] + chi_prompt = double_3dvec(n_ang, double_2dvec(energy_groups, + double_1dvec(energy_groups, 0.))); + + // chi_delayed; [temperature][phi][theta][in group][out group][delay group] + chi_delayed = double_4dvec(n_ang, double_3dvec(energy_groups, + double_2dvec(energy_groups, double_1dvec(num_delayed_groups, 0.)))); + } + + + for (int a = 0; a < n_ang; a++) { + if (scatter_format == ANGLE_HISTOGRAM) { + // scatter[a] = std::make_unique(ScattDataHistogram); + scatter.emplace_back(new ScattDataHistogram); + } else if (scatter_format == ANGLE_TABULAR) { + // scatter[a] = std::make_unique(ScattDataTabular); + scatter.emplace_back(new ScattDataTabular); + } else if (scatter_format == ANGLE_LEGENDRE) { + // scatter[a] = std::make_unique(ScattDataLegendre); + scatter.emplace_back(new ScattDataLegendre); + } + } +} + +//============================================================================== + +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) +{ + // Reconstruct the dimension information so it doesn't need to be passed + int n_ang = n_pol * n_azi; + int energy_groups = total[0].size(); + int delayed_groups = decay_rate[0].size(); + + // Set the fissionable-specific data + if (fissionable) { + fission_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, delayed_groups, + is_isotropic); + } + // Get the non-fission-specific data + read_nd_vector(xsdata_grp, "decay_rate", decay_rate); + read_nd_vector(xsdata_grp, "absorption", absorption, true); + read_nd_vector(xsdata_grp, "inverse-velocity", inverse_velocity); + + // Get scattering data + scatter_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, scatter_format, + final_scatter_format, order_data, max_order, legendre_to_tabular_points); + + // Check absorption to ensure it is not 0 since it is often the + // denominator in tally methods + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + if (absorption[a][gin] == 0.) absorption[a][gin] = 1.e-10; + } + } + + // Get or calculate the total x/s + if (object_exists(xsdata_grp, "total")) { + read_nd_vector(xsdata_grp, "total", total); + } else { + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + total[a][gin] = absorption[a][gin] + scatter[a]->scattxs[gin]; + } + } + } + + // Fix if total is 0, since it is in the denominator when tallying + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + if (total[a][gin] == 0.) total[a][gin] = 1.e-10; + } + } +} + +//============================================================================== + +void +XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, + int energy_groups, int delayed_groups, bool is_isotropic) +{ + int n_ang = n_pol * n_azi; + // Get the fission and kappa_fission data xs; these are optional + read_nd_vector(xsdata_grp, "fission", fission); + read_nd_vector(xsdata_grp, "kappa-fission", kappa_fission); + + // Set/get beta + double_3dvec temp_beta =double_3dvec(n_ang, double_2dvec(energy_groups, + double_1dvec(delayed_groups, 0.))); + if (object_exists(xsdata_grp, "beta")) { + hid_t xsdata = open_dataset(xsdata_grp, "beta"); + int ndims = dataset_ndims(xsdata); + + // raise ndims to make the isotropic ndims the same as angular + if (is_isotropic) ndims += 2; + + if (ndims == 3) { + // Beta is input as [delayed group] + double_1dvec temp_arr(n_pol * n_azi * delayed_groups); + read_nd_vector(xsdata_grp, "beta", temp_arr); + + // Broadcast to all incoming groups + int temp_idx = 0; + for (int a = 0; a < n_ang; a++) { + for (int dg = 0; dg < delayed_groups; dg++) { + // Set the first group index and copy the rest + temp_beta[a][0][dg] = temp_arr[temp_idx++]; + for (int gin = 1; gin < energy_groups; gin++) { + temp_beta[a][gin] = temp_beta[a][0]; + } + } + } + } else if (ndims == 4) { + // Beta is input as [in group][delayed group] + read_nd_vector(xsdata_grp, "beta", temp_beta); + } else { + fatal_error("beta must be provided as a 3D or 4D array!"); + } + } + + // If chi is provided, set chi-prompt and chi-delayed + if (object_exists(xsdata_grp, "chi")) { + double_2dvec temp_arr(n_ang, double_1dvec(energy_groups)); + read_nd_vector(xsdata_grp, "chi", temp_arr); + + for (int a = 0; a < n_ang; a++) { + // First set the first group + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][0][gout] = temp_arr[a][gout]; + } + + // Now normalize this data + double chi_sum = std::accumulate(chi_prompt[a][0].begin(), + chi_prompt[a][0].end(), + 0.); + if (chi_sum <= 0.) { + fatal_error("Encountered chi for a group that is <= 0!"); + } + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][0][gout] /= chi_sum; + } + + // And extend to the remaining incoming groups + for (int gin = 1; gin < energy_groups; gin++) { + chi_prompt[a][gin] = chi_prompt[a][0]; + } + + // Finally set chi-delayed equal to chi-prompt + // Set chi-delayed to chi-prompt + for(int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[a][gin][gout][dg] = + chi_prompt[a][gin][gout]; + } + } + } + } + } + + // If nu-fission is provided, set prompt- and delayed-nu-fission; + // if nu-fission is a matrix, set chi-prompt and chi-delayed. + if (object_exists(xsdata_grp, "nu-fission")) { + hid_t xsdata = open_dataset(xsdata_grp, "nu-fission"); + int ndims = dataset_ndims(xsdata); + // raise ndims to make the isotropic ndims the same as angular + if (is_isotropic) ndims += 2; + + if (ndims == 3) { + // nu-fission is a 3-d array + read_nd_vector(xsdata_grp, "nu-fission", prompt_nu_fission); + + // set delayed-nu-fission and correct prompt-nu-fission with beta + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + delayed_nu_fission[a][gin][dg] = + temp_beta[a][gin][dg] * prompt_nu_fission[a][gin]; + } + + // Correct the prompt-nu-fission using the delayed neutron fraction + if (delayed_groups > 0) { + double beta_sum = std::accumulate(temp_beta[a][gin].begin(), + temp_beta[a][gin].end(), 0.); + prompt_nu_fission[a][gin] *= (1. - beta_sum); + } + } + } + + } else if (ndims == 4) { + // nu-fission is a matrix + read_nd_vector(xsdata_grp, "nu-fission", chi_prompt); + + // Normalize the chi info so the CDF is 1. + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + double chi_sum = std::accumulate(chi_prompt[a][gin].begin(), + chi_prompt[a][gin].end(), 0.); + // Set the vector nu-fission from the matrix nu-fission + prompt_nu_fission[a][gin] = chi_sum; + + if (chi_sum >= 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][gin][gout] /= chi_sum; + } + } else { + fatal_error("Encountered chi for a group that is <= 0!"); + } + } + + // set chi-delayed to chi-prompt + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[a][gin][gout][dg] = + chi_prompt[a][gin][gout]; + } + } + } + + // Set the delayed-nu-fission and correct prompt-nu-fission with beta + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + delayed_nu_fission[a][gin][dg] = + temp_beta[a][gin][dg] * + prompt_nu_fission[a][gin]; + } + + // Correct prompt-nu-fission using the delayed neutron fraction + if (delayed_groups > 0) { + double beta_sum = std::accumulate(temp_beta[a][gin].begin(), + temp_beta[a][gin].end(), 0.); + prompt_nu_fission[a][gin] *= (1. - beta_sum); + } + } + } + } else { + fatal_error("nu-fission must be provided as a 3D or 4D array!"); + } + + close_dataset(xsdata); + } + + // If chi-prompt is provided, set chi-prompt + if (object_exists(xsdata_grp, "chi-prompt")) { + double_2dvec temp_arr(n_ang, double_1dvec(energy_groups)); + read_nd_vector(xsdata_grp, "chi-prompt", temp_arr); + + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][gin][gout] = temp_arr[a][gout]; + } + + // Normalize chi so its CDF goes to 1 + double chi_sum = std::accumulate(chi_prompt[a][gin].begin(), + chi_prompt[a][gin].end(), 0.); + if (chi_sum >= 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][gin][gout] /= chi_sum; + } + } else { + fatal_error("Encountered chi-prompt for a group that is <= 0.!"); + } + } + } + } + + // If chi-delayed is provided, set chi-delayed + if (object_exists(xsdata_grp, "chi-delayed")) { + hid_t xsdata = open_dataset(xsdata_grp, "chi-delayed"); + int ndims = dataset_ndims(xsdata); + // raise ndims to make the isotropic ndims the same as angular + if (is_isotropic) ndims += 2; + close_dataset(xsdata); + + if (ndims == 3) { + // chi-delayed is a [in group] vector + double_2dvec temp_arr(n_ang, double_1dvec(energy_groups)); + read_nd_vector(xsdata_grp, "chi-delayed", temp_arr); + + for (int a = 0; a < n_ang; a++) { + // normalize the chi CDF to 1 + double chi_sum = std::accumulate(temp_arr[a].begin(), + temp_arr[a].end(), 0.); + if (chi_sum <= 0.) { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); + } + + // set chi-delayed + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[a][gin][gout][dg] = temp_arr[a][gout] / chi_sum; + } + } + } + } + } else if (ndims == 4) { + // chi_delayed is a matrix + read_nd_vector(xsdata_grp, "chi-delayed", chi_delayed); + + // Normalize the chi info so the CDF is 1. + for (int a = 0; a < n_ang; a++) { + for (int dg = 0; dg < delayed_groups; dg++) { + for (int gin = 0; gin < energy_groups; gin++) { + double chi_sum = 0.; + for (int gout = 0; gout < energy_groups; gout++) { + chi_sum += chi_delayed[a][gin][gout][dg]; + } + + if (chi_sum > 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_delayed[a][gin][gout][dg] /= chi_sum; + } + } else { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); + } + } + } + } + } else { + fatal_error("chi-delayed must be provided as a 3D or 4D array!"); + } + } + + // Get prompt-nu-fission, if present + if (object_exists(xsdata_grp, "prompt-nu-fission")) { + hid_t xsdata = open_dataset(xsdata_grp, "prompt-nu-fission"); + int ndims = dataset_ndims(xsdata); + // raise ndims to make the isotropic ndims the same as angular + if (is_isotropic) ndims += 2; + close_dataset(xsdata); + + if (ndims == 3) { + // prompt-nu-fission is a [in group] vector + read_nd_vector(xsdata_grp, "prompt-nu-fission", + prompt_nu_fission); + } else if (ndims == 4) { + // prompt nu fission is a matrix, + // so set prompt_nu_fiss & chi_prompt + double_3dvec temp_arr(n_ang, double_2dvec(energy_groups, + double_1dvec(energy_groups))); + read_nd_vector(xsdata_grp, "prompt-nu-fission", temp_arr); + + // The prompt_nu_fission vector from the matrix form + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + double prompt_sum = std::accumulate(temp_arr[a][gin].begin(), + temp_arr[a][gin].end(), 0.); + prompt_nu_fission[a][gin] = prompt_sum; + } + + // The chi_prompt data is just the normalized fission matrix + for (int gin= 0; gin < energy_groups; gin++) { + if (prompt_nu_fission[a][gin] > 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][gin][gout] = + temp_arr[a][gin][gout] / prompt_nu_fission[a][gin]; + } + } else { + fatal_error("Encountered chi-prompt for a group that is <= 0!"); + } + } + } + + } else { + fatal_error("prompt-nu-fission must be provided as a 3D or 4D array!"); + } + } + + // Get delayed-nu-fission, if present + if (object_exists(xsdata_grp, "delayed-nu-fission")) { + hid_t xsdata = open_dataset(xsdata_grp, "delayed-nu-fission"); + int ndims = dataset_ndims(xsdata); + close_dataset(xsdata); + // raise ndims to make the isotropic ndims the same as angular + if (is_isotropic) ndims += 2; + + if (ndims == 3) { + // delayed-nu-fission is an [in group] vector + if (temp_beta[0][0][0] == 0.) { + fatal_error("cannot set delayed-nu-fission with a 1D array if " + "beta is not provided"); + } + double_2dvec temp_arr(n_ang, double_1dvec(energy_groups)); + read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_arr); + + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + // Set delayed-nu-fission using beta + delayed_nu_fission[a][gin][dg] = + temp_beta[a][gin][dg] * temp_arr[a][gin]; + } + } + } + + } else if (ndims == 4) { + read_nd_vector(xsdata_grp, "delayed-nu-fission", + delayed_nu_fission); + + } else if (ndims == 5) { + // This will contain delayed-nu-fision and chi-delayed data + double_4dvec temp_arr(n_ang, double_3dvec(energy_groups, + double_2dvec(energy_groups, double_1dvec(delayed_groups)))); + read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_arr); + + // Set the 3D delayed-nu-fission matrix and 4D chi-delayed matrix + // from the 4D delayed-nu-fission matrix + for (int a = 0; a < n_ang; a++) { + for (int dg = 0; dg < delayed_groups; dg++) { + for (int gin = 0; gin < energy_groups; gin++) { + double gout_sum = 0.; + for (int gout = 0; gout < energy_groups; gout++) { + gout_sum += temp_arr[a][gin][gout][dg]; + chi_delayed[a][gin][gout][dg] = temp_arr[a][gin][gout][dg]; + } + delayed_nu_fission[a][gin][dg] = gout_sum; + // Normalize chi-delayed + if (gout_sum > 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_delayed[a][gin][gout][dg] /= gout_sum; + } + } else { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); + } + } + } + } + + } else { + fatal_error("prompt-nu-fission must be provided as a 3D, 4D, or 5D " + "array!"); + } + } + + // Combine prompt_nu_fission and delayed_nu_fission into nu_fission + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + nu_fission[a][gin] = + std::accumulate(delayed_nu_fission[a][gin].begin(), + delayed_nu_fission[a][gin].end(), + prompt_nu_fission[a][gin]); + } + } +} + +//============================================================================== + +void +XsData::scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, + int energy_groups, int scatter_format, int final_scatter_format, + int order_data, int max_order, int legendre_to_tabular_points) +{ + int n_ang = n_pol * n_azi; + if (!object_exists(xsdata_grp, "scatter_data")) { + fatal_error("Must provide scatter_data group!"); + } + hid_t scatt_grp = open_group(xsdata_grp, "scatter_data"); + + // Get the outgoing group boundary indices + int_2dvec gmin(n_ang, int_1dvec(energy_groups)); + read_nd_vector(scatt_grp, "g_min", gmin, true); + int_2dvec gmax(n_ang, int_1dvec(energy_groups)); + read_nd_vector(scatt_grp, "g_max", gmax, true); + + // Make gmin and gmax start from 0 vice 1 as they do in the library + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + gmin[a][gin] -= 1; + gmax[a][gin] -= 1; + } + } + + // Now use this info to find the length of a vector to hold the flattened + // data. + int length = 0; + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + length += order_data * (gmax[a][gin] - gmin[a][gin] + 1); + } + } + double_1dvec temp_arr(length); + read_nd_vector(scatt_grp, "scatter_matrix", temp_arr, true); + + // Compare the number of orders given with the max order of the problem; + // 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; + } else { + order_dim = order_data; + } + + // convert the flattened temp_arr to a jagged array for passing to + // scatt data + double_4dvec input_scatt(n_ang, double_3dvec(energy_groups)); + + int temp_idx = 0; + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + input_scatt[a][gin].resize(gmax[a][gin] - gmin[a][gin] + 1); + for (int i_gout = 0; i_gout < input_scatt[a][gin].size(); i_gout++) { + input_scatt[a][gin][i_gout].resize(order_dim); + for (int l = 0; l < order_dim; l++) { + input_scatt[a][gin][i_gout][l] = temp_arr[temp_idx++]; + } + // Adjust index for the orders we didnt take + temp_idx += (order_data - order_dim); + } + } + } + temp_arr.clear(); + + // Get multiplication matrix + double_3dvec temp_mult(n_ang, double_2dvec(energy_groups)); + if (object_exists(scatt_grp, "multiplicity_matrix")) { + temp_arr.resize(length / order_data); + read_nd_vector(scatt_grp, "multiplicity_matrix", temp_arr); + + // convert the flat temp_arr to a jagged array for passing to scatt data + int temp_idx = 0; + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + temp_mult[a][gin].resize(gmax[a][gin] - gmin[a][gin] + 1); + for (int i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) { + temp_mult[a][gin][i_gout] = temp_arr[temp_idx++]; + } + } + } + } else { + // Use a default: multiplicities are 1.0. + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + temp_mult[a][gin].resize(gmax[a][gin] - gmin[a][gin] + 1); + for (int i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) { + temp_mult[a][gin][i_gout] = 1.; + } + } + } + } + temp_arr.clear(); + close_group(scatt_grp); + + // Finally, convert the Legendre data to tabular, if needed + if (scatter_format == ANGLE_LEGENDRE && + final_scatter_format == ANGLE_TABULAR) { + for (int a = 0; a < n_ang; a++) { + ScattDataLegendre legendre_scatt; + legendre_scatt.init(gmin[a], gmax[a], temp_mult[a], input_scatt[a]); + + // Now create a tabular version of legendre_scatt + convert_legendre_to_tabular(legendre_scatt, + *static_cast(scatter[a].get()), + legendre_to_tabular_points); + + scatter_format = final_scatter_format; + } + } else { + // We are sticking with the current representation + // Initialize the ScattData object with this data + for (int a = 0; a < n_ang; a++) { + scatter[a]->init(gmin[a], gmax[a], temp_mult[a], input_scatt[a]); + } + } +} + +//============================================================================== + +void +XsData::combine(const std::vector& those_xs, + const double_1dvec& scalars) +{ + // Combine the non-scattering data + for (int i = 0; i < those_xs.size(); i++) { + XsData* that = those_xs[i]; + if (!equiv(*that)) fatal_error("Cannot combine the XsData objects!"); + double scalar = scalars[i]; + for (int a = 0; a < total.size(); a++) { + for (int gin = 0; gin < total[a].size(); gin++) { + total[a][gin] += scalar * that->total[a][gin]; + absorption[a][gin] += scalar * that->absorption[a][gin]; + if (i == 0) { + inverse_velocity[a][gin] = that->inverse_velocity[a][gin]; + } + if (that->prompt_nu_fission.size() > 0) { + nu_fission[a][gin] += scalar * that->nu_fission[a][gin]; + prompt_nu_fission[a][gin] += + scalar * that->prompt_nu_fission[a][gin]; + kappa_fission[a][gin] += scalar * that->kappa_fission[a][gin]; + fission[a][gin] += scalar * that->fission[a][gin]; + + for (int dg = 0; dg < delayed_nu_fission[a][gin].size(); dg++) { + delayed_nu_fission[a][gin][dg] += + scalar * that->delayed_nu_fission[a][gin][dg]; + } + + for (int gout = 0; gout < chi_prompt[a][gin].size(); gout++) { + chi_prompt[a][gin][gout] += + scalar * that->chi_prompt[a][gin][gout]; + + for (int dg = 0; dg < chi_delayed[a][gin][gout].size(); dg++) { + chi_delayed[a][gin][gout][dg] += + scalar * that->chi_delayed[a][gin][gout][dg]; + } + } + } + } + + for (int dg = 0; dg < decay_rate[a].size(); dg++) { + decay_rate[a][dg] += scalar * that->decay_rate[a][dg]; + } + + // Normalize chi + if (chi_prompt.size() > 0) { + for (int gin = 0; gin < chi_prompt[a].size(); gin++) { + double norm = std::accumulate(chi_prompt[a][gin].begin(), + chi_prompt[a][gin].end(), 0.); + if (norm > 0.) { + for (int gout = 0; gout < chi_prompt[a][gin].size(); gout++) { + chi_prompt[a][gin][gout] /= norm; + } + } + + for (int dg = 0; dg < chi_delayed[a][gin][0].size(); dg++) { + norm = 0.; + for (int gout = 0; gout < chi_delayed[a][gin].size(); gout++) { + norm += chi_delayed[a][gin][gout][dg]; + } + if (norm > 0.) { + for (int gout = 0; gout < chi_delayed[a][gin].size(); gout++) { + chi_delayed[a][gin][gout][dg] /= norm; + } + } + } + } + } + } + } + + // Allow the ScattData object to combine itself + for (int a = 0; a < total.size(); a++) { + // Build vector of the scattering objects to incorporate + std::vector those_scatts(those_xs.size()); + for (int i = 0; i < those_xs.size(); i++) { + those_scatts[i] = those_xs[i]->scatter[a].get(); + } + + // Now combine these guys + scatter[a]->combine(those_scatts, scalars); + } +} + +//============================================================================== + +bool +XsData::equiv(const XsData& that) +{ + return ((absorption.size() == that.absorption.size()) && + (absorption[0].size() == that.absorption[0].size())); +} + +} //namespace openmc diff --git a/src/xsdata.h b/src/xsdata.h new file mode 100644 index 0000000000..c855c67d89 --- /dev/null +++ b/src/xsdata.h @@ -0,0 +1,118 @@ +//! \file xsdata.h +//! A collection of classes for containing the Multi-Group Cross Section data + +#ifndef XSDATA_H +#define XSDATA_H + +#include +#include + +#include "hdf5_interface.h" +#include "scattdata.h" + + +namespace openmc { + +//============================================================================== +// XSDATA contains the temperature-independent cross section data for an MGXS +//============================================================================== + +class XsData { + + private: + //! \brief Reads scattering data from the HDF5 file + void + scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, int energy_groups, + int scatter_format, int final_scatter_format, int order_data, + int max_order, int legendre_to_tabular_points); + + //! \brief Reads fission data from the HDF5 file + void + fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, int energy_groups, + int delayed_groups, bool is_isotropic); + + public: + + // The following quantities have the following dimensions: + // [angle][incoming group] + double_2dvec total; + double_2dvec absorption; + double_2dvec nu_fission; + double_2dvec prompt_nu_fission; + double_2dvec kappa_fission; + double_2dvec fission; + double_2dvec inverse_velocity; + + // decay_rate has the following dimensions: + // [angle][delayed group] + double_2dvec decay_rate; + // delayed_nu_fission has the following dimensions: + // [angle][incoming group][delayed group] + double_3dvec delayed_nu_fission; + // chi_prompt has the following dimensions: + // [angle][incoming group][outgoing group] + double_3dvec chi_prompt; + // chi_delayed has the following dimensions: + // [angle][incoming group][outgoing group][delayed group] + double_4dvec chi_delayed; + // scatter has the following dimensions: [angle] + std::vector > scatter; + + XsData() = default; + + //! \brief Constructs the XsData object metadata. + //! + //! @param num_groups Number of energy groups. + //! @param num_delayed_groups Number of delayed groups. + //! @param fissionable Is this a fissionable data set or not. + //! @param scatter_format The scattering representation of the file. + //! @param n_pol Number of polar angles. + //! @param n_azi Number of azimuthal angles. + XsData(int num_groups, int num_delayed_groups, bool fissionable, + int scatter_format, int n_pol, int n_azi); + + //! \brief Loads the XsData object from the HDF5 file + //! + //! @param xs_id HDF5 group id for the cross section data. + //! @param fissionable Is this a fissionable data set or not. + //! @param scatter_format The scattering representation of the file. + //! @param final_scatter_format The scattering representation after reading; + //! 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 n_azi); + + //! \brief Combines the microscopic data to a macroscopic object. + //! + //! @param micros Microscopic objects to combine. + //! @param scalars Scalars to multiply the microscopic data by. + void + combine(const std::vector& those_xs, const double_1dvec& scalars); + + //! \brief Checks to see if this and that are able to be combined + //! + //! This comparison is used when building macroscopic cross sections + //! from microscopic cross sections. + //! @param that The other XsData to compare to this one. + //! @return True if they can be combined. + bool + equiv(const XsData& that); +}; + + +} //namespace openmc +#endif // XSDATA_H \ No newline at end of file diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 03746430d9..406ff0515f 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -37,9 +37,10 @@ def sm150(): @pytest.fixture(scope='module') def gd154(): - """Gd154 ENDF data (contains Reich Moore resonance range)""" + """Gd154 ENDF data (contains Reich Moore resonance range and reosnance + covariance with LCOMP=1).""" filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-064_Gd_154.endf') - return openmc.data.IncidentNeutron.from_endf(filename) + return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') @@ -77,6 +78,13 @@ def na22(): return openmc.data.IncidentNeutron.from_endf(filename) +@pytest.fixture(scope='module') +def na23(): + """Na23 ENDF data (contains MLBW resonance covariance with LCOMP=0).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-011_Na_023.endf') + return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) + + @pytest.fixture(scope='module') def be9(): """Be9 ENDF data (contains laboratory angle-energy distribution).""" @@ -97,6 +105,28 @@ def am244(): return openmc.data.IncidentNeutron.from_njoy(endf_file) +@pytest.fixture(scope='module') +def ti50(): + """Ti50 ENDF data (contains Multi-level Breit-Wigner resonance range and + resonance covariance with LCOMP=1).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-022_Ti_050.endf') + return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) + + +@pytest.fixture(scope='module') +def cf252(): + """Cf252 ENDF data (contains RM resonance covariance with LCOMP=0).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-098_Cf_252.endf') + return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) + + +@pytest.fixture(scope='module') +def th232(): + """Th232 ENDF data (contains RM resonance covariance with LCOMP=2).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-090_Th_232.endf') + return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) + + def test_attributes(pu239): assert pu239.name == 'Pu239' assert pu239.mass_number == 239 @@ -277,6 +307,101 @@ def test_rml(cl35): assert isinstance(group, openmc.data.SpinGroup) +def test_mlbw_cov_lcomp0(cf252): + # Testing on first range only + cov = cf252.resonance_covariance.ranges[0] + res = cf252.resonances.ranges[0] + assert cov.parameters['energy'][0] == pytest.approx(-3.5) + assert res.parameters['energy'][0] == cov.parameters['energy'][0] + assert isinstance(cov, openmc.data.resonance_covariance.MultiLevelBreitWignerCovariance) + assert cov.energy_min == pytest.approx(1e-5) + assert cov.energy_max == pytest.approx(1000.) + assert cov.covariance[0,0] == pytest.approx(1.225e-05) + + subset = cov.subset('energy', [0, 100]) + assert not subset.parameters.empty + assert (subset.file2res.parameters['energy'] < 100).all() + samples = cov.sample(1) + xs = samples[0].reconstruct([10., 100., 1000.]) + assert sorted(xs.keys()) == [2, 18, 102] + + +def test_mlbw_cov_lcomp1(ti50): + # Testing on first range only + cov = ti50.resonance_covariance.ranges[0] + res = ti50.resonances.ranges[0] + assert cov.parameters['energy'][0] == pytest.approx(-21020.) + assert res.parameters['energy'][0] == cov.parameters['energy'][0] + assert isinstance(cov, openmc.data.resonance_covariance.MultiLevelBreitWignerCovariance) + assert cov.energy_min == pytest.approx(1e-5) + assert cov.energy_max == pytest.approx(587000.) + assert cov.covariance[0,0] == pytest.approx(1.410177e5) + + subset = cov.subset('L', [1, 1]) + assert not subset.parameters.empty + assert (subset.file2res.parameters['L'] == 1).all() + samples = cov.sample(1) + xs = samples[0].reconstruct([10., 100., 1000.]) + assert sorted(xs.keys()) == [2, 18, 102] + + +def test_mlbw_cov_lcomp2(na23): + # Testing on first range only + cov = na23.resonance_covariance.ranges[0] + res = na23.resonances.ranges[0] + assert cov.parameters['energy'][0] == pytest.approx(2810.) + assert res.parameters['energy'][0] == cov.parameters['energy'][0] + assert isinstance(cov, openmc.data.resonance_covariance.MultiLevelBreitWignerCovariance) + assert cov.energy_min == pytest.approx(600) + assert cov.energy_max == pytest.approx(500000.) + assert cov.covariance[0,0] == pytest.approx(16.1064163584) + + subset = cov.subset('L', [1, 1]) + assert not subset.parameters.empty + assert (subset.file2res.parameters['L'] == 1).all() + samples = cov.sample(1) + xs = samples[0].reconstruct([10., 100., 1000.]) + assert sorted(xs.keys()) == [2, 18, 102] + + +def test_rmcov_lcomp1(gd154): + # Testing on first range only + cov = gd154.resonance_covariance.ranges[0] + res = gd154.resonances.ranges[0] + assert cov.parameters['energy'][0] == pytest.approx(-2.200001) + assert res.parameters['energy'][0] == cov.parameters['energy'][0] + assert isinstance(cov, openmc.data.resonance_covariance.ReichMooreCovariance) + assert cov.energy_min == pytest.approx(1e-5) + assert cov.energy_max == pytest.approx(2760.) + assert cov.covariance[0,0] == pytest.approx(0.8895997) + + subset = cov.subset('energy', [0, 100]) + assert not subset.parameters.empty + assert (subset.file2res.parameters['energy'] < 100).all() + samples = cov.sample(1) + xs = samples[0].reconstruct([10., 100., 1000.]) + assert sorted(xs.keys()) == [2, 18, 102] + + +def test_rmcov_lcomp2(th232): + # Testing on first range only + cov = th232.resonance_covariance.ranges[0] + res = th232.resonances.ranges[0] + assert cov.parameters['energy'][0] == pytest.approx(-2000) + assert res.parameters['energy'][0] == cov.parameters['energy'][0] + assert isinstance(cov, openmc.data.resonance_covariance.ReichMooreCovariance) + assert cov.energy_min == pytest.approx(1e-5) + assert cov.energy_max == pytest.approx(4000.) + assert cov.covariance[0,0] == pytest.approx(246.6043092496) + + subset = cov.subset('energy', [0, 100]) + assert not subset.parameters.empty + assert (subset.file2res.parameters['energy'] < 100).all() + samples = cov.sample(1) + xs = samples[0].reconstruct([10., 100., 1000.]) + assert sorted(xs.keys()) == [2, 18, 102] + + def test_madland_nix(am241): fission = am241.reactions[18] prompt_neutron = fission.products[0] diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 2342cdadbc..67c8a4d96d 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -4,6 +4,9 @@ set -ex # Install NJOY 2016 ./tools/ci/travis-install-njoy.sh +# Upgrade pip before doing anything else +pip install --upgrade pip + # Running OpenMC's setup.py requires numpy/cython already. NumPy float # formatting changed in version 1.14, so stick with a lower version until we can # handle it in our test suite diff --git a/src/faddeeva/Faddeeva.c b/vendor/faddeeva/Faddeeva.c similarity index 100% rename from src/faddeeva/Faddeeva.c rename to vendor/faddeeva/Faddeeva.c diff --git a/src/faddeeva/Faddeeva.cc b/vendor/faddeeva/Faddeeva.cc similarity index 100% rename from src/faddeeva/Faddeeva.cc rename to vendor/faddeeva/Faddeeva.cc diff --git a/src/faddeeva/Faddeeva.h b/vendor/faddeeva/Faddeeva.h similarity index 100% rename from src/faddeeva/Faddeeva.h rename to vendor/faddeeva/Faddeeva.h diff --git a/src/pugixml/pugiconfig.hpp b/vendor/pugixml/pugiconfig.hpp similarity index 100% rename from src/pugixml/pugiconfig.hpp rename to vendor/pugixml/pugiconfig.hpp diff --git a/src/pugixml/pugixml.cpp b/vendor/pugixml/pugixml.cpp similarity index 100% rename from src/pugixml/pugixml.cpp rename to vendor/pugixml/pugixml.cpp diff --git a/src/pugixml/pugixml.hpp b/vendor/pugixml/pugixml.hpp similarity index 100% rename from src/pugixml/pugixml.hpp rename to vendor/pugixml/pugixml.hpp