diff --git a/.gitignore b/.gitignore index 5eefc08d4..53d0fa31c 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,7 @@ examples/jupyter/plots *.c *.html *.so + +.cache/ +.tox/ +.python-version diff --git a/.travis.yml b/.travis.yml index 64fbdc9df..19bb96040 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,21 +28,7 @@ matrix: env: OPENMC_CONFIG="check_source" before_install: - # ============== Handle Python third-party packages ============== - - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; - else - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - fi - - bash miniconda.sh -b -p $HOME/miniconda - - export PATH="$HOME/miniconda/bin:$PATH" - - hash -r - - conda config --set always_yes yes --set changeps1 no - - conda update -q conda - - conda info -a - if [[ $OPENMC_CONFIG != "check_source" ]]; then - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION six numpy scipy h5py=2.5 pandas; - source activate test-environment; sudo add-apt-repository ppa:nschloe/hdf5-backports -y; sudo apt-get update -q; sudo apt-get install libhdf5-serial-dev libhdf5-mpich-dev -y; @@ -52,7 +38,11 @@ before_install: export HDF5_DIR=/usr; fi -install: true +install: + - if [[ $OPENMC_CONFIG != "check_source" ]]; then + pip install numpy cython; + pip install -e .[test]; + fi before_script: - if [[ $OPENMC_CONFIG != "check_source" ]]; then @@ -71,6 +61,7 @@ script: - if [[ $OPENMC_CONFIG == "check_source" ]]; then ./check_source.py; else - ./run_tests.py -C $OPENMC_CONFIG -j 2; + ./run_tests.py -C $OPENMC_CONFIG -j 2 && + pytest --cov=../openmc unit_tests/; fi - cd .. diff --git a/CMakeLists.txt b/CMakeLists.txt index 8364476cd..9f74b42df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,8 +119,8 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU) list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2) if(debug) list(REMOVE_ITEM f90flags -O2) - list(APPEND f90flags -g -Wall -pedantic -fbounds-check - -ffpe-trap=invalid,overflow,underflow) + list(APPEND f90flags -g -Wall -Wno-unused-dummy-argument -pedantic + -fbounds-check -ffpe-trap=invalid,overflow,underflow) list(APPEND ldflags -g) endif() if(profile) @@ -320,10 +320,8 @@ set(LIBOPENMC_FORTRAN_SRC src/endf_header.F90 src/energy_distribution.F90 src/error.F90 - src/finalize.F90 src/geometry.F90 src/geometry_header.F90 - src/global.F90 src/hdf5_interface.F90 src/initialize.F90 src/input_xml.F90 @@ -362,6 +360,8 @@ set(LIBOPENMC_FORTRAN_SRC src/secondary_nbody.F90 src/secondary_uncorrelated.F90 src/set_header.F90 + src/settings.F90 + src/simulation_header.F90 src/simulation.F90 src/source.F90 src/source_header.F90 @@ -370,21 +370,37 @@ set(LIBOPENMC_FORTRAN_SRC src/string.F90 src/summary.F90 src/surface_header.F90 - src/tally.F90 - src/tally_filter.F90 - src/tally_filter_header.F90 - src/tally_header.F90 - src/tally_initialize.F90 src/timer_header.F90 src/tracking.F90 src/track_output.F90 - src/trigger.F90 - src/trigger_header.F90 src/urr_header.F90 src/vector_header.F90 src/volume_calc.F90 src/volume_header.F90 - src/xml_interface.F90) + src/xml_interface.F90 + src/tallies/tally.F90 + src/tallies/tally_derivative_header.F90 + src/tallies/tally_filter.F90 + src/tallies/tally_filter_header.F90 + src/tallies/tally_filter_azimuthal.F90 + src/tallies/tally_filter_cell.F90 + src/tallies/tally_filter_cellborn.F90 + src/tallies/tally_filter_cellfrom.F90 + src/tallies/tally_filter_delayedgroup.F90 + src/tallies/tally_filter_distribcell.F90 + src/tallies/tally_filter_energy.F90 + src/tallies/tally_filter_energyfunc.F90 + src/tallies/tally_filter_material.F90 + src/tallies/tally_filter_mesh.F90 + src/tallies/tally_filter_mu.F90 + src/tallies/tally_filter_particle.F90 + src/tallies/tally_filter_polar.F90 + src/tallies/tally_filter_surface.F90 + src/tallies/tally_filter_universe.F90 + src/tallies/tally_header.F90 + src/tallies/trigger.F90 + src/tallies/trigger_header.F90 +) add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC}) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc) add_executable(${program} src/main.F90) @@ -415,7 +431,7 @@ target_link_libraries(${program} ${ldflags} libopenmc) add_custom_command(TARGET libopenmc POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ - ${CMAKE_CURRENT_SOURCE_DIR}/openmc/_$ + ${CMAKE_CURRENT_SOURCE_DIR}/openmc/capi/$ COMMENT "Copying libopenmc to Python module directory") #=============================================================================== @@ -455,6 +471,11 @@ file(GLOB_RECURSE TESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_*.py) # Loop through all the tests foreach(test ${TESTS}) + # Remove unit tests + if(test MATCHES ".*unit_tests.*") + continue() + endif() + # Get test information get_filename_component(TEST_NAME ${test} NAME) get_filename_component(TEST_PATH ${test} PATH) diff --git a/CTestConfig.cmake b/CTestConfig.cmake deleted file mode 100644 index e127a8ddf..000000000 --- a/CTestConfig.cmake +++ /dev/null @@ -1,25 +0,0 @@ -## This file should be placed in the root directory of your project. -## Then modify the CMakeLists.txt file in the root directory of your -## project to incorporate the testing dashboard. -## # The following are required to uses Dart and the Cdash dashboard -## ENABLE_TESTING() -## INCLUDE(CTest) - -# Generic information about CDASH site -set(CTEST_PROJECT_NAME "OpenMC") -set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") -set(CTEST_DROP_METHOD "http") -set(CTEST_DROP_SITE "openmc.mit.edu") -set(CTEST_DROP_LOCATION "/cdash/submit.php?project=OpenMC") -set(CTEST_DROP_SITE_CDASH TRUE) - -# Set file size larger to see more output -set(CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE "20000") -set(CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE "20000") - -# User/password to CDASH site -# Please contact Nick Horelik or -# Bryan Herman if you want to push -# test suite information to our CDASH site. -set(CTEST_DROP_SITE_USER "") -set(CTEST_DROP_SITE_PASSWORD "") diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..04348222f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,35 @@ +include CMakeLists.txt +include LICENSE +include schemas.xml +include openmc/data/reconstruct.pyx +include docs/source/_templates/layout.html +include docs/sphinxext/LICENSE +recursive-include . *.rst +recursive-include cmake *.cmake +recursive-include docs *.css +recursive-include docs *.dia +recursive-include docs *.png +recursive-include docs *.py +recursive-include docs *.svg +recursive-include docs *.tex +recursive-include docs *.txt +recursive-include docs Makefile +recursive-include examples *.h5 +recursive-include examples *.ipynb +recursive-include examples *.png +recursive-include examples *.py +recursive-include examples *.xml +recursive-include man *.1 +recursive-include src *.F90 +recursive-include src *.c +recursive-include src *.cc +recursive-include src *.cpp +recursive-include src *.h +recursive-include src *.hpp +recursive-include src *.rnc +recursive-include src *.rng +recursive-include tests *.dat +recursive-include tests *.h5 +recursive-include tests *.py +recursive-include tests *.xml +prune docs/build diff --git a/Makefile b/Makefile deleted file mode 100644 index 990f22d9d..000000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -all: - mkdir -p build - cmake -H. -Bbuild - make -s -C build -clean: - make -s -C build clean -distclean: - rm -fr build -test: - make -s -C build test -install: - make -s -C build install - -.PHONY: all clean distclean test install diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 9bb1b2c66..581f358d4 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -4,21 +4,32 @@ C API ===== -.. c:function:: void openmc_calculate_voumes() +.. c:function:: void openmc_calculate_volumes() Run a stochastic volume calculation -.. c:function:: int openmc_cell_set_temperature(int id, double T, int* instance) +.. c:function:: int openmc_cell_get_id(int32_t index, int32_t* id) + + Get the ID of a cell + + :param index: Index in the cells array + :type index: int32_t + :param id: ID of the cell + :type id: int32_t* + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_cell_set_temperature(index index, double T, int32_t* instance) Set the temperature of a cell. - :param id: ID of the cell - :type id: int + :param index: Index in the cells array + :type index: int32_t :param T: Temperature in Kelvin :type T: double :param instance: Which instance of the cell. To set the temperature for all instances, pass a null pointer. - :type instance: int* + :type instance: int32_t* :return: Return status (negative if an error occurred) :rtype: int @@ -26,7 +37,7 @@ C API Finalize a simulation -.. c:function:: void openmc_find(double* xyz, int rtype, int* id, int* instance) +.. c:function:: void openmc_find(double* xyz, int rtype, int32_t* id, int32_t* instance) Determine the ID of the cell/material containing a given point @@ -36,10 +47,65 @@ C API :type rtype: int :param id: ID of the cell/material found. If a material is requested and the point is in a void, the ID is 0. If an error occurs, the ID is -1. - :type id: int + :type id: int32_t* :param instance: If a cell is repetaed in the geometry, the instance of the cell that was found and zero otherwise. - :type instance: int + :type instance: int32_t* + +.. c:function:: int openmc_get_cell_index(int32_t id, int32_t* index) + + Get the index in the cells array for a cell with a given ID + + :param id: ID of the cell + :type id: int32_t + :param index: Index in the cells array + :type index: int32_t* + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_get_keff(double k_combined[]) + + :param k_combined: Combined estimate of k-effective + :type k_combined: double[2] + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_get_nuclide_index(char name[], int* index) + + Get the index in the nuclides array for a nuclide with a given name + + :param name: Name of the nuclide + :type name: char[] + :param index: Index in the nuclides array + :type index: int* + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_get_tally_index(int32_t id, int32_t* index) + + Get the index in the tallies array for a tally with a given ID + + :param id: ID of the tally + :type id: int32_t + :param index: Index in the tallies array + :type index: int32_t* + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_get_material_index(int32_t id, int32_t* index) + + Get the index in the materials array for a material with a given ID + + :param id: ID of the material + :type id: int32_t + :param index: Index in the materials array + :type index: int32_t* + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: void openmc_hard_reset() + + Reset tallies, timers, and pseudo-random number generator state .. c:function:: void openmc_init(int intracomm) @@ -57,13 +123,13 @@ C API :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_material_add_nuclide(int id, char name[], double density) +.. c:function:: int openmc_material_add_nuclide(int32_t index, char name[], double density) Add a nuclide to an existing material. If the nuclide already exists, the density is overwritten. - :param id: ID of the material - :type id: int + :param index: Index in the materials array + :type index: int32_t :param name: Name of the nuclide :type name: char[] :param density: Density in atom/b-cm @@ -71,28 +137,67 @@ C API :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_material_get_densities(int id, double* ptr) +.. c:function:: int openmc_material_get_densities(int32_t index, int* nuclides[], double* densities[]) - Get an array of nuclide densities for a material. + Get density for each nuclide in a material. - :param id: ID of the material - :type id: int - :param ptr: Pointer to the array of densities - :type ptr: double* - :return: Length of the array + :param index: Index in the materials array + :type index: int32_t + :param nuclides: Pointer to array of nuclide indices + :type nuclides: int** + :param densities: Pointer to the array of densities + :type densities: double** + :param n: Length of the array + :type n: int + :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_material_set_density(int id, double density) +.. c:function:: int openmc_material_get_id(int32_t index, int32_t* id) + + Get the ID of a material + + :param index: Index in the materials array + :type index: int32_t + :param id: ID of the material + :type id: int32_t* + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_material_set_density(int32_t index, double density) Set the density of a material. - :param id: ID of the material - :type id: int + :param index: Index in the materials array + :type index: int32_t :param density: Density of the material in atom/b-cm :type density: double :return: Return status (negative if an error occurs) :rtype: int +.. c:function:: int openmc_material_set_densities(int32_t, n, char* name[], double density[]) + + :param index: Index in the materials array + :type index: int32_t + :param n: Length of name/density + :type n: int + :param name: Array of nuclide names + :type name: char** + :param density: Array of densities + :type density: double[] + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_nuclide_name(int index, char* name[]) + + Get name of a nuclide + + :param index: Index in the nuclides array + :type index: int + :param name: Name of the nuclide + :type name: char** + :return: Return status (negative if an error occurs) + :rtype: int + .. c:function:: void openmc_plot_geometry() Run plotting mode. @@ -105,13 +210,52 @@ C API Run a simulation -.. c:function:: void openmc_tally_results(int id, double** ptr, int shape_[3]) +.. c:function:: int openmc_tally_get_id(int32_t index, int32_t* id) + + Get the ID of a tally + + :param index: Index in the tallies array + :type index: int32_t + :param id: ID of the tally + :type id: int32_t* + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_get_nuclides(int32_t index, int* nuclides[], int* n) + + Get nuclides specified in a tally + + :param index: Index in the tallies array + :type index: int32_t + :param nuclides: Array of nuclide indices + :type nuclides: int** + :param n: Number of nuclides + :type n: int* + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_results(int32_t index, double** ptr, int shape_[3]) Get a pointer to tally results array. - :param id: ID of the tally - :type id: int + :param index: Index in the tallies array + :type index: int32_t :param ptr: Pointer to the results array :type ptr: double** :param shape_: Shape of the results array :type shape_: int[3] + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_set_nuclides(int32_t index, int n, char* nuclides[]) + + Set the nuclides for a tally + + :param index: Index in the tallies array + :type index: int32_t + :param n: Number of nuclides + :type n: int + :param nuclides: Array of nuclide names + :type nuclides: char** + :return: Return status (negative if an error occurred) + :rtype: int diff --git a/docs/source/io_formats/tallies.rst b/docs/source/io_formats/tallies.rst index 9c0a2515e..35a205a35 100644 --- a/docs/source/io_formats/tallies.rst +++ b/docs/source/io_formats/tallies.rst @@ -123,9 +123,9 @@ to the scored values. The ``filter`` element has the following attributes/sub-elements: :type: - The type of the filter. Accepted options are "cell", "cellborn", - "material", "universe", "energy", "energyout", "mu", "polar", - "azimuthal", "mesh", "distribcell", "delayedgroup", and + The type of the filter. Accepted options are "cell", "cellfrom", + "cellborn", "surface", "material", "universe", "energy", "energyout", "mu", + "polar", "azimuthal", "mesh", "distribcell", "delayedgroup", and "energyfunction". :bins: @@ -154,14 +154,31 @@ For each filter type, the following table describes what the ``bins`` attribute should be set to: :cell: - A list of unique IDs for cells in which the tally should be accumulated. + A list of unique IDs for cells in which the tally should be + accumulated. + +:surface: + This filter allows the tally to be scored when crossing a surface. A list of + surface IDs should be given. By default, net currents are tallied, and to + tally a partial current from one cell to another, this should be used in + combination with a cell or cell_from filter that defines the other cell. + This filter should not be used in combination with a meshfilter. + +:cellfrom: + This filter allows the tally to be scored when crossing a surface and the + particle came from a specified cell. A list of cell IDs should be + given. + To tally a partial current from a cell to another, this filter should be + used in combination with a cell filter, to define the other cell. + This filter should not be used in combination with a meshfilter. :cellborn: This filter allows the tally to be scored to only when particles were - originally born in a specified cell. A list of cell IDs should be given. + originally born in a specified cell. A list of cell IDs should be + given. :material: - A list of unique IDs for matreials in which the tally should be accumulated. + A list of unique IDs for materials in which the tally should be accumulated. :universe: A list of unique IDs for universes in which the tally should be accumulated. diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index dde1c9119..e6cf1e1cc 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -117,6 +117,7 @@ Constructing Tallies openmc.UniverseFilter openmc.MaterialFilter openmc.CellFilter + openmc.CellFromFilter openmc.CellbornFilter openmc.SurfaceFilter openmc.MeshFilter diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index af00363cc..457f05320 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -2,16 +2,40 @@ :data:`openmc.capi` -- Python bindings to the C API --------------------------------------------------- +.. automodule:: openmc.capi + +Functions +--------- + .. autosummary:: :toctree: generated :nosignatures: :template: myfunction.rst - openmc.capi.lib_context + openmc.capi.calculate_volumes + openmc.capi.finalize + openmc.capi.find_cell + openmc.capi.find_material + openmc.capi.hard_reset + openmc.capi.init + openmc.capi.keff + openmc.capi.load_nuclide + openmc.capi.plot_geometry + openmc.capi.reset + openmc.capi.run + openmc.capi.run_in_memory + +Classes +------- .. autosummary:: :toctree: generated :nosignatures: :template: myclass.rst - openmc.capi.OpenMCLibrary + openmc.capi.Cell + openmc.capi.EnergyFilter + openmc.capi.MaterialFilter + openmc.capi.Material + openmc.capi.Nuclide + openmc.capi.Tally diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 497e09b4d..09fc4520c 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -414,16 +414,19 @@ distributions. various HDF5 files, h5py is needed to provide access to data within these files from Python. -.. admonition:: Optional - :class: note - `Matplotlib `_ Matplotlib is used to providing plotting functionality in the API like the :meth:`Universe.plot` method and the :func:`openmc.plot_xs` function. `uncertainties `_ - Uncertainties are optionally used for decay data in the :mod:`openmc.data` - module. + Uncertainties are used for decay data in the :mod:`openmc.data` module. + + `lxml `_ + lxml is used for the :ref:`scripts_validate` script and various other + parts of the Python API. + +.. admonition:: Optional + :class: note `Cython `_ Cython is used for resonance reconstruction for ENDF data converted to @@ -437,8 +440,8 @@ distributions. The silomesh package is needed to convert voxel and track files to SILO format. - `lxml `_ - lxml is used for the :ref:`scripts_validate` script. + `pytest `_ + The pytest framework is used for unit testing the Python API. .. _usersguide_nxml: diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index 3ee2e99d7..3a742b59e 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -261,12 +261,19 @@ The following tables show all valid scores: +----------------------+---------------------------------------------------+ |Score | Description | +======================+===================================================+ - |current |Partial currents on the boundaries of each cell in | - | |a mesh. Units are particles per source | - | |particle. Note that this score can only be used if | - | |a mesh filter has been specified. Furthermore, it | - | |may not be used in conjunction with any other | - | |score. | + |current |Used in combination with a mesh filter: | + | |Partial currents on the boundaries of each cell in | + | |a mesh. It may not be used in conjunction with any | + | |other score. Only energy and mesh filters may be | + | |used. | + | |Used in combination with a surface filter: | + | |Net currents on any surface previously defined in | + | |the geometry. It may be used along with any other | + | |filter, except mesh filters. | + | |Surfaces can alternatively be defined with cell | + | |from and cell filters thereby resulting in tallying| + | |partial currents. | + | |Units are particles per source particle. | +----------------------+---------------------------------------------------+ |events |Number of scoring events. Units are events per | | |source particle. | diff --git a/examples/jupyter/mgxs-part-ii.ipynb b/examples/jupyter/mgxs-part-ii.ipynb index b12cc4143..b03838477 100644 --- a/examples/jupyter/mgxs-part-ii.ipynb +++ b/examples/jupyter/mgxs-part-ii.ipynb @@ -131,7 +131,7 @@ "outputs": [], "source": [ "# Instantiate a Materials collection\n", - "materials_file = openmc.Materials((fuel, water, zircaloy))\n", + "materials_file = openmc.Materials([fuel, water, zircaloy])\n", "\n", "# Export to \"materials.xml\"\n", "materials_file.export_to_xml()" diff --git a/examples/jupyter/mgxs-part-iii.ipynb b/examples/jupyter/mgxs-part-iii.ipynb index 65d8a069f..addca822f 100644 --- a/examples/jupyter/mgxs-part-iii.ipynb +++ b/examples/jupyter/mgxs-part-iii.ipynb @@ -134,7 +134,7 @@ "outputs": [], "source": [ "# Instantiate a Materials object\n", - "materials_file = openmc.Materials((fuel, water, zircaloy))\n", + "materials_file = openmc.Materials([fuel, water, zircaloy])\n", "\n", "# Export to \"materials.xml\"\n", "materials_file.export_to_xml()" diff --git a/examples/jupyter/nuclear-data.ipynb b/examples/jupyter/nuclear-data.ipynb index 4302e5295..7087681a0 100644 --- a/examples/jupyter/nuclear-data.ipynb +++ b/examples/jupyter/nuclear-data.ipynb @@ -205,7 +205,7 @@ { "data": { "text/plain": [ - "{'294K': }" + "{'294K': }" ] }, "execution_count": 8, @@ -315,7 +315,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 12, @@ -324,9 +324,9 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n1QAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -422,7 +422,7 @@ { "data": { "text/plain": [ - "{'294K': }" + "{'294K': }" ] }, "execution_count": 15, @@ -453,9 +453,9 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -510,7 +510,7 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, "execution_count": 18, @@ -540,36 +540,36 @@ { "data": { "text/plain": [ - "[,\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", + " ]" ] }, "execution_count": 19, @@ -598,9 +598,9 @@ "outputs": [ { "data": { - "image/png": "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\nRK5CYII=\n", + "image/png": "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\n2oFEPhuJq6XrnVymJEmSJN2xhk5Ad9scKZJutidsp/+a/vg4+hDxdMStg5YtW2DKFHj+eZg3r9rh\n/0tPZ8Dhw7yYkECz/ft5JymJnJKSfyqMGgXff1++vDR6NNYqU7aHbkdRFB5b9xiXCy430BVKkiRJ\n0v1BBi53aP2x9QwJH0J/z/5sD92OlbGV9op79kBQEDzxBHz2Gdxwh5FaCOacPcvk06eZ0rQpZ7t2\nZYyjIx+mpOC+bx+vJCSQXlRUXnnECPjxR9i2DZ58EhcjO7aHbq/KyltYWq/PspIkSZKk+4oMXO7A\nioMrCPkhhOD2wfww6gdMDEy0Vzx2DIYMgW7d4LvvNPK0FKnVhMbH82FKCp94efG5tzceJiZ85u1N\ncrduzHR15euMDDz272fyqVMkFhTA4MGwdWv5pt1hw2hj3pyfg38mKi2KhbsX3qWrlyRJ0s3q1atR\nqVRaX3p6ehw4cKBe+snMzGTy5Mm4urpiYmKCh4cHz2lZkq/JwoULUalU+PhoubkCiIyMpFevXpiZ\nmeHs7MyMGTPIy8ursd3k5OSq6160aJHWOqGhoahUKiwtLWs9bh8fH5o3b37bOj179sTZ2Rm1Wl3r\n9u8Xco+LDrRtzt15difTfpnGzK4z+WTgJ6iUW8SAKSnw6KPQrBn89FP5rc0VsktKGHHsGAevX+f7\ndu140t5e41R7Q0Pe8/TkVXd3vkxPZ0lqKl9nZDDawYHXe/bEZ9u28oBo8GC6b93KnF5zWPT3IkI6\nhMjNupIk3VcURWHBggVaf7G2aNHijts/f/48PXr0QKVSMXXqVFxcXEhPT691UJSWlsb777+Pubm5\n1uNxcXH079+ftm3bsnTpUs6fP8/ixYtJSEhg27ZtOvVhYmJCeHg4c+fO1SjPz89n69atmJjc4kNw\nDcaMGcOcOXPYs2cPvXr1qnY8OTmZ/fv3M336dFSqhpu3aOjNuQgh5OsWL8APEDExMeJms3+fLVw+\ncRFqtbrasSqFhUJ06iRE8+ZCpKdrHErMzxct9+8Xdnv2iMgrV7SefmXPFRE3IE4kvJYgSq6WiPzS\nUvHF+fOiWWSkICJCzE5IEOq//hLCzEyIwEBRcC1HeC/zFoHfBt5+XJIk3XdiYmLErX7eNHarVq0S\nKpWqQa/tscceE15eXuLy5ct31M7o0aNF//79RZ8+fUSHDh209uPi4iJyc3Oryr7++muhUqnE77//\n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\n+APeIHSDgel4A791B147yK4J6bpxnXj55ZfVOLcJirOVuETdqFFe4hLBmEuJ7PjCQt4ZPJiXjjqK\nFbt2MaS8nIvmzWO5P7uuiMSneGqcG2occKVz7hq8cVvucc6NwBtqPyqDz8WTzHTNUdRUxTnFbNy5\nMegwksuoUbBqFcydG3Qkzc7M+HrbtswdOpRHe/dm6qZN9PngA360eDGV1TGfWURE4lCkkyx2Bd7z\nf64C6icb+SMwDbj6MOOKK5ktlLg0VX1VkXNO3Tuj5eSTITfXK3Xx51tJNRlpaVzVqRMXtGvHb1at\n4t4VK3hy7Vp+3q0bV3fuTFZa8jTXa2wuH5F4FNRnNdLEZR1QDCwHVgDHARVAKZB031Yt8hpOQC0H\n0jq3NbWulm17ttGyRcugw0kOLVrA6ad7icuNNwYdTaDyMzK4taSEqzp25Lbly/nR4sU8vmYNE3r2\n5KzWrYMO77C0adOG3Nxcxo4dG3QoIk2Wm5tLmzZtmvWckSYubwGjgVl4bV0mmNk3gWPwxnJJKr++\n512eemY0Y8aMUTuXQwgd9l+JSxSNGgU/+AFs3gytkn8m4UPp0KIFj/buzbhOnfifRYsY9dFHjCou\nZkLPnvTOzQ06vIh07dqVefPm8fnnn4e9766Vu1h681J2zN1Bx8s70vHyjqRlJE8pVDxzzlFZ+Sar\nVt1PdfVG2re/iI4dLyM9/fBmWE4Ubdq0oWvXrvusi/Xs0JFOspgGpDnnavzX5wMnAAuBx5xzUZuv\nKEj1kyy+/vrdnH76j4MOJyHMXDuTssfLKL+ynCEdNVFc1KxYAd26wQsvwLe+FXQ0ccU5x4uff871\nixaxZs8eru3cmZtLSijMiPS5LDHV1dSx4tcrWHbbMgrKCuj3p37k9krMJC4R1dbuZMWKu1ix4h6y\nstrRq9dDtGnztaDDClRcTbLonKurT1r818855651zj2ULElLqMzMdkGHkDA00WKMdO0K/funVLfo\npjIzvtG2LfOGDeOWbt14dM0aen/wAU+uXUtdCvXESstIo+TmEoa8N4SaTTXMOHoGax5fQyQPpxK+\n9PRcSkt/ybBhn5CXN5C5c7/OggXfo7Z2Z9ChJZ3DGcelyMxuMLMn/eV6MyuOZnDxIitLjXObSolL\nDI0aBa+9BnV1QUcSl3LS0/l5SQkLhg3j9KIiLl+wgGHl5bwbo+LqeNVyWEvKZpXRfmx7Pr3qU+Zd\n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\nNnoOAAAAAElFTkSuQmCC\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -660,7 +660,7 @@ { "data": { "text/plain": [ - "{'294K': }" + "{'294K': }" ] }, "execution_count": 22, @@ -691,7 +691,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 23, @@ -700,9 +700,9 @@ }, { "data": { - "image/png": "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\nYII=\n", + "image/png": "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\nln8MOElr/feVGtxCwDAMrr76atatW8f73/9+fve73/H5z3+eN73pTfj9fiYmJrj88st59NFHue++\n+zJapBfCunXr2LdvH5OTkzniZzEwPT1NIBDIKPfu7OxkdHR0/nk0Gs0QKdX2pOSb9ThdpBSa7ikm\nklLN6p56iJR6yaKAMjl1yVLOXLoUgH3RKPcMDvKHgQG+/fJL3BR/jhbTxyldvWzsWsLGrl5eEWms\nz2AorDjxlAjxuPtVTMQ1wwN2TtVQtSq2o3sm+OnRWwlmtepvP6KbM267sKRjBkwwwLUAuiVs0xJ2\n/1wbpvv10YZCZ8VMFDplVnHFqbdKOtJnpTBKeRu+Bvisw/I7SU462LRUorrHjXe84x1s3LiRD3/4\nw2zatIn29nZWrlzJSy+9RCKR4Ac/+AEnnXRS0cdNTUL4l7/8hRNOOKGiY24GotFoRhQFoKurK0Ok\nuEVSapXuyY6kJBKJ+fF4iZTsEmmnY2cvr3Z1T6Ole6rpb7UTiskRY76wo40Wzmtt4bzWlViv0Dw3\nM869w/3cMzzAF557krjWLA2EOKm9h9UznRzr72aJGfY+SQ0IhgyCHrMHzMY04yM6p2ooWfGmMYzM\n5bbtXvBSKNO7JpjeldmqX0/G0LOJkjrZRnyKsw/xMeuQYRmJabbvLTH1EtSO6sfyFVLW7M1C6LPS\nUNU9abTiXGocx3teqYanUtU9bqxdu5Y77riDxx9/nDvuuIP+/n6WL1/O29/+9vmeJ6UcExavSJme\nns7p7tvZ2ZlhJs4WKW6RlGqle5wiKam+Ll7N3CqV7imGQiMp9eiX4nRTNBS0thuO6YxwK5Bnajml\n5la7XCptK8dVhlIc097JMe2dvGfVOqatBI+MDnP/yAD3Dw/y86k9aOAgI8KxgW6O83dzjL+bbrN5\n/CyJBLz0fAwzS7wEggqfL+AoEE0fdPSUdudO9E/Q9w+3YbQnhV3cF+SQz70J31xaKNY3jd1qYQSd\nUygRnyLicEcrpM+Kz6cxlMbO6jWjwgr/2QY66443GzPYu7MF06HznC9msWTfZM5yJ/bsyb25N1Ma\nqNGqe1L8GXgr8Lms5W8DdpQ9okXA+vXrWb9+fUWO1d3dTU9PD88++2xFjtdsOEVSstM9hXpSap3u\nAfdIimEYJRtns89XDU9KLSMpSs3NeWMoWlpNZ/+E0w3TANOvPSfKCYZhxSEBxzb1fo+usNnWyYjp\n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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -990,7 +990,7 @@ { "data": { "text/plain": [ - "{'0K': }" + "{'0K': }" ] }, "execution_count": 31, @@ -1048,8 +1048,8 @@ { "data": { "text/plain": [ - "[,\n", - " ]" + "[,\n", + " ]" ] }, "execution_count": 33, @@ -1107,7 +1107,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -1116,9 +1116,9 @@ }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiYAAAGBCAYAAABSP3qNAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAAPYQAAD2EBqD+naQAAIABJREFUeJzt3WmYHGX19/HvyWQShiQkhEDYFQQhRogkhLAF5I+AoIwg\n6zzsixACAqOAyqqIqCxOAAmCLAGBgaCoQTYFEUQ2SVhkFQQkskSWZIAkkGXu58Xd5dT09D5VXdXd\nv8919dXd1dVVpzJJ6sy5N3POISIiIpIGA5IOQERERCSgxERERERSQ4mJiIiIpIYSExEREUkNJSYi\nIiKSGkpMREREJDWUmIiIiEhqKDERERGR1FBiIiIiIqmhxERERERSQ4mJiIiIpEbdJSZm1mJmr5nZ\neUnHIiIiIuWpu8QEOA14OOkgREREpHx1lZiY2QbARsCdScciIiIi5aurxAS4APgeYEkHIiIiIuVL\nRWJiZpPNbJaZvWFm3WbWmmOfY83sVTNbbGaPmNnErM9bgRedcy8Hm6oRu4iIiEQnFYkJMAR4EpgK\nuOwPzWw/4ELgLGAz4CngbjMbFdptS2B/M3sFXzk50sxOjztwERERiY451ycPSJSZdQN7OOdmhbY9\nAjzqnDsh896AucDFzrk+o2/M7BBgrHPulDznWAXYBXgN+DjyixAREalfKwCfBu52zr0X9cEHRn3A\nqJlZMzABODfY5pxzZnYPsFWFh90FuCGC8ERERBrVAcCNUR809YkJMApoAuZlbZ+HH4HTh3Pu2iLH\nfA3g+uuvZ8yYMf0Krr29nY6Ojqodo9C++T6rdoxRHEfXqevUdcYTYxTHKLZfrs91nfHEGNUxyvm7\n+/zzz3PggQdC5l4atVpITOLwMcCYMWMYP358vw40fPjwqh6j0L75Pqt2jFEcR9ep69R1xhNjFMco\ntl+uz3Wd8cQY1TEq+btLTF0haiExeRdYDozO2j4aeLs/B25vb2f48OG0tbXR1tZW0TEq/V6lxyi0\nbxSxxH3sUo+j69R1RqFRrjOq40dxnVHFEuexdZ3l7Rt81tnZSWdnJ11dXf2OrSDnXKoeQDfQmrXt\nEeCi0Pug8+vJFZ5jPOBmz57tGsHuu++edAhVoeusL7rO+qLrrB+zZ892+BG0410MeUAqKiZmNgTY\ngJ65R9Y3s3HA+865ucDPgBlmNht4DGgHVgRmJBCuiIiIxCQViQmwOXAfPgNz+DlLAK4FDnfOzczM\nWXI2vgnnSWAX59w7SQRba+IuIaeFrrO+6Drri65TSpW6eUyqwczGA7O32267fvcxERERaQThPiYP\nPPAAwATn3Jyoz9PQicns2bMj6T0tIiLSKObMmcOECRMgpsQkLVPSi4iIiCgxERERkfRQYiIiIiKp\nkZZROYmIYoI1ERGRRlCtCdbU+VWdX0VEREqmzq8iIiLSMJSYiIiISGooMREREZHUUOdXdX4VEREp\nSp1fY6TOryIiIpVR51cRERFpGEpMREREJDWUmIiIiEhqKDERERGR1NCoHI3KERERKUqjcmKkUTki\nIiKV0agcERERaRhKTERERCQ1lJiIiIhIaigxERERkdRQYiIiIiKpocREREREUkPzmGgeExERkaI0\nj0mMNI+JiIhIZTSPiYiIiDQMJSYiIiKSGkpMREREJDWUmIiIiEhqKDERERGR1FBiIiIiIqmhxERE\nRERSQxOsaYI1ERGRojTBWow0wZqIiEhlNMGaiIiINAwlJiIiIpIaSkxEREQkNZSYiIiISGooMRER\nEZHUUGIiIiIiqaHERERERFJDiYmIiIikhhITERERSQ0lJiIiIpIaSkxEREQkNZSYiIiISGpodWGt\nLiwiIlKUVheOkVYXFhERqYxWFxYREZGGocREREREUkOJiYiIiKSGEhMRERFJDSUmIiIikhpKTERE\nRGLy2GNw881JR1FbGnoeExERkThNmuSf99sv2ThqiSomIiIikhpKTERERCQ1lJiIiIhIaigxERER\nkdRQYiIiIiKpocREREREUkOJiYiISBW9+WbSEaRb3SQmZjbczP5uZnPM7GkzOzLpmERERML++EdY\nay14/PGkI0mvuklMgA+Ayc658cAk4FQzWznhmERERP7n2Wf986uvJhtHmtXNzK/OOQd8nHnbknm2\nhMIRERHJy7mkI0iveqqYBM05TwKvA+c7595POiYREREpXSoSEzObbGazzOwNM+s2s9Yc+xxrZq+a\n2WIze8TMJmbv45zrcs59AVgPOMDMVq1G/CIiIuUw1fPzSkViAgwBngSmAn0KXGa2H3AhcBawGfAU\ncLeZjcp1MOfcO5l9JscVsEijcA4WLYIPPvAPlaBFJE6p6GPinLsLuAvALGce2Q5c7py7LrPPFOAr\nwOHAeZltqwGLnHMfmdlwYDtgehXCF6kL8+fD7Nm+c96zz8Jzz8Ebb8Dbb8PHH/fs19wMo0fDmDEw\n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\nRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1336,8 +1336,8 @@ { "data": { "text/plain": [ - "{'0K': ,\n", - " '294K': }" + "{'0K': ,\n", + " '294K': }" ] }, "execution_count": 38, @@ -1448,12 +1448,156 @@ "source": [ "Note that 0 K elastic scattering data is automatically added when using `from_njoy()` so that resonance elastic scattering treatments can be used." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Windowed multipole\n", + "\n", + "OpenMC can also be used with an experimental format called windowed multipole. Windowed multipole allows for analytic on-the-fly Doppler broadening of the resolved resonance range. Windowed multipole data can be downloaded with the `openmc-get-multipole-data` script. This data can be used in the transport solver, but it can also be used directly in the Python API." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "url = 'https://anl.box.com/shared/static/ulhcoohm12gduwdalknmf8dpnepzkxj0.h5'\n", + "filename, headers = urllib.request.urlretrieve(url, '092238.h5')" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "u238_multipole = openmc.data.WindowedMultipole.from_hdf5('092238.h5')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `WindowedMultipole` object can be called with energy and temperature values. Calling the object gives a tuple of 3 cross sections: total, radiative capture, and fission." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(array(9.638243132516015),\n", + " array(0.5053244245010787),\n", + " array(2.931753364280356e-06))" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "u238_multipole(1.0, 294)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An array can be passed for the energy argument." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "E = np.linspace(5, 25, 1000)\n", + "plt.semilogy(E, u238_multipole(E, 293.606)[1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The real advantage to multipole is that it can be used to generate cross sections at any temperature. For example, this plot shows the Doppler broadening of the 6.67 eV resonance between 0 K and 900 K." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\nAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "E = np.linspace(6.1, 7.1, 1000)\n", + "plt.semilogy(E, u238_multipole(E, 0)[1])\n", + "plt.semilogy(E, u238_multipole(E, 900)[1])" + ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python [default]", + "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/examples/jupyter/pandas-dataframes.ipynb b/examples/jupyter/pandas-dataframes.ipynb index 8c409822e..72d5d7313 100644 --- a/examples/jupyter/pandas-dataframes.ipynb +++ b/examples/jupyter/pandas-dataframes.ipynb @@ -79,7 +79,7 @@ "outputs": [], "source": [ "# Instantiate a Materials collection\n", - "materials_file = openmc.Materials((fuel, water, zircaloy))\n", + "materials_file = openmc.Materials([fuel, water, zircaloy])\n", "\n", "# Export to \"materials.xml\"\n", "materials_file.export_to_xml()" diff --git a/examples/jupyter/post-processing.ipynb b/examples/jupyter/post-processing.ipynb index 72fb73758..b0e955298 100644 --- a/examples/jupyter/post-processing.ipynb +++ b/examples/jupyter/post-processing.ipynb @@ -105,7 +105,7 @@ "outputs": [], "source": [ "# Instantiate a Materials collection\n", - "materials_file = openmc.Materials((fuel, water, zircaloy))\n", + "materials_file = openmc.Materials([fuel, water, zircaloy])\n", "\n", "# Export to \"materials.xml\"\n", "materials_file.export_to_xml()" diff --git a/examples/jupyter/search.ipynb b/examples/jupyter/search.ipynb index b84411e20..5c754049a 100644 --- a/examples/jupyter/search.ipynb +++ b/examples/jupyter/search.ipynb @@ -71,7 +71,7 @@ " water.add_element('B', ppm_Boron * 1E-6)\n", " \n", " # Instantiate a Materials object\n", - " materials = openmc.Materials((fuel, zircaloy, water))\n", + " materials = openmc.Materials([fuel, zircaloy, water])\n", " \n", " # Create cylinders for the fuel and clad\n", " fuel_outer_radius = openmc.ZCylinder(R=0.39218)\n", diff --git a/examples/jupyter/tally-arithmetic.ipynb b/examples/jupyter/tally-arithmetic.ipynb index 1b189769d..de5917d7b 100644 --- a/examples/jupyter/tally-arithmetic.ipynb +++ b/examples/jupyter/tally-arithmetic.ipynb @@ -11,7 +11,7 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -19,7 +19,6 @@ "\n", "from IPython.display import Image\n", "import numpy as np\n", - "\n", "import openmc" ] }, @@ -65,7 +64,7 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -100,12 +99,12 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ "# Instantiate a Materials collection\n", - "materials_file = openmc.Materials((fuel, water, zircaloy))\n", + "materials_file = openmc.Materials([fuel, water, zircaloy])\n", "\n", "# Export to \"materials.xml\"\n", "materials_file.export_to_xml()" @@ -122,7 +121,7 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -151,7 +150,7 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -188,7 +187,7 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -215,7 +214,7 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -228,7 +227,7 @@ "cell_type": "code", "execution_count": 9, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -283,7 +282,7 @@ "cell_type": "code", "execution_count": 11, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -310,9 +309,7 @@ { "cell_type": "code", "execution_count": 12, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -333,13 +330,11 @@ { "cell_type": "code", "execution_count": 13, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX////pgJFyEhJNv8RV\nUZDeAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EEDRUZLksd2dYAAALKSURBVGje7dpLcqQwDAbgHHE2\nYeEj+D4cwQucBUfo+3CEXoSp8OhuhF70T4qpKXmdr21LogK2Pj7A8QmNP+HDhw8fPnz48Kf6VH9G\n+66vy+je8k19jnf8C5dXIPv86ms56lPdjvaYbyodx3ze+XLE76cXFiD4zPji99z0/AJ4n1lfvJ6f\nnl0A6x+578efMSg1wPr172/jPO5yFXM+Ef78gdblM+WPHyguP//t1/g6pA0wfln+ho/fwgYYn19C\n/xwDvwHGc9OvC+hs37DTrwuwfWanXxdQTC9Mvyygs3wjTL8uwPJpn/tNDbSGz7T0SBEWw4vLXzbQ\n6b6RoveIoO6TvPxlA63qs7z8ZQPF9F+SH22vbX8OQKf5Rtv+EgDNJ3X58wZaxWd1+fMGiuFvir8b\nvjp8J/tGy/6jAmRvhW8fwL3vVT+o3grfPoB7r/IpALI3tz8FoJN84/NV873hB8UnM3xzANtf8nb4\ndwmg3grfFEDJO8JPE0i9Ff4pAYL3pI8mkHor/HMCeO9JH00g9SafEsh7T/ppARBvp48UwJnelT5S\nACd7O31TAlnvKx9SQCd7B58KgPO+8iMFuPWe9E8F8BveWX7bAjzX9y4//Jve+fhsH6Ctv7n8PTzj\nvY/v9gEOHz58+PBX+6v/f/wPvnd54f3j6venE/yl769Xv7+j3x/o98/V32/o9+fl389Xnx+g5x/o\n+Qt6/oOeP6HnX+j5G3z+h54/ouefV5/foufP6Pk3ev4On/+j9w/o/Qd6/4Le/6D3T/D9V67Y/ZsV\nQBq+s+8f0ftP+P41axXguP9NWgDuu/Cdfv+N3r/D9/9TAID+A7T/Ae2/gPs/0P4TtP8F7r9J3AIO\n9P+g/Udw/9Oygbf7r9D+L7j/DO1/Q/vv4P4/tP8Q7n9E+y/h/k+0/xTuf4X7b+H+X7T/+BPuf3aM\n8OHDhw8fPnz4w/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMDQtMTNUMTc6MjU6\nNDUtMDQ6MDCJ1tNgAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTA0LTEzVDE3OjI1OjQ1LTA0OjAw\n+Itr3AAAAABJRU5ErkJggg==\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8Tq\nQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EKGA0jE/weoLoAAALKSURBVGje7dpLcqQwDAbgHHE2\nYeEj+D4cwQucBUfo+3CEXoSp8OhuhF70T4qpKXmdr21LogK2Pj7A8QmNP+HDhw8fPnz48Kf6VH9G\n+66vy+je8k19jnf8C5dXIPv86ms56lPdjvaYbyodx3ze+XLE76cXFiD4zPji99z0/AJ4n1lfvJ6f\nnl0A6x+578efMSg1wPr172/jPO5yFXM+Ef78gdblM+WPHyguP//t1/g6pA0wfln+ho/fwgYYn19C\n/xwDvwHGc9OvC+hs37DTrwuwfWanXxdQTC9Mvyygs3wjTL8uwPJpn/tNDbSGz7T0SBEWw4vLXzbQ\n6b6RoveIoO6TvPxlA63qs7z8ZQPF9F+SH22vbX8OQKf5Rtv+EgDNJ3X58wZaxWd1+fMGiuFvir8b\nvjp8J/tGy/6jAmRvhW8fwL3vVT+o3grfPoB7r/IpALI3tz8FoJN84/NV873hB8UnM3xzANtf8nb4\ndwmg3grfFEDJO8JPE0i9Ff4pAYL3pI8mkHor/HMCeO9JH00g9SafEsh7T/ppARBvp48UwJnelT5S\nACd7O31TAlnvKx9SQCd7B58KgPO+8iMFuPWe9E8F8BveWX7bAjzX9y4//Jve+fhsH6Ctv7n8PTzj\nvY/v9gEOHz58+PBX+6v/f/wPvnd54f3j6venE/yl769Xv7+j3x/o98/V32/o9+fl389Xnx+g5x/o\n+Qt6/oOeP6HnX+j5G3z+h54/ouefV5/foufP6Pk3ev4On/+j9w/o/Qd6/4Le/6D3T/D9V67Y/ZsV\nQBq+s+8f0ftP+P41axXguP9NWgDuu/Cdfv+N3r/D9/9TAID+A7T/Ae2/gPs/0P4TtP8F7r9J3AIO\n9P+g/Udw/9Oygbf7r9D+L7j/DO1/Q/vv4P4/tP8Q7n9E+y/h/k+0/xTuf4X7b+H+X7T/+BPuf3aM\n8OHDhw8fPnz4w/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTAtMjRUMTM6MzU6\nMTktMDU6MDCdcfAWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEwLTI0VDEzOjM1OjE5LTA1OjAw\n7CxIqgAAAABJRU5ErkJggg==\n", "text/plain": [ "" ] @@ -368,7 +363,7 @@ "cell_type": "code", "execution_count": 14, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -380,7 +375,7 @@ "cell_type": "code", "execution_count": 15, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -457,7 +452,7 @@ "cell_type": "code", "execution_count": 17, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -472,7 +467,7 @@ "cell_type": "code", "execution_count": 18, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ @@ -522,10 +517,25 @@ { "cell_type": "code", "execution_count": 21, - "metadata": { - "collapsed": false - }, - "outputs": [], + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=1.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another MeshFilter instance already exists with id=5.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=6.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=3.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=2.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "# Export to \"tallies.xml\"\n", "tallies_file.export_to_xml()" @@ -542,7 +552,6 @@ "cell_type": "code", "execution_count": 22, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [ @@ -578,25 +587,24 @@ " | The OpenMC Monte Carlo Code\n", " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | bc4683be1c853fe6d0e31bccad416ef219e3efaf\n", - " Date/Time | 2017-04-13 17:26:05\n", - " MPI Processes | 1\n", - " OpenMP Threads | 1\n", + " Version | 0.9.0\n", + " Git SHA1 | 5ca1d06b0c6ac3b56060ef289b7e5215210e7332\n", + " Date/Time | 2017-10-24 13:35:19\n", + " OpenMP Threads | 4\n", "\n", " Reading settings XML file...\n", - " Reading geometry XML file...\n", " Reading materials XML file...\n", " Reading cross sections XML file...\n", - " Reading U235 from /home/smharper/openmc/data/nndc_hdf5/U235.h5\n", - " Reading U238 from /home/smharper/openmc/data/nndc_hdf5/U238.h5\n", - " Reading O16 from /home/smharper/openmc/data/nndc_hdf5/O16.h5\n", - " Reading H1 from /home/smharper/openmc/data/nndc_hdf5/H1.h5\n", - " Reading B10 from /home/smharper/openmc/data/nndc_hdf5/B10.h5\n", - " Reading Zr90 from /home/smharper/openmc/data/nndc_hdf5/Zr90.h5\n", + " Reading geometry XML file...\n", + " Building neighboring cells lists for each surface...\n", + " Reading U235 from /home/romano/openmc/scripts/nndc_hdf5/U235.h5\n", + " Reading U238 from /home/romano/openmc/scripts/nndc_hdf5/U238.h5\n", + " Reading O16 from /home/romano/openmc/scripts/nndc_hdf5/O16.h5\n", + " Reading H1 from /home/romano/openmc/scripts/nndc_hdf5/H1.h5\n", + " Reading B10 from /home/romano/openmc/scripts/nndc_hdf5/B10.h5\n", + " Reading Zr90 from /home/romano/openmc/scripts/nndc_hdf5/Zr90.h5\n", " Maximum neutron transport energy: 2.00000E+07 eV for U235\n", " Reading tallies XML file...\n", - " Building neighboring cells lists for each surface...\n", " Initializing source particles...\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -627,20 +635,20 @@ "\n", " =======================> TIMING STATISTICS <=======================\n", "\n", - " Total time for initialization = 2.2576E-01 seconds\n", - " Reading cross sections = 1.8005E-01 seconds\n", - " Total time in simulation = 9.6105E+00 seconds\n", - " Time in transport only = 9.5952E+00 seconds\n", - " Time in inactive batches = 1.4678E+00 seconds\n", - " Time in active batches = 8.1427E+00 seconds\n", - " Time synchronizing fission bank = 2.6325E-03 seconds\n", - " Sampling source sites = 1.5038E-03 seconds\n", - " SEND/RECV source sites = 8.8069E-04 seconds\n", - " Time accumulating tallies = 2.0568E-04 seconds\n", - " Total time for finalization = 1.5435E-03 seconds\n", - " Total time elapsed = 9.8506E+00 seconds\n", - " Calculation Rate (inactive) = 8516.30 neutrons/second\n", - " Calculation Rate (active) = 4605.35 neutrons/second\n", + " Total time for initialization = 4.1497E-01 seconds\n", + " Reading cross sections = 3.6232E-01 seconds\n", + " Total time in simulation = 3.6447E+00 seconds\n", + " Time in transport only = 3.5939E+00 seconds\n", + " Time in inactive batches = 4.4241E-01 seconds\n", + " Time in active batches = 3.2022E+00 seconds\n", + " Time synchronizing fission bank = 2.7734E-03 seconds\n", + " Sampling source sites = 1.1981E-03 seconds\n", + " SEND/RECV source sites = 1.5506E-03 seconds\n", + " Time accumulating tallies = 1.2237E-04 seconds\n", + " Total time for finalization = 1.4924E-03 seconds\n", + " Total time elapsed = 4.0823E+00 seconds\n", + " Calculation Rate (inactive) = 28254.0 neutrons/second\n", + " Calculation Rate (active) = 11710.5 neutrons/second\n", "\n", " ============================> RESULTS <============================\n", "\n", @@ -689,7 +697,7 @@ "cell_type": "code", "execution_count": 23, "metadata": { - "collapsed": false, + "collapsed": true, "scrolled": true }, "outputs": [], @@ -710,14 +718,25 @@ { "cell_type": "code", "execution_count": 24, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "\n", " \n", " \n", @@ -777,14 +796,25 @@ { "cell_type": "code", "execution_count": 25, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
\n", " \n", " \n", @@ -803,7 +833,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -812,11 +842,11 @@ "" ], "text/plain": [ - " energy low [eV] energy high [eV] nuclide score mean \\\n", - "0 0.00e+00 6.25e-01 total (absorption + current) 6.94e-01 \n", + " energy low [eV] energy high [eV] nuclide \\\n", + "0 0.00e+00 6.25e-01 total \n", "\n", - " std. dev. \n", - "0 4.61e-03 " + " score mean std. dev. \n", + "0 ((absorption + current) / (absorption + current)) 6.94e-01 4.61e-03 " ] }, "execution_count": 25, @@ -845,14 +875,25 @@ { "cell_type": "code", "execution_count": 26, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
0.00.625total(absorption + current)((absorption + current) / (absorption + current))0.6943680.004606
\n", " \n", " \n", @@ -871,7 +912,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -880,8 +921,11 @@ "" ], "text/plain": [ - " energy low [eV] energy high [eV] nuclide score mean std. dev.\n", - "0 0.00e+00 6.25e-01 total nu-fission 1.20e+00 9.61e-03" + " energy low [eV] energy high [eV] nuclide score \\\n", + "0 0.00e+00 6.25e-01 total (nu-fission / nu-fission) \n", + "\n", + " mean std. dev. \n", + "0 1.20e+00 9.61e-03 " ] }, "execution_count": 26, @@ -908,14 +952,25 @@ { "cell_type": "code", "execution_count": 27, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
0.00.625totalnu-fission(nu-fission / nu-fission)1.2030990.009615
\n", " \n", " \n", @@ -934,9 +989,9 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -945,11 +1000,11 @@ "" ], "text/plain": [ - " energy low [eV] energy high [eV] cell nuclide score mean \\\n", - "0 0.00e+00 6.25e-01 10000 total absorption 7.49e-01 \n", + " energy low [eV] energy high [eV] cell nuclide score \\\n", + "0 0.00e+00 6.25e-01 1 total (absorption / absorption) \n", "\n", - " std. dev. \n", - "0 6.09e-03 " + " mean std. dev. \n", + "0 7.49e-01 6.09e-03 " ] }, "execution_count": 27, @@ -974,14 +1029,25 @@ { "cell_type": "code", "execution_count": 28, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
00.00.625100001totalabsorption(absorption / absorption)0.7494230.006089
\n", " \n", " \n", @@ -1000,7 +1066,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1011,11 +1077,11 @@ "" ], "text/plain": [ - " energy low [eV] energy high [eV] cell nuclide \\\n", - "0 0.00e+00 6.25e-01 10000 total \n", + " energy low [eV] energy high [eV] cell nuclide score \\\n", + "0 0.00e+00 6.25e-01 1 total (nu-fission / absorption) \n", "\n", - " score mean std. dev. \n", - "0 (nu-fission / absorption) 1.66e+00 1.44e-02 " + " mean std. dev. \n", + "0 1.66e+00 1.44e-02 " ] }, "execution_count": 28, @@ -1039,14 +1105,25 @@ { "cell_type": "code", "execution_count": 29, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
00.00.625100001total(nu-fission / absorption)1.663727
\n", " \n", " \n", @@ -1065,7 +1142,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1074,11 +1151,11 @@ "" ], "text/plain": [ - " energy low [eV] energy high [eV] nuclide score mean \\\n", - "0 0.00e+00 6.25e-01 total (absorption + current) 9.85e-01 \n", + " energy low [eV] energy high [eV] nuclide \\\n", + "0 0.00e+00 6.25e-01 total \n", "\n", - " std. dev. \n", - "0 5.51e-03 " + " score mean std. dev. \n", + "0 ((absorption + current) / (absorption + current)) 9.85e-01 5.51e-03 " ] }, "execution_count": 29, @@ -1101,14 +1178,25 @@ { "cell_type": "code", "execution_count": 30, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
0.00.625total(absorption + current)((absorption + current) / (absorption + current))0.9846680.005509
\n", " \n", " \n", @@ -1163,14 +1251,25 @@ { "cell_type": "code", "execution_count": 31, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
\n", " \n", " \n", @@ -1189,9 +1288,9 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1200,11 +1299,11 @@ "" ], "text/plain": [ - " energy low [eV] energy high [eV] cell nuclide \\\n", - "0 0.00e+00 6.25e-01 10000 total \n", + " energy low [eV] energy high [eV] cell nuclide \\\n", + "0 0.00e+00 6.25e-01 1 total \n", "\n", " score mean std. dev. \n", - "0 ((((((absorption + current) * nu-fission) * ab... 1.02e+00 1.88e-02 " + "0 (((((((absorption + current) / (absorption + c... 1.02e+00 1.88e-02 " ] }, "execution_count": 31, @@ -1230,7 +1329,7 @@ "cell_type": "code", "execution_count": 32, "metadata": { - "collapsed": false, + "collapsed": true, "scrolled": true }, "outputs": [], @@ -1245,14 +1344,25 @@ { "cell_type": "code", "execution_count": 33, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
00.00.625100001total((((((absorption + current) * nu-fission) * ab...(((((((absorption + current) / (absorption + c...1.0230020.018791
\n", " \n", " \n", @@ -1269,7 +1379,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1279,7 +1389,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1289,7 +1399,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1299,7 +1409,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1309,7 +1419,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1319,7 +1429,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1329,7 +1439,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1339,7 +1449,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1352,15 +1462,15 @@ "" ], "text/plain": [ - " cell energy low [eV] energy high [eV] nuclide \\\n", - "0 10000 0.00e+00 6.25e-01 (U238 / total) \n", - "1 10000 0.00e+00 6.25e-01 (U238 / total) \n", - "2 10000 0.00e+00 6.25e-01 (U235 / total) \n", - "3 10000 0.00e+00 6.25e-01 (U235 / total) \n", - "4 10000 6.25e-01 2.00e+07 (U238 / total) \n", - "5 10000 6.25e-01 2.00e+07 (U238 / total) \n", - "6 10000 6.25e-01 2.00e+07 (U235 / total) \n", - "7 10000 6.25e-01 2.00e+07 (U235 / total) \n", + " cell energy low [eV] energy high [eV] nuclide \\\n", + "0 1 0.00e+00 6.25e-01 (U238 / total) \n", + "1 1 0.00e+00 6.25e-01 (U238 / total) \n", + "2 1 0.00e+00 6.25e-01 (U235 / total) \n", + "3 1 0.00e+00 6.25e-01 (U235 / total) \n", + "4 1 6.25e-01 2.00e+07 (U238 / total) \n", + "5 1 6.25e-01 2.00e+07 (U238 / total) \n", + "6 1 6.25e-01 2.00e+07 (U235 / total) \n", + "7 1 6.25e-01 2.00e+07 (U235 / total) \n", "\n", " score mean std. dev. \n", "0 (nu-fission / flux) 6.66e-07 5.63e-09 \n", @@ -1393,9 +1503,7 @@ { "cell_type": "code", "execution_count": 34, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1425,9 +1533,7 @@ { "cell_type": "code", "execution_count": 35, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1449,9 +1555,7 @@ { "cell_type": "code", "execution_count": 36, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1480,14 +1584,25 @@ { "cell_type": "code", "execution_count": 37, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
01000010.0006.250000e-01(U238 / total)
11000010.0006.250000e-01(U238 / total)
21000010.0006.250000e-01(U235 / total)
31000010.0006.250000e-01(U235 / total)
41000010.6252.000000e+07(U238 / total)
51000010.6252.000000e+07(U238 / total)
61000010.6252.000000e+07(U235 / total)
71000010.6252.000000e+07(U235 / total)
\n", " \n", " \n", @@ -1504,7 +1619,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1514,7 +1629,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1524,7 +1639,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1534,7 +1649,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1547,11 +1662,11 @@ "" ], "text/plain": [ - " cell energy low [eV] energy high [eV] nuclide score mean \\\n", - "0 10000 0.00e+00 6.25e-01 U238 nu-fission 1.60e-06 \n", - "1 10000 0.00e+00 6.25e-01 U235 nu-fission 8.55e-01 \n", - "2 10000 6.25e-01 2.00e+07 U238 nu-fission 8.30e-02 \n", - "3 10000 6.25e-01 2.00e+07 U235 nu-fission 9.06e-02 \n", + " cell energy low [eV] energy high [eV] nuclide score mean \\\n", + "0 1 0.00e+00 6.25e-01 U238 nu-fission 1.60e-06 \n", + "1 1 0.00e+00 6.25e-01 U235 nu-fission 8.55e-01 \n", + "2 1 6.25e-01 2.00e+07 U238 nu-fission 8.30e-02 \n", + "3 1 6.25e-01 2.00e+07 U235 nu-fission 9.06e-02 \n", "\n", " std. dev. \n", "0 9.68e-09 \n", @@ -1574,14 +1689,25 @@ { "cell_type": "code", "execution_count": 38, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", + "\n", "
01000010.0006.250000e-01U238
11000010.0006.250000e-01U235
21000010.6252.000000e+07U238
31000010.6252.000000e+07U235
\n", " \n", " \n", @@ -1598,7 +1724,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1608,7 +1734,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1618,7 +1744,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1628,7 +1754,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1638,7 +1764,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1648,7 +1774,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1658,7 +1784,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1668,7 +1794,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1678,7 +1804,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1691,16 +1817,16 @@ "" ], "text/plain": [ - " cell energy low [eV] energy high [eV] nuclide score mean \\\n", - "0 10002 1.00e-02 1.08e-01 H1 scatter 4.54e+00 \n", - "1 10002 1.08e-01 1.17e+00 H1 scatter 2.00e+00 \n", - "2 10002 1.17e+00 1.26e+01 H1 scatter 1.64e+00 \n", - "3 10002 1.26e+01 1.36e+02 H1 scatter 1.82e+00 \n", - "4 10002 1.36e+02 1.47e+03 H1 scatter 2.03e+00 \n", - "5 10002 1.47e+03 1.59e+04 H1 scatter 2.12e+00 \n", - "6 10002 1.59e+04 1.71e+05 H1 scatter 2.18e+00 \n", - "7 10002 1.71e+05 1.85e+06 H1 scatter 2.01e+00 \n", - "8 10002 1.85e+06 2.00e+07 H1 scatter 3.73e-01 \n", + " cell energy low [eV] energy high [eV] nuclide score mean \\\n", + "0 3 1.00e-02 1.08e-01 H1 scatter 4.54e+00 \n", + "1 3 1.08e-01 1.17e+00 H1 scatter 2.00e+00 \n", + "2 3 1.17e+00 1.26e+01 H1 scatter 1.64e+00 \n", + "3 3 1.26e+01 1.36e+02 H1 scatter 1.82e+00 \n", + "4 3 1.36e+02 1.47e+03 H1 scatter 2.03e+00 \n", + "5 3 1.47e+03 1.59e+04 H1 scatter 2.12e+00 \n", + "6 3 1.59e+04 1.71e+05 H1 scatter 2.18e+00 \n", + "7 3 1.71e+05 1.85e+06 H1 scatter 2.01e+00 \n", + "8 3 1.85e+06 2.00e+07 H1 scatter 3.73e-01 \n", "\n", " std. dev. \n", "0 2.52e-02 \n", @@ -1744,9 +1870,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/python/pincell/build-xml.py b/examples/python/pincell/build-xml.py index 4d41a3126..a9e6a5a74 100644 --- a/examples/python/pincell/build-xml.py +++ b/examples/python/pincell/build-xml.py @@ -119,7 +119,7 @@ settings_file.export_to_xml() ############################################################################### # Instantiate a tally mesh -mesh = openmc.Mesh(mesh_id=1) +mesh = openmc.Mesh() mesh.type = 'regular' mesh.dimension = [100, 100, 1] mesh.lower_left = [-0.62992, -0.62992, -1.e50] diff --git a/examples/xml/basic/tallies.xml b/examples/xml/basic/tallies.xml index 26e61995d..a125e9ca2 100644 --- a/examples/xml/basic/tallies.xml +++ b/examples/xml/basic/tallies.xml @@ -1,21 +1,30 @@ + + 100 + + + + 0 20.0e6 + + + + 0 20.0e6 + + - + 1 total scatter nu-scatter absorption fission nu-fission - - + 1 2 total scatter nu-scatter absorption fission nu-fission - - - + 1 2 3 scatter nu-scatter nu-fission diff --git a/examples/xml/lattice/nested/tallies.xml b/examples/xml/lattice/nested/tallies.xml index 89c0774f1..d342174a9 100644 --- a/examples/xml/lattice/nested/tallies.xml +++ b/examples/xml/lattice/nested/tallies.xml @@ -8,8 +8,12 @@ 1.0 1.0 + + 1 + + - + 1 total diff --git a/examples/xml/lattice/simple/tallies.xml b/examples/xml/lattice/simple/tallies.xml index 89c0774f1..d342174a9 100644 --- a/examples/xml/lattice/simple/tallies.xml +++ b/examples/xml/lattice/simple/tallies.xml @@ -8,8 +8,12 @@ 1.0 1.0 + + 1 + + - + 1 total diff --git a/examples/xml/pincell/settings.xml b/examples/xml/pincell/settings.xml index 733de1926..5845898c8 100644 --- a/examples/xml/pincell/settings.xml +++ b/examples/xml/pincell/settings.xml @@ -22,10 +22,11 @@ - + -0.39218 -0.39218 -1.e50 0.39218 0.39218 1.e50 10 10 1 - + + 1 diff --git a/examples/xml/pincell/tallies.xml b/examples/xml/pincell/tallies.xml index f740530cf..0ae36eced 100644 --- a/examples/xml/pincell/tallies.xml +++ b/examples/xml/pincell/tallies.xml @@ -1,15 +1,22 @@ - + 100 100 1 -0.62992 -0.62992 -1.e50 0.62992 0.62992 1.e50 + + 1 + + + + 0. 4. 20.0e6 + + - - + 1 2 flux fission nu-fission diff --git a/examples/xml/pincell_multigroup/tallies.xml b/examples/xml/pincell_multigroup/tallies.xml index 1591a556b..d84e129f2 100644 --- a/examples/xml/pincell_multigroup/tallies.xml +++ b/examples/xml/pincell_multigroup/tallies.xml @@ -5,9 +5,14 @@ -0.63 -0.63 -1e+50 0.63 0.63 1e+50 + + 1e-05 0.0635 10.0 100.0 1000.0 500000.0 1000000.0 20000000.0 + + + 1 + - - + 1 2 flux fission nu-fission diff --git a/openmc/__init__.py b/openmc/__init__.py index a0fe26491..d692ebbae 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -27,6 +27,5 @@ from openmc.particle_restart import * from openmc.mixin import * from openmc.plotter import * from openmc.search import * -from openmc.capi import * __version__ = '0.9.0' diff --git a/openmc/arithmetic.py b/openmc/arithmetic.py index 57afacac3..6a781cb5a 100644 --- a/openmc/arithmetic.py +++ b/openmc/arithmetic.py @@ -108,9 +108,9 @@ class CrossNuclide(object): Parameters ---------- - left_nuclide : Nuclide or CrossNuclide + left_nuclide : openmc.Nuclide or CrossNuclide The left nuclide in the outer product - right_nuclide : Nuclide or CrossNuclide + right_nuclide : openmc.Nuclide or CrossNuclide The right nuclide in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to @@ -118,9 +118,9 @@ class CrossNuclide(object): Attributes ---------- - left_nuclide : Nuclide or CrossNuclide + left_nuclide : openmc.Nuclide or CrossNuclide The left nuclide in the outer product - right_nuclide : Nuclide or CrossNuclide + right_nuclide : openmc.Nuclide or CrossNuclide The right nuclide in the outer product binary_op : str The tally arithmetic binary operator (e.g., '+', '-', etc.) used to @@ -510,7 +510,7 @@ class AggregateNuclide(object): Parameters ---------- - nuclides : Iterable of str or Nuclide or CrossNuclide + nuclides : Iterable of str or openmc.Nuclide or CrossNuclide The nuclides included in the aggregation aggregate_op : str The tally aggregation operator (e.g., 'sum', 'avg', etc.) used @@ -518,7 +518,7 @@ class AggregateNuclide(object): Attributes ---------- - nuclides : Iterable of str or Nuclide or CrossNuclide + nuclides : Iterable of str or openmc.Nuclide or CrossNuclide The nuclides included in the aggregation aggregate_op : str The tally aggregation operator (e.g., 'sum', 'avg', etc.) used diff --git a/openmc/capi.py b/openmc/capi.py deleted file mode 100644 index bb9241d7d..000000000 --- a/openmc/capi.py +++ /dev/null @@ -1,304 +0,0 @@ -from contextlib import contextmanager -from ctypes import CDLL, c_int, c_int32, c_double, c_char_p, POINTER -import sys -from warnings import warn - -import numpy as np -from numpy.ctypeslib import as_array - -import pkg_resources - -__all__ = ['OpenMCLibrary', 'lib', 'lib_context'] - -_int3 = c_int*3 -_double3 = c_double*3 -_double_array = POINTER(POINTER(c_double)) - - -class OpenMCLibrary(object): - """Provides bindings to C functions defined by OpenMC shared library. - - This class is normally not directly instantiated. Instead, when the - :mod:`openmc` package is imported, an instance is automatically created with - the name :data:`openmc.lib`. Calls to the OpenMC can then be made using that - instance, for example: - - .. code-block:: python - - openmc.lib.init() - openmc.lib.run() - - """ - def __init__(self, filename): - self._dll = CDLL(filename) - - # Set argument/return types - self._dll.openmc_calculate_volumes.restype = None - self._dll.openmc_cell_set_temperature.argtypes = [ - c_int32, c_double, c_int32] - self._dll.openmc_cell_set_temperature.restype = c_int - self._dll.openmc_finalize.restype = None - self._dll.openmc_find.argtypes = [ - POINTER(_double3), c_int, POINTER(c_int32), POINTER(c_int32)] - self._dll.openmc_find.restype = None - self._dll.openmc_init.argtypes = [POINTER(c_int)] - self._dll.openmc_init.restype = None - self._dll.openmc_load_nuclide.argtypes = [c_char_p] - self._dll.openmc_load_nuclide.restype = c_int - self._dll.openmc_material_add_nuclide.argtypes = [ - c_int32, c_char_p, c_double] - self._dll.openmc_material_add_nuclide.restype = c_int - self._dll.openmc_material_get_densities.argtypes = [ - c_int32, _double_array] - self._dll.openmc_material_get_densities.restype = c_int - self._dll.openmc_material_set_density.argtypes = [c_int32, c_double] - self._dll.openmc_material_set_density.restype = c_int - self._dll.openmc_plot_geometry.restype = None - self._dll.openmc_run.restype = None - self._dll.openmc_reset.restype = None - self._dll.openmc_tally_results.argtypes = [ - c_int32, _double_array, POINTER(_int3)] - self._dll.openmc_tally_results.restype = None - - def calculate_volumes(self): - """Run stochastic volume calculation""" - return self._dll.openmc_calculate_volumes() - - def cell_set_temperature(self, cell_id, T, instance=None): - """Set the temperature of a cell - - Parameters - ---------- - cell_id : int - ID of the cell - T : float - Temperature in K - instance : int or None - Which instance of the cell - - """ - if instance is not None: - return self._dll.openmc_cell_set_temperature( - cell_id, T, instance) - else: - return self._dll.openmc_cell_set_temperature(cell_id, T, None) - - def finalize(self): - """Finalize simulation and free memory""" - return self._dll.openmc_finalize() - - def find(self, xyz, rtype='cell'): - """Find the cell or material at a given point - - Parameters - ---------- - xyz : iterable of float - Cartesian coordinates of position - rtype : {'cell', 'material'} - Whether to return the cell or material ID - - Returns - ------- - int or None - ID of the cell or material. If 'material' is requested and no - material exists at the given coordinate, None is returned. - int - If the cell at the given point is repeated in the geometry, this - indicates which instance it is, i.e., 0 would be the first instance. - - """ - # Set second argument to openmc_find - if rtype == 'cell': - r_int = 1 - elif rtype == 'material': - r_int = 2 - else: - raise ValueError('Unknown return type: {}'.format(rtype)) - - # Call openmc_find - uid = c_int32() - instance = c_int32() - self._dll.openmc_find(_double3(*xyz), r_int, uid, instance) - return (uid.value if uid != 0 else None), instance.value - - def init(self, intracomm=None): - """Initialize OpenMC - - Parameters - ---------- - intracomm : mpi4py.MPI.Intracomm or None - MPI intracommunicator - - """ - if intracomm is not None: - # If an mpi4py communicator was passed, convert it to an integer to - # be passed to openmc_init - try: - intracomm = intracomm.py2f() - except AttributeError: - pass - return self._dll.openmc_init(c_int(intracomm)) - else: - return self._dll.openmc_init(None) - - def load_nuclide(self, name): - """Load cross section data for a nuclide. - - Parameters - ---------- - name : str - Name of nuclide, e.g. 'U235' - - Returns - ------- - int - Return status (negative if an error occurs). - - """ - return self._dll.openmc_load_nuclide(name.encode()) - - def material_add_nuclide(self, mat_id, name, density): - """Add a nuclide to a material. - - Parameters - ---------- - mat_id : int - ID of the material - name : str - Name of nuclide, e.g. 'U235' - density : float - Density in atom/b-cm - - Returns - ------- - int - Return status (negative if an error occurs). - - """ - return self._dll.openmc_material_add_nuclide( - mat_id, name.encode(), density) - - def material_get_densities(self, mat_id): - """Get atom densities in a material. - - Parameters - ---------- - mat_id : int - ID of the material - - Returns - ------- - numpy.ndarray - Array of densities in atom/b-cm - - """ - data = POINTER(c_double)() - n = self._dll.openmc_material_get_densities(mat_id, data) - if data: - return as_array(data, (n,)) - else: - return None - - def material_set_density(self, mat_id, density): - """Set density of a material. - - Parameters - ---------- - mat_id : int - ID of the material - density : float - Density in atom/b-cm - - Returns - ------- - int - Return status (negative if an error occurs). - - """ - return self._dll.openmc_material_set_density(mat_id, density) - - def plot_geometry(self): - """Plot geometry""" - return self._dll.openmc_plot_geometry() - - def reset(self): - """Reset tallies""" - return self._dll.openmc_reset() - - def run(self): - """Run simulation""" - return self._dll.openmc_run() - - def tally_results(self, tally_id): - """Get tally results array - - Parameters - ---------- - tally_id : int - ID of tally - - Returns - ------- - numpy.ndarray - Array that exposes the internal tally results array - - """ - data = POINTER(c_double)() - shape = _int3() - self._dll.openmc_tally_results(tally_id, data, shape) - if data: - return as_array(data, tuple(shape[::-1])) - else: - return None - - def __getattr__(self, key): - # Fall-back for other functions that may be available from library - try: - return getattr(self._dll, 'openmc_{}'.format(key)) - except AttributeError: - raise AttributeError("OpenMC library doesn't have a '{}' function" - .format(key)) - - -@contextmanager -def lib_context(intracomm=None): - """Provides context manager for calling OpenMC shared library functions. - - This function is intended to be used in a 'with' statement and ensures that - OpenMC is properly initialized/finalized. At the completion of the 'with' - block, all memory that was allocated during the block is freed. For - example:: - - with openmc.lib_context() as lib: - for i in range(n_iters): - lib.reset() - do_stuff() - lib.run() - - Parameters - ---------- - intracomm : mpi4py.MPI.Intracomm or None - MPI intracommunicator - - """ - lib.init(comm) - yield lib - lib.finalize() - - -# Determine shared-library suffix -if sys.platform == 'darwin': - suffix = 'dylib' -else: - suffix = 'so' - -# Open shared library -filename = pkg_resources.resource_filename( - __name__, '_libopenmc.{}'.format(suffix)) -try: - lib = OpenMCLibrary(filename) -except OSError: - warn("OpenMC shared library is not available from the Python API. This " - "means you will not be able to use openmc.lib to make in-memory " - "calls to OpenMC.") - lib = None diff --git a/openmc/capi/__init__.py b/openmc/capi/__init__.py new file mode 100644 index 000000000..d302001c9 --- /dev/null +++ b/openmc/capi/__init__.py @@ -0,0 +1,55 @@ +""" +This module provides bindings to C functions defined by OpenMC shared library. +When the :mod:`openmc` package is imported, the OpenMC shared library is +automatically loaded. Calls to the OpenMC library can then be via functions or +objects in the :mod:`openmc.capi` subpackage, for example: + +.. code-block:: python + + openmc.capi.init() + openmc.capi.run() + openmc.capi.finalize() + +""" + +from ctypes import CDLL +import os +import sys +from warnings import warn + +import pkg_resources + + +# Determine shared-library suffix +if sys.platform == 'darwin': + _suffix = 'dylib' +else: + _suffix = 'so' + +if os.environ.get('READTHEDOCS', None) != 'True': + # Open shared library + _filename = pkg_resources.resource_filename( + __name__, 'libopenmc.{}'.format(_suffix)) + _dll = CDLL(_filename) +else: + # For documentation builds, we don't actually have the shared library + # available. Instead, we create a mock object so that when the modules + # within the openmc.capi package try to configure arguments and return + # values for symbols, no errors occur + try: + from unittest.mock import Mock + except ImportError: + from mock import Mock + _dll = Mock() + +from .error import * +from .core import * +from .nuclide import * +from .material import * +from .cell import * +from .filter import * +from .tally import * +from .settings import settings + +warn("The Python bindings to OpenMC's C API are still unstable " + "and may change substantially in future releases.", FutureWarning) diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py new file mode 100644 index 000000000..fe3382589 --- /dev/null +++ b/openmc/capi/cell.py @@ -0,0 +1,128 @@ +from collections import Mapping, Iterable +from ctypes import c_int, c_int32, c_double, c_char_p, POINTER +from weakref import WeakValueDictionary + +import numpy as np +from numpy.ctypeslib import as_array + +from . import _dll +from .core import _FortranObjectWithID +from .error import _error_handler +from .material import Material + +__all__ = ['Cell', 'cells'] + +# Cell functions +_dll.openmc_cell_get_id.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_cell_get_id.restype = c_int +_dll.openmc_cell_get_id.errcheck = _error_handler +_dll.openmc_cell_get_fill.argtypes = [ + c_int32, POINTER(c_int), POINTER(POINTER(c_int32)), POINTER(c_int32)] +_dll.openmc_cell_set_fill.argtypes = [ + c_int32, c_int, c_int32, POINTER(c_int32)] +_dll.openmc_cell_set_fill.restype = c_int +_dll.openmc_cell_set_fill.errcheck = _error_handler +_dll.openmc_cell_set_temperature.argtypes = [ + c_int32, c_double, POINTER(c_int32)] +_dll.openmc_cell_set_temperature.restype = c_int +_dll.openmc_cell_set_temperature.errcheck = _error_handler +_dll.openmc_get_cell_index.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_get_cell_index.restype = c_int +_dll.openmc_get_cell_index.errcheck = _error_handler + + +class Cell(_FortranObjectWithID): + """Cell stored internally. + + This class exposes a cell that is stored internally in the OpenMC + library. To obtain a view of a cell with a given ID, use the + :data:`openmc.capi.nuclides` mapping. + + Parameters + ---------- + index : int + Index in the `cells` array. + + Attributes + ---------- + id : int + ID of the cell + + """ + __instances = WeakValueDictionary() + + def __new__(cls, *args): + if args not in cls.__instances: + instance = super(Cell, self).__new__(cls) + cls.__instances[args] = instance + return cls.__instances[args] + + @property + def id(self): + cell_id = c_int32() + _dll.openmc_cell_get_id(self._index, cell_id) + return cell_id.value + + @property + def fill(self): + fill_type = c_int() + indices = POINTER(c_int32)() + n = c_int32() + _dll.openmc_cell_get_fill(self._index, fill_type, indices, n) + + if fill_type.value == 1: + if n.value > 1: + return [Material(index=i) for i in indices[:n.value]] + else: + return Material(index=indices[0]) + else: + raise NotImplementedError + + @fill.setter + def fill(self, fill): + if isinstance(fill, Iterable): + n = len(fill) + indices = (c_int*n)(*(m._index for m in fill)) + _dll.openmc_cell_set_fill(self._index, 1, 1, indices) + elif isinstance(fill, Material): + materials = [fill] + indices = (c_int*1)(fill._index) + _dll.openmc_cell_set_fill(self._index, 1, 1, indices) + else: + raise NotImplementedError + + def set_temperature(self, T, instance=None): + """Set the temperature of a cell + + Parameters + ---------- + T : float + Temperature in K + instance : int or None + Which instance of the cell + + """ + _dll.openmc_cell_set_temperature(self._index, T, instance) + + +class _CellMapping(Mapping): + def __getitem__(self, key): + index = c_int32() + try: + _dll.openmc_get_cell_index(key, index) + except (AllocationError, InvalidIDError) as e: + # __contains__ expects a KeyError to work correctly + raise KeyError(str(e)) + return Cell(index.value) + + def __iter__(self): + for i in range(len(self)): + yield Cell(i + 1).id + + def __len__(self): + return c_int32.in_dll(_dll, 'n_cells').value + + def __repr__(self): + return repr(dict(self)) + +cells = _CellMapping() diff --git a/openmc/capi/core.py b/openmc/capi/core.py new file mode 100644 index 000000000..0d8ff5c43 --- /dev/null +++ b/openmc/capi/core.py @@ -0,0 +1,186 @@ +from contextlib import contextmanager +from ctypes import CDLL, c_int, c_int32, c_double, POINTER +from warnings import warn + +from . import _dll +from .error import _error_handler + + +_dll.openmc_calculate_volumes.restype = None +_dll.openmc_finalize.restype = None +_dll.openmc_find.argtypes = [POINTER(c_double*3), c_int, POINTER(c_int32), + POINTER(c_int32)] +_dll.openmc_find.restype = c_int +_dll.openmc_find.errcheck = _error_handler +_dll.openmc_hard_reset.restype = None +_dll.openmc_init.argtypes = [POINTER(c_int)] +_dll.openmc_init.restype = None +_dll.openmc_get_keff.argtypes = [POINTER(c_double*2)] +_dll.openmc_get_keff.restype = c_int +_dll.openmc_get_keff.errcheck = _error_handler +_dll.openmc_plot_geometry.restype = None +_dll.openmc_run.restype = None +_dll.openmc_reset.restype = None + + +def calculate_volumes(): + """Run stochastic volume calculation""" + _dll.openmc_calculate_volumes() + + +def finalize(): + """Finalize simulation and free memory""" + _dll.openmc_finalize() + + +def find_cell(xyz): + """Find the cell at a given point + + Parameters + ---------- + xyz : iterable of float + Cartesian coordinates of position + + Returns + ------- + int + ID of the cell. + int + If the cell at the given point is repeated in the geometry, this + indicates which instance it is, i.e., 0 would be the first instance. + + """ + uid = c_int32() + instance = c_int32() + _dll.openmc_find((c_double*3)(*xyz), 1, uid, instance) + return uid.value, instance.value + + +def find_material(xyz): + """Find the material at a given point + + Parameters + ---------- + xyz : iterable of float + Cartesian coordinates of position + + Returns + ------- + int or None + ID of the material or None is no material is found + + """ + uid = c_int32() + instance = c_int32() + _dll.openmc_find((c_double*3)(*xyz), 2, uid, instance) + return uid.value if uid != 0 else None + + +def hard_reset(): + """Reset tallies, timers, and pseudo-random number generator state.""" + _dll.openmc_hard_reset() + + +def init(intracomm=None): + """Initialize OpenMC + + Parameters + ---------- + intracomm : mpi4py.MPI.Intracomm or None + MPI intracommunicator + + """ + if intracomm is not None: + # If an mpi4py communicator was passed, convert it to an integer to + # be passed to openmc_init + try: + intracomm = intracomm.py2f() + except AttributeError: + pass + _dll.openmc_init(c_int(intracomm)) + else: + _dll.openmc_init(None) + + +def keff(): + """Return the calculated k-eigenvalue and its standard deviation. + + Returns + ------- + tuple + Mean k-eigenvalue and standard deviation of the mean + + """ + k = (c_double*2)() + _dll.openmc_get_keff(k) + return tuple(k) + + +def plot_geometry(): + """Plot geometry""" + _dll.openmc_plot_geometry() + + +def reset(): + """Reset tallies and timers.""" + _dll.openmc_reset() + + +def run(): + """Run simulation""" + _dll.openmc_run() + + +@contextmanager +def run_in_memory(intracomm=None): + """Provides context manager for calling OpenMC shared library functions. + + This function is intended to be used in a 'with' statement and ensures that + OpenMC is properly initialized/finalized. At the completion of the 'with' + block, all memory that was allocated during the block is freed. For + example:: + + with openmc.capi.run_in_memory(): + for i in range(n_iters): + openmc.capi.reset() + do_stuff() + openmc.capi.run() + + Parameters + ---------- + intracomm : mpi4py.MPI.Intracomm or None + MPI intracommunicator + + """ + init(intracomm) + try: + yield + finally: + finalize() + + +class _DLLGlobal(object): + """Data descriptor that exposes global variables from libopenmc.""" + def __init__(self, ctype, name): + self.ctype = ctype + self.name = name + + def __get__(self, instance, owner): + return self.ctype.in_dll(_dll, self.name).value + + def __set__(self, instance, value): + self.ctype.in_dll(_dll, self.name).value = value + + +class _FortranObject(object): + def __repr__(self): + return "{}[{}]".format(type(self).__name__, self._index) + + +class _FortranObjectWithID(_FortranObject): + def __init__(self, uid=None, new=True, index=None): + # Creating the object has already been handled by __new__. In the + # initializer, all we do is make sure that the object returned has an ID + # assigned. If the array index of the object is out of bounds, an + # OutOfBoundsError will be raised here by virtue of referencing self.id + self.id diff --git a/openmc/capi/error.py b/openmc/capi/error.py new file mode 100644 index 000000000..98d43ae46 --- /dev/null +++ b/openmc/capi/error.py @@ -0,0 +1,73 @@ +from ctypes import c_int, c_char + +from . import _dll + + +class Error(Exception): + """Root exception class for OpenMC.""" + + +class GeometryError(Error): + """Geometry-related error""" + + +class InvalidIDError(Error): + """Use of an ID that is invalid.""" + + +class AllocationError(Error): + """Error related to memory allocation.""" + + +class OutOfBoundsError(Error): + """Index in array out of bounds.""" + + +class DataError(Error): + """Error relating to nuclear data.""" + + +class PhysicsError(Error): + """Error relating to performing physics.""" + + +class InvalidArgumentError(Error): + """Argument passed was invalid.""" + + +class InvalidTypeError(Error): + """Tried to perform an operation on the wrong type.""" + + +def _error_handler(err, func, args): + """Raise exception according to error code.""" + + # Get error code corresponding to global constant. + def errcode(s): + return c_int.in_dll(_dll, s).value + + # Get error message set by OpenMC library + errmsg = (c_char*256).in_dll(_dll, 'openmc_err_msg') + msg = errmsg.value.decode() + + # Raise exception type corresponding to error code + if err == errcode('e_allocate'): + raise AllocationError(msg) + elif err == errcode('e_out_of_bounds'): + raise OutOfBoundsError(msg) + elif err == errcode('e_invalid_argument'): + raise InvalidArgumentError(msg) + elif err == errcode('e_invalid_type'): + raise InvalidTypeError(msg) + if err == errcode('e_invalid_id'): + raise InvalidIDError(msg) + elif err == errcode('e_geometry'): + raise GeometryError(msg) + elif err == errcode('e_data'): + raise DataError(msg) + elif err == errcode('e_physics'): + raise PhysicsError(msg) + elif err == errcode('e_warning'): + warn(msg) + elif err < 0: + raise Exception("Unknown error encountered (code {}).".format(err)) diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py new file mode 100644 index 000000000..1b52af6db --- /dev/null +++ b/openmc/capi/filter.py @@ -0,0 +1,256 @@ +from collections import Mapping +from ctypes import c_int, c_int32, c_double, c_char_p, POINTER, \ + create_string_buffer +from weakref import WeakValueDictionary + +import numpy as np +from numpy.ctypeslib import as_array + +from . import _dll +from .core import _FortranObjectWithID +from .error import _error_handler, AllocationError, InvalidIDError +from .material import Material + + +__all__ = ['Filter', 'AzimuthalFilter', 'CellFilter', + 'CellbornFilter', 'CellfromFilter', 'DistribcellFilter', + 'DelayedGroupFilter', 'EnergyFilter', 'EnergyoutFilter', + 'EnergyFunctionFilter', 'MaterialFilter', 'MeshFilter', + 'MuFilter', 'PolarFilter', 'SurfaceFilter', + 'UniverseFilter', 'filters'] + +# Tally functions +_dll.openmc_energy_filter_get_bins.argtypes = [ + c_int32, POINTER(POINTER(c_double)), POINTER(c_int32)] +_dll.openmc_energy_filter_get_bins.restype = c_int +_dll.openmc_energy_filter_get_bins.errcheck = _error_handler +_dll.openmc_energy_filter_set_bins.argtypes = [c_int32, c_int32, POINTER(c_double)] +_dll.openmc_energy_filter_set_bins.restype = c_int +_dll.openmc_energy_filter_set_bins.errcheck = _error_handler +_dll.openmc_extend_filters.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)] +_dll.openmc_extend_filters.restype = c_int +_dll.openmc_extend_filters.errcheck = _error_handler +_dll.openmc_filter_get_id.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_filter_get_id.restype = c_int +_dll.openmc_filter_get_id.errcheck = _error_handler +_dll.openmc_filter_get_type.argtypes = [c_int32, c_char_p] +_dll.openmc_filter_get_type.restype = c_int +_dll.openmc_filter_get_type.errcheck = _error_handler +_dll.openmc_filter_set_id.argtypes = [c_int32, c_int32] +_dll.openmc_filter_set_id.restype = c_int +_dll.openmc_filter_set_id.errcheck = _error_handler +_dll.openmc_filter_set_type.argtypes = [c_int32, c_char_p] +_dll.openmc_filter_set_type.restype = c_int +_dll.openmc_filter_set_type.errcheck = _error_handler +_dll.openmc_get_filter_index.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_get_filter_index.restype = c_int +_dll.openmc_get_filter_index.errcheck = _error_handler +_dll.openmc_material_filter_get_bins.argtypes = [ + c_int32, POINTER(POINTER(c_int32)), POINTER(c_int32)] +_dll.openmc_material_filter_get_bins.restype = c_int +_dll.openmc_material_filter_get_bins.errcheck = _error_handler +_dll.openmc_material_filter_set_bins.argtypes = [c_int32, c_int32, POINTER(c_int32)] +_dll.openmc_material_filter_set_bins.restype = c_int +_dll.openmc_material_filter_set_bins.errcheck = _error_handler +_dll.openmc_mesh_filter_set_mesh.argtypes = [c_int32, c_int32] +_dll.openmc_mesh_filter_set_mesh.restype = c_int +_dll.openmc_mesh_filter_set_mesh.errcheck = _error_handler + + +class Filter(_FortranObjectWithID): + __instances = WeakValueDictionary() + + def __new__(cls, obj=None, uid=None, new=True, index=None): + mapping = filters + if index is None: + if new: + # Determine ID to assign + if uid is None: + try: + uid = max(mapping) + 1 + except ValueError: + uid = 1 + else: + if uid in mapping: + raise AllocationError('A filter with ID={} has already ' + 'been allocated.'.format(uid)) + + # Resize internal array + index = c_int32() + _dll.openmc_extend_filters(1, index, None) + + # Set the filter type -- note that the filter_type attribute + # only exists on subclasses! + _dll.openmc_filter_set_type(index, cls.filter_type.encode()) + index = index.value + else: + index = mapping[uid]._index + + if index not in cls.__instances: + instance = super(Filter, cls).__new__(cls) + instance._index = index + if uid is not None: + instance.id = uid + cls.__instances[index] = instance + + return cls.__instances[index] + + @property + def id(self): + filter_id = c_int32() + _dll.openmc_filter_get_id(self._index, filter_id) + return filter_id.value + + @id.setter + def id(self, filter_id): + _dll.openmc_filter_set_id(self._index, filter_id) + + +class EnergyFilter(Filter): + filter_type = 'energy' + + def __init__(self, bins=None, uid=None, new=True, index=None): + super(EnergyFilter, self).__init__(uid, new, index) + if bins is not None: + self.bins = bins + + @property + def bins(self): + energies = POINTER(c_double)() + n = c_int32() + _dll.openmc_energy_filter_get_bins(self._index, energies, n) + return as_array(energies, (n.value,)) + + @bins.setter + def bins(self, bins): + # Get numpy array as a double* + energies = np.asarray(bins) + energies_p = energies.ctypes.data_as(POINTER(c_double)) + + _dll.openmc_energy_filter_set_bins( + self._index, len(energies), energies_p) + + +class EnergyoutFilter(Filter): + filter_type = 'energyout' + + +class AzimuthalFilter(Filter): + filter_type = 'azimuthal' + + +class CellFilter(Filter): + filter_type = 'cell' + + +class CellbornFilter(Filter): + filter_type = 'cellborn' + + +class CellfromFilter(Filter): + filter_type = 'cellfrom' + + +class DelayedGroupFilter(Filter): + filter_type = 'delayedgroup' + + +class DistribcellFilter(Filter): + filter_type = 'distribcell' + + +class EnergyFunctionFilter(Filter): + filter_type = 'energyfunction' + + +class MaterialFilter(Filter): + filter_type = 'material' + + def __init__(self, bins=None, uid=None, new=True, index=None): + super(MaterialFilter, self).__init__(uid, new, index) + if bins is not None: + self.bins = bins + + @property + def bins(self): + materials = POINTER(c_int32)() + n = c_int32() + _dll.openmc_material_filter_get_bins(self._index, materials, n) + return [Material(index=materials[i]) for i in range(n.value)] + + @bins.setter + def bins(self, materials): + # Get material indices as int32_t[] + n = len(materials) + bins = (c_int32*n)(*(m._index for m in materials)) + + _dll.openmc_material_filter_set_bins(self._index, n, bins) + + +class MeshFilter(Filter): + filter_type = 'mesh' + + +class MuFilter(Filter): + filter_type = 'mu' + + +class PolarFilter(Filter): + filter_type = 'polar' + + +class SurfaceFilter(Filter): + filter_type = 'surface' + + +class UniverseFilter(Filter): + filter_type = 'universe' + + +_FILTER_TYPE_MAP = { + 'azimuthal': AzimuthalFilter, + 'cell': CellFilter, + 'cellborn': CellbornFilter, + 'cellfrom': CellfromFilter, + 'delayedgroup': DelayedGroupFilter, + 'distribcell': DistribcellFilter, + 'energy': EnergyFilter, + 'energyout': EnergyoutFilter, + 'energyfunction': EnergyFunctionFilter, + 'material': MaterialFilter, + 'mesh': MeshFilter, + 'mu': MuFilter, + 'polar': PolarFilter, + 'surface': SurfaceFilter, + 'universe': UniverseFilter, +} + + +def _get_filter(index): + filter_type = create_string_buffer(20) + _dll.openmc_filter_get_type(index, filter_type) + filter_type = filter_type.value.decode() + return _FILTER_TYPE_MAP[filter_type](index=index) + + +class _FilterMapping(Mapping): + def __getitem__(self, key): + index = c_int32() + try: + _dll.openmc_get_filter_index(key, index) + except (AllocationError, InvalidIDError) as e: + # __contains__ expects a KeyError to work correctly + raise KeyError(str(e)) + return _get_filter(index.value) + + def __iter__(self): + for i in range(len(self)): + yield _get_filter(i + 1).id + + def __len__(self): + return c_int32.in_dll(_dll, 'n_filters').value + + def __repr__(self): + return repr(dict(self)) + +filters = _FilterMapping() diff --git a/openmc/capi/material.py b/openmc/capi/material.py new file mode 100644 index 000000000..af0e9893e --- /dev/null +++ b/openmc/capi/material.py @@ -0,0 +1,214 @@ +from collections import Mapping +from ctypes import c_int, c_int32, c_double, c_char_p, POINTER +from weakref import WeakValueDictionary + +import numpy as np +from numpy.ctypeslib import as_array + +from . import _dll, Nuclide +from .core import _FortranObjectWithID +from .error import _error_handler, AllocationError, InvalidIDError + + +__all__ = ['Material', 'materials'] + +# Material functions +_dll.openmc_extend_materials.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)] +_dll.openmc_extend_materials.restype = c_int +_dll.openmc_extend_materials.errcheck = _error_handler +_dll.openmc_get_material_index.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_get_material_index.restype = c_int +_dll.openmc_get_material_index.errcheck = _error_handler +_dll.openmc_material_add_nuclide.argtypes = [ + c_int32, c_char_p, c_double] +_dll.openmc_material_add_nuclide.restype = c_int +_dll.openmc_material_add_nuclide.errcheck = _error_handler +_dll.openmc_material_get_id.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_material_get_id.restype = c_int +_dll.openmc_material_get_id.errcheck = _error_handler +_dll.openmc_material_get_densities.argtypes = [ + c_int32, POINTER(POINTER(c_int)), POINTER(POINTER(c_double)), + POINTER(c_int)] +_dll.openmc_material_get_densities.restype = c_int +_dll.openmc_material_get_densities.errcheck = _error_handler +_dll.openmc_material_set_density.argtypes = [c_int32, c_double] +_dll.openmc_material_set_density.restype = c_int +_dll.openmc_material_set_density.errcheck = _error_handler +_dll.openmc_material_set_densities.argtypes = [ + c_int32, c_int, POINTER(c_char_p), POINTER(c_double)] +_dll.openmc_material_set_densities.restype = c_int +_dll.openmc_material_set_densities.errcheck = _error_handler +_dll.openmc_material_set_id.argtypes = [c_int32, c_int32] +_dll.openmc_material_set_id.restype = c_int +_dll.openmc_material_set_id.errcheck = _error_handler + + +class Material(_FortranObjectWithID): + """Material stored internally. + + This class exposes a material that is stored internally in the OpenMC + library. To obtain a view of a material with a given ID, use the + :data:`openmc.capi.materials` mapping. + + Parameters + ---------- + uid : int or None + Unique ID of the tally + new : bool + When `index` is None, this argument controls whether a new object is + created or a view to an existing object is returned. + index : int or None + Index in the `materials` array. + + Attributes + ---------- + id : int + ID of the material + nuclides : list of str + List of nuclides in the material + densities : numpy.ndarray + Array of densities in atom/b-cm + + """ + __instances = WeakValueDictionary() + + def __new__(cls, uid=None, new=True, index=None): + mapping = materials + if index is None: + if new: + # Determine ID to assign + if uid is None: + try: + uid = max(mapping) + 1 + except ValueError: + uid = 1 + else: + if uid in mapping: + raise AllocationError('A material with ID={} has already ' + 'been allocated.'.format(uid)) + + index = c_int32() + _dll.openmc_extend_materials(1, index, None) + index = index.value + else: + index = mapping[uid]._index + + if index not in cls.__instances: + instance = super(Material, cls).__new__(cls) + instance._index = index + if uid is not None: + instance.id = uid + cls.__instances[index] = instance + + return cls.__instances[index] + + @property + def id(self): + mat_id = c_int32() + _dll.openmc_material_get_id(self._index, mat_id) + return mat_id.value + + @id.setter + def id(self, mat_id): + _dll.openmc_material_set_id(self._index, mat_id) + + @property + def nuclides(self): + return self._get_densities()[0] + return nuclides + + @property + def densities(self): + return self._get_densities()[1] + + def _get_densities(self): + """Get atom densities in a material. + + Returns + ------- + list of string + List of nuclide names + numpy.ndarray + Array of densities in atom/b-cm + + """ + # Allocate memory for arguments that are written to + nuclides = POINTER(c_int)() + densities = POINTER(c_double)() + n = c_int() + + # Get nuclide names and densities + _dll.openmc_material_get_densities(self._index, nuclides, densities, n) + + # Convert to appropriate types and return + nuclide_list = [Nuclide(nuclides[i]).name for i in range(n.value)] + density_array = as_array(densities, (n.value,)) + return nuclide_list, density_array + + def add_nuclide(self, name, density): + """Add a nuclide to a material. + + Parameters + ---------- + name : str + Name of nuclide, e.g. 'U235' + density : float + Density in atom/b-cm + + """ + _dll.openmc_material_add_nuclide(self._index, name.encode(), density) + + def set_density(self, density): + """Set density of a material. + + Parameters + ---------- + density : float + Density in atom/b-cm + + """ + _dll.openmc_material_set_density(self._index, density) + + def set_densities(self, nuclides, densities): + """Set the densities of a list of nuclides in a material + + Parameters + ---------- + nuclides : iterable of str + Nuclide names + densities : iterable of float + Corresponding densities in atom/b-cm + + """ + # Convert strings to an array of char* + nucs = (c_char_p * len(nuclides))() + nucs[:] = [x.encode() for x in nuclides] + + # Get numpy array as a double* + d = np.asarray(densities) + dp = d.ctypes.data_as(POINTER(c_double)) + + _dll.openmc_material_set_densities(self._index, len(nuclides), nucs, dp) + + +class _MaterialMapping(Mapping): + def __getitem__(self, key): + index = c_int32() + try: + _dll.openmc_get_material_index(key, index) + except (AllocationError, InvalidIDError) as e: + # __contains__ expects a KeyError to work correctly + raise KeyError(str(e)) + return Material(index=index.value) + + def __iter__(self): + for i in range(len(self)): + yield Material(index=i + 1).id + + def __len__(self): + return c_int32.in_dll(_dll, 'n_materials').value + + def __repr__(self): + return repr(dict(self)) + +materials = _MaterialMapping() diff --git a/openmc/capi/nuclide.py b/openmc/capi/nuclide.py new file mode 100644 index 000000000..e07872e58 --- /dev/null +++ b/openmc/capi/nuclide.py @@ -0,0 +1,101 @@ +from collections import Mapping +from ctypes import c_int, c_char_p, POINTER +from weakref import WeakValueDictionary + +import numpy as np +from numpy.ctypeslib import as_array + +from . import _dll +from .core import _FortranObject +from .error import _error_handler, DataError, AllocationError + + +__all__ = ['Nuclide', 'nuclides', 'load_nuclide'] + +# Nuclide functions +_dll.openmc_get_nuclide_index.argtypes = [c_char_p, POINTER(c_int)] +_dll.openmc_get_nuclide_index.restype = c_int +_dll.openmc_get_nuclide_index.errcheck = _error_handler +_dll.openmc_load_nuclide.argtypes = [c_char_p] +_dll.openmc_load_nuclide.restype = c_int +_dll.openmc_load_nuclide.errcheck = _error_handler +_dll.openmc_nuclide_name.argtypes = [c_int, POINTER(c_char_p)] +_dll.openmc_nuclide_name.restype = c_int +_dll.openmc_nuclide_name.errcheck = _error_handler + + +def load_nuclide(name): + """Load cross section data for a nuclide. + + Parameters + ---------- + name : str + Name of the nuclide, e.g. 'U235' + + """ + _dll.openmc_load_nuclide(name.encode()) + + +class Nuclide(_FortranObject): + """Nuclide stored internally. + + This class exposes a nuclide that is stored internally in the OpenMC + solver. To obtain a view of a nuclide with a given name, use the + :data:`openmc.capi.nuclides` mapping. + + Parameters + ---------- + index : int + Index in the `nuclides` array. + + Attributes + ---------- + name : str + Name of the nuclide, e.g. 'U235' + + """ + __instances = WeakValueDictionary() + + def __new__(cls, *args): + if args not in cls.__instances: + instance = super(Nuclide, cls).__new__(cls) + cls.__instances[args] = instance + return cls.__instances[args] + + def __init__(self, index): + self._index = index + + @property + def name(self): + name = c_char_p() + _dll.openmc_nuclide_name(self._index, name) + + # Find blank in name + i = 0 + while name.value[i:i+1] != b' ': + i += 1 + return name.value[:i].decode() + + +class _NuclideMapping(Mapping): + """Provide mapping from nuclide name to index in nuclides array.""" + def __getitem__(self, key): + index = c_int() + try: + _dll.openmc_get_nuclide_index(key.encode(), index) + except (DataError, AllocationError) as e: + # __contains__ expects a KeyError to work correctly + raise KeyError(str(e)) + return Nuclide(index.value) + + def __iter__(self): + for i in range(len(self)): + yield Nuclide(i + 1).name + + def __len__(self): + return c_int.in_dll(_dll, 'n_nuclides').value + + def __repr__(self): + return repr(dict(self)) + +nuclides = _NuclideMapping() diff --git a/openmc/capi/settings.py b/openmc/capi/settings.py new file mode 100644 index 000000000..eede6cf47 --- /dev/null +++ b/openmc/capi/settings.py @@ -0,0 +1,53 @@ +from ctypes import c_int, c_int32, c_int64, c_double, c_char_p, POINTER + +from . import _dll +from .core import _DLLGlobal +from .error import _error_handler + +_RUN_MODES = {1: 'fixed source', + 2: 'eigenvalue', + 3: 'plot', + 4: 'particle restart', + 5: 'volume'} + +_dll.openmc_set_seed.argtypes = [c_int64] +_dll.openmc_set_seed.restype = c_int +_dll.openmc_set_seed.errcheck = _error_handler + + +class _Settings(object): + # Attributes that are accessed through a descriptor + batches = _DLLGlobal(c_int32, 'n_batches') + generations_per_batch = _DLLGlobal(c_int32, 'gen_per_batch') + inactive = _DLLGlobal(c_int32, 'n_inactive') + particles = _DLLGlobal(c_int64, 'n_particles') + verbosity = _DLLGlobal(c_int, 'verbosity') + + @property + def run_mode(self): + i = c_int.in_dll(_dll, 'run_mode').value + try: + return _RUN_MODES[i] + except KeyError: + return None + + @run_mode.setter + def run_mode(self, mode): + current_idx = c_int.in_dll(_dll, 'run_mode') + for idx, mode_value in _RUN_MODES.items(): + if mode_value == mode: + current_idx.value = idx + break + else: + raise ValueError('Invalid run mode: {}'.format(mode)) + + @property + def seed(self): + return c_int64.in_dll(_dll, 'seed').value + + @seed.setter + def seed(self, seed): + _dll.openmc_set_seed(seed) + + +settings = _Settings() diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py new file mode 100644 index 000000000..799cb42ee --- /dev/null +++ b/openmc/capi/tally.py @@ -0,0 +1,209 @@ +from collections import Mapping +from ctypes import c_int, c_int32, c_double, c_char_p, POINTER +from weakref import WeakValueDictionary + +from numpy.ctypeslib import as_array + +from . import _dll, Nuclide +from .core import _FortranObjectWithID +from .error import _error_handler, AllocationError, InvalidIDError +from .filter import _get_filter + + +__all__ = ['Tally', 'tallies'] + +# Tally functions +_dll.openmc_get_tally_index.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_get_tally_index.restype = c_int +_dll.openmc_get_tally_index.errcheck = _error_handler +_dll.openmc_extend_tallies.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)] +_dll.openmc_extend_tallies.restype = c_int +_dll.openmc_extend_tallies.errcheck = _error_handler +_dll.openmc_tally_get_id.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_tally_get_id.restype = c_int +_dll.openmc_tally_get_id.errcheck = _error_handler +_dll.openmc_tally_get_filters.argtypes = [ + c_int32, POINTER(POINTER(c_int32)), POINTER(c_int)] +_dll.openmc_tally_get_filters.restype = c_int +_dll.openmc_tally_get_filters.errcheck = _error_handler +_dll.openmc_tally_get_nuclides.argtypes = [ + c_int32, POINTER(POINTER(c_int)), POINTER(c_int)] +_dll.openmc_tally_get_nuclides.restype = c_int +_dll.openmc_tally_get_nuclides.errcheck = _error_handler +_dll.openmc_tally_results.argtypes = [ + c_int32, POINTER(POINTER(c_double)), POINTER(c_int*3)] +_dll.openmc_tally_results.restype = c_int +_dll.openmc_tally_results.errcheck = _error_handler +_dll.openmc_tally_set_filters.argtypes = [c_int32, c_int, POINTER(c_int32)] +_dll.openmc_tally_set_filters.restype = c_int +_dll.openmc_tally_set_filters.errcheck = _error_handler +_dll.openmc_tally_set_id.argtypes = [c_int32, c_int32] +_dll.openmc_tally_set_id.restype = c_int +_dll.openmc_tally_set_id.errcheck = _error_handler +_dll.openmc_tally_set_nuclides.argtypes = [c_int32, c_int, POINTER(c_char_p)] +_dll.openmc_tally_set_nuclides.restype = c_int +_dll.openmc_tally_set_nuclides.errcheck = _error_handler +_dll.openmc_tally_set_scores.argtypes = [c_int32, c_int, POINTER(c_char_p)] +_dll.openmc_tally_set_scores.restype = c_int +_dll.openmc_tally_set_scores.errcheck = _error_handler +_dll.openmc_tally_set_type.argtypes = [c_int32, c_char_p] +_dll.openmc_tally_set_type.restype = c_int +_dll.openmc_tally_set_type.errcheck = _error_handler + + +class Tally(_FortranObjectWithID): + """Tally stored internally. + + This class exposes a tally that is stored internally in the OpenMC + library. To obtain a view of a tally with a given ID, use the + :data:`openmc.capi.tallies` mapping. + + Parameters + ---------- + uid : int or None + Unique ID of the tally + new : bool + When `index` is None, this argument controls whether a new object is + created or a view of an existing object is returned. + index : int or None + Index in the `tallies` array. + + Attributes + ---------- + id : int + ID of the tally + filters : list + List of tally filters + nuclides : list of str + List of nuclides to score results for + results : numpy.ndarray + Array of tally results + + """ + __instances = WeakValueDictionary() + + def __new__(cls, uid=None, new=True, index=None): + mapping = tallies + if index is None: + if new: + # Determine ID to assign + if uid is None: + try: + uid = max(mapping) + 1 + except ValueError: + uid = 1 + else: + if uid in mapping: + raise AllocationError('A tally with ID={} has already ' + 'been allocated.'.format(uid)) + + index = c_int32() + _dll.openmc_extend_tallies(1, index, None) + _dll.openmc_tally_set_type(index, b'generic') + index = index.value + else: + index = mapping[uid]._index + + if index not in cls.__instances: + instance = super(Tally, cls).__new__(cls) + instance._index = index + if uid is not None: + instance.id = uid + cls.__instances[index] = instance + + return cls.__instances[index] + + @property + def id(self): + tally_id = c_int32() + _dll.openmc_tally_get_id(self._index, tally_id) + return tally_id.value + + @id.setter + def id(self, tally_id): + _dll.openmc_tally_set_id(self._index, tally_id) + + @property + def filters(self): + filt_idx = POINTER(c_int32)() + n = c_int() + _dll.openmc_tally_get_filters(self._index, filt_idx, n) + return [_get_filter(filt_idx[i]) for i in range(n.value)] + + @property + def nuclides(self): + nucs = POINTER(c_int)() + n = c_int() + _dll.openmc_tally_get_nuclides(self._index, nucs, n) + return [Nuclide(nucs[i]).name if nucs[i] > 0 else 'total' + for i in range(n.value)] + + @property + def results(self): + data = POINTER(c_double)() + shape = (c_int*3)() + _dll.openmc_tally_results(self._index, data, shape) + return as_array(data, tuple(shape[::-1])) + + @filters.setter + def filters(self, filters): + # Get filter indices as int32_t[] + n = len(filters) + indices = (c_int32*n)(*(f._index for f in filters)) + + _dll.openmc_tally_set_filters(self._index, n, indices) + + @nuclides.setter + def nuclides(self, nuclides): + nucs = (c_char_p * len(nuclides))() + nucs[:] = [x.encode() for x in nuclides] + _dll.openmc_tally_set_nuclides(self._index, len(nuclides), nucs) + + @property + def scores(self): + pass + + @scores.setter + def scores(self, scores): + scores_ = (c_char_p * len(scores))() + scores_[:] = [x.encode() for x in scores] + _dll.openmc_tally_set_scores(self._index, len(scores), scores_) + + @classmethod + def new(cls, tally_id=None): + # Determine ID to assign + if tally_id is None: + try: + tally_id = max(tallies) + 1 + except ValueError: + tally_id = 1 + + index = c_int32() + _dll.openmc_extend_tallies(1, index, None) + _dll.openmc_tally_set_type(index, b'generic') + tally = cls(index.value) + tally.id = tally_id + return tally + + +class _TallyMapping(Mapping): + def __getitem__(self, key): + index = c_int32() + try: + _dll.openmc_get_tally_index(key, index) + except (AllocationError, InvalidIDError) as e: + # __contains__ expects a KeyError to work correctly + raise KeyError(str(e)) + return Tally(index=index.value) + + def __iter__(self): + for i in range(len(self)): + yield Tally(index=i + 1).id + + def __len__(self): + return c_int32.in_dll(_dll, 'n_tallies').value + + def __repr__(self): + return repr(dict(self)) + +tallies = _TallyMapping() diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 0894dac1e..50d700841 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -867,12 +867,13 @@ class IncidentNeutron(EqualityMixin): data.fission_energy = FissionEnergyRelease.from_endf(ev, data) # Add 0K elastic scattering cross section - pendf = Evaluation(pendf_file) - file_obj = StringIO(pendf.section[3, 2]) - get_head_record(file_obj) - params, xs = get_tab1_record(file_obj) - data.energy['0K'] = xs.x - data[2].xs['0K'] = xs + if '0K' not in data.energy: + pendf = Evaluation(pendf_file) + file_obj = StringIO(pendf.section[3, 2]) + get_head_record(file_obj) + params, xs = get_tab1_record(file_obj) + data.energy['0K'] = xs.x + data[2].xs['0K'] = xs finally: # Get rid of temporary files diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index b6fce5e3e..ac98af7fb 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -50,48 +50,45 @@ _THERMAL_DATA = { 75: ThermalTuple('ouo2', [8016, 8017, 8018], 1), } - -_PENDF_TEMPLATE = """ +_TEMPLATE_RECONR = """ reconr / %%%%%%%%%%%%%%%%%%% Reconstruct XS for neutrons %%%%%%%%%%%%%%%%%%%%%%% -20 22 +{nendf} {npendf} '{library} PENDF for {zsymam}'/ {mat} 2/ -0.001 0.0 0.003/ err tempr errmax +{error}/ err '{library}: {zsymam}'/ 'Processed by NJOY'/ 0/ -stop """ -_ACE_TEMPLATE = """ -reconr / %%%%%%%%%%%%%%%%%%% Reconstruct XS for neutrons %%%%%%%%%%%%%%%%%%%%%%% -20 21 -'{library} PENDF for {zsymam}'/ -{mat} 2/ -0.001 0.0 0.003/ err tempr errmax -'{library}: {zsymam}'/ -'Processed by NJOY'/ -0/ +_TEMPLATE_BROADR = """ broadr / %%%%%%%%%%%%%%%%%%%%%%% Doppler broaden XS %%%%%%%%%%%%%%%%%%%%%%%%%%%% -20 21 22 +{nendf} {npendf} {nbroadr} {mat} {num_temp} 0 0 0. / -0.001 1.0e6 0.003 / +{error}/ errthn {temps} 0/ +""" + +_TEMPLATE_HEATR = """ heatr / %%%%%%%%%%%%%%%%%%%%%%%%% Add heating kerma %%%%%%%%%%%%%%%%%%%%%%%%%%%% -20 22 23 / +{nendf} {nheatr_in} {nheatr} / {mat} 3 / 302 318 402 / +""" + +_TEMPLATE_PURR = """ purr / %%%%%%%%%%%%%%%%%%%%%%%% Add probability tables %%%%%%%%%%%%%%%%%%%%%%%%% -20 23 24 +{nendf} {npurr_in} {npurr} / {mat} {num_temp} 1 20 64 / {temps} 1.e10 0/ """ -_ACE_TEMPLATE_ACER = """acer / -20 24 0 {nace} {ndir} +_TEMPLATE_ACER = """ +acer / %%%%%%%%%%%%%%%%%%%%%%%% Write out in ACE format %%%%%%%%%%%%%%%%%%%%%%%% +{nendf} {nacer_in} 0 {nace} {ndir} 1 0 1 .{ext} / '{library}: {zsymam} at {temperature}'/ {mat} {temperature} @@ -99,35 +96,22 @@ _ACE_TEMPLATE_ACER = """acer / / """ -_ACE_THERMAL_TEMPLATE = """ -reconr / %%%%%%%%%%%%%%%%%%% Reconstruct XS for neutrons %%%%%%%%%%%%%%%%%%%%%%% -20 22 -'{library} PENDF for {zsymam}'/ -{mat} 2/ -0.001 0. 0.001/ err tempr errmax -'{library}: PENDF for {zsymam}'/ -'Processed by NJOY'/ -0/ -broadr / %%%%%%%%%%%%%%%%%%%%%%% Doppler broaden XS %%%%%%%%%%%%%%%%%%%%%%%%%%%% -20 22 23 -{mat} {num_temp} 0 0 0./ -0.001 2.0e+6 0.001/ errthn thnmax errmax -{temps} -0/ +_THERMAL_TEMPLATE_THERMR = """ thermr / %%%%%%%%%%%%%%%% Add thermal scattering data (free gas) %%%%%%%%%%%%%%% -0 23 62 +0 {nthermr1_in} {nthermr1} 0 {mat} 12 {num_temp} 1 0 {iform} 1 221 1/ {temps} -0.001 {energy_max} +{error} {energy_max} thermr / %%%%%%%%%%%%%%%% Add thermal scattering data (bound) %%%%%%%%%%%%%%%%%% -60 62 27 +{nthermal_endf} {nthermr2_in} {nthermr2} {mat_thermal} {mat} 16 {num_temp} {inelastic} {elastic} {iform} {natom} 222 1/ {temps} -0.001 {energy_max} +{error} {energy_max} """ -_ACE_THERMAL_TEMPLATE_ACER = """acer / -20 27 0 {nace} {ndir} +_THERMAL_TEMPLATE_ACER = """ +acer / %%%%%%%%%%%%%%%%%%%%%%%% Write out in ACE format %%%%%%%%%%%%%%%%%%%%%%%% +{nendf} {nthermal_acer_in} 0 {nace} {ndir} 2 0 1 .{ext}/ '{library}: {zsymam_thermal} processed by NJOY'/ {mat} {temperature} '{data.name}' / @@ -136,7 +120,8 @@ _ACE_THERMAL_TEMPLATE_ACER = """acer / """ -def run(commands, tapein, tapeout, stdout=False, njoy_exec='njoy'): +def run(commands, tapein, tapeout, input_filename=None, stdout=False, + njoy_exec='njoy'): """Run NJOY with given commands Parameters @@ -147,6 +132,8 @@ def run(commands, tapein, tapeout, stdout=False, njoy_exec='njoy'): Dictionary mapping tape numbers to paths for any input files tapeout : dict Dictionary mapping tape numbers to paths for any output files + input_filename : str, optional + File name to write out NJOY input commands stdout : bool, optional Whether to display output when running NJOY njoy_exec : str, optional @@ -159,6 +146,10 @@ def run(commands, tapein, tapeout, stdout=False, njoy_exec='njoy'): """ + if input_filename is not None: + with open(input_filename, 'w') as f: + f.write(commands) + # Create temporary directory -- it would be preferable to use # TemporaryDirectory(), but it is only available in Python 3.2 tmpdir = tempfile.mkdtemp() @@ -195,7 +186,7 @@ def run(commands, tapein, tapeout, stdout=False, njoy_exec='njoy'): return njoy.returncode -def make_pendf(filename, pendf='pendf', stdout=False): +def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): """Generate ACE file from an ENDF file Parameters @@ -204,6 +195,8 @@ def make_pendf(filename, pendf='pendf', stdout=False): Path to ENDF file pendf : str, optional Path of pointwise ENDF file to write + error : float, optional + Fractional error tolerance for NJOY processing stdout : bool Whether to display NJOY standard output @@ -213,21 +206,14 @@ def make_pendf(filename, pendf='pendf', stdout=False): Return code of NJOY process """ - ev = endf.Evaluation(filename) - mat = ev.material - zsymam = ev.target['zsymam'] - # Determine name of library - library = '{}-{}.{}'.format(*ev.info['library']) - - commands = _PENDF_TEMPLATE.format(**locals()) - tapein = {20: filename} - tapeout = {22: pendf} - return run(commands, tapein, tapeout, stdout) + return make_ace(filename, pendf=pendf, error=error, broadr=False, + heatr=False, purr=False, acer=False, stdout=stdout) -def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', - pendf=None, **kwargs): +def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, + error=0.001, broadr=True, heatr=True, purr=True, acer=True, + **kwargs): """Generate incident neutron ACE file from an ENDF file Parameters @@ -243,6 +229,16 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', Path of xsdir file to write pendf : str, optional Path of pendf file to write. If omitted, the pendf file is not saved. + error : float, optional + Fractional error tolerance for NJOY processing + broadr : bool, optional + Indicating whether to Doppler broaden XS when running NJOY + heatr : bool, optional + Indicating whether to add heating kerma when running NJOY + purr : bool, optional + Indicating whether to add probability table when running NJOY + acer : bool, optional + Indicating whether to generate ACE file when running NJOY **kwargs Keyword arguments passed to :func:`openmc.data.njoy.run` @@ -264,26 +260,59 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', num_temp = len(temperatures) temps = ' '.join(str(i) for i in temperatures) - commands = _ACE_TEMPLATE.format(**locals()) - tapein = {20: filename} + # Create njoy commands by modules + commands = "" + + nendf, npendf = 20, 21 + tapein = {nendf: filename} tapeout = {} if pendf is not None: - tapeout[21] = pendf - fname = '{}_{:.1f}' - for i, temperature in enumerate(temperatures): - # Extend input with an ACER run for each temperature - nace = 25 + 2*i - ndir = 25 + 2*i + 1 - ext = '{:02}'.format(i + 1) - commands += _ACE_TEMPLATE_ACER.format(**locals()) + tapeout[npendf] = pendf - # Indicate tapes to save for each ACER run - tapeout[nace] = fname.format(ace, temperature) - tapeout[ndir] = fname.format(xsdir, temperature) + # reconr + commands += _TEMPLATE_RECONR + nlast = npendf + + # broadr + if broadr: + nbroadr = nlast + 1 + commands += _TEMPLATE_BROADR + nlast = nbroadr + + # heatr + if heatr: + nheatr_in = nlast + nheatr = nheatr_in + 1 + commands += _TEMPLATE_HEATR + nlast = nheatr + + # purr + if purr: + npurr_in = nlast + npurr = npurr_in + 1 + commands += _TEMPLATE_PURR + nlast = npurr + + commands = commands.format(**locals()) + + # acer + if acer: + nacer_in = nlast + fname = '{}_{:.1f}' + for i, temperature in enumerate(temperatures): + # Extend input with an ACER run for each temperature + nace = nacer_in + 1 + 2*i + ndir = nace + 1 + ext = '{:02}'.format(i + 1) + commands += _TEMPLATE_ACER.format(**locals()) + + # Indicate tapes to save for each ACER run + tapeout[nace] = fname.format(ace, temperature) + tapeout[ndir] = fname.format(xsdir, temperature) commands += 'stop\n' retcode = run(commands, tapein, tapeout, **kwargs) - if retcode == 0: + if acer and retcode == 0: with open(ace, 'w') as ace_file, open(xsdir, 'w') as xsdir_file: for temperature in temperatures: # Get contents of ACE file @@ -311,8 +340,8 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', return retcode -def make_ace_thermal(filename, filename_thermal, temperatures=None, - ace='ace', xsdir='xsdir', **kwargs): +def make_ace_thermal(filename, filename_thermal, temperatures=None, + ace='ace', xsdir='xsdir', error=0.001, **kwargs): """Generate thermal scattering ACE file from ENDF files Parameters @@ -328,6 +357,8 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, Path of ACE file to write xsdir : str, optional Path of xsdir file to write + error : float, optional + Fractional error tolerance for NJOY processing **kwargs Keyword arguments passed to :func:`openmc.data.njoy.run` @@ -390,16 +421,41 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, num_temp = len(temperatures) temps = ' '.join(str(i) for i in temperatures) - commands = _ACE_THERMAL_TEMPLATE.format(**locals()) - tapein = {20: filename, 60: filename_thermal} + # Create njoy commands by modules + commands = "" + + nendf, nthermal_endf, npendf = 20, 21, 22 + tapein = {nendf: filename, nthermal_endf:filename_thermal} tapeout = {} + + # reconr + commands += _TEMPLATE_RECONR + nlast = npendf + + # broadr + nbroadr = nlast + 1 + commands += _TEMPLATE_BROADR + nlast = nbroadr + + # thermr + nthermr1_in = nlast + nthermr1 = nthermr1_in + 1 + nthermr2_in = nthermr1 + nthermr2 = nthermr2_in + 1 + commands += _THERMAL_TEMPLATE_THERMR + nlast = nthermr2 + + commands = commands.format(**locals()) + + # acer + nthermal_acer_in = nlast fname = '{}_{:.1f}' for i, temperature in enumerate(temperatures): # Extend input with an ACER run for each temperature - nace = 28 + 2*i - ndir = 28 + 2*i + 1 + nace = nthermal_acer_in + 1 + 2*i + ndir = nace + 1 ext = '{:02}'.format(i + 1) - commands += _ACE_THERMAL_TEMPLATE_ACER.format(**locals()) + commands += _THERMAL_TEMPLATE_ACER.format(**locals()) # Indicate tapes to save for each ACER run tapeout[nace] = fname.format(ace, temperature) diff --git a/openmc/element.py b/openmc/element.py index edfbee136..fc019a5d3 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -124,6 +124,15 @@ class Element(object): is a tuple consisting of an openmc.Nuclide instance and the natural abundance of the isotope. + Notes + ----- + When the `enrichment` argument is specified, a correlation from + `ORNL/CSD/TM-244 `_ is used to + calculate the weight fractions of U234, U235, U236, and U238. Namely, + the weight fraction of U234 and U236 are taken to be 0.89% and 0.46%, + respectively, of the U235 weight fraction. The remainder of the isotopic + weight is assigned to U238. + """ # Get the nuclides present in nature @@ -217,9 +226,10 @@ class Element(object): if enrichment is not None: # Calculate the mass fractions of isotopes - abundances['U234'] = 0.008 * enrichment + abundances['U234'] = 0.0089 * enrichment abundances['U235'] = enrichment - abundances['U238'] = 100.0 - 1.008 * enrichment + abundances['U236'] = 0.0046 * enrichment + abundances['U238'] = 100.0 - 1.0135 * enrichment # Convert the mass fractions to mole fractions for nuclide in abundances.keys(): diff --git a/openmc/filter.py b/openmc/filter.py index d971163ee..9d2f9c697 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -17,7 +17,8 @@ from .mixin import IDManagerMixin _FILTER_TYPES = ['universe', 'material', 'cell', 'cellborn', 'surface', 'mesh', 'energy', 'energyout', 'mu', 'polar', 'azimuthal', - 'distribcell', 'delayedgroup', 'energyfunction', 'particle'] + 'distribcell', 'delayedgroup', 'energyfunction', 'cellfrom', + 'particle'] _CURRENT_NAMES = {1: 'x-min out', 2: 'x-min in', 3: 'x-max out', 4: 'x-max in', @@ -620,6 +621,39 @@ class CellFilter(WithIDFilter): self._smart_set_bins(bins, openmc.Cell) +class CellFromFilter(WithIDFilter): + """Bins tally on which Cell the neutron came from. + + Parameters + ---------- + bins : openmc.Cell, Integral, or iterable thereof + The Cell(s) to tally. Either openmc.Cell objects or their + Integral ID numbers can be used. + filter_id : int + Unique identifier for the filter + + Attributes + ---------- + bins : Integral or Iterable of Integral + openmc.Cell IDs. + id : int + Unique identifier for the filter + num_bins : Integral + The number of filter bins + stride : Integral + The number of filter, nuclide and score bins within each of this + filter's bins. + + """ + @property + def bins(self): + return self._bins + + @bins.setter + def bins(self, bins): + self._smart_set_bins(bins, openmc.Cell) + + class CellbornFilter(WithIDFilter): """Bins tally events based on which Cell the neutron was born in. diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index 7ebe6cb8c..3cdd0fbf5 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -606,7 +606,7 @@ class Library(object): Parameters ---------- - domain : Material or Cell or Universe or Integral + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh or Integral The material, cell, or universe object of interest (or its ID) mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', chi', 'chi-prompt', 'inverse-velocity', 'prompt-nu-fission', 'prompt-nu-fission matrix', 'delayed-nu-fission', 'delayed-nu-fission matrix', 'chi-delayed', 'beta'} The type of multi-group cross section object to return @@ -668,7 +668,7 @@ class Library(object): Returns ------- - Library + openmc.mgxs.Library A new multi-group cross section library condensed to the group structure of interest @@ -880,7 +880,7 @@ class Library(object): Returns ------- - Library + openmc.mgxs.Library A Library object loaded from the pickle binary file See also diff --git a/openmc/mgxs/mdgxs.py b/openmc/mgxs/mdgxs.py index 055bf267d..d3f1a0646 100644 --- a/openmc/mgxs/mdgxs.py +++ b/openmc/mgxs/mdgxs.py @@ -71,7 +71,7 @@ class MDGXS(MGXS): Reaction type (e.g., 'chi-delayed', 'beta', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -948,7 +948,7 @@ class ChiDelayed(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -1462,7 +1462,7 @@ class DelayedNuFissionXS(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -1598,7 +1598,7 @@ class Beta(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -1782,7 +1782,7 @@ class DecayRate(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -1959,7 +1959,7 @@ class MatrixMDGXS(MDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2551,7 +2551,7 @@ class DelayedNuFissionMatrixXS(MatrixMDGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 3325e0b15..192f484a5 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -154,7 +154,7 @@ class MGXS(object): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2018,7 +2018,7 @@ class MatrixMGXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2512,7 +2512,7 @@ class TotalXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2649,7 +2649,7 @@ class TransportXS(MGXS): If True, the cross section data will include neutron multiplication by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2858,7 +2858,7 @@ class AbsorptionXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -2986,7 +2986,7 @@ class CaptureXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -3140,7 +3140,7 @@ class FissionXS(MGXS): If true, computes cross sections which only includes prompt neutrons by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -3309,7 +3309,7 @@ class KappaFissionXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -3441,7 +3441,7 @@ class ScatterXS(MGXS): If True, the cross section data will include neutron multiplication by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -3658,7 +3658,7 @@ class ScatterMatrixXS(MatrixMGXS): If True, the cross section data will include neutron multiplication by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -4020,7 +4020,7 @@ class ScatterMatrixXS(MatrixMGXS): # Override the nuclides for tally arithmetic correction.nuclides = scatter_p1.nuclides self._xs_tally -= correction - + self._compute_xs() return self._xs_tally @@ -4749,7 +4749,7 @@ class MultiplicityMatrixXS(MatrixMGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -4915,7 +4915,7 @@ class ScatterProbabilityMatrix(MatrixMGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -5088,7 +5088,7 @@ class NuFissionMatrixXS(MatrixMGXS): If true, computes cross sections which only includes prompt neutrons by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -5245,7 +5245,7 @@ class Chi(MGXS): If true, computes cross sections which only includes prompt neutrons by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization @@ -5823,7 +5823,7 @@ class InverseVelocity(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe or Mesh + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh Domain for spatial homogenization domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization diff --git a/openmc/nuclide.py b/openmc/nuclide.py index 03062ee0e..fc0d7ef07 100644 --- a/openmc/nuclide.py +++ b/openmc/nuclide.py @@ -11,12 +11,12 @@ class Nuclide(object): Parameters ---------- name : str - Name of the nuclide, e.g. U235 + Name of the nuclide, e.g. 'U235' Attributes ---------- name : str - Name of the nuclide, e.g. U235 + Name of the nuclide, e.g. 'U235' scattering : 'data' or 'iso-in-lab' or None The type of angular scattering distribution to use diff --git a/openmc/settings.py b/openmc/settings.py index 5f8b53075..760155015 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -968,20 +968,15 @@ class Settings(object): subelement = ET.SubElement(element, key) subelement.text = str(value) - def _create_entropy_subelement(self, root): - if self._entropy_mesh is not None: - element = ET.SubElement(root, "entropy") + def _create_entropy_mesh_subelement(self, root): + if self.entropy_mesh is not None: + # See if a element already exists -- if not, add it + path = "./mesh[@id='{}']".format(self.entropy_mesh.id) + if root.find(path) is None: + root.append(self.entropy_mesh.to_xml_element()) - if self._entropy_mesh.dimension is not None: - subelement = ET.SubElement(element, "dimension") - subelement.text = ' '.join( - str(x) for x in self._entropy_mesh.dimension) - subelement = ET.SubElement(element, "lower_left") - subelement.text = ' '.join( - str(x) for x in self._entropy_mesh.lower_left) - subelement = ET.SubElement(element, "upper_right") - subelement.text = ' '.join( - str(x) for x in self._entropy_mesh.upper_right) + subelement = ET.SubElement(root, "entropy_mesh") + subelement.text = str(self.entropy_mesh.id) def _create_trigger_subelement(self, root): if self._trigger_active is not None: @@ -1038,18 +1033,15 @@ class Settings(object): element = ET.SubElement(root, "track") element.text = ' '.join(map(str, self._track)) - def _create_ufs_subelement(self, root): - if self._ufs_mesh is not None: - element = ET.SubElement(root, "uniform_fs") - subelement = ET.SubElement(element, "dimension") - subelement.text = ' '.join(str(x) for x in - self._ufs_mesh.dimension) - subelement = ET.SubElement(element, "lower_left") - subelement.text = ' '.join(str(x) for x in - self._ufs_mesh.lower_left) - subelement = ET.SubElement(element, "upper_right") - subelement.text = ' '.join(str(x) for x in - self._ufs_mesh.upper_right) + def _create_ufs_mesh_subelement(self, root): + if self.ufs_mesh is not None: + # See if a element already exists -- if not, add it + path = "./mesh[@id='{}']".format(self.ufs_mesh.id) + if root.find(path) is None: + root.append(self.ufs_mesh.to_xml_element()) + + subelement = ET.SubElement(root, "ufs_mesh") + subelement.text = str(self.ufs_mesh.id) def _create_dd_subelement(self, root): if self._dd_mesh_lower_left is not None and \ @@ -1137,7 +1129,7 @@ class Settings(object): self._create_seed_subelement(root_element) self._create_survival_biasing_subelement(root_element) self._create_cutoff_subelement(root_element) - self._create_entropy_subelement(root_element) + self._create_entropy_mesh_subelement(root_element) self._create_trigger_subelement(root_element) self._create_no_reduce_subelement(root_element) self._create_threads_subelement(root_element) @@ -1146,7 +1138,7 @@ class Settings(object): self._create_temperature_subelements(root_element) self._create_trace_subelement(root_element) self._create_track_subelement(root_element) - self._create_ufs_subelement(root_element) + self._create_ufs_mesh_subelement(root_element) self._create_dd_subelement(root_element) self._create_resonance_scattering_subelement(root_element) self._create_volume_calcs_subelement(root_element) diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 474b03033..16d5b5d84 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -402,14 +402,6 @@ class StatePoint(object): scores = group['score_bins'].value n_score_bins = group['n_score_bins'].value - # Compute and set the filter strides - for i in range(n_filters): - tally_filter = tally.filters[i] - tally_filter.stride = n_score_bins * len(nuclide_names) - - for j in range(i+1, n_filters): - tally_filter.stride *= tally.filters[j].num_bins - # Read scattering moment order strings (e.g., P3, Y1,2, etc.) moments = group['moment_orders'].value @@ -423,6 +415,9 @@ class StatePoint(object): tally.scores.append(score) + # Compute and set the filter strides + tally._update_filter_strides() + # Add Tally to the global dictionary of all Tallies tally.sparse = self.sparse self._tallies[tally_id] = tally diff --git a/openmc/tallies.py b/openmc/tallies.py index 6853db494..3c91da8c9 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -4,7 +4,7 @@ from collections import Iterable, MutableSequence import copy import re from functools import partial -import itertools +from itertools import product from numbers import Integral, Real import warnings from xml.etree import ElementTree as ET @@ -502,7 +502,7 @@ class Tally(IDManagerMixin): Parameters ---------- - nuclide : str, Nuclide, CrossNuclide or AggregateNuclide + nuclide : str, openmc.Nuclide, CrossNuclide or AggregateNuclide Nuclide to add to the tally. The nuclide should be a Nuclide object when a user is adding nuclides to a Tally for input file generation. The nuclide is a str when a Tally is created from a StatePoint file @@ -1329,7 +1329,7 @@ class Tally(IDManagerMixin): if isinstance(self_filter, openmc.MeshFilter): dimension = self_filter.mesh.dimension xyz = [range(1, x+1) for x in dimension] - bins = list(itertools.product(*xyz)) + bins = list(product(*xyz)) # Create list of 2-tuples for energy boundary bins elif isinstance(self_filter, (openmc.EnergyFilter, @@ -1364,7 +1364,7 @@ class Tally(IDManagerMixin): indices *= self_filter.num_bins # Apply outer product sum between all filter bin indices - filter_indices = list(map(sum, itertools.product(*filter_indices))) + filter_indices = list(map(sum, product(*filter_indices))) # If user did not specify any specific Filters, use them all else: @@ -1664,8 +1664,10 @@ class Tally(IDManagerMixin): new_column.extend(['']*delta_len) columns[i] = tuple(new_column) - # Create and set a MultiIndex for the DataFrame's columns - df.columns = pd.MultiIndex.from_tuples(columns) + # Create and set a MultiIndex for the DataFrame's columns, but only + # if any column actually is multi-level (e.g., a mesh filter) + if any(len(c) > 1 for c in columns): + df.columns = pd.MultiIndex.from_tuples(columns) # Modify the df.to_string method so that it prints formatted strings. # Credit to http://stackoverflow.com/users/3657742/chrisb for this trick @@ -1880,7 +1882,7 @@ class Tally(IDManagerMixin): new_tally.filters.append(self_filter) else: all_filters = [self_copy.filters, other_copy.filters] - for self_filter, other_filter in itertools.product(*all_filters): + for self_filter, other_filter in product(*all_filters): new_filter = openmc.CrossFilter(self_filter, other_filter, binary_op) new_tally.filters.append(new_filter) @@ -1891,20 +1893,31 @@ class Tally(IDManagerMixin): new_tally.nuclides.append(self_nuclide) else: all_nuclides = [self_copy.nuclides, other_copy.nuclides] - for self_nuclide, other_nuclide in itertools.product(*all_nuclides): - new_nuclide = \ - openmc.CrossNuclide(self_nuclide, other_nuclide, binary_op) + for self_nuclide, other_nuclide in product(*all_nuclides): + new_nuclide = openmc.CrossNuclide(self_nuclide, other_nuclide, + binary_op) new_tally.nuclides.append(new_nuclide) + # Define helper function that handles score units appropriately + # depending on the binary operator + def cross_score(score1, score2, binary_op): + if binary_op == '+' or binary_op == '-': + if score1 == score2: + return score1 + else: + return openmc.CrossScore(score1, score2, binary_op) + else: + return openmc.CrossScore(score1, score2, binary_op) + # Add scores to the new tally if score_product == 'entrywise': for self_score in self_copy.scores: - new_tally.scores.append(self_score) + new_score = cross_score(self_score, self_score, binary_op) + new_tally.scores.append(new_score) else: all_scores = [self_copy.scores, other_copy.scores] - for self_score, other_score in itertools.product(*all_scores): - new_score = openmc.CrossScore(self_score, other_score, - binary_op) + for self_score, other_score in product(*all_scores): + new_score = cross_score(self_score, other_score, binary_op) new_tally.scores.append(new_score) # Update the new tally's filter strides @@ -2142,7 +2155,7 @@ class Tally(IDManagerMixin): std_dev = {} # Store the data from the misaligned structure - for i, (bin1, bin2) in enumerate(itertools.product(filter1_bins, filter2_bins)): + for i, (bin1, bin2) in enumerate(product(filter1_bins, filter2_bins)): filter_bins = [(bin1,), (bin2,)] if self.mean is not None: @@ -2163,7 +2176,7 @@ class Tally(IDManagerMixin): self._update_filter_strides() # Realign the data - for i, (bin1, bin2) in enumerate(itertools.product(filter1_bins, filter2_bins)): + for i, (bin1, bin2) in enumerate(product(filter1_bins, filter2_bins)): filter_bins = [(bin1,), (bin2,)] indices = self.get_filter_indices(filters, filter_bins) @@ -3503,7 +3516,7 @@ class Tallies(cv.CheckedList): if f not in already_written: root_element.append(f.to_xml_element()) already_written[f] = f.id - else: + elif f.id != already_written[f]: # Set the IDs of identical filters with different # user-defined IDs to the same value f.id = already_written[f] diff --git a/readme.rst b/readme.rst index 34d2a5d09..75e4a1438 100644 --- a/readme.rst +++ b/readme.rst @@ -2,8 +2,7 @@ OpenMC Monte Carlo Particle Transport Code ========================================== -.. image:: https://travis-ci.org/mit-crpg/openmc.svg?branch=develop - :target: https://travis-ci.org/mit-crpg/openmc +|licensebadge| |travisbadge| The OpenMC project aims to provide a fully-featured Monte Carlo particle transport code based on modern methods. It is a constructive solid geometry, @@ -55,3 +54,11 @@ OpenMC is distributed under the MIT/X license_. .. _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) diff --git a/scripts/openmc-update-inputs b/scripts/openmc-update-inputs index 72aee9c0f..3eb850207 100755 --- a/scripts/openmc-update-inputs +++ b/scripts/openmc-update-inputs @@ -247,6 +247,11 @@ def update_materials(root): if 'name' in nuclide.attrib: nucname = nuclide.attrib['name'] nucname = nucname.replace('-', '') + # If a nuclide name is in the ZAID notation (e.g., a number), + # convert it to the proper nuclide name. + if nucname.strip().isnumeric(): + nucname = \ + openmc.data.neutron._get_metadata(int(nucname))[0] nucname = nucname.replace('Nat', '0') if nucname.endswith('m'): nucname = nucname[:-1] + '_m1' diff --git a/setup.py b/setup.py index ab93748b0..5a5c5ba41 100755 --- a/setup.py +++ b/setup.py @@ -3,13 +3,8 @@ import glob import sys import numpy as np -try: - from setuptools import setup - have_setuptools = True -except ImportError: - from distutils.core import setup - have_setuptools = False +from setuptools import setup, find_packages try: from Cython.Build import cythonize have_cython = True @@ -28,46 +23,53 @@ else: with open('openmc/__init__.py', 'r') as f: version = f.readlines()[-1].split()[-1].strip("'") -kwargs = {'name': 'openmc', - 'version': version, - 'packages': ['openmc', 'openmc.data', 'openmc.mgxs', 'openmc.model', - 'openmc.stats'], - 'scripts': glob.glob('scripts/openmc-*'), +kwargs = { + 'name': 'openmc', + 'version': version, + 'packages': find_packages(), + 'scripts': glob.glob('scripts/openmc-*'), - # Data files and librarries - 'package_data': { - 'openmc': ['_libopenmc.{}'.format(suffix)], - 'openmc.data': ['mass.mas12', '*.h5'] - }, + # Data files and librarries + 'package_data': { + 'openmc.capi': ['libopenmc.{}'.format(suffix)], + 'openmc.data': ['mass.mas12', '*.h5'] + }, - # Metadata - 'author': 'Will Boyd', - 'author_email': 'wbinventor@gmail.com', - 'description': 'OpenMC Python API', - 'url': 'https://github.com/mit-crpg/openmc', - 'classifiers': [ - 'Intended Audience :: Developers', - 'Intended Audience :: End Users/Desktop', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Programming Language :: Python', - 'Topic :: Scientific/Engineering' - ]} + # Metadata + 'author': 'The OpenMC Development Team', + 'author_email': 'openmc-dev@googlegroups.com', + 'description': 'OpenMC', + 'url': 'https://github.com/mit-crpg/openmc', + 'classifiers': [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: End Users/Desktop', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Topic :: Scientific/Engineering' + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], -if have_setuptools: - kwargs.update({ - # Required dependencies - 'install_requires': ['six', 'numpy>=1.9', 'h5py', 'scipy', 'pandas>=0.17.0'], + # Required dependencies + 'install_requires': [ + 'six', 'numpy>=1.9', 'h5py', 'scipy', 'ipython', 'matplotlib', + 'pandas', 'lxml', 'uncertainties' + ], - # Optional dependencies - 'extras_require': { - 'decay': ['uncertainties'], - 'plot': ['matplotlib', 'ipython'], - 'vtk': ['vtk', 'silomesh'], - 'validate': ['lxml'] - }, - }) + # Optional dependencies + 'extras_require': { + 'test': ['pytest', 'pytest-cov'], + 'vtk': ['vtk', 'silomesh'], + }, +} # If Cython is present, add resonance reconstruction capability if have_cython: diff --git a/src/api.F90 b/src/api.F90 index b00193fcf..f5b27e718 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -2,82 +2,177 @@ module openmc_api use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T + use hdf5, only: HID_T, h5tclose_f, h5close_f use constants, only: K_BOLTZMANN - use eigenvalue, only: k_sum - use finalize, only: openmc_finalize + use eigenvalue, only: k_sum, openmc_get_keff + use error use geometry, only: find_cell - use global + use geometry_header use hdf5_interface - use message_passing, only: master + use material_header + use mesh_header + use message_passing + use nuclide_header use initialize, only: openmc_init - use input_xml, only: assign_0K_elastic_scattering, check_data_version use particle_header, only: Particle use plot, only: openmc_plot_geometry + use random_lcg, only: seed, openmc_set_seed + use settings + use simulation_header + use tally_header + use tally_filter_header + use tally_filter + use tally, only: openmc_tally_set_type use simulation, only: openmc_run + use string, only: to_f_string + use timer_header use volume_calc, only: openmc_calculate_volumes + implicit none + private public :: openmc_calculate_volumes + public :: openmc_cell_get_id + public :: openmc_cell_get_fill + public :: openmc_cell_set_fill public :: openmc_cell_set_temperature + public :: openmc_energy_filter_get_bins + public :: openmc_energy_filter_set_bins + public :: openmc_extend_filters + public :: openmc_extend_materials + public :: openmc_extend_tallies + public :: openmc_filter_get_id + public :: openmc_filter_get_type + public :: openmc_filter_set_id + public :: openmc_filter_set_type public :: openmc_finalize public :: openmc_find + public :: openmc_get_cell_index + public :: openmc_get_keff + public :: openmc_get_filter_index + public :: openmc_get_filter_next_id + public :: openmc_get_material_index + public :: openmc_get_nuclide_index + public :: openmc_get_tally_index + public :: openmc_hard_reset public :: openmc_init public :: openmc_load_nuclide public :: openmc_material_add_nuclide + public :: openmc_material_get_id public :: openmc_material_get_densities public :: openmc_material_set_density + public :: openmc_material_set_densities + public :: openmc_material_set_id + public :: openmc_material_filter_get_bins + public :: openmc_material_filter_set_bins + public :: openmc_mesh_filter_set_mesh + public :: openmc_nuclide_name public :: openmc_plot_geometry public :: openmc_reset public :: openmc_run + public :: openmc_tally_get_id + public :: openmc_tally_get_filters + public :: openmc_tally_get_nuclides public :: openmc_tally_results + public :: openmc_tally_set_filters + public :: openmc_tally_set_id + public :: openmc_tally_set_nuclides + public :: openmc_tally_set_scores + public :: openmc_tally_set_type contains !=============================================================================== -! OPENMC_CELL_SET_TEMPERATURE sets the temperature of a cell +! OPENMC_FINALIZE frees up memory by deallocating arrays and resetting global +! variables !=============================================================================== - function openmc_cell_set_temperature(id, T, instance) result(err) bind(C) - integer(C_INT32_T), value, intent(in) :: id ! id of cell - real(C_DOUBLE), value, intent(in) :: T - integer(C_INT32_T), optional, intent(in) :: instance - integer(C_INT) :: err + subroutine openmc_finalize() bind(C) - integer :: i, n + integer :: err - err = -1 - if (allocated(cells)) then - if (cell_dict % has_key(id)) then - i = cell_dict % get_key(id) - associate (c => cells(i)) - if (allocated(c % sqrtkT)) then - n = size(c % sqrtkT) - if (present(instance) .and. n > 1) then - if (instance >= 0 .and. instance < n) then - c % sqrtkT(instance + 1) = sqrt(K_BOLTZMANN * T) - err = 0 - end if - else - c % sqrtkT(:) = sqrt(K_BOLTZMANN * T) - err = 0 - end if - end if - end associate - end if - end if - end function openmc_cell_set_temperature + ! Clear results + call openmc_reset() + + ! Reset global variables + assume_separate = .false. + check_overlaps = .false. + confidence_intervals = .false. + create_fission_neutrons = .true. + energy_cutoff = ZERO + energy_max_neutron = INFINITY + energy_min_neutron = ZERO + entropy_on = .false. + gen_per_batch = 1 + keff = ONE + legendre_to_tabular = .true. + legendre_to_tabular_points = 33 + n_batch_interval = 1 + n_particles = 0 + n_source_points = 0 + n_state_points = 0 + n_tallies = 0 + output_summary = .true. + output_tallies = .true. + particle_restart_run = .false. + pred_batches = .false. + reduce_tallies = .true. + res_scat_on = .false. + res_scat_method = RES_SCAT_ARES + res_scat_energy_min = 0.01_8 + res_scat_energy_max = 1000.0_8 + restart_run = .false. + root_universe = -1 + run_CE = .true. + run_mode = NONE + satisfy_triggers = .false. + seed = 1_8 + source_latest = .false. + source_separate = .false. + source_write = .true. + survival_biasing = .false. + temperature_default = 293.6_8 + temperature_method = TEMPERATURE_NEAREST + temperature_multipole = .false. + temperature_range = [ZERO, ZERO] + temperature_tolerance = 10.0_8 + total_gen = 0 + trigger_on = .false. + ufs = .false. + urr_ptables_on = .true. + verbosity = 7 + weight_cutoff = 0.25_8 + weight_survive = ONE + write_all_tracks = .false. + write_initial_source = .false. + + ! Deallocate arrays + call free_memory() + + ! Release compound datatypes + call h5tclose_f(hdf5_bank_t, err) + + ! Close FORTRAN interface. + call h5close_f(err) + +#ifdef MPI + ! Free all MPI types + call MPI_TYPE_FREE(MPI_BANK, err) +#endif + + end subroutine openmc_finalize !=============================================================================== ! OPENMC_FIND determines the ID or a cell or material at a given point in space !=============================================================================== - subroutine openmc_find(xyz, rtype, id, instance) bind(C) + function openmc_find(xyz, rtype, id, instance) result(err) bind(C) real(C_DOUBLE), intent(in) :: xyz(3) ! Cartesian point integer(C_INT), intent(in), value :: rtype ! 1 for cell, 2 for material integer(C_INT32_T), intent(out) :: id integer(C_INT32_T), intent(out) :: instance + integer(C_INT) :: err logical :: found type(Particle) :: p @@ -89,6 +184,8 @@ contains id = -1 instance = -1 + err = E_UNASSIGNED + if (found) then if (rtype == 1) then id = cells(p % coord(p % n_coord) % cell) % id @@ -100,202 +197,53 @@ contains end if end if instance = p % cell_instance - 1 - end if - end subroutine openmc_find - -!=============================================================================== -! OPENMC_LOAD_NUCLIDE loads a nuclide from the cross section library -!=============================================================================== - - function openmc_load_nuclide(name) result(err) bind(C) - character(kind=C_CHAR) :: name(*) - integer(C_INT) :: err - - integer :: n - integer(HID_T) :: file_id - integer(HID_T) :: group_id - character(:), allocatable :: name_ - real(8) :: minmax(2) = [ZERO, INFINITY] - type(VectorReal) :: temperature - type(Nuclide), allocatable :: new_nuclides(:) - - ! Copy array of C_CHARs to normal Fortran string - name_ = to_f_string(name) - - err = -1 - if (.not. nuclide_dict % has_key(to_lower(name_))) then - if (library_dict % has_key(to_lower(name_))) then - ! allocate extra space in nuclides array - n = n_nuclides_total - allocate(new_nuclides(n + 1)) - new_nuclides(1:n) = nuclides(:) - call move_alloc(FROM=new_nuclides, TO=nuclides) - n = n + 1 - - i_library = library_dict % get_key(to_lower(name_)) - - ! Open file and make sure version is sufficient - file_id = file_open(libraries(i_library) % path, 'r') - call check_data_version(file_id) - - ! Read nuclide data from HDF5 - group_id = open_group(file_id, name_) - call nuclides(n) % from_hdf5(group_id, temperature, & - temperature_method, temperature_tolerance, minmax, & - master) - call close_group(group_id) - call file_close(file_id) - - ! Add entry to nuclide dictionary - call nuclide_dict % add_key(to_lower(name_), n) - n_nuclides_total = n - - ! Assign resonant scattering data - if (res_scat_on) call assign_0K_elastic_scattering(nuclides(n)) - - ! Initialize nuclide grid - call nuclides(n) % init_grid(energy_min_neutron, & - energy_max_neutron, n_log_bins) - - err = 0 - else - err = -2 - end if + err = 0 + else + err = E_GEOMETRY + call set_errmsg("Could not find cell/material at position (" // & + trim(to_str(xyz(1))) // "," // trim(to_str(xyz(2))) // "," // & + trim(to_str(xyz(3))) // ").") end if - end function openmc_load_nuclide + end function openmc_find !=============================================================================== -! OPENMC_MATERIAL_ADD_NUCLIDE +! OPENMC_HARD_RESET reset tallies and timers as well as the pseudorandom +! generator state !=============================================================================== - function openmc_material_add_nuclide(id, name, density) result(err) bind(C) - integer(C_INT32_T), value, intent(in) :: id - character(kind=C_CHAR) :: name(*) - real(C_DOUBLE), value, intent(in) :: density - integer(C_INT) :: err + subroutine openmc_hard_reset() bind(C) + integer :: err - integer :: i, j, k, n - integer :: err2 - real(8) :: awr - integer, allocatable :: new_nuclide(:) - real(8), allocatable :: new_density(:) - character(:), allocatable :: name_ + ! Reset all tallies and timers + call openmc_reset() - name_ = to_f_string(name) + ! Reset total generations and keff guess + keff = ONE + total_gen = 0 - err = -1 - if (allocated(materials)) then - if (material_dict % has_key(id)) then - i = material_dict % get_key(id) - associate (m => materials(i)) - ! Check if nuclide is already in material - do j = 1, size(m % nuclide) - k = m % nuclide(j) - if (nuclides(k) % name == name_) then - awr = nuclides(k) % awr - m % density = m % density + density - m % atom_density(j) - m % density_gpcc = m % density_gpcc + (density - & - m % atom_density(j)) * awr * MASS_NEUTRON / N_AVOGADRO - m % atom_density(j) = density - err = 0 - end if - end do - - ! If nuclide wasn't found, extend nuclide/density arrays - if (err /= 0) then - ! If nuclide hasn't been loaded, load it now - err2 = openmc_load_nuclide(name) - - if (err2 /= -2) then - ! Extend arrays - n = size(m % nuclide) - allocate(new_nuclide(n + 1)) - new_nuclide(1:n) = m % nuclide - call move_alloc(FROM=new_nuclide, TO=m % nuclide) - - allocate(new_density(n + 1)) - new_density(1:n) = m % atom_density - call move_alloc(FROM=new_density, TO=m % atom_density) - - ! Append new nuclide/density - k = nuclide_dict % get_key(to_lower(name_)) - m % nuclide(n + 1) = k - m % atom_density(n + 1) = density - m % density = m % density + density - m % density_gpcc = m % density_gpcc + & - density * nuclides(k) % awr * MASS_NEUTRON / N_AVOGADRO - m % n_nuclides = n + 1 - - err = 0 - end if - end if - end associate - end if - end if - - end function openmc_material_add_nuclide + ! Reset the random number generator state + err = openmc_set_seed(1_8) + end subroutine openmc_hard_reset !=============================================================================== -! OPENMC_MATERIAL_GET_DENSITIES returns an array of nuclide densities in a -! material -!=============================================================================== - - function openmc_material_get_densities(id, ptr) result(n) bind(C) - integer(C_INT32_T), intent(in), value :: id - type(C_PTR), intent(out) :: ptr - integer(C_INT) :: n - - ptr = C_NULL_PTR - n = 0 - if (allocated(materials)) then - if (material_dict % has_key(id)) then - i = material_dict % get_key(id) - associate (m => materials(i)) - if (allocated(m % atom_density)) then - ptr = C_LOC(m % atom_density(1)) - n = size(m % atom_density) - end if - end associate - end if - end if - end function openmc_material_get_densities - -!=============================================================================== -! OPENMC_MATERIAL_SET_DENSITY sets the total density of a material in atom/b-cm -!=============================================================================== - - function openmc_material_set_density(id, density) result(err) bind(C) - integer(C_INT32_T), value, intent(in) :: id - real(C_DOUBLE), value, intent(in) :: density - integer(C_INT) :: err - - integer :: i - - err = -1 - if (allocated(materials)) then - if (material_dict % has_key(id)) then - i = material_dict % get_key(id) - associate (m => materials(i)) - err = m % set_density(density, nuclides) - end associate - end if - end if - end function openmc_material_set_density - -!=============================================================================== -! OPENMC_RESET resets all tallies +! OPENMC_RESET resets tallies and timers !=============================================================================== subroutine openmc_reset() bind(C) integer :: i - do i = 1, size(tallies) - tallies(i) % n_realizations = 0 - if (allocated(tallies(i) % results)) then - tallies(i) % results(:, :, :) = ZERO - end if - end do + if (allocated(tallies)) then + do i = 1, size(tallies) + associate (t => tallies(i) % obj) + t % active = .false. + t % n_realizations = 0 + if (allocated(t % results)) then + t % results(:, :, :) = ZERO + end if + end associate + end do + end if ! Reset global tallies n_realizations = 0 @@ -307,10 +255,6 @@ contains k_abs_tra = ZERO k_sum(:) = ZERO - ! Turn off tally flags - tallies_on = .false. - active_batches = .false. - ! Clear active tally lists call active_analog_tallies % clear() call active_tracklength_tallies % clear() @@ -318,50 +262,61 @@ contains call active_collision_tallies % clear() call active_tallies % clear() + ! Reset timers + call time_total % reset() + call time_total % reset() + call time_initialize % reset() + call time_read_xs % reset() + call time_unionize % reset() + call time_bank % reset() + call time_bank_sample % reset() + call time_bank_sendrecv % reset() + call time_tallies % reset() + call time_inactive % reset() + call time_active % reset() + call time_transport % reset() + call time_finalize % reset() + end subroutine openmc_reset !=============================================================================== -! OPENMC_TALLY_RESULTS returns a pointer to a tally results array along with its -! shape. This allows a user to obtain in-memory tally results from Python -! directly. +! FREE_MEMORY deallocates and clears all global allocatable arrays in the +! program !=============================================================================== - subroutine openmc_tally_results(id, ptr, shape_) bind(C) - integer(C_INT32_T), intent(in), value :: id - type(C_PTR), intent(out) :: ptr - integer(C_INT), intent(out) :: shape_(3) + subroutine free_memory() - integer :: i + use cmfd_header + use mgxs_header + use plot_header + use sab_header + use settings + use source_header + use surface_header + use tally_derivative_header + use trigger_header + use volume_header - ptr = C_NULL_PTR - if (allocated(tallies)) then - if (tally_dict % has_key(id)) then - i = tally_dict % get_key(id) - if (allocated(tallies(i) % results)) then - ptr = C_LOC(tallies(i) % results(1,1,1)) - shape_(:) = shape(tallies(i) % results) - end if - end if - end if - end subroutine openmc_tally_results + call free_memory_geometry() + call free_memory_surfaces() + call free_memory_material() + call free_memory_plot() + call free_memory_volume() + 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() + call free_memory_tally() + call free_memory_tally_filter() + call free_memory_tally_derivative() + call free_memory_bank() - function to_f_string(c_string) result(f_string) - character(kind=C_CHAR), intent(in) :: c_string(*) - character(:), allocatable :: f_string + ! Deallocate CMFD + call deallocate_cmfd(cmfd) - integer :: i, n - - ! Determine length of original string - n = 0 - do while (c_string(n + 1) /= C_NULL_CHAR) - n = n + 1 - end do - - ! Copy C string character by character - allocate(character(len=n) :: f_string) - do i = 1, n - f_string(i:i) = c_string(i) - end do - end function to_f_string + end subroutine free_memory end module openmc_api diff --git a/src/bank_header.F90 b/src/bank_header.F90 index 5444d6b14..ceffe30ae 100644 --- a/src/bank_header.F90 +++ b/src/bank_header.F90 @@ -19,4 +19,34 @@ module bank_header integer(C_INT) :: particle ! particle type (neutron, photon, etc.) end type Bank + ! Source and fission bank + type(Bank), allocatable, target :: source_bank(:) + type(Bank), allocatable, target :: fission_bank(:) +#ifdef _OPENMP + type(Bank), allocatable, target :: master_fission_bank(:) +#endif + + integer(8) :: n_bank ! # of sites in fission bank + +!$omp threadprivate(fission_bank, n_bank) + +contains + +!=============================================================================== +! FREE_MEMORY_BANK deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_bank() + + ! Deallocate fission and source bank and entropy +!$omp parallel + if (allocated(fission_bank)) deallocate(fission_bank) +!$omp end parallel +#ifdef _OPENMP + if (allocated(master_fission_bank)) deallocate(master_fission_bank) +#endif + if (allocated(source_bank)) deallocate(source_bank) + + end subroutine free_memory_bank + end module bank_header diff --git a/src/cmfd_data.F90 b/src/cmfd_data.F90 index 177f140b6..5eb2158e9 100644 --- a/src/cmfd_data.F90 +++ b/src/cmfd_data.F90 @@ -5,8 +5,10 @@ module cmfd_data ! parameters for CMFD calculation. !============================================================================== + use cmfd_header, only: allocate_cmfd, cmfd, cmfd_coremap, & + cmfd_downscatter, cmfd_tallies, dhat_reset use constants - use tally_filter, only: MeshFilter + use tally_filter_mesh, only: MeshFilter implicit none private @@ -20,9 +22,7 @@ contains subroutine set_up_cmfd() - use cmfd_header, only: allocate_cmfd use constants, only: CMFD_NOACCEL - use global, only: cmfd, cmfd_coremap, cmfd_downscatter ! Check for core map and set it up if ((cmfd_coremap) .and. (cmfd%mat_dim == CMFD_NOACCEL)) call set_coremap() @@ -56,12 +56,9 @@ contains IN_BACK, IN_FRONT, IN_BOTTOM, IN_TOP, CMFD_NOACCEL, & ZERO, ONE, TINY_BIT use error, only: fatal_error - use global, only: cmfd, n_cmfd_tallies, cmfd_tallies, meshes, & - filters, filter_matches - use mesh, only: mesh_indices_to_bin - use mesh_header, only: RegularMesh + use mesh_header, only: RegularMesh, meshes use string, only: to_str - use tally_header, only: TallyObject + use tally_filter_header, only: filters, filter_matches integer :: nx ! number of mesh cells in x direction integer :: ny ! number of mesh cells in y direction @@ -76,7 +73,6 @@ contains integer :: ital ! tally object index integer :: ijk(3) ! indices for mesh cell integer :: score_index ! index to pull from tally object - integer :: i_mesh ! index in meshes array integer :: i_filt ! index in filters array integer :: i_filter_mesh ! index for mesh filter integer :: i_filter_ein ! index for incoming energy filter @@ -85,7 +81,6 @@ contains integer :: stride_surf ! stride for surface filter logical :: energy_filters! energy filters present real(8) :: flux ! temp variable for flux - type(TallyObject), pointer :: t ! pointer for tally object type(RegularMesh), pointer :: m ! pointer for mesh object ! Extract spatial and energy indices from object @@ -99,13 +94,14 @@ contains cmfd % openmc_src = ZERO ! Associate tallies and mesh - t => cmfd_tallies(1) - i_filt = t % filter(t % find_filter(FILTER_MESH)) + associate (t => cmfd_tallies(1) % obj) + i_filt = t % filter(t % find_filter(FILTER_MESH)) + end associate + select type(filt => filters(i_filt) % obj) type is (MeshFilter) - i_mesh = filt % mesh + m => meshes(filt % mesh) end select - m => meshes(i_mesh) ! Set mesh widths cmfd % hxyz(1,:,:,:) = m % width(1) ! set x width @@ -115,16 +111,15 @@ contains cmfd % keff_bal = ZERO ! Begin loop around tallies - TAL: do ital = 1, n_cmfd_tallies + TAL: do ital = 1, size(cmfd_tallies) ! Associate tallies and mesh - t => cmfd_tallies(ital) + associate (t => cmfd_tallies(ital) % obj) i_filt = t % filter(t % find_filter(FILTER_MESH)) select type(filt => filters(i_filt) % obj) type is (MeshFilter) - i_mesh = filt % mesh + m => meshes(filt % mesh) end select - m => meshes(i_mesh) ! Check for energy filters energy_filters = (t % find_filter(FILTER_ENERGYIN) > 0) @@ -166,7 +161,7 @@ contains ! Get bin number for mesh indices filter_matches(i_filter_mesh) % bins % data(1) = & - mesh_indices_to_bin(m,ijk) + m % get_bin_from_indices(ijk) ! Apply energy in filter if (energy_filters) then @@ -217,7 +212,7 @@ contains ! Get bin number for mesh indices filter_matches(i_filter_mesh) % bins % data(1) = & - mesh_indices_to_bin(m,ijk) + m % get_bin_from_indices(ijk) if (energy_filters) then ! Apply energy in filter @@ -266,7 +261,7 @@ contains ! Get the bin for this mesh cell filter_matches(i_filter_mesh) % bins % data(1) = & - mesh_indices_to_bin(m, (/ i, j, k /)) + m % get_bin_from_indices([ i, j, k ]) score_index = 1 do l = 1, size(t % filter) @@ -321,13 +316,13 @@ contains end do ZLOOP + end associate end do TAL ! Normalize openmc source distribution cmfd % openmc_src = cmfd % openmc_src/sum(cmfd % openmc_src)*cmfd%norm ! Nullify all pointers - if (associated(t)) nullify(t) if (associated(m)) nullify(m) end subroutine compute_xs @@ -339,7 +334,6 @@ contains subroutine set_coremap() use constants, only: CMFD_NOACCEL - use global, only: cmfd integer :: counter=1 ! counter for unique fuel assemblies integer :: nx ! number of mesh cells in x direction @@ -400,7 +394,7 @@ contains subroutine neutron_balance() use constants, only: ONE, ZERO, CMFD_NOACCEL, CMFD_NORES - use global, only: cmfd, keff, current_batch + use simulation_header, only: keff, current_batch integer :: nx ! number of mesh cells in x direction integer :: ny ! number of mesh cells in y direction @@ -509,7 +503,6 @@ contains subroutine compute_dtilde() use constants, only: CMFD_NOACCEL, ZERO_FLUX, TINY_BIT - use global, only: cmfd, cmfd_coremap integer :: nx ! maximum number of cells in x direction integer :: ny ! maximum number of cells in y direction @@ -651,7 +644,6 @@ contains subroutine compute_dhat() use constants, only: CMFD_NOACCEL, ZERO - use global, only: cmfd, cmfd_coremap, dhat_reset use output, only: write_message use string, only: to_str @@ -801,7 +793,6 @@ contains function get_reflector_albedo(l, g, i, j, k) use constants, only: ONE - use global, only: cmfd real(8) :: get_reflector_albedo ! reflector albedo integer, intent(in) :: i ! iteration counter for x @@ -840,7 +831,6 @@ contains subroutine compute_effective_downscatter() use constants, only: ZERO, CMFD_NOACCEL - use global, only: cmfd integer :: nx ! number of mesh cells in x direction integer :: ny ! number of mesh cells in y direction diff --git a/src/cmfd_execute.F90 b/src/cmfd_execute.F90 index 0d4a9f4d3..140166e0a 100644 --- a/src/cmfd_execute.F90 +++ b/src/cmfd_execute.F90 @@ -5,7 +5,9 @@ module cmfd_execute ! cross section generation, diffusion calculation, and source re-weighting !============================================================================== - use global + use cmfd_header + use settings + use simulation_header implicit none private @@ -63,10 +65,6 @@ contains subroutine cmfd_init_batch() - use global, only: cmfd_begin, cmfd_on, & - cmfd_reset, cmfd_run, & - current_batch - ! Check to activate CMFD diffusion and possible feedback ! this guarantees that when cmfd begins at least one batch of tallies are ! accumulated @@ -91,7 +89,6 @@ contains subroutine calc_fission_source() use constants, only: CMFD_NOACCEL, ZERO, TWO - use global, only: cmfd, cmfd_coremap, entropy_on, current_batch use message_passing use string, only: to_str @@ -108,6 +105,9 @@ contains real(8) :: hxyz(3) ! cell dimensions of current ijk cell real(8) :: vol ! volume of cell real(8),allocatable :: source(:,:,:,:) ! tmp source array for entropy +#ifdef MPI + integer :: mpi_err ! MPI error code +#endif ! Get maximum of spatial and group indices nx = cmfd % indices(1) @@ -211,11 +211,11 @@ contains subroutine cmfd_reweight(new_weights) use algorithm, only: binary_search + use bank_header, only: source_bank use constants, only: ZERO, ONE use error, only: warning, fatal_error - use global, only: meshes, source_bank, work, n_user_meshes, cmfd use mesh_header, only: RegularMesh - use mesh, only: count_bank_sites, get_mesh_indices + use mesh, only: count_bank_sites use message_passing use string, only: to_str @@ -226,15 +226,17 @@ contains integer :: nz ! maximum number of cells in z direction integer :: ng ! maximum number of energy groups integer :: i ! iteration counter + integer :: g ! index for group integer :: ijk(3) ! spatial bin location integer :: e_bin ! energy bin of source particle + integer :: mesh_bin ! mesh bin of soruce particle integer :: n_groups ! number of energy groups + real(8) :: norm ! normalization factor logical :: outside ! any source sites outside mesh logical :: in_mesh ! source site is inside mesh - type(RegularMesh), pointer :: m ! point to mesh - - ! Associate pointer - m => meshes(n_user_meshes + 1) +#ifdef MPI + integer :: mpi_err +#endif ! Get maximum of spatial and group indices nx = cmfd % indices(1) @@ -244,7 +246,7 @@ contains ! allocate arrays in cmfd object (can take out later extend to multigroup) if (.not.allocated(cmfd%sourcecounts)) then - allocate(cmfd%sourcecounts(ng,nx,ny,nz)) + allocate(cmfd%sourcecounts(ng, nx*ny*nz)) cmfd % sourcecounts = 0 end if if (.not.allocated(cmfd % weightfactors)) then @@ -259,9 +261,8 @@ contains cmfd%weightfactors = ONE ! Count bank sites in mesh and reverse due to egrid structure - call count_bank_sites(m, source_bank, cmfd%sourcecounts, cmfd % egrid, & - sites_outside=outside, size_bank=work) - cmfd % sourcecounts = cmfd%sourcecounts(ng:1:-1,:,:,:) + call count_bank_sites(cmfd_mesh, source_bank, cmfd%sourcecounts, & + cmfd % egrid, sites_outside=outside, size_bank=work) ! Check for sites outside of the mesh if (master .and. outside) then @@ -270,10 +271,21 @@ contains ! Have master compute weight factors (watch for 0s) if (master) then - where(cmfd % cmfd_src > ZERO .and. cmfd % sourcecounts > ZERO) - cmfd % weightfactors = cmfd % cmfd_src/sum(cmfd % cmfd_src)* & - sum(cmfd % sourcecounts) / cmfd % sourcecounts - end where + ! Calculate normalization factor + norm = sum(cmfd % sourcecounts) / sum(cmfd % cmfd_src) + + do mesh_bin = 1, nx*ny*nz + call cmfd_mesh % get_indices_from_bin(mesh_bin, ijk) + do g = 1, ng + if (cmfd % sourcecounts(ng - g + 1, mesh_bin) > ZERO) then + if (cmfd % cmfd_src(g,ijk(1),ijk(2),ijk(3)) > ZERO) then + cmfd % weightfactors(g,ijk(1),ijk(2),ijk(3)) = & + cmfd % cmfd_src(g,ijk(1),ijk(2),ijk(3)) * norm & + / cmfd % sourcecounts(ng - g + 1, mesh_bin) + end if + end if + end do + end do end if if (.not. cmfd_feedback) return @@ -289,7 +301,7 @@ contains do i = 1, int(work,4) ! Determine spatial bin - call get_mesh_indices(m, source_bank(i) % xyz, ijk, in_mesh) + call cmfd_mesh % get_indices(source_bank(i) % xyz, ijk, in_mesh) ! Determine energy bin n_groups = size(cmfd % egrid) - 1 @@ -313,8 +325,7 @@ contains ! Reweight particle source_bank(i) % wgt = source_bank(i) % wgt * & - cmfd % weightfactors(e_bin, ijk(1), ijk(2), ijk(3)) - + cmfd % weightfactors(e_bin, ijk(1), ijk(2), ijk(3)) end do end subroutine cmfd_reweight @@ -325,8 +336,6 @@ contains function get_matrix_idx(g, i, j, k, ng, nx, ny) result (matidx) - use global, only: cmfd, cmfd_coremap - integer :: matidx ! the index location in matrix integer, intent(in) :: i ! current x index integer, intent(in) :: j ! current y index @@ -357,7 +366,6 @@ contains subroutine cmfd_tally_reset() - use global, only: cmfd_tallies use output, only: write_message integer :: i ! loop counter @@ -367,8 +375,8 @@ contains ! Reset CMFD tallies do i = 1, size(cmfd_tallies) - cmfd_tallies(i) % n_realizations = 0 - cmfd_tallies(i) % results(:,:,:) = ZERO + cmfd_tallies(i) % obj % n_realizations = 0 + cmfd_tallies(i) % obj % results(:,:,:) = ZERO end do end subroutine cmfd_tally_reset diff --git a/src/cmfd_header.F90 b/src/cmfd_header.F90 index e743cc928..2e6162b49 100644 --- a/src/cmfd_header.F90 +++ b/src/cmfd_header.F90 @@ -1,6 +1,10 @@ module cmfd_header use constants, only: CMFD_NOACCEL, ZERO, ONE + use mesh_header, only: RegularMesh + use set_header, only: SetInt + use tally_header, only: TallyContainer + use timer_header, only: Timer implicit none private @@ -49,7 +53,7 @@ module cmfd_header real(8), allocatable :: openmc_src(:,:,:,:) ! Source sites in each mesh box - real(8), allocatable :: sourcecounts(:,:,:,:) + real(8), allocatable :: sourcecounts(:,:) ! Weight adjustment factors real(8), allocatable :: weightfactors(:,:,:,:) @@ -88,6 +92,67 @@ module cmfd_header end type cmfd_type + ! Main object + type(cmfd_type), public :: cmfd + + type(RegularMesh), public, pointer :: cmfd_mesh => null() + + ! Pointers for different tallies + type(TallyContainer), public, pointer :: cmfd_tallies(:) => null() + + ! Timing objects + type(Timer), public :: time_cmfd ! timer for whole cmfd calculation + type(Timer), public :: time_cmfdbuild ! timer for matrix build + type(Timer), public :: time_cmfdsolve ! timer for solver + + ! Flag for active core map + logical, public :: cmfd_coremap = .false. + + ! Flag to reset dhats to zero + logical, public :: dhat_reset = .false. + + ! Flag to activate neutronic feedback via source weights + logical, public :: cmfd_feedback = .false. + + ! Adjoint method type + character(len=10), public :: cmfd_adjoint_type = 'physical' + + ! Number of incomplete ilu factorization levels + integer, public :: cmfd_ilu_levels = 1 + + ! Batch to begin cmfd + integer, public :: cmfd_begin = 1 + + ! Tally reset list + integer, public :: n_cmfd_resets + type(SetInt), public :: cmfd_reset + + ! Compute effective downscatter cross section + logical, public :: cmfd_downscatter = .false. + + ! Convergence monitoring + logical, public :: cmfd_power_monitor = .false. + + ! Cmfd output + logical, public :: cmfd_write_matrices = .false. + + ! Run an adjoint calculation (last batch only) + logical, public :: cmfd_run_adjoint = .false. + + ! CMFD run logicals + logical, public :: cmfd_on = .false. + + ! CMFD display info + character(len=25), public :: cmfd_display = 'balance' + + ! Estimate of spectral radius of CMFD matrices and tolerances + real(8), public :: cmfd_spectral = ZERO + real(8), public :: cmfd_shift = 1.e6 + real(8), public :: cmfd_ktol = 1.e-8_8 + real(8), public :: cmfd_stol = 1.e-8_8 + real(8), public :: cmfd_atoli = 1.e-10_8 + real(8), public :: cmfd_rtoli = 1.e-5_8 + contains !============================================================================== @@ -133,7 +198,7 @@ contains if (.not. allocated(this % openmc_src)) allocate(this % openmc_src(ng,nx,ny,nz)) ! Allocate source weight modification vars - if (.not. allocated(this % sourcecounts)) allocate(this % sourcecounts(ng,nx,ny,nz)) + if (.not. allocated(this % sourcecounts)) allocate(this % sourcecounts(ng,nx*ny*nz)) if (.not. allocated(this % weightfactors)) allocate(this % weightfactors(ng,nx,ny,nz)) ! Allocate batchwise parameters diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index d57c21beb..a1827febd 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -1,6 +1,13 @@ module cmfd_input - use global + use, intrinsic :: ISO_C_BINDING + + use cmfd_header + use mesh_header, only: mesh_dict + use mgxs_header, only: energy_bins + use tally + use tally_header + use timer_header implicit none private @@ -14,21 +21,9 @@ contains subroutine configure_cmfd() - use cmfd_header, only: allocate_cmfd - use message_passing, only: master - - integer :: color ! color group of processor - ! Read in cmfd input file call read_cmfd_xml() - ! Assign color - if (master) then - color = 1 - else - color = 2 - end if - ! Initialize timers call time_cmfd % reset() call time_cmfdbuild % reset() @@ -47,7 +42,6 @@ contains use constants, only: ZERO, ONE use error, only: fatal_error, warning - use global use output, only: write_message use string, only: to_lower use xml_interface @@ -241,20 +235,19 @@ contains ! There are 3 tally types: ! 1: Only an energy in filter-> flux,total,p1 scatter ! 2: Energy in and energy out filter-> nu-scatter,nu-fission -! 3: Surface current +! 3: Mesh current !=============================================================================== subroutine create_cmfd_tally(root) use constants, only: MAX_LINE_LEN use error, only: fatal_error, warning - use mesh_header, only: RegularMesh + use mesh_header, only: RegularMesh, openmc_extend_meshes use string - use tally, only: setup_active_cmfdtallies - use tally_header, only: TallyObject + use tally, only: openmc_tally_set_type + use tally_header, only: openmc_extend_tallies use tally_filter_header use tally_filter - use tally_initialize, only: add_tallies use xml_interface type(XMLNode), intent(in) :: root ! XML root element @@ -262,25 +255,28 @@ contains logical :: energy_filters integer :: i ! loop counter integer :: n ! size of arrays in mesh specification - integer :: ng ! number of energy groups (default 1) + integer(C_INT32_T) :: ng ! number of energy groups (default 1) integer :: n_filter ! number of filters - integer :: i_filt ! index in filters array + integer :: i_start, i_end + integer :: i_filt_start, i_filt_end + integer(C_INT32_T), allocatable :: filter_indices(:) + integer(C_INT) :: err + integer :: i_filt ! index in filters array + integer :: filt_id integer :: iarray3(3) ! temp integer array real(8) :: rarray3(3) ! temp double array - type(TallyObject), pointer :: t + real(C_DOUBLE), allocatable :: energies(:) type(RegularMesh), pointer :: m type(XMLNode) :: node_mesh - ! Set global variables if they are 0 (this can happen if there is no tally - ! file) - if (n_meshes == 0) n_meshes = n_user_meshes + n_cmfd_meshes + err = openmc_extend_meshes(1, i_start) ! Allocate mesh - if (.not. allocated(meshes)) allocate(meshes(n_meshes)) - m => meshes(n_user_meshes+1) + cmfd_mesh => meshes(i_start) + m => meshes(i_start) ! Set mesh id - m % id = n_user_meshes + 1 + m % id = i_start ! Set mesh type to rectangular m % type = LATTICE_RECT @@ -374,71 +370,54 @@ contains m % volume_frac = ONE/real(product(m % dimension),8) ! Add mesh to dictionary - call mesh_dict % add_key(m % id, n_user_meshes + 1) + call mesh_dict % set(m % id, i_start) ! Determine number of filters energy_filters = check_for_node(node_mesh, "energy") - n_cmfd_filters = merge(5, 3, energy_filters) + n = merge(5, 3, energy_filters) ! Extend filters array so we can add CMFD filters - call add_filters(n_cmfd_filters) + err = openmc_extend_filters(n, i_filt_start, i_filt_end) ! Set up mesh filter - i_filt = n_user_filters + 1 - allocate(MeshFilter :: filters(i_filt) % obj) - select type (filt => filters(i_filt) % obj) - type is (MeshFilter) - filt % id = i_filt - filt % n_bins = product(m % dimension) - filt % mesh = n_user_meshes + 1 - ! Add filter to dictionary - call filter_dict % add_key(filt % id, i_filt) - end select + i_filt = i_filt_start + err = openmc_filter_set_type(i_filt, C_CHAR_'mesh' // C_NULL_CHAR) + call openmc_get_filter_next_id(filt_id) + err = openmc_filter_set_id(i_filt, filt_id) + err = openmc_mesh_filter_set_mesh(i_filt, i_start) if (energy_filters) then ! Read and set incoming energy mesh filter i_filt = i_filt + 1 - allocate(EnergyFilter :: filters(i_filt) % obj) - select type (filt => filters(i_filt) % obj) - type is (EnergyFilter) - filt % id = i_filt - ng = node_word_count(node_mesh, "energy") - filt % n_bins = ng - 1 - allocate(filt % bins(ng)) - call get_node_array(node_mesh, "energy", filt % bins) - ! Add filter to dictionary - call filter_dict % add_key(filt % id, i_filt) - end select + err = openmc_filter_set_type(i_filt, C_CHAR_'energy' // C_NULL_CHAR) + call openmc_get_filter_next_id(filt_id) + err = openmc_filter_set_id(i_filt, filt_id) + + ! Get energies and set bins + ng = node_word_count(node_mesh, "energy") + allocate(energies(ng)) + call get_node_array(node_mesh, "energy", energies) + err = openmc_energy_filter_set_bins(i_filt, ng, energies) ! Read and set outgoing energy mesh filter i_filt = i_filt + 1 - allocate(EnergyoutFilter :: filters(i_filt) % obj) - select type (filt => filters(i_filt) % obj) - type is (EnergyoutFilter) - filt % id = i_filt - ng = node_word_count(node_mesh, "energy") - filt % n_bins = ng - 1 - allocate(filt % bins(ng)) - call get_node_array(node_mesh, "energy", filt % bins) - ! Add filter to dictionary - call filter_dict % add_key(filt % id, i_filt) - end select + err = openmc_filter_set_type(i_filt, C_CHAR_'energyout' // C_NULL_CHAR) + call openmc_get_filter_next_id(filt_id) + err = openmc_filter_set_id(i_filt, filt_id) + err = openmc_energy_filter_set_bins(i_filt, ng, energies) end if - ! Duplicate the mesh filter for the surface current tally since other + ! Duplicate the mesh filter for the mesh current tally since other ! tallies use this filter and we need to change the dimension i_filt = i_filt + 1 - allocate(MeshFilter :: filters(i_filt) % obj) - select type (filt => filters(i_filt) % obj) - type is (MeshFilter) - filt % id = i_filt - ! We need to increase the dimension by one since we also need - ! currents coming into and out of the boundary mesh cells. - filt % n_bins = product(m % dimension + 1) - filt % mesh = n_user_meshes + 1 - ! Add filter to dictionary - call filter_dict % add_key(filt % id, i_filt) - end select + err = openmc_filter_set_type(i_filt, C_CHAR_'mesh' // C_NULL_CHAR) + call openmc_get_filter_next_id(filt_id) + err = openmc_filter_set_id(i_filt, filt_id) + err = openmc_mesh_filter_set_mesh(i_filt, i_start) + + ! We need to increase the dimension by one since we also need + ! currents coming into and out of the boundary mesh cells. + filters(i_filt) % obj % n_bins = product(m % dimension + 1) ! Set up surface filter i_filt = i_filt + 1 @@ -458,32 +437,41 @@ contains end if filt % current = .true. ! Add filter to dictionary - call filter_dict % add_key(filt % id, i_filt) + call filter_dict % set(filt % id, i_filt) end select + ! Initialize filters + do i = i_filt_start, i_filt_end + select type (filt => filters(i) % obj) + type is (SurfaceFilter) + ! Don't do anything + class default + call filt % initialize() + end select + end do + ! Allocate tallies - call add_tallies("cmfd", n_cmfd_tallies) + err = openmc_extend_tallies(3, i_start, i_end) + cmfd_tallies => tallies(i_start:i_end) ! Begin loop around tallies - do i = 1, n_cmfd_tallies + do i = 1, size(cmfd_tallies) + ! Allocate tally + err = openmc_tally_set_type(i_start + i - 1, C_CHAR_'generic' // C_NULL_CHAR) ! Point t to tally variable - t => cmfd_tallies(i) + associate (t => cmfd_tallies(i) % obj) ! Set reset property if (check_for_node(root, "reset")) then call get_node_value(root, "reset", t % reset) end if - ! Set the mesh filter index in the tally find_filter array - n_filter = 1 - t % find_filter(FILTER_MESH) = n_filter - ! Set the incoming energy mesh filter index in the tally find_filter ! array + n_filter = 1 if (energy_filters) then n_filter = n_filter + 1 - t % find_filter(FILTER_ENERGYIN) = n_filter end if ! Set number of nucilde bins @@ -492,7 +480,7 @@ contains t % n_nuclide_bins = 1 ! Record tally id which is equivalent to loop number - t % id = i_cmfd_tallies + i + t % id = i_start + i - 1 if (i == 1) then @@ -506,11 +494,13 @@ contains t % type = TALLY_VOLUME ! Allocate and set filters - allocate(t % filter(n_filter)) - t % filter(1) = n_user_filters + 1 + allocate(filter_indices(n_filter)) + filter_indices(1) = i_filt_start if (energy_filters) then - t % filter(2) = n_user_filters + 2 + filter_indices(2) = i_filt_start + 1 end if + err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices) + deallocate(filter_indices) ! Allocate scoring bins allocate(t % score_bins(3)) @@ -542,16 +532,17 @@ contains ! array if (energy_filters) then n_filter = n_filter + 1 - t % find_filter(FILTER_ENERGYOUT) = n_filter end if ! Allocate and set indices in filters array - allocate(t % filter(n_filter)) - t % filter(1) = n_user_filters + 1 + allocate(filter_indices(n_filter)) + filter_indices(1) = i_filt_start if (energy_filters) then - t % filter(2) = n_user_filters + 2 - t % filter(3) = n_user_filters + 3 + filter_indices(2) = i_filt_start + 1 + filter_indices(3) = i_filt_start + 2 end if + err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices) + deallocate(filter_indices) ! Allocate macro reactions allocate(t % score_bins(2)) @@ -576,15 +567,16 @@ contains ! Set the surface filter index in the tally find_filter array n_filter = n_filter + 1 - t % find_filter(FILTER_SURFACE) = n_filter ! Allocate and set filters - allocate(t % filter(n_filter)) - t % filter(1) = n_user_filters + n_cmfd_filters - 1 - t % filter(n_filter) = n_user_filters + n_cmfd_filters + allocate(filter_indices(n_filter)) + filter_indices(1) = i_filt_end - 1 + filter_indices(n_filter) = i_filt_end if (energy_filters) then - t % filter(2) = n_user_filters + 2 + filter_indices(2) = i_filt_start + 1 end if + err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices) + deallocate(filter_indices) ! Allocate macro reactions allocate(t % score_bins(1)) @@ -597,14 +589,14 @@ contains ! Set macro bins t % score_bins(1) = SCORE_CURRENT - t % type = TALLY_SURFACE_CURRENT + t % type = TALLY_MESH_CURRENT end if - end do + ! Make CMFD tallies active from the start + t % active = .true. - ! Put cmfd tallies into active tally array and turn tallies on - call setup_active_cmfdtallies() - tallies_on = .true. + end associate + end do end subroutine create_cmfd_tally diff --git a/src/cmfd_loss_operator.F90 b/src/cmfd_loss_operator.F90 index 7dfeed1db..50a75e52d 100644 --- a/src/cmfd_loss_operator.F90 +++ b/src/cmfd_loss_operator.F90 @@ -1,7 +1,7 @@ module cmfd_loss_operator use constants, only: CMFD_NOACCEL, ZERO - use global, only: cmfd, cmfd_coremap + use cmfd_header, only: cmfd, cmfd_coremap use matrix_header, only: Matrix implicit none diff --git a/src/cmfd_prod_operator.F90 b/src/cmfd_prod_operator.F90 index 7779c2455..91ce43b35 100644 --- a/src/cmfd_prod_operator.F90 +++ b/src/cmfd_prod_operator.F90 @@ -1,7 +1,7 @@ module cmfd_prod_operator use constants, only: CMFD_NOACCEL - use global, only: cmfd, cmfd_coremap + use cmfd_header, only: cmfd, cmfd_coremap use matrix_header, only: Matrix implicit none diff --git a/src/cmfd_solver.F90 b/src/cmfd_solver.F90 index fb687d10b..99d482480 100644 --- a/src/cmfd_solver.F90 +++ b/src/cmfd_solver.F90 @@ -53,7 +53,7 @@ contains subroutine cmfd_solver_execute(adjoint) - use global, only: cmfd_adjoint_type, time_cmfdbuild, time_cmfdsolve + use cmfd_header, only: cmfd_adjoint_type, time_cmfdbuild, time_cmfdsolve logical, optional, intent(in) :: adjoint ! adjoint calc @@ -100,8 +100,8 @@ contains subroutine init_data(adjoint) use constants, only: ONE, ZERO - use global, only: cmfd_shift, keff, cmfd_ktol, cmfd_stol, & - cmfd_write_matrices + use cmfd_header, only: cmfd_shift, cmfd_ktol, cmfd_stol, cmfd_write_matrices + use simulation_header, only: keff logical, intent(in) :: adjoint @@ -167,7 +167,7 @@ contains subroutine compute_adjoint() use error, only: fatal_error - use global, only: cmfd_write_matrices + use cmfd_header, only: cmfd_write_matrices ! Transpose matrices loss = loss % transpose() @@ -190,7 +190,7 @@ contains use constants, only: ONE use error, only: fatal_error - use global, only: cmfd, cmfd_atoli, cmfd_rtoli + use cmfd_header, only: cmfd, cmfd_atoli, cmfd_rtoli integer :: i ! iteration counter integer :: innerits ! # of inner iterations @@ -304,7 +304,7 @@ contains use, intrinsic :: ISO_FORTRAN_ENV use constants, only: ONE, ZERO - use global, only: cmfd_power_monitor + use cmfd_header, only: cmfd_power_monitor use message_passing, only: master integer, intent(in) :: iter ! outer iteration number @@ -346,7 +346,7 @@ contains use constants, only: ONE, ZERO use error, only: fatal_error - use global, only: cmfd, cmfd_spectral + use cmfd_header, only: cmfd, cmfd_spectral type(Matrix), intent(inout) :: A ! coefficient matrix type(Vector), intent(inout) :: b ! right hand side vector @@ -452,7 +452,7 @@ contains use constants, only: ONE, ZERO use error, only: fatal_error - use global, only: cmfd, cmfd_spectral + use cmfd_header, only: cmfd, cmfd_spectral type(Matrix), intent(inout) :: A ! coefficient matrix type(Vector), intent(inout) :: b ! right hand side vector @@ -597,7 +597,7 @@ contains use constants, only: ONE, ZERO use error, only: fatal_error - use global, only: cmfd, cmfd_spectral + use cmfd_header, only: cmfd, cmfd_spectral type(Matrix), intent(inout) :: A ! coefficient matrix type(Vector), intent(inout) :: b ! right hand side vector @@ -693,7 +693,8 @@ contains subroutine extract_results() - use global, only: cmfd, cmfd_write_matrices, current_batch + use cmfd_header, only: cmfd, cmfd_write_matrices + use simulation_header, only: current_batch character(len=25) :: filename ! name of file to write data integer :: n ! problem size @@ -750,7 +751,7 @@ contains subroutine matrix_to_indices(irow, g, i, j, k, ng, nx, ny, nz) - use global, only: cmfd, cmfd_coremap + use cmfd_header, only: cmfd, cmfd_coremap integer, intent(out) :: i ! iteration counter for x integer, intent(out) :: j ! iteration counter for y diff --git a/src/constants.F90 b/src/constants.F90 index cdc3be5c5..7330fe943 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -302,7 +302,8 @@ module constants ! Tally type integer, parameter :: & TALLY_VOLUME = 1, & - TALLY_SURFACE_CURRENT = 2 + TALLY_MESH_CURRENT = 2, & + TALLY_SURFACE = 3 ! Tally estimator types integer, parameter :: & @@ -332,7 +333,7 @@ module constants SCORE_FISSION = -10, & ! fission rate SCORE_NU_FISSION = -11, & ! neutron production rate SCORE_KAPPA_FISSION = -12, & ! fission energy production rate - SCORE_CURRENT = -13, & ! partial current + SCORE_CURRENT = -13, & ! current SCORE_FLUX_YN = -14, & ! angular moment of flux SCORE_TOTAL_YN = -15, & ! angular moment of total reaction rate SCORE_SCATTER_YN = -16, & ! angular flux-weighted scattering moment (0:N) @@ -366,7 +367,7 @@ module constants integer, parameter :: NO_BIN_FOUND = -1 ! Tally filter and map types - integer, parameter :: N_FILTER_TYPES = 15 + integer, parameter :: N_FILTER_TYPES = 16 integer, parameter :: & FILTER_UNIVERSE = 1, & FILTER_MATERIAL = 2, & @@ -382,7 +383,8 @@ module constants FILTER_AZIMUTHAL = 12, & FILTER_DELAYEDGROUP = 13, & FILTER_ENERGYFUNCTION = 14, & - FILTER_PARTICLE = 15 + FILTER_CELLFROM = 15, & + FILTER_PARTICLE = 16 ! Mesh types integer, parameter :: & diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 2a787f788..1176fc13e 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -3,17 +3,19 @@ module cross_section use algorithm, only: binary_search use constants use error, only: fatal_error - use global use list_header, only: ListElemInt - use material_header, only: Material + use material_header, only: Material, materials use math, only: faddeeva, w_derivative, broaden_wmp_polynomials use multipole_header, only: FORM_RM, FORM_MLBW, MP_EA, RM_RT, RM_RA, RM_RF, & MLBW_RT, MLBW_RX, MLBW_RA, MLBW_RF, FIT_T, FIT_A,& FIT_F, MultipoleArray use nuclide_header use particle_header, only: Particle + use photon_header, only: micro_photon_xs, elements use random_lcg, only: prn, future_prn, prn_set_stream - use sab_header, only: SAlphaBeta + use sab_header, only: SAlphaBeta, sab_tables + use settings + use simulation_header implicit none diff --git a/src/dict_header.F90 b/src/dict_header.F90 index ffe488d52..d2bf71721 100644 --- a/src/dict_header.F90 +++ b/src/dict_header.F90 @@ -7,438 +7,535 @@ module dict_header ! pairs. This data structure is used to provide lookup features, e.g. cells and ! surfaces by name. ! -! The original version was roughly based on capabilities in the 'flibs' open -! source package. However, it was rewritten from scratch so that it could be -! used stand-alone without relying on the implementation of lists. As with -! lists, it was considered writing a single dictionary used unlimited -! polymorphism, but again compiler support is spotty and doesn't always prevent -! duplication of code. +! The implementation is based on Algorithm D from Knuth Vol. 3 Sec. 6.4 (open +! addressing with double hashing). Hash table sizes M are chosen such that M +! and M - 2 are twin primes, which helps reduce clustering. The sequence of +! twin primes used for the table sizes comes from +! https://github.com/anholt/hash_table/blob/master/hash_table.c. These values +! were selected so that the table would grow by approximately a factor of two +! each time the maximum load factor is exceeded. An upper limit is placed on +! the load factor to prevent exponential performance degradation as the number +! of entries approaches the number of buckets. !=============================================================================== implicit none - integer, parameter, private :: HASH_SIZE = 4993 - integer, parameter, private :: HASH_MULTIPLIER = 31 - integer, parameter, private :: DICT_NULL = -huge(0) - integer, parameter :: DICT_KEY_LENGTH = 255 + integer, parameter :: EMPTY = -huge(0) + integer, parameter, private :: DELETED = -huge(0) + 1 + integer, parameter, private :: KEY_CHAR_LENGTH = 255 + integer, parameter, private :: TABLE_SIZES(30) = & + [5, 7, 13, 19, 43, 73, 151, 283, 571, 1153, 2269, 4519, 9013, 18043, & + 36109, 72091, 144409, 288361, 576883, 1153459, 2307163, 4613893, & + 9227641, 18455029, 36911011, 73819861, 147639589, 295279081, & + 590559793, 1181116273] + real(8), parameter, private :: MAX_LOAD_FACTOR = 0.65 !=============================================================================== -! ELEMKEYVALUE* contains (key,value) pairs and a pointer to the next (key,value) -! pair +! DICTENTRY* contains (key,value) pairs. !=============================================================================== - type ElemKeyValueCI - type(ElemKeyValueCI), pointer :: next => null() - character(len=DICT_KEY_LENGTH) :: key - integer :: value - end type ElemKeyValueCI + type DictEntryII + integer :: key = EMPTY + integer :: value = EMPTY + end type DictEntryII - type ElemKeyValueII - type(ElemKeyValueII), pointer :: next => null() - integer :: key - integer :: value - end type ElemKeyValueII + type DictEntryCI + character(len=KEY_CHAR_LENGTH) :: key + integer :: value = EMPTY + end type DictEntryCI !=============================================================================== -! HASHLIST* types contain a single pointer to a linked list of (key,value) -! pairs. This type is necesssary so that the Dict types can be dynamically -! allocated. +! BUCKET* contains an allocatable DictEntry object for storing a (key,value) +! pair and the hash value for fast comparisons. Integer (key,value) pairs are +! stored directly in the hash table since their memory requirement is small. !=============================================================================== - type, private :: HashListCI - type(ElemKeyValueCI), pointer :: list => null() - end type HashListCI - - type, private :: HashListII - type(ElemKeyValueII), pointer :: list => null() - end type HashListII + type, private :: BucketCI + type(DictEntryCI), allocatable :: entry + integer :: hash = EMPTY + end type BucketCI !=============================================================================== ! DICT* is a dictionary of (key,value) pairs with convenience methods as -! type-bound procedures. DictCharInt has character(*) keys and integer values, -! and DictIntInt has integer keys and values. +! type-bound procedures. DictIntInt has integer keys and values, and +! DictCharInt has character(*) keys and integer values. !=============================================================================== - type, public :: DictCharInt - private - type(HashListCI), pointer :: table(:) => null() - contains - procedure :: add_key => dict_add_key_ci - procedure :: get_key => dict_get_key_ci - procedure :: has_key => dict_has_key_ci - procedure :: keys => dict_keys_ci - procedure :: clear => dict_clear_ci - procedure, private :: get_elem => dict_get_elem_ci - end type DictCharInt - type, public :: DictIntInt - private - type(HashListII), pointer :: table(:) => null() + integer, private :: entries = 0 + integer, private :: capacity = 0 + type(DictEntryII), allocatable, private :: table(:) contains - procedure :: add_key => dict_add_key_ii - procedure :: get_key => dict_get_key_ii - procedure :: has_key => dict_has_key_ii - procedure :: keys => dict_keys_ii - procedure :: clear => dict_clear_ii - procedure, private :: get_elem => dict_get_elem_ii + procedure :: set => set_ii + procedure :: get => get_ii + procedure :: has => has_ii + procedure :: remove => remove_ii + procedure :: next_entry => next_entry_ii + procedure :: clear => clear_ii + procedure :: size => size_ii + procedure, private :: get_entry => get_entry_ii + procedure, private :: resize => resize_ii end type DictIntInt + type, public :: DictCharInt + integer, private :: entries = 0 + integer, private :: capacity = 0 + type(BucketCI), allocatable, private :: table(:) + contains + procedure :: set => set_ci + procedure :: get => get_ci + procedure :: has => has_ci + procedure :: remove => remove_ci + procedure :: next_entry => next_entry_ci + procedure :: clear => clear_ci + procedure :: size => size_ci + procedure, private :: get_entry => get_entry_ci + procedure, private :: resize => resize_ci + end type DictCharInt + contains !=============================================================================== -! DICT_ADD_KEY adds a (key,value) entry to a dictionary. If the key is already -! in the dictionary, the value is replaced by the new specified value. +! GET_ENTRY returns the index of the (key,value) pair in the table for a given +! key. This method is private. !=============================================================================== - subroutine dict_add_key_ci(this, key, value) + function get_entry_ii(this, key) result(i) - class(DictCharInt) :: this - character(*), intent(in) :: key - integer, intent(in) :: value + class(DictIntInt) :: this + integer, intent(in) :: key + integer :: i integer :: hash - type(ElemKeyValueCI), pointer :: elem => null() - type(ElemKeyValueCI), pointer :: new_elem => null() + integer :: c - elem => this % get_elem(key) - - if (associated(elem)) then - elem % value = value - else - ! Get hash - hash = dict_hash_key_ci(key) - - ! Create new element - allocate(new_elem) - new_elem % key = key - new_elem % value = value - - ! Add element to front of list - new_elem % next => this % table(hash) % list - this % table(hash) % list => new_elem + if (.not. allocated(this % table)) then + allocate(this % table(TABLE_SIZES(1))) + this % capacity = TABLE_SIZES(1) end if - end subroutine dict_add_key_ci + hash = hash_ii(key) + i = 1 + mod(hash, this % capacity) + c = 2 + mod(hash, this % capacity - 2) - subroutine dict_add_key_ii(this, key, value) + do + if (this % table(i) % key == key .or. & + this % table(i) % key == EMPTY) exit - class(DictIntInt) :: this + i = 1 + mod(i + c - 1, this % capacity) + end do + + end function get_entry_ii + + function get_entry_ci(this, key) result(i) + + class(DictCharInt) :: this + character(*), intent(in) :: key + integer :: i + + integer :: hash + integer :: c + + if (.not. allocated(this % table)) then + allocate(this % table(TABLE_SIZES(1))) + this % capacity = TABLE_SIZES(1) + end if + + hash = hash_ci(key) + i = 1 + mod(hash, this % capacity) + c = 2 + mod(hash, this % capacity - 2) + + do + if (this % table(i) % hash == hash) then + if (allocated(this % table(i) % entry)) then + if (this % table(i) % entry % key == key) exit + end if + end if + + if (this % table(i) % hash == EMPTY) exit + + i = 1 + mod(i + c - 1, this % capacity) + end do + + end function get_entry_ci + +!=============================================================================== +! RESIZE allocates a new hash table to accomodate the number of entries and +! reinserts all of the entries into the new table. This method is private. +!=============================================================================== + + subroutine resize_ii(this) + + class(DictIntInt) :: this + + type(DictEntryII), allocatable :: table(:) + integer :: new_size + integer :: i + + do i = 1, size(TABLE_SIZES) + if (TABLE_SIZES(i) > this % capacity) exit + end do + new_size = TABLE_SIZES(i) + + call move_alloc(this % table, table) + allocate(this % table(new_size)) + this % capacity = new_size + this % entries = 0 + + ! Rehash each entry into the new table + do i = 1, size(table) + if (table(i) % key /= EMPTY .and. table(i) % key /= DELETED) then + call this % set(table(i) % key, table(i) % value) + end if + end do + + deallocate(table) + + end subroutine resize_ii + + subroutine resize_ci(this) + + class(DictCharInt) :: this + + type(BucketCI), allocatable :: table(:) + integer :: new_size + integer :: i + + do i = 1, size(TABLE_SIZES) + if (TABLE_SIZES(i) > this % capacity) exit + end do + new_size = TABLE_SIZES(i) + + call move_alloc(this % table, table) + allocate(this % table(new_size)) + this % capacity = new_size + this % entries = 0 + + ! Rehash each entry into the new table + do i = 1, size(table) + if (table(i) % hash /= EMPTY .and. table(i) % hash /= DELETED) then + call this % set(table(i) % entry % key, table(i) % entry % value) + end if + end do + + deallocate(table) + + end subroutine resize_ci + +!=============================================================================== +! SET adds a (key,value) entry to a dictionary. If the key is already in the +! dictionary, the value is replaced by the new specified value. +!=============================================================================== + + subroutine set_ii(this, key, value) + + class(DictIntInt) :: this integer, intent(in) :: key integer, intent(in) :: value integer :: hash - type(ElemKeyValueII), pointer :: elem => null() - type(ElemKeyValueII), pointer :: new_elem => null() + integer :: i + integer :: c - elem => this % get_elem(key) - - if (associated(elem)) then - elem % value = value - else - ! Get hash - hash = dict_hash_key_ii(key) - - ! Create new element - allocate(new_elem) - new_elem % key = key - new_elem % value = value - - ! Add element to front of list - new_elem % next => this % table(hash) % list - this % table(hash) % list => new_elem + if (.not. allocated(this % table)) then + allocate(this % table(TABLE_SIZES(1))) + this % capacity = TABLE_SIZES(1) + else if (real(this % entries + 1, 8) / this % capacity > MAX_LOAD_FACTOR) then + call this % resize() end if - end subroutine dict_add_key_ii + hash = hash_ii(key) + i = 1 + mod(hash, this % capacity) + c = 2 + mod(hash, this % capacity - 2) -!=============================================================================== -! DICT_GET_ELEM returns a pointer to the (key,value) pair for a given key. This -! method is private. -!=============================================================================== + do + if (this % table(i) % key == EMPTY .or. & + this % table(i) % key == DELETED) then + this % table(i) % key = key + this % table(i) % value = value + this % entries = this % entries + 1 + exit + else if (this % table(i) % key == key) then + this % table(i) % value = value + exit + end if - function dict_get_elem_ci(this, key) result(elem) - - class(DictCharInt) :: this - character(*), intent(in) :: key - type(ElemKeyValueCI), pointer :: elem - - integer :: hash - - ! Check for dictionary not being allocated - if (.not. associated(this % table)) then - allocate(this % table(HASH_SIZE)) - end if - - hash = dict_hash_key_ci(key) - elem => this % table(hash) % list - do while (associated(elem)) - if (elem % key == key) exit - elem => elem % next + i = 1 + mod(i + c - 1, this % capacity) end do - end function dict_get_elem_ci + end subroutine set_ii - function dict_get_elem_ii(this, key) result(elem) + subroutine set_ci(this, key, value) - class(DictIntInt) :: this - integer, intent(in) :: key - type(ElemKeyValueII), pointer :: elem - - integer :: hash - - ! Check for dictionary not being allocated - if (.not. associated(this % table)) then - allocate(this % table(HASH_SIZE)) - end if - - hash = dict_hash_key_ii(key) - elem => this % table(hash) % list - do while (associated(elem)) - if (elem % key == key) exit - elem => elem % next - end do - - end function dict_get_elem_ii - -!=============================================================================== -! DICT_GET_KEY returns the value matching a given key. If the dictionary does -! not contain the key, the value DICT_NULL is returned. -!=============================================================================== - - function dict_get_key_ci(this, key) result(value) - - class(DictCharInt) :: this + class(DictCharInt) :: this character(*), intent(in) :: key - integer :: value + integer, intent(in) :: value - type(ElemKeyValueCI), pointer :: elem + integer :: hash + integer :: i + integer :: c - elem => this % get_elem(key) - - if (associated(elem)) then - value = elem % value - else - value = DICT_NULL + if (.not. allocated(this % table)) then + allocate(this % table(TABLE_SIZES(1))) + this % capacity = TABLE_SIZES(1) + else if (real(this % entries + 1, 8) / this % capacity > MAX_LOAD_FACTOR) then + call this % resize() end if - end function dict_get_key_ci + hash = hash_ci(key) + i = 1 + mod(hash, this % capacity) + c = 2 + mod(hash, this % capacity - 2) - function dict_get_key_ii(this, key) result(value) + do + if (this % table(i) % hash == EMPTY .or. & + this % table(i) % hash == DELETED) then + if (.not. allocated(this % table(i) % entry)) then + allocate(this % table(i) % entry) + end if + this % table(i) % hash = hash + this % table(i) % entry % key = key + this % table(i) % entry % value = value + this % entries = this % entries + 1 + exit + else if (this % table(i) % hash == hash .and. & + this % table(i) % entry % key == key) then + this % table(i) % entry % value = value + exit + end if + + i = 1 + mod(i + c - 1, this % capacity) + end do + + end subroutine set_ci + +!=============================================================================== +! GET returns the value matching a given key. If the dictionary does not contain +! the key, the value EMPTY is returned. +!=============================================================================== + + function get_ii(this, key) result(value) class(DictIntInt) :: this integer, intent(in) :: key integer :: value - type(ElemKeyValueII), pointer :: elem + integer :: i - elem => this % get_elem(key) + i = this % get_entry(key) + value = this % table(i) % value - if (associated(elem)) then - value = elem % value + end function get_ii + + function get_ci(this, key) result(value) + + class(DictCharInt) :: this + character(*), intent(in) :: key + integer :: value + + integer :: i + + i = this % get_entry(key) + if (allocated(this % table(i) % entry)) then + value = this % table(i) % entry % value else - value = DICT_NULL + value = EMPTY end if - end function dict_get_key_ii + end function get_ci !=============================================================================== -! DICT_HAS_KEY determines whether a dictionary has a (key,value) pair with a -! given key. +! HAS determines whether a dictionary has a (key,value) pair with a given key. !=============================================================================== - function dict_has_key_ci(this, key) result(has) - - class(DictCharInt) :: this - character(*), intent(in) :: key - logical :: has - - type(ElemKeyValueCI), pointer :: elem - - elem => this % get_elem(key) - has = associated(elem) - - end function dict_has_key_ci - - function dict_has_key_ii(this, key) result(has) + function has_ii(this, key) result(has) class(DictIntInt) :: this integer, intent(in) :: key logical :: has - type(ElemKeyValueII), pointer :: elem + integer :: i - elem => this % get_elem(key) - has = associated(elem) + i = this % get_entry(key) + has = (this % table(i) % key /= EMPTY) - end function dict_has_key_ii + end function has_ii -!=============================================================================== -! DICT_HASH_KEY returns the hash value for a given key -!=============================================================================== - - function dict_hash_key_ci(key) result(val) + function has_ci(this, key) result(has) + class(DictCharInt) :: this character(*), intent(in) :: key - integer :: val + logical :: has integer :: i - val = 0 + i = this % get_entry(key) + has = (this % table(i) % hash /= EMPTY) - do i = 1, len_trim(key) - val = HASH_MULTIPLIER * val + ichar(key(i:i)) - end do + end function has_ci - ! Added the absolute val on val-1 since the sum in the do loop is - ! susceptible to integer overflow - val = 1 + mod(abs(val-1), HASH_SIZE) +!=============================================================================== +! REMOVE deletes a (key,value) entry from a dictionary. +!=============================================================================== - end function dict_hash_key_ci - - function dict_hash_key_ii(key) result(val) + subroutine remove_ii(this, key) + class(DictIntInt) :: this integer, intent(in) :: key - integer :: val - - val = 0 - - ! Added the absolute val on val-1 since the sum in the do loop is - ! susceptible to integer overflow - val = 1 + mod(abs(key-1), HASH_SIZE) - - end function dict_hash_key_ii - -!=============================================================================== -! DICT_KEYS returns a pointer to a linked list of all (key,value) pairs -!=============================================================================== - - function dict_keys_ci(this) result(keys) - class(DictCharInt) :: this - type(ElemKeyValueCI), pointer :: keys integer :: i - type(ElemKeyValueCI), pointer :: current => null() - type(ElemKeyValueCI), pointer :: elem => null() - keys => null() - - do i = 1, size(this % table) - ! Get pointer to start of bucket i - elem => this % table(i) % list - - do while (associated(elem)) - ! Allocate (key,value) pair - if (.not. associated(keys)) then - allocate(keys) - current => keys - else - allocate(current % next) - current => current % next - end if - - ! Copy (key,value) pair - current % key = elem % key - current % value = elem % value - - ! Move to next element in bucket i - elem => elem % next - end do - end do - - end function dict_keys_ci - - function dict_keys_ii(this) result(keys) - class(DictIntInt) :: this - type(ElemKeyValueII), pointer :: keys - - integer :: i - type(ElemKeyValueII), pointer :: current => null() - type(ElemKeyValueII), pointer :: elem => null() - - keys => null() - - do i = 1, size(this % table) - ! Get pointer to start of bucket i - elem => this % table(i) % list - - do while (associated(elem)) - ! Allocate (key,value) pair - if (.not. associated(keys)) then - allocate(keys) - current => keys - else - allocate(current % next) - current => current % next - end if - - ! Copy (key,value) pair - current % key = elem % key - current % value = elem % value - - ! Move to next element in bucket i - elem => elem % next - end do - end do - - end function dict_keys_ii - -!=============================================================================== -! DICT_CLEAR Deletes and deallocates the dictionary item -!=============================================================================== - - subroutine dict_clear_ci(this) - - class(DictCharInt) :: this - - integer :: i - type(ElemKeyValueCI), pointer :: current - type(ElemKeyValueCI), pointer :: next - - if (associated(this % table)) then - do i = 1, size(this % table) - current => this % table(i) % list - do while (associated(current)) - if (associated(current % next)) then - next => current % next - else - nullify(next) - end if - deallocate(current) - current => next - end do - if (associated(this % table(i) % list)) & - nullify(this % table(i) % list) - end do - deallocate(this % table) + i = this % get_entry(key) + if (this % table(i) % key /= EMPTY) then + this % table(i) % key = DELETED + this % table(i) % value = EMPTY + this % entries = this % entries - 1 end if - end subroutine dict_clear_ci + end subroutine remove_ii - subroutine dict_clear_ii(this) + subroutine remove_ci(this, key) + + class(DictCharInt) :: this + character(*), intent(in) :: key + + integer :: i + + i = this % get_entry(key) + if (this % table(i) % hash /= EMPTY) then + this % table(i) % hash = DELETED + if (allocated(this % table(i) % entry)) then + deallocate(this % table(i) % entry) + end if + this % entries = this % entries - 1 + end if + + end subroutine remove_ci + +!=============================================================================== +! NEXT_ENTRY finds the next (key,value) pair. The value of current_entry is +! updated with the (key,value) pair, and the value of i is updated with the +! index of the entry in the table. Passing in i = 0 will locate the first entry +! in the dictionary. If there are no more entries, i will be set to 0. +!=============================================================================== + + subroutine next_entry_ii(this, current_entry, i) + + class(DictIntInt) :: this + type(DictEntryII), intent(inout) :: current_entry + integer, intent(inout) :: i + + if (.not. allocated(this % table)) return + + do + i = i + 1 + if (i > size(this % table)) then + i = 0 + exit + else if (this % table(i) % key /= EMPTY .and. & + this % table(i) % key /= DELETED) then + current_entry = this % table(i) + exit + end if + end do + + end subroutine next_entry_ii + + subroutine next_entry_ci(this, current_entry, i) + + class(DictCharInt) :: this + type(DictEntryCI), intent(inout) :: current_entry + integer, intent(inout) :: i + + if (.not. allocated(this % table)) return + + do + i = i + 1 + if (i > size(this % table)) then + i = 0 + exit + else if (this % table(i) % hash /= EMPTY .and. & + this % table(i) % hash /= DELETED) then + current_entry = this % table(i) % entry + exit + end if + end do + + end subroutine next_entry_ci + +!=============================================================================== +! CLEAR deletes and deallocates the dictionary item +!=============================================================================== + + subroutine clear_ii(this) class(DictIntInt) :: this + if (allocated(this % table)) deallocate(this % table) + this % entries = 0 + this % capacity = 0 + + end subroutine clear_ii + + subroutine clear_ci(this) + + class(DictCharInt) :: this + + if (allocated(this % table)) deallocate(this % table) + this % entries = 0 + this % capacity = 0 + + end subroutine clear_ci + +!=============================================================================== +! SIZE returns the number of entries in the dictionary +!=============================================================================== + + pure function size_ii(this) result(size) + + class(DictIntInt), intent(in) :: this + integer :: size + + size = this % entries + + end function size_ii + + pure function size_ci(this) result(size) + + class(DictCharInt), intent(in) :: this + integer :: size + + size = this % entries + + end function size_ci + +!=============================================================================== +! HASH returns the hash value for a given key +!=============================================================================== + + pure function hash_ii(key) result(hash) + + integer, intent(in) :: key + integer :: hash + + hash = abs(key - 1) + + end function hash_ii + + pure function hash_ci(key) result(hash) + + character(*), intent(in) :: key + integer :: hash + integer :: i - type(ElemKeyValueII), pointer :: current - type(ElemKeyValueII), pointer :: next - if (associated(this % table)) then - do i = 1, size(this % table) - current => this % table(i) % list - do while (associated(current)) - if (associated(current % next)) then - next => current % next - else - nullify(next) - end if - deallocate(current) - current => next - end do - if (associated(this % table(i) % list)) & - nullify(this % table(i) % list) - end do - deallocate(this % table) - end if + hash = 0 - end subroutine dict_clear_ii + do i = 1, len_trim(key) + hash = 31 * hash + ichar(key(i:i)) + end do + + hash = abs(hash - 1) + + end function hash_ci end module dict_header diff --git a/src/distribution_multivariate.F90 b/src/distribution_multivariate.F90 index 68b246df8..650ab26fa 100644 --- a/src/distribution_multivariate.F90 +++ b/src/distribution_multivariate.F90 @@ -1,9 +1,11 @@ module distribution_multivariate use constants, only: ONE, TWO, PI - use distribution_univariate, only: Distribution + use distribution_univariate + use error, only: fatal_error use random_lcg, only: prn use math, only: rotate_angle + use xml_interface implicit none @@ -58,10 +60,17 @@ module distribution_multivariate type, abstract :: SpatialDistribution contains + procedure(spatial_distribution_from_xml_), deferred :: from_xml procedure(spatial_distribution_sample_), deferred :: sample end type SpatialDistribution abstract interface + subroutine spatial_distribution_from_xml_(this, node) + import SpatialDistribution, XMLNode + class(SpatialDistribution), intent(inout) :: this + type(XMLNode), intent(in) :: node + end subroutine spatial_distribution_from_xml_ + function spatial_distribution_sample_(this) result(xyz) import SpatialDistribution class(SpatialDistribution), intent(in) :: this @@ -74,6 +83,7 @@ module distribution_multivariate class(Distribution), allocatable :: y class(Distribution), allocatable :: z contains + procedure :: from_xml => cartesian_independent_from_xml procedure :: sample => cartesian_independent_sample end type CartesianIndependent @@ -82,12 +92,14 @@ module distribution_multivariate real(8) :: upper_right(3) logical :: only_fissionable = .false. contains + procedure :: from_xml => spatial_box_from_xml procedure :: sample => spatial_box_sample end type SpatialBox type, extends(SpatialDistribution) :: SpatialPoint real(8) :: xyz(3) contains + procedure :: from_xml => spatial_point_from_xml procedure :: sample => spatial_point_sample end type SpatialPoint @@ -132,6 +144,55 @@ contains uvw(:) = this % reference_uvw end function monodirectional_sample + subroutine cartesian_independent_from_xml(this, node) + class(CartesianIndependent), intent(inout) :: this + type(XMLNode), intent(in) :: node + + type(XMLNode) :: node_dist + + ! Read distribution for x coordinate + if (check_for_node(node, "x")) then + node_dist = node % child("x") + call distribution_from_xml(this % x, node_dist) + else + allocate(Discrete :: this % x) + select type (dist => this % x) + type is (Discrete) + allocate(dist % x(1), dist % p(1)) + dist % x(1) = ZERO + dist % p(1) = ONE + end select + end if + + ! Read distribution for y coordinate + if (check_for_node(node, "y")) then + node_dist = node % child("y") + call distribution_from_xml(this % y, node_dist) + else + allocate(Discrete :: this % y) + select type (dist => this % y) + type is (Discrete) + allocate(dist % x(1), dist % p(1)) + dist % x(1) = ZERO + dist % p(1) = ONE + end select + end if + + if (check_for_node(node, "z")) then + node_dist = node % child("z") + call distribution_from_xml(this % z, node_dist) + else + allocate(Discrete :: this % z) + select type (dist => this % z) + type is (Discrete) + allocate(dist % x(1), dist % p(1)) + dist % x(1) = ZERO + dist % p(1) = ONE + end select + end if + + end subroutine cartesian_independent_from_xml + function cartesian_independent_sample(this) result(xyz) class(CartesianIndependent), intent(in) :: this real(8) :: xyz(3) @@ -141,6 +202,26 @@ contains xyz(3) = this % z % sample() end function cartesian_independent_sample + subroutine spatial_box_from_xml(this, node) + class(SpatialBox), intent(inout) :: this + type(XMLNode), intent(in) :: node + + real(8), allocatable :: temp_real(:) + + ! Make sure correct number of parameters are given + if (node_word_count(node, "parameters") /= 6) then + call fatal_error('Box/fission spatial source must have & + &six parameters specified.') + end if + + ! Read lower-right/upper-left coordinates + allocate(temp_real(6)) + call get_node_array(node, "parameters", temp_real) + this % lower_left(:) = temp_real(1:3) + this % upper_right(:) = temp_real(4:6) + deallocate(temp_real) + end subroutine spatial_box_from_xml + function spatial_box_sample(this) result(xyz) class(SpatialBox), intent(in) :: this real(8) :: xyz(3) @@ -152,6 +233,20 @@ contains xyz(:) = this % lower_left + r*(this % upper_right - this % lower_left) end function spatial_box_sample + subroutine spatial_point_from_xml(this, node) + class(SpatialPoint), intent(inout) :: this + type(XMLNode), intent(in) :: node + + ! Make sure correct number of parameters are given + if (node_word_count(node, "parameters") /= 3) then + call fatal_error('Point spatial source must have & + &three parameters specified.') + end if + + ! Read location of point source + call get_node_array(node, "parameters", this % xyz) + end subroutine spatial_point_from_xml + function spatial_point_sample(this) result(xyz) class(SpatialPoint), intent(in) :: this real(8) :: xyz(3) diff --git a/src/eigenvalue.F90 b/src/eigenvalue.F90 index f370ab38e..5f99d1580 100644 --- a/src/eigenvalue.F90 +++ b/src/eigenvalue.F90 @@ -1,16 +1,20 @@ module eigenvalue + use, intrinsic :: ISO_C_BINDING use algorithm, only: binary_search use constants, only: ZERO use error, only: fatal_error, warning - use global use math, only: t_percentile use mesh, only: count_bank_sites - use mesh_header, only: RegularMesh + use mesh_header, only: RegularMesh, meshes use message_passing use random_lcg, only: prn, set_particle_seed, advance_prn_seed + use settings + use simulation_header use string, only: to_str + use tally_header + use timer_header implicit none @@ -39,6 +43,7 @@ contains & temp_sites(:) ! local array of extra sites on each node #ifdef MPI + integer :: mpi_err ! MPI error code integer(8) :: n ! number of sites to send/recv integer :: neighbor ! processor to send/recv data from #ifdef MPIF08 @@ -297,69 +302,34 @@ contains subroutine shannon_entropy() integer :: ent_idx ! entropy index - integer :: i, j, k ! index for bank sites - integer :: n ! # of boxes in each dimension + integer :: i ! index for mesh elements logical :: sites_outside ! were there sites outside entropy box? - type(RegularMesh), pointer :: m - ! Get pointer to entropy mesh - m => entropy_mesh - - ! On the first pass through this subroutine, we need to determine how big - ! the entropy mesh should be in each direction and then allocate a - ! three-dimensional array to store the fraction of source sites in each mesh - ! box - - if (.not. allocated(entropy_p)) then - if (.not. allocated(m % dimension)) then - ! If the user did not specify how many mesh cells are to be used in - ! each direction, we automatically determine an appropriate number of - ! cells - n = ceiling((n_particles/20)**(ONE/THREE)) - - ! copy dimensions - m % n_dimension = 3 - allocate(m % dimension(3)) - m % dimension = n - - ! determine width - m % width = (m % upper_right - m % lower_left) / m % dimension + associate (m => meshes(index_entropy_mesh)) + ! count number of fission sites over mesh + call count_bank_sites(m, fission_bank, entropy_p, & + size_bank=n_bank, sites_outside=sites_outside) + ! display warning message if there were sites outside entropy box + if (sites_outside) then + if (master) call warning("Fission source site(s) outside of entropy box.") end if - ! allocate p - allocate(entropy_p(1, m % dimension(1), m % dimension(2), & - m % dimension(3))) - end if + ! sum values to obtain shannon entropy + if (master) then + ! Normalize to total weight of bank sites + entropy_p = entropy_p / sum(entropy_p) - ! count number of fission sites over mesh - call count_bank_sites(m, fission_bank, entropy_p, & - size_bank=n_bank, sites_outside=sites_outside) - - ! display warning message if there were sites outside entropy box - if (sites_outside) then - if (master) call warning("Fission source site(s) outside of entropy box.") - end if - - ! sum values to obtain shannon entropy - if (master) then - ! Normalize to total weight of bank sites - entropy_p = entropy_p / sum(entropy_p) - - ent_idx = current_gen + gen_per_batch*(current_batch - 1) - entropy(ent_idx) = ZERO - do i = 1, m % dimension(1) - do j = 1, m % dimension(2) - do k = 1, m % dimension(3) - if (entropy_p(1,i,j,k) > ZERO) then - entropy(ent_idx) = entropy(ent_idx) - & - entropy_p(1,i,j,k) * log(entropy_p(1,i,j,k))/log(TWO) - end if - end do + ent_idx = current_gen + gen_per_batch*(current_batch - 1) + entropy(ent_idx) = ZERO + do i = 1, size(entropy_p, 2) + if (entropy_p(1,i) > ZERO) then + entropy(ent_idx) = entropy(ent_idx) - & + entropy_p(1,i) * log(entropy_p(1,i))/log(TWO) + end if end do - end do - end if - + end if + end associate end subroutine shannon_entropy !=============================================================================== @@ -371,6 +341,9 @@ contains subroutine calculate_generation_keff() integer :: i ! overall generation +#ifdef MPI + integer :: mpi_err ! MPI error code +#endif ! Get keff for this generation by subtracting off the starting value keff_generation = global_tallies(RESULT_VALUE, K_TRACKLENGTH) - keff_generation @@ -439,8 +412,8 @@ contains end subroutine calculate_average_keff !=============================================================================== -! CALCULATE_COMBINED_KEFF calculates a minimum variance estimate of k-effective -! based on a linear combination of the collision, absorption, and tracklength +! OPENMC_GET_KEFF calculates a minimum variance estimate of k-effective based on +! a linear combination of the collision, absorption, and tracklength ! estimates. The theory behind this can be found in M. Halperin, "Almost ! linearly-optimum combination of unbiased estimates," J. Am. Stat. Assoc., 56, ! 36-43 (1961), doi:10.1080/01621459.1961.10482088. The implementation here @@ -448,7 +421,9 @@ contains ! of keff confidence intervals in MCNP," Nucl. Technol., 111, 169-182 (1995). !=============================================================================== - subroutine calculate_combined_keff() + function openmc_get_keff(k_combined) result(err) bind(C) + real(C_DOUBLE), intent(out) :: k_combined(2) + integer(C_INT) :: err integer :: l ! loop index integer :: i, j, k ! indices referring to collision, absorption, or track @@ -459,9 +434,14 @@ contains real(8) :: g ! sum of weighting factors real(8) :: S(3) ! sums used for variance calculation + k_combined = ZERO + ! Make sure we have at least four realizations. Notice that at the end, ! there is a N-3 term in a denominator. - if (n_realizations <= 3) return + if (n_realizations <= 3) then + err = -1 + return + end if ! Initialize variables n = real(n_realizations, 8) @@ -523,7 +503,6 @@ contains ! Initialize variables g = ZERO S = ZERO - k_combined = ZERO do l = 1, 3 ! Permutations of estimates @@ -590,8 +569,9 @@ contains k_combined(2) = sqrt(k_combined(2)) end if + err = 0 - end subroutine calculate_combined_keff + end function openmc_get_keff !=============================================================================== ! COUNT_SOURCE_FOR_UFS determines the source fraction in each UFS mesh cell and @@ -606,18 +586,21 @@ contains logical :: sites_outside ! were there sites outside the ufs mesh? #ifdef MPI integer :: n ! total number of ufs mesh cells + integer :: mpi_err ! MPI error code #endif + associate (m => meshes(index_ufs_mesh)) + if (current_batch == 1 .and. current_gen == 1) then ! On the first generation, just assume that the source is already evenly ! distributed so that effectively the production of fission sites is not ! biased - source_frac = ufs_mesh % volume_frac + source_frac = m % volume_frac else ! count number of source sites in each ufs mesh cell - call count_bank_sites(ufs_mesh, source_bank, source_frac, & + call count_bank_sites(m, source_bank, source_frac, & sites_outside=sites_outside, size_bank=work) ! Check for sites outside of the mesh @@ -627,7 +610,7 @@ contains #ifdef MPI ! Send source fraction to all processors - n = product(ufs_mesh % dimension) + n = product(m % dimension) call MPI_BCAST(source_frac, n, MPI_REAL8, 0, mpi_intracomm, mpi_err) #endif @@ -641,6 +624,8 @@ contains source_bank % wgt = source_bank % wgt * n_particles / total end if + end associate + end subroutine count_source_for_ufs #ifdef _OPENMP diff --git a/src/error.F90 b/src/error.F90 index 1bb6b5257..92902a04c 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -1,14 +1,57 @@ module error + use, intrinsic :: ISO_C_BINDING use, intrinsic :: ISO_FORTRAN_ENV - use constants + use constants use message_passing implicit none + private + public :: fatal_error + public :: warning + + ! Error codes + integer(C_INT), public, bind(C) :: E_UNASSIGNED = -1 + integer(C_INT), public, bind(C) :: E_ALLOCATE = -2 + integer(C_INT), public, bind(C) :: E_OUT_OF_BOUNDS = -3 + integer(C_INT), public, bind(C) :: E_INVALID_SIZE = -4 + integer(C_INT), public, bind(C) :: E_INVALID_ARGUMENT = -5 + integer(C_INT), public, bind(C) :: E_INVALID_TYPE = -6 + integer(C_INT), public, bind(C) :: E_INVALID_ID = -7 + integer(C_INT), public, bind(C) :: E_GEOMETRY = -8 + integer(C_INT), public, bind(C) :: E_DATA = -9 + integer(C_INT), public, bind(C) :: E_PHYSICS = -10 + + ! Warning codes + integer(C_INT), public, bind(C) :: E_WARNING = 1 + + ! Error message + character(kind=C_CHAR), public, bind(C) :: openmc_err_msg(256) + + public :: set_errmsg + contains +!=============================================================================== +! SET_ERRMSG sets the 'openmc_err_msg' module variable that is exposed via the C +! API +!=============================================================================== + + subroutine set_errmsg(f_string) + character(*), intent(in) :: f_string + + integer :: i, n + + ! Copy Fortran string to null-terminated C char array + n = len_trim(f_string) + do i = 1, n + openmc_err_msg(i) = f_string(i:i) + end do + openmc_err_msg(n + 1) = C_NULL_CHAR + end subroutine set_errmsg + !=============================================================================== ! WARNING issues a warning to the user in the log file and the standard output ! stream. diff --git a/src/finalize.F90 b/src/finalize.F90 deleted file mode 100644 index 6164371a0..000000000 --- a/src/finalize.F90 +++ /dev/null @@ -1,40 +0,0 @@ -module finalize - - use, intrinsic :: ISO_C_BINDING - - use hdf5, only: h5tclose_f, h5close_f - - use global - use hdf5_interface, only: hdf5_bank_t - use message_passing - - implicit none - -contains - -!=============================================================================== -! FINALIZE_RUN does all post-simulation tasks such as calculating tally -! statistics and writing out tallies -!=============================================================================== - - subroutine openmc_finalize() bind(C) - - integer :: hdf5_err - - ! Deallocate arrays - call free_memory() - - ! Release compound datatypes - call h5tclose_f(hdf5_bank_t, hdf5_err) - - ! Close FORTRAN interface. - call h5close_f(hdf5_err) - -#ifdef MPI - ! Free all MPI types - call MPI_TYPE_FREE(MPI_BANK, mpi_err) -#endif - - end subroutine openmc_finalize - -end module finalize diff --git a/src/geometry.F90 b/src/geometry.F90 index 444456232..1cc27e09e 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -2,16 +2,17 @@ module geometry use constants use error, only: fatal_error, warning - use geometry_header, only: Cell, Universe, Lattice, & - &RectLattice, HexLattice - use global + use geometry_header use output, only: write_message use particle_header, only: LocalCoord, Particle use particle_restart_write, only: write_particle_restart + use simulation_header + use settings use surface_header use stl_vector, only: VectorInt use string, only: to_str use tally, only: score_surface_current + use tally_header implicit none @@ -384,9 +385,8 @@ contains ! the geometry, is reflected, or crosses into a new lattice or cell !=============================================================================== - subroutine cross_surface(p, last_cell) + subroutine cross_surface(p) type(Particle), intent(inout) :: p - integer, intent(in) :: last_cell ! last cell particle was in real(8) :: u ! x-component of direction real(8) :: v ! y-component of direction @@ -425,9 +425,7 @@ contains end if ! Score to global leakage tally - if (tallies_on) then - global_tally_leakage = global_tally_leakage + p % wgt - end if + global_tally_leakage = global_tally_leakage + p % wgt ! Display message if (verbosity >= 10 .or. trace) then @@ -469,7 +467,7 @@ contains p%coord(1)%uvw(:) = [u, v, w] / norm ! Reassign particle's cell and surface - p % coord(1) % cell = last_cell + p % coord(1) % cell = p % last_cell(p % last_n_coord) p % surface = -p % surface ! If a reflective surface is coincident with a lattice or universe @@ -1251,8 +1249,8 @@ contains class(Lattice), pointer :: lat ! pointer to current lattice ! Don't research places already checked - if (found(universe_dict % get_key(univ % id), map)) then - count = counts(universe_dict % get_key(univ % id), map) + if (found(universe_dict % get(univ % id), map)) then + count = counts(universe_dict % get(univ % id), map) return end if @@ -1260,8 +1258,8 @@ contains ! Count = 1, then quit if (univ % id == univ_id) then count = 1 - counts(universe_dict % get_key(univ % id), map) = 1 - found(universe_dict % get_key(univ % id), map) = .true. + counts(universe_dict % get(univ % id), map) = 1 + found(universe_dict % get(univ % id), map) = .true. return end if @@ -1357,8 +1355,8 @@ contains end if end do - counts(universe_dict % get_key(univ % id), map) = count - found(universe_dict % get_key(univ % id), map) = .true. + counts(universe_dict % get(univ % id), map) = count + found(universe_dict % get(univ % id), map) = .true. end function count_target diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 0d3ae2640..dd2cad7f1 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -1,10 +1,14 @@ module geometry_header + use, intrinsic :: ISO_C_BINDING + use algorithm, only: find use constants, only: HALF, TWO, THREE, INFINITY, K_BOLTZMANN, & MATERIAL_VOID, NONE use dict_header, only: DictCharInt, DictIntInt - use material_header, only: Material + use material_header, only: Material, materials, material_dict, n_materials + use nuclide_header + use sab_header use stl_vector, only: VectorReal use string, only: to_lower @@ -155,6 +159,19 @@ module geometry_header ! array index of the root universe integer :: root_universe = -1 + integer(C_INT32_T), bind(C) :: n_cells ! # of cells + integer(C_INT32_T), bind(C) :: n_universes ! # of universes + integer(C_INT32_T), bind(C) :: n_lattices ! # of lattices + + type(Cell), allocatable, target :: cells(:) + type(Universe), allocatable, target :: universes(:) + type(LatticeContainer), allocatable, target :: lattices(:) + + ! Dictionaries which map user IDs to indices in the global arrays + type(DictIntInt) :: cell_dict + type(DictIntInt) :: universe_dict + type(DictIntInt) :: lattice_dict + contains !=============================================================================== @@ -330,16 +347,8 @@ contains ! temperatures to read (which may be different if interpolation is used) !=============================================================================== - subroutine get_temperatures(cells, materials, material_dict, nuclide_dict, & - n_nucs, nuc_temps, sab_dict, n_sabs, sab_temps) - type(Cell), allocatable, intent(in) :: cells(:) - type(Material), allocatable, intent(in) :: materials(:) - type(DictIntInt), intent(in) :: material_dict - type(DictCharInt), intent(in) :: nuclide_dict - integer, intent(in) :: n_nucs + subroutine get_temperatures(nuc_temps, sab_temps) type(VectorReal), allocatable, intent(out) :: nuc_temps(:) - type(DictCharInt), optional, intent(in) :: sab_dict - integer, optional, intent(in) :: n_sabs type(VectorReal), optional, allocatable, intent(out) :: sab_temps(:) integer :: i, j, k @@ -348,8 +357,8 @@ contains integer :: i_material real(8) :: temperature ! temperature in Kelvin - allocate(nuc_temps(n_nucs)) - if (present(n_sabs) .and. present(sab_temps)) allocate(sab_temps(n_sabs)) + allocate(nuc_temps(n_nuclides)) + if (present(sab_temps)) allocate(sab_temps(n_sab_tables)) do i = 1, size(cells) do j = 1, size(cells(i) % material) @@ -364,11 +373,12 @@ contains temperature = cells(i) % sqrtkT(1)**2 / K_BOLTZMANN end if - i_material = material_dict % get_key(cells(i) % material(j)) + i_material = cells(i) % material(j) + associate (mat => materials(i_material)) NUC_NAMES_LOOP: do k = 1, size(mat % names) ! Get index in nuc_temps array - i_nuclide = nuclide_dict % get_key(to_lower(mat % names(k))) + i_nuclide = nuclide_dict % get(to_lower(mat % names(k))) ! Add temperature if it hasn't already been added if (find(nuc_temps(i_nuclide), temperature) == -1) then @@ -376,11 +386,10 @@ contains end if end do NUC_NAMES_LOOP - if (present(sab_temps) .and. present(sab_dict) .and. & - mat % n_sab > 0) then + if (present(sab_temps) .and. mat % n_sab > 0) then SAB_NAMES_LOOP: do k = 1, size(mat % sab_names) ! Get index in nuc_temps array - i_sab = sab_dict % get_key(to_lower(mat % sab_names(k))) + i_sab = sab_dict % get(to_lower(mat % sab_names(k))) ! Add temperature if it hasn't already been added if (find(sab_temps(i_sab), temperature) == -1) then @@ -394,4 +403,239 @@ contains end subroutine get_temperatures +!=============================================================================== +! FREE_MEMORY_GEOMETRY deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_geometry() + + n_cells = 0 + n_universes = 0 + n_lattices = 0 + + if (allocated(cells)) deallocate(cells) + if (allocated(universes)) deallocate(universes) + if (allocated(lattices)) deallocate(lattices) + + call cell_dict % clear() + call universe_dict % clear() + call lattice_dict % clear() + + end subroutine free_memory_geometry + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_get_cell_index(id, index) result(err) bind(C) + ! Return the index in the cells array of a cell with a given ID + integer(C_INT32_T), value :: id + integer(C_INT32_T), intent(out) :: index + integer(C_INT) :: err + + if (allocated(cells)) then + if (cell_dict % has(id)) then + index = cell_dict % get(id) + err = 0 + else + err = E_INVALID_ID + call set_errmsg("No cell exists with ID=" // trim(to_str(id)) // ".") + end if + else + err = E_ALLOCATE + call set_errmsg("Memory has not been allocated for cells.") + end if + end function openmc_get_cell_index + + + function openmc_cell_get_fill(index, type, indices, n) result(err) bind(C) + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT), intent(out) :: type + integer(C_INT32_T), intent(out) :: n + type(C_PTR), intent(out) :: indices + integer(C_INT) :: err + + err = 0 + if (index >= 1 .and. index <= size(cells)) then + associate (c => cells(index)) + type = c % type + select case (type) + case (FILL_MATERIAL) + n = size(c % material) + indices = C_LOC(c % material(1)) + case (FILL_UNIVERSE, FILL_LATTICE) + n = 1 + indices = C_LOC(c % fill) + end select + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in cells array is out of bounds.") + end if + end function openmc_cell_get_fill + + + function openmc_cell_get_id(index, id) result(err) bind(C) + ! Return the ID of a cell + integer(C_INT32_T), value :: index + integer(C_INT32_T), intent(out) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(cells)) then + id = cells(index) % id + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in cells array is out of bounds.") + end if + end function openmc_cell_get_id + + + function openmc_cell_set_fill(index, type, n, indices) result(err) bind(C) + ! Set the fill for a fill + integer(C_INT32_T), value, intent(in) :: index ! index in cells + integer(C_INT), value, intent(in) :: type + integer(c_INT32_T), value, intent(in) :: n + integer(C_INT32_T), intent(in) :: indices(n) + integer(C_INT) :: err + + integer :: i, j + + err = 0 + if (index >= 1 .and. index <= size(cells)) then + associate (c => cells(index)) + select case (type) + case (FILL_MATERIAL) + if (allocated(c % material)) deallocate(c % material) + allocate(c % material(n)) + + c % type = FILL_MATERIAL + do i = 1, n + j = indices(i) + if (j == 0) then + c % material(i) = MATERIAL_VOID + else + if (j >= 1 .and. j <= n_materials) then + c % material(i) = j + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index " // trim(to_str(j)) // " in the & + &materials array is out of bounds.") + end if + end if + end do + case (FILL_UNIVERSE) + c % type = FILL_UNIVERSE + case (FILL_LATTICE) + c % type = FILL_LATTICE + end select + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in cells array is out of bounds.") + end if + + end function openmc_cell_set_fill + + + function openmc_cell_set_temperature(index, T, instance) result(err) bind(C) + ! Set the temperature of a cell + integer(C_INT32_T), value, intent(in) :: index ! index in cells + real(C_DOUBLE), value, intent(in) :: T ! temperature + integer(C_INT32_T), optional, intent(in) :: instance ! cell instance + + integer(C_INT) :: err ! error code + integer :: j ! looping variable + integer :: n ! number of cell instances + integer :: material_index ! material index in materials array + integer :: num_nuclides ! num nuclides in material + integer :: nuclide_index ! index of nuclide in nuclides array + real(8) :: min_temp ! min common-denominator avail temp + real(8) :: max_temp ! max common-denominator avail temp + real(8) :: temp ! actual temp we'll assign + logical :: outside_low ! lower than available data + logical :: outside_high ! higher than available data + + outside_low = .false. + outside_high = .false. + + err = E_UNASSIGNED + + if (index >= 1 .and. index <= size(cells)) then + + ! error if the cell is filled with another universe + if (cells(index) % fill /= NONE) then + err = E_GEOMETRY + call set_errmsg("Cannot set temperature on a cell filled & + &with a universe.") + else + ! 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) + else + material_index = cells(index) % material(1) + end if + + ! number of nuclides associated with this material + num_nuclides = size(materials(material_index) % nuclide) + + min_temp = ZERO + max_temp = INFINITY + + do j = 1, num_nuclides + nuclide_index = materials(material_index) % nuclide(j) + min_temp = max(min_temp, minval(nuclides(nuclide_index) % kTs)) + max_temp = min(max_temp, maxval(nuclides(nuclide_index) % kTs)) + end do + + ! adjust the temperature to be within bounds if necessary + if (K_BOLTZMANN * T < min_temp) then + outside_low = .true. + temp = min_temp / K_BOLTZMANN + else if (K_BOLTZMANN * T > max_temp) then + outside_high = .true. + temp = max_temp / K_BOLTZMANN + else + temp = T + end if + + associate (c => cells(index)) + if (allocated(c % sqrtkT)) then + n = size(c % sqrtkT) + if (present(instance) .and. n > 1) then + if (instance >= 0 .and. instance < n) then + c % sqrtkT(instance + 1) = sqrt(K_BOLTZMANN * temp) + err = 0 + end if + else + c % sqrtkT(:) = sqrt(K_BOLTZMANN * temp) + err = 0 + end if + end if + end associate + + ! Assign error codes for outside of temperature bounds provided the + ! temperature was changed correctly. This needs to be done after + ! changing the temperature based on the logical structure above. + if (err == 0) then + if (outside_low) then + err = E_WARNING + call set_errmsg("Nuclear data has not been loaded beyond lower & + &bound of T=" // trim(to_str(T)) // " K.") + else if (outside_high) then + err = E_WARNING + call set_errmsg("Nuclear data has not been loaded beyond upper & + &bound of T=" // trim(to_str(T)) // " K.") + end if + end if + + end if + + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in cells array is out of bounds.") + end if + end function openmc_cell_set_temperature + end module geometry_header diff --git a/src/global.F90 b/src/global.F90 deleted file mode 100644 index 30d824a6d..000000000 --- a/src/global.F90 +++ /dev/null @@ -1,590 +0,0 @@ -module global - - use, intrinsic :: ISO_C_BINDING - -#ifdef MPIF08 - use mpi_f08 -#endif - - use bank_header, only: Bank - use cmfd_header - use constants - use dict_header, only: DictCharInt, DictIntInt - use geometry_header, only: Cell, Universe, Lattice, LatticeContainer - use material_header, only: Material - use mesh_header, only: RegularMesh - use mgxs_header, only: Mgxs, MgxsContainer - use nuclide_header - use photon_header, only: PhotonInteraction, ElementMicroXS - use plot_header, only: ObjectPlot - use sab_header, only: SAlphaBeta - use set_header, only: SetInt - use stl_vector, only: VectorInt - use surface_header, only: SurfaceContainer - use source_header, only: SourceDistribution - use tally_header, only: TallyObject, TallyDerivative - use tally_filter_header, only: TallyFilterContainer, TallyFilterMatch - use trigger_header, only: KTrigger - use timer_header, only: Timer - use volume_header, only: VolumeCalculation - - implicit none - - ! ============================================================================ - ! GEOMETRY-RELATED VARIABLES - - ! Main arrays - type(Cell), allocatable, target :: cells(:) - type(Universe), allocatable, target :: universes(:) - type(LatticeContainer), allocatable, target :: lattices(:) - type(SurfaceContainer), allocatable, target :: surfaces(:) - type(Material), allocatable, target :: materials(:) - type(ObjectPlot), allocatable, target :: plots(:) - - type(VolumeCalculation), allocatable :: volume_calcs(:) - - ! Size of main arrays - integer :: n_cells ! # of cells - integer :: n_universes ! # of universes - integer :: n_lattices ! # of lattices - integer :: n_surfaces ! # of surfaces - integer :: n_materials ! # of materials - integer :: n_plots ! # of plots - - ! These dictionaries provide a fast lookup mechanism -- the key is the - ! user-specified identifier and the value is the index in the corresponding - ! array - type(DictIntInt) :: cell_dict - type(DictIntInt) :: universe_dict - type(DictIntInt) :: lattice_dict - type(DictIntInt) :: surface_dict - type(DictIntInt) :: material_dict - type(DictIntInt) :: mesh_dict - type(DictIntInt) :: filter_dict - type(DictIntInt) :: tally_dict - type(DictIntInt) :: plot_dict - - ! Number of lost particles - integer :: n_lost_particles - - ! ============================================================================ - ! ENERGY TREATMENT RELATED VARIABLES - logical :: run_CE = .true. ! Run in CE mode? - - ! ============================================================================ - ! CROSS SECTION RELATED VARIABLES NEEDED REGARDLESS OF CE OR MG - - integer :: n_nuclides_total ! Number of nuclide cross section tables - integer :: n_elements ! Number of photon cross section tables - - ! Cross section caches - type(NuclideMicroXS), allocatable :: micro_xs(:) ! Cache for each nuclide - type(ElementMicroXS), allocatable :: micro_photon_xs(:) ! Cache for each element - type(MaterialMacroXS) :: material_xs ! Cache for current material - - ! Dictionaries to look up cross sections and listings - type(DictCharInt) :: nuclide_dict - type(DictCharInt) :: element_dict - type(DictCharInt) :: library_dict - - ! Cross section libraries - type(Library), allocatable :: libraries(:) - - ! ============================================================================ - ! CONTINUOUS-ENERGY CROSS SECTION RELATED VARIABLES - - ! Cross section arrays - type(Nuclide), allocatable, target :: nuclides(:) ! Nuclide cross-sections - type(PhotonInteraction), allocatable :: elements(:) ! Photon cross sections - type(SAlphaBeta), allocatable, target :: sab_tables(:) ! S(a,b) tables - - integer :: n_sab_tables ! Number of S(a,b) thermal scattering tables - - ! Minimum/maximum energies - real(8) :: energy_min_neutron = ZERO - real(8) :: energy_max_neutron = INFINITY - - ! Dictionaries to look up cross sections and listings - type(DictCharInt) :: sab_dict - - ! Unreoslved resonance probablity tables - logical :: urr_ptables_on = .true. - - ! Default temperature and method for choosing temperatures - integer :: temperature_method = TEMPERATURE_NEAREST - logical :: temperature_multipole = .false. - real(8) :: temperature_tolerance = 10.0_8 - real(8) :: temperature_default = 293.6_8 - real(8) :: temperature_range(2) = [ZERO, ZERO] - - integer :: n_log_bins ! number of bins for logarithmic grid - real(8) :: log_spacing ! spacing on logarithmic grid - - logical :: photon_transport = .false. - integer :: electron_treatment = ELECTRON_LED - - ! ============================================================================ - ! MULTI-GROUP CROSS SECTION RELATED VARIABLES - - ! 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 - real(8), allocatable :: energy_bins(:) - - ! Midpoint of the energy group structure - real(8), allocatable :: energy_bin_avg(:) - - ! Maximum Data Order - integer :: 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 - - ! ============================================================================ - ! TALLY-RELATED VARIABLES - - type(RegularMesh), allocatable, target :: meshes(:) - type(TallyObject), allocatable, target :: tallies(:) - type(TallyFilterContainer), allocatable, target :: filters(:) - type(TallyFilterMatch), allocatable :: filter_matches(:) - - ! Pointers for different tallies - type(TallyObject), pointer :: user_tallies(:) => null() - type(TallyObject), pointer :: cmfd_tallies(:) => null() - - ! Starting index (minus 1) in tallies for each tally group - integer :: i_user_tallies = -1 - integer :: i_cmfd_tallies = -1 - - ! Active tally lists - type(VectorInt) :: active_analog_tallies - type(VectorInt) :: active_tracklength_tallies - type(VectorInt) :: active_current_tallies - type(VectorInt) :: active_collision_tallies - type(VectorInt) :: active_tallies - - ! Global tallies - ! 1) collision estimate of k-eff - ! 2) absorption estimate of k-eff - ! 3) track-length estimate of k-eff - ! 4) leakage fraction - - real(C_DOUBLE), allocatable, target :: global_tallies(:,:) - - ! It is possible to protect accumulate operations on global tallies by using - ! an atomic update. However, when multiple threads accumulate to the same - ! global tally, it can cause a higher cache miss rate due to - ! invalidation. Thus, we use threadprivate variables to accumulate global - ! tallies and then reduce at the end of a generation. - real(8) :: global_tally_collision = ZERO - real(8) :: global_tally_absorption = ZERO - real(8) :: global_tally_tracklength = ZERO - real(8) :: global_tally_leakage = ZERO -!$omp threadprivate(global_tally_collision, global_tally_absorption, & -!$omp& global_tally_tracklength, global_tally_leakage) - - integer :: n_meshes = 0 ! # of structured meshes - integer :: n_user_meshes = 0 ! # of structured user meshes - integer :: n_filters = 0 ! # of filters - integer :: n_user_filters = 0 ! # of user filters - integer :: n_tallies = 0 ! # of tallies - integer :: n_user_tallies = 0 ! # of user tallies - - ! Tally derivatives - type(TallyDerivative), allocatable :: tally_derivs(:) -!$omp threadprivate(tally_derivs) - - ! Normalization for statistics - integer :: n_realizations = 0 ! # of independent realizations - real(8) :: total_weight ! total starting particle weight in realization - - ! Flag for turning tallies on - logical :: tallies_on = .false. - logical :: active_batches = .false. - - ! Assume all tallies are spatially distinct - logical :: assume_separate = .false. - - ! Use confidence intervals for results instead of standard deviations - logical :: confidence_intervals = .false. - - ! ============================================================================ - ! EIGENVALUE SIMULATION VARIABLES - - integer(8) :: n_particles = 0 ! # of particles per generation - integer :: n_batches ! # of batches - integer :: n_inactive ! # of inactive batches - integer :: n_active ! # of active batches - integer :: gen_per_batch = 1 ! # of generations per batch - integer :: current_batch = 0 ! current batch - integer :: current_gen = 0 ! current generation within a batch - integer :: total_gen = 0 ! total number of generations simulated - - ! ============================================================================ - ! TALLY PRECISION TRIGGER VARIABLES - - integer :: n_max_batches ! max # of batches - integer :: n_batch_interval = 1 ! batch interval for triggers - logical :: pred_batches = .false. ! predict batches for triggers - logical :: trigger_on = .false. ! flag for turning triggers on/off - type(KTrigger) :: keff_trigger ! trigger for k-effective - logical :: satisfy_triggers = .false. ! whether triggers are satisfied - - ! External source - type(SourceDistribution), allocatable :: external_source(:) - - ! Source and fission bank - type(Bank), allocatable, target :: source_bank(:) - type(Bank), allocatable, target :: fission_bank(:) -#ifdef _OPENMP - type(Bank), allocatable, target :: master_fission_bank(:) -#endif - integer(8) :: n_bank ! # of sites in fission bank - integer(8) :: work ! number of particles per processor - integer(8), allocatable :: work_index(:) ! starting index in source bank for each process - integer(8) :: current_work ! index in source bank of current history simulated - - ! Temporary k-effective values - real(8), allocatable :: k_generation(:) ! single-generation estimates of k - real(8) :: keff = ONE ! average k over active batches - real(8) :: keff_std ! standard deviation of average k - real(8) :: k_col_abs = ZERO ! sum over batches of k_collision * k_absorption - real(8) :: k_col_tra = ZERO ! sum over batches of k_collision * k_tracklength - real(8) :: k_abs_tra = ZERO ! sum over batches of k_absorption * k_tracklength - real(8) :: k_combined(2) ! combined best estimate of k-effective - - ! Shannon entropy - logical :: entropy_on = .false. - real(8), allocatable :: entropy(:) ! shannon entropy at each generation - real(8), allocatable :: entropy_p(:,:,:,:) ! % of source sites in each cell - type(RegularMesh), pointer :: entropy_mesh - - ! Uniform fission source weighting - logical :: ufs = .false. - type(RegularMesh), pointer :: ufs_mesh => null() - real(8), allocatable :: source_frac(:,:,:,:) - - ! Write source at end of simulation - logical :: source_separate = .false. - logical :: source_write = .true. - logical :: source_latest = .false. - - ! ============================================================================ - ! PARALLEL PROCESSING VARIABLES - -#ifdef _OPENMP - integer :: n_threads = NONE ! number of OpenMP threads - integer :: thread_id ! ID of a given thread -#endif - - ! No reduction at end of batch - logical :: reduce_tallies = .true. - - ! ============================================================================ - ! TIMING VARIABLES - - type(Timer) :: time_total ! timer for total run - type(Timer) :: time_initialize ! timer for initialization - type(Timer) :: time_read_xs ! timer for reading cross sections - type(Timer) :: time_unionize ! timer for material xs-energy grid union - type(Timer) :: time_bank ! timer for fission bank synchronization - type(Timer) :: time_bank_sample ! timer for fission bank sampling - type(Timer) :: time_bank_sendrecv ! timer for fission bank SEND/RECV - type(Timer) :: time_tallies ! timer for accumulate tallies - type(Timer) :: time_inactive ! timer for inactive batches - type(Timer) :: time_active ! timer for active batches - type(Timer) :: time_transport ! timer for transport only - type(Timer) :: time_finalize ! timer for finalization - - ! =========================================================================== - ! VARIANCE REDUCTION VARIABLES - - logical :: survival_biasing = .false. - real(8) :: weight_cutoff = 0.25_8 - real(8) :: energy_cutoff(4) = [ZERO, 1000.0_8, ZERO, ZERO] - real(8) :: weight_survive = ONE - - ! ============================================================================ - ! MISCELLANEOUS VARIABLES - - ! Mode to run in (fixed source, eigenvalue, plotting, etc) - integer :: run_mode = NONE - - ! Restart run - logical :: restart_run = .false. - integer :: restart_batch - - character(MAX_FILE_LEN) :: path_input ! Path to input file - character(MAX_FILE_LEN) :: path_cross_sections = '' ! Path to cross_sections.xml - character(MAX_FILE_LEN) :: path_multipole ! Path to wmp library - character(MAX_FILE_LEN) :: path_source = '' ! Path to binary source - character(MAX_FILE_LEN) :: path_state_point ! Path to binary state point - character(MAX_FILE_LEN) :: path_source_point ! Path to binary source point - character(MAX_FILE_LEN) :: path_particle_restart ! Path to particle restart - character(MAX_FILE_LEN) :: path_output = '' ! Path to output directory - - ! The verbosity controls how much information will be printed to the - ! screen and in logs - integer :: verbosity = 7 - - ! Flag for enabling cell overlap checking during transport - logical :: check_overlaps = .false. - integer(8), allocatable :: overlap_check_cnt(:) - - ! Trace for single particle - logical :: trace - integer :: trace_batch - integer :: trace_gen - integer(8) :: trace_particle - - ! Particle tracks - logical :: write_all_tracks = .false. - integer, allocatable :: track_identifiers(:,:) - - ! Particle restart run - logical :: particle_restart_run = .false. - - ! Number of distribcell maps - integer :: n_maps - - ! Write out initial source - logical :: write_initial_source = .false. - - ! Whether create fission neutrons or not. Only applied for MODE_FIXEDSOURCE - logical :: create_fission_neutrons = .true. - - ! ============================================================================ - ! CMFD VARIABLES - - ! Main object - type(cmfd_type) :: cmfd - - ! Is CMFD active - logical :: cmfd_run = .false. - - ! Timing objects - type(Timer) :: time_cmfd ! timer for whole cmfd calculation - type(Timer) :: time_cmfdbuild ! timer for matrix build - type(Timer) :: time_cmfdsolve ! timer for solver - - ! Flag for active core map - logical :: cmfd_coremap = .false. - - ! Flag to reset dhats to zero - logical :: dhat_reset = .false. - - ! Flag to activate neutronic feedback via source weights - logical :: cmfd_feedback = .false. - - ! User-defined tally information - integer :: n_cmfd_meshes = 1 ! # of structured meshes - integer :: n_cmfd_filters = 0 ! # of filters - integer :: n_cmfd_tallies = 3 ! # of user-defined tallies - - ! Adjoint method type - character(len=10) :: cmfd_adjoint_type = 'physical' - - ! Number of incomplete ilu factorization levels - integer :: cmfd_ilu_levels = 1 - - ! Batch to begin cmfd - integer :: cmfd_begin = 1 - - ! Tally reset list - integer :: n_cmfd_resets - type(SetInt) :: cmfd_reset - - ! Compute effective downscatter cross section - logical :: cmfd_downscatter = .false. - - ! Convergence monitoring - logical :: cmfd_power_monitor = .false. - - ! Cmfd output - logical :: cmfd_write_matrices = .false. - - ! Run an adjoint calculation (last batch only) - logical :: cmfd_run_adjoint = .false. - - ! CMFD run logicals - logical :: cmfd_on = .false. - - ! CMFD display info - character(len=25) :: cmfd_display = 'balance' - - ! Estimate of spectral radius of CMFD matrices and tolerances - real(8) :: cmfd_spectral = ZERO - real(8) :: cmfd_shift = 1.e6 - real(8) :: cmfd_ktol = 1.e-8_8 - real(8) :: cmfd_stol = 1.e-8_8 - real(8) :: cmfd_atoli = 1.e-10_8 - real(8) :: cmfd_rtoli = 1.e-5_8 - - ! Information about state points to be written - integer :: n_state_points = 0 - type(SetInt) :: statepoint_batch - - ! Information about source points to be written - integer :: n_source_points = 0 - type(SetInt) :: sourcepoint_batch - - ! Various output options - logical :: output_summary = .true. - logical :: output_tallies = .true. - - ! ============================================================================ - ! RESONANCE SCATTERING VARIABLES - - logical :: res_scat_on = .false. ! is resonance scattering treated? - integer :: res_scat_method = RES_SCAT_ARES ! resonance scattering method - real(8) :: res_scat_energy_min = 0.01_8 - real(8) :: res_scat_energy_max = 1000.0_8 - character(10), allocatable :: res_scat_nuclides(:) - -!$omp threadprivate(micro_xs, micro_photon_xs, material_xs, fission_bank, & -!$omp& n_bank, trace, thread_id, current_work, filter_matches) - -contains - -!=============================================================================== -! FREE_MEMORY deallocates and clears all global allocatable arrays in the -! program -!=============================================================================== - - subroutine free_memory() - - integer :: i ! Loop Index - - ! Deallocate cells, surfaces, materials - if (allocated(cells)) deallocate(cells) - if (allocated(universes)) deallocate(universes) - if (allocated(lattices)) deallocate(lattices) - if (allocated(surfaces)) deallocate(surfaces) - if (allocated(materials)) deallocate(materials) - if (allocated(plots)) deallocate(plots) - - ! Deallocate geometry debugging information - if (allocated(overlap_check_cnt)) deallocate(overlap_check_cnt) - - ! Deallocate cross section data, listings, and cache - if (allocated(nuclides)) then - ! First call the clear routines - do i = 1, size(nuclides) - call nuclides(i) % clear() - end do - deallocate(nuclides) - end if - if (allocated(elements)) deallocate(elements) - - if (allocated(res_scat_nuclides)) deallocate(res_scat_nuclides) - - if (allocated(nuclides_MG)) deallocate(nuclides_MG) - - if (allocated(macro_xs)) deallocate(macro_xs) - - if (allocated(sab_tables)) deallocate(sab_tables) - if (allocated(micro_xs)) deallocate(micro_xs) - - ! Deallocate external source - if (allocated(external_source)) deallocate(external_source) - - ! Deallocate k and entropy - if (allocated(k_generation)) deallocate(k_generation) - if (allocated(entropy)) deallocate(entropy) - if (allocated(entropy_p)) deallocate(entropy_p) - - ! Deallocate tally-related arrays - if (allocated(global_tallies)) deallocate(global_tallies) - if (allocated(meshes)) deallocate(meshes) - if (allocated(filters)) deallocate(filters) - if (allocated(tallies)) deallocate(tallies) - if (allocated(filter_matches)) deallocate(filter_matches) - - ! Deallocate fission and source bank and entropy -!$omp parallel - if (allocated(fission_bank)) deallocate(fission_bank) -!$omp end parallel -#ifdef _OPENMP - if (allocated(master_fission_bank)) deallocate(master_fission_bank) -#endif - if (allocated(source_bank)) deallocate(source_bank) - if (allocated(entropy_p)) deallocate(entropy_p) - - ! Deallocate array of work indices - if (allocated(work_index)) deallocate(work_index) - - ! Deallocate CMFD - call deallocate_cmfd(cmfd) - - ! Deallocate tally node lists - call active_analog_tallies % clear() - call active_tracklength_tallies % clear() - call active_current_tallies % clear() - call active_collision_tallies % clear() - call active_tallies % clear() - - ! Deallocate track_identifiers - if (allocated(track_identifiers)) deallocate(track_identifiers) - - ! Deallocate dictionaries - call cell_dict % clear() - call universe_dict % clear() - call lattice_dict % clear() - call surface_dict % clear() - call material_dict % clear() - call mesh_dict % clear() - call filter_dict % clear() - call tally_dict % clear() - call plot_dict % clear() - call nuclide_dict % clear() - call sab_dict % clear() - - ! Clear statepoint and sourcepoint batch set - call statepoint_batch % clear() - call sourcepoint_batch % clear() - - ! Deallocate entropy mesh - if (associated(entropy_mesh)) then - if (allocated(entropy_mesh % lower_left)) & - deallocate(entropy_mesh % lower_left) - if (allocated(entropy_mesh % upper_right)) & - deallocate(entropy_mesh % upper_right) - if (allocated(entropy_mesh % width)) deallocate(entropy_mesh % width) - deallocate(entropy_mesh) - end if - - ! Deallocate ufs - if (allocated(source_frac)) deallocate(source_frac) - if (associated(ufs_mesh)) then - if (allocated(ufs_mesh % lower_left)) deallocate(ufs_mesh % lower_left) - if (allocated(ufs_mesh % upper_right)) & - deallocate(ufs_mesh % upper_right) - if (allocated(ufs_mesh % width)) deallocate(ufs_mesh % width) - deallocate(ufs_mesh) - end if - - end subroutine free_memory - -!=============================================================================== -! OVERALL_GENERATION determines the overall generation number -!=============================================================================== - - pure function overall_generation() result(gen) - integer :: gen - gen = gen_per_batch*(current_batch - 1) + current_gen - end function overall_generation - -end module global diff --git a/src/initialize.F90 b/src/initialize.F90 index 7031da6b4..4bd6b3a38 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -9,30 +9,26 @@ module initialize use bank_header, only: Bank use constants - use dict_header, only: DictIntInt, ElemKeyValueII use set_header, only: SetInt use error, only: fatal_error, warning - use geometry, only: neighbor_lists, count_instance, calc_offsets, & - maximum_levels use geometry_header, only: Cell, Universe, Lattice, RectLattice, HexLattice,& root_universe - use global use hdf5_interface, only: file_open, read_attribute, file_close, & hdf5_bank_t, hdf5_integer8_t - use input_xml, only: read_input_xml, read_plots_xml + use input_xml, only: read_input_xml use material_header, only: Material use message_passing use mgxs_data, only: read_mgxs, create_macro_xs - use output, only: print_version, write_message, print_usage, & - print_plot - use random_lcg, only: initialize_prng - use state_point, only: load_state_point + use output, only: print_version, write_message, print_usage + use random_lcg, only: openmc_set_seed, seed + use settings +#ifdef _OPENMP + use simulation_header, only: n_threads +#endif use string, only: to_str, starts_with, ends_with, str_to_int - use summary, only: write_summary use tally_header, only: TallyObject - use tally_initialize,only: configure_tallies use tally_filter - use tally, only: init_tally_routines + use timer_header implicit none @@ -48,6 +44,8 @@ contains subroutine openmc_init(intracomm) bind(C) integer, intent(in), optional :: intracomm ! MPI intracommunicator + integer :: err + ! Copy the communicator to a new variable. This is done to avoid changing ! the signature of this subroutine. If MPI is being used but no communicator ! was passed, assume MPI_COMM_WORLD. @@ -84,75 +82,16 @@ contains ! Read command line arguments call read_command_line() + ! Initialize random number generator -- if the user specifies a seed, it + ! will be re-initialized later + err = openmc_set_seed(seed) + ! Read XML input files call read_input_xml() - ! Initialize random number generator -- this has to be done after the input - ! files have been read in case the user specified a seed for the random - ! number generator - - call initialize_prng() - - ! Read plots.xml if it exists -- this has to be done separate from the other - ! XML files because we need the PRNG to be initialized first - if (run_mode == MODE_PLOTTING) call read_plots_xml() - - ! Use dictionaries to redefine index pointers - call adjust_indices() - - ! Initialize distribcell_filters - call prepare_distribcell() - - ! After reading input and basic geometry setup is complete, build lists of - ! neighboring cells for efficient tracking - call neighbor_lists() - - ! Check to make sure there are not too many nested coordinate levels in the - ! geometry since the coordinate list is statically allocated for performance - ! reasons - if (maximum_levels(universes(root_universe)) > MAX_COORD) then - call fatal_error("Too many nested coordinate levels in the geometry. & - &Try increasing the maximum number of coordinate levels by & - &providing the CMake -Dmaxcoord= option.") - end if - - if (run_mode /= MODE_PLOTTING) then - ! Allocate and setup tally stride, filter_matches, and tally maps - call configure_tallies() - - ! Set up tally procedure pointers - call init_tally_routines() - - ! Determine how much work each processor should do - call calculate_work() - - ! Allocate source bank, and for eigenvalue simulations also allocate the - ! fission bank - call allocate_banks() - - ! If this is a restart run, load the state point data and binary source - ! file - if (restart_run) call load_state_point() - end if - - if (master) then - if (run_mode == MODE_PLOTTING) then - ! Display plotting information - if (verbosity >= 5) call print_plot() - else - ! Write summary information - if (output_summary) call write_summary() - end if - end if - ! Check for particle restart run if (particle_restart_run) run_mode = MODE_PARTICLE - ! Warn if overlap checking is on - if (master .and. check_overlaps .and. run_mode /= MODE_PLOTTING) then - call warning("Cell overlap checking is ON.") - end if - ! Stop initialization timer call time_initialize%stop() @@ -172,6 +111,7 @@ contains integer, intent(in) :: intracomm ! MPI intracommunicator #endif + integer :: mpi_err ! MPI error code integer :: bank_blocks(6) ! Count for each datatype #ifdef MPIF08 type(MPI_Datatype) :: bank_types(6) @@ -427,450 +367,4 @@ contains end subroutine read_command_line -!=============================================================================== -! ADJUST_INDICES changes the values for 'surfaces' for each cell and the -! material index assigned to each to the indices in the surfaces and material -! array rather than the unique IDs assigned to each surface and material. Also -! assigns boundary conditions to surfaces based on those read into the bc_dict -! dictionary -!=============================================================================== - - subroutine adjust_indices() - - integer :: i ! index for various purposes - integer :: j ! index for various purposes - integer :: k ! loop index for lattices - integer :: m ! loop index for lattices - integer :: lid ! lattice IDs - integer :: i_array ! index in surfaces/materials array - integer :: id ! user-specified id - type(Cell), pointer :: c => null() - class(Lattice), pointer :: lat => null() - - do i = 1, n_cells - ! ======================================================================= - ! ADJUST REGION SPECIFICATION FOR EACH CELL - - c => cells(i) - do j = 1, size(c%region) - id = c%region(j) - ! Make sure that only regions are checked. Since OP_UNION is the - ! operator with the lowest integer value, anything below it must denote - ! a half-space - if (id < OP_UNION) then - if (surface_dict%has_key(abs(id))) then - i_array = surface_dict%get_key(abs(id)) - c%region(j) = sign(i_array, id) - else - call fatal_error("Could not find surface " // trim(to_str(abs(id)))& - &// " specified on cell " // trim(to_str(c%id))) - end if - end if - end do - - ! Also adjust the indices in the reverse Polish notation - do j = 1, size(c%rpn) - id = c%rpn(j) - ! Again, make sure that only regions are checked - if (id < OP_UNION) then - i_array = surface_dict%get_key(abs(id)) - c%rpn(j) = sign(i_array, id) - end if - end do - - ! ======================================================================= - ! ADJUST UNIVERSE INDEX FOR EACH CELL - - id = c%universe - if (universe_dict%has_key(id)) then - c%universe = universe_dict%get_key(id) - else - call fatal_error("Could not find universe " // trim(to_str(id)) & - &// " specified on cell " // trim(to_str(c%id))) - end if - - ! ======================================================================= - ! ADJUST MATERIAL/FILL POINTERS FOR EACH CELL - - if (c % material(1) == NONE) then - id = c % fill - if (universe_dict % has_key(id)) then - c % type = FILL_UNIVERSE - c % fill = universe_dict % get_key(id) - elseif (lattice_dict % has_key(id)) then - lid = lattice_dict % get_key(id) - c % type = FILL_LATTICE - c % fill = lid - else - call fatal_error("Specified fill " // trim(to_str(id)) // " on cell "& - // trim(to_str(c % id)) // " is neither a universe nor a & - &lattice.") - end if - else - do j = 1, size(c % material) - id = c % material(j) - if (id == MATERIAL_VOID) then - c % type = FILL_MATERIAL - else if (material_dict % has_key(id)) then - c % type = FILL_MATERIAL - c % material(j) = material_dict % get_key(id) - else - call fatal_error("Could not find material " // trim(to_str(id)) & - // " specified on cell " // trim(to_str(c % id))) - end if - end do - end if - end do - - ! ========================================================================== - ! ADJUST UNIVERSE INDICES FOR EACH LATTICE - - do i = 1, n_lattices - lat => lattices(i)%obj - select type (lat) - - type is (RectLattice) - do m = 1, lat%n_cells(3) - do k = 1, lat%n_cells(2) - do j = 1, lat%n_cells(1) - id = lat%universes(j,k,m) - if (universe_dict%has_key(id)) then - lat%universes(j,k,m) = universe_dict%get_key(id) - else - call fatal_error("Invalid universe number " & - &// trim(to_str(id)) // " specified on lattice " & - &// trim(to_str(lat%id))) - end if - end do - end do - end do - - type is (HexLattice) - do m = 1, lat%n_axial - do k = 1, 2*lat%n_rings - 1 - do j = 1, 2*lat%n_rings - 1 - if (j + k < lat%n_rings + 1) then - cycle - else if (j + k > 3*lat%n_rings - 1) then - cycle - end if - id = lat%universes(j, k, m) - if (universe_dict%has_key(id)) then - lat%universes(j, k, m) = universe_dict%get_key(id) - else - call fatal_error("Invalid universe number " & - &// trim(to_str(id)) // " specified on lattice " & - &// trim(to_str(lat%id))) - end if - end do - end do - end do - - end select - - if (lat%outer /= NO_OUTER_UNIVERSE) then - if (universe_dict%has_key(lat%outer)) then - lat%outer = universe_dict%get_key(lat%outer) - else - call fatal_error("Invalid universe number " & - &// trim(to_str(lat%outer)) & - &// " specified on lattice " // trim(to_str(lat%id))) - end if - end if - - end do - - ! ======================================================================= - ! ADJUST INDICES FOR EACH TALLY FILTER - - FILTER_LOOP: do i = 1, n_filters - - select type(filt => filters(i) % obj) - type is (SurfaceFilter) - ! Check if this is a surface filter only for surface currents - if (.not. filt % current) call filt % initialize() - class default - call filt % initialize() - end select - - end do FILTER_LOOP - - end subroutine adjust_indices - -!=============================================================================== -! CALCULATE_WORK determines how many particles each processor should simulate -!=============================================================================== - - subroutine calculate_work() - - integer :: i ! loop index - integer :: remainder ! Number of processors with one extra particle - integer(8) :: i_bank ! Running count of number of particles - integer(8) :: min_work ! Minimum number of particles on each proc - integer(8) :: work_i ! Number of particles on rank i - - allocate(work_index(0:n_procs)) - - ! Determine minimum amount of particles to simulate on each processor - min_work = n_particles/n_procs - - ! Determine number of processors that have one extra particle - remainder = int(mod(n_particles, int(n_procs,8)), 4) - - i_bank = 0 - work_index(0) = 0 - do i = 0, n_procs - 1 - ! Number of particles for rank i - if (i < remainder) then - work_i = min_work + 1 - else - work_i = min_work - end if - - ! Set number of particles - if (rank == i) work = work_i - - ! Set index into source bank for rank i - i_bank = i_bank + work_i - work_index(i+1) = i_bank - end do - - end subroutine calculate_work - -!=============================================================================== -! ALLOCATE_BANKS allocates memory for the fission and source banks -!=============================================================================== - - subroutine allocate_banks() - - integer :: alloc_err ! allocation error code - - ! Allocate source bank - allocate(source_bank(work), STAT=alloc_err) - - ! Check for allocation errors - if (alloc_err /= 0) then - call fatal_error("Failed to allocate source bank.") - end if - - if (run_mode == MODE_EIGENVALUE) then -#ifdef _OPENMP - ! If OpenMP is being used, each thread needs its own private fission - ! bank. Since the private fission banks need to be combined at the end of - ! a generation, there is also a 'master_fission_bank' that is used to - ! collect the sites from each thread. - - n_threads = omp_get_max_threads() - -!$omp parallel - thread_id = omp_get_thread_num() - - if (thread_id == 0) then - allocate(fission_bank(3*work)) - else - allocate(fission_bank(3*work/n_threads)) - end if -!$omp end parallel - allocate(master_fission_bank(3*work), STAT=alloc_err) -#else - allocate(fission_bank(3*work), STAT=alloc_err) -#endif - - ! Check for allocation errors - if (alloc_err /= 0) then - call fatal_error("Failed to allocate fission bank.") - end if - end if - - end subroutine allocate_banks - -!=============================================================================== -! PREPARE_DISTRIBCELL initializes any distribcell filters present and sets the -! offsets for distribcells -!=============================================================================== - - subroutine prepare_distribcell() - - integer :: i, j ! Tally, filter loop counters - logical :: distribcell_active ! Does simulation use distribcell? - integer, allocatable :: univ_list(:) ! Target offsets - integer, allocatable :: counts(:,:) ! Target count - logical, allocatable :: found(:,:) ! Target found - - ! Assume distribcell is not needed until proven otherwise. - distribcell_active = .false. - - ! We need distribcell if any tallies have distribcell filters. - do i = 1, n_tallies - do j = 1, size(tallies(i) % filter) - select type(filt => filters(tallies(i) % filter(j)) % obj) - type is (DistribcellFilter) - distribcell_active = .true. - end select - end do - end do - - ! We also need distribcell if any distributed materials or distributed - ! temperatues are present. - if (.not. distribcell_active) then - do i = 1, n_cells - if (size(cells(i) % material) > 1 .or. size(cells(i) % sqrtkT) > 1) then - distribcell_active = .true. - exit - end if - end do - end if - - ! If distribcell isn't used in this simulation then no more work left to do. - if (.not. distribcell_active) return - - ! Count the number of instances of each cell. - call count_instance(universes(root_universe)) - - ! Set the number of bins in all distribcell filters. - do i = 1, n_tallies - do j = 1, size(tallies(i) % filter) - select type(filt => filters(tallies(i) % filter(j)) % obj) - type is (DistribcellFilter) - ! Set the number of bins to the number of instances of the cell. - filt % n_bins = cells(filt % cell) % instances - end select - end do - end do - - ! Make sure the number of materials and temperatures matches the number of - ! cell instances. - do i = 1, n_cells - associate (c => cells(i)) - if (size(c % material) > 1) then - if (size(c % material) /= c % instances) then - call fatal_error("Cell " // trim(to_str(c % id)) // " was & - &specified with " // trim(to_str(size(c % material))) & - // " materials but has " // trim(to_str(c % instances)) & - // " distributed instances. The number of materials must & - &equal one or the number of instances.") - end if - end if - if (size(c % sqrtkT) > 1) then - if (size(c % sqrtkT) /= c % instances) then - call fatal_error("Cell " // trim(to_str(c % id)) // " was & - &specified with " // trim(to_str(size(c % sqrtkT))) & - // " temperatures but has " // trim(to_str(c % instances)) & - // " distributed instances. The number of temperatures must & - &equal one or the number of instances.") - end if - end if - end associate - end do - - ! Allocate offset maps at each level in the geometry - call allocate_offsets(univ_list, counts, found) - - ! Calculate offsets for each target distribcell - do i = 1, n_maps - do j = 1, n_universes - call calc_offsets(univ_list(i), i, universes(j), counts, found) - end do - end do - - end subroutine prepare_distribcell - -!=============================================================================== -! ALLOCATE_OFFSETS determines the number of maps needed and allocates required -! memory for distribcell offset tables -!=============================================================================== - - recursive subroutine allocate_offsets(univ_list, counts, found) - - integer, intent(out), allocatable :: univ_list(:) ! Target offsets - integer, intent(out), allocatable :: counts(:,:) ! Target count - logical, intent(out), allocatable :: found(:,:) ! Target found - - integer :: i, j, k ! Loop counters - type(SetInt) :: cell_list ! distribells to track - - ! Begin gathering list of cells in distribcell tallies - n_maps = 0 - - ! List all cells referenced in distribcell filters. - do i = 1, n_tallies - do j = 1, size(tallies(i) % filter) - select type(filt => filters(tallies(i) % filter(j)) % obj) - type is (DistribcellFilter) - call cell_list % add(filt % cell) - end select - end do - end do - - ! List all cells with multiple (distributed) materials or temperatures. - do i = 1, n_cells - if (size(cells(i) % material) > 1 .or. size(cells(i) % sqrtkT) > 1) then - call cell_list % add(i) - end if - end do - - ! Compute the number of unique universes containing these distribcells - ! to determine the number of offset tables to allocate - do i = 1, n_universes - do j = 1, size(universes(i) % cells) - if (cell_list % contains(universes(i) % cells(j))) then - n_maps = n_maps + 1 - end if - end do - end do - - ! Allocate the list of offset tables for each unique universe - allocate(univ_list(n_maps)) - - ! Allocate list to accumulate target distribcell counts in each universe - allocate(counts(n_universes, n_maps)) - counts(:,:) = 0 - - ! Allocate list to track if target distribcells are found in each universe - allocate(found(n_universes, n_maps)) - found(:,:) = .false. - - - ! Search through universes for distributed cells and assign each one a - ! unique distribcell array index. - k = 1 - do i = 1, n_universes - do j = 1, size(universes(i) % cells) - if (cell_list % contains(universes(i) % cells(j))) then - cells(universes(i) % cells(j)) % distribcell_index = k - univ_list(k) = universes(i) % id - k = k + 1 - end if - end do - end do - - ! Allocate the offset tables for lattices - do i = 1, n_lattices - associate(lat => lattices(i) % obj) - select type(lat) - - type is (RectLattice) - allocate(lat % offset(n_maps, lat % n_cells(1), lat % n_cells(2), & - lat % n_cells(3))) - type is (HexLattice) - allocate(lat % offset(n_maps, 2 * lat % n_rings - 1, & - 2 * lat % n_rings - 1, lat % n_axial)) - end select - - lat % offset(:, :, :, :) = 0 - end associate - end do - - ! Allocate offset table for fill cells - do i = 1, n_cells - if (cells(i) % type /= FILL_MATERIAL) then - allocate(cells(i) % offset(n_maps)) - end if - end do - - ! Free up memory - call cell_list % clear() - - end subroutine allocate_offsets - end module initialize diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 84c70b87b..0a3d9769a 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1,35 +1,49 @@ module input_xml + use, intrinsic :: ISO_C_BINDING + use algorithm, only: find use cmfd_input, only: configure_cmfd + use cmfd_header, only: cmfd_mesh use constants - use dict_header, only: DictIntInt, DictCharInt, ElemKeyValueCI + use dict_header, only: DictIntInt, DictCharInt, DictEntryCI use distribution_multivariate use distribution_univariate use endf, only: reaction_name use error, only: fatal_error, warning - use geometry_header, only: Cell, Lattice, RectLattice, HexLattice, & - get_temperatures, root_universe - use global + use geometry, only: calc_offsets, maximum_levels, count_instance, & + neighbor_lists + use geometry_header use hdf5_interface use list_header, only: ListChar, ListInt, ListReal - use mesh_header, only: RegularMesh + use material_header + use mesh_header use message_passing use mgxs_data, only: create_macro_xs, read_mgxs + use mgxs_header use multipole, only: multipole_read - use output, only: write_message, title, header + use nuclide_header + use output, only: write_message, title, header, print_plot + use photon_header use plot_header - use random_lcg, only: prn, seed + use random_lcg, only: prn, openmc_set_seed use surface_header use set_header, only: SetChar + use settings + use source_header use stl_vector, only: VectorInt, VectorReal, VectorChar use string, only: to_lower, to_str, str_to_int, str_to_real, & starts_with, ends_with, tokenize, split_string, & - zero_padded - use tally_header, only: TallyObject - use tally_filter_header, only: TallyFilterContainer + zero_padded, to_c_string + use summary, only: write_summary + use tally + use tally_header, only: openmc_extend_tallies + use tally_derivative_header + use tally_filter_header use tally_filter - use tally_initialize, only: add_tallies + use timer_header, only: time_read_xs + use trigger_header + use volume_header use xml_interface implicit none @@ -44,25 +58,87 @@ contains subroutine read_input_xml() - call read_settings_xml() - call read_geometry_xml() - call read_materials() - call read_tallies_xml() - if (cmfd_run) call configure_cmfd() + type(VectorReal), allocatable :: nuc_temps(:) ! List of T to read for each nuclide + type(VectorReal), allocatable :: sab_temps(:) ! List of T to read for each S(a,b) + real(8), allocatable :: material_temps(:) - if (.not. run_CE) then - ! Create material macroscopic data for MGXS + call read_settings_xml() + call read_cross_sections_xml() + call read_materials_xml(material_temps) + call read_geometry_xml() + + ! Set up neighbor lists, convert user IDs -> indices, assign temperatures + call finalize_geometry(material_temps, nuc_temps, sab_temps) + + if (run_mode /= MODE_PLOTTING) then call time_read_xs % start() - call read_mgxs() - call create_macro_xs() + if (run_CE) then + ! Read continuous-energy cross sections + call read_ce_cross_sections(nuc_temps, sab_temps) + else + ! Create material macroscopic data for MGXS + call read_mgxs() + call create_macro_xs() + end if call time_read_xs % stop() end if - ! Normalize atom/weight percents - if (run_mode /= MODE_PLOTTING) call normalize_ao() + call read_tallies_xml() + + ! Initialize distribcell_filters + call prepare_distribcell() + + if (cmfd_run) call configure_cmfd() + + if (run_mode == MODE_PLOTTING) then + ! Read plots.xml if it exists + call read_plots_xml() + if (master .and. verbosity >= 5) call print_plot() + + else + ! Normalize atom/weight percents + call normalize_ao() + + ! Write summary information + if (master .and. output_summary) call write_summary() + + ! Warn if overlap checking is on + if (master .and. check_overlaps) & + call warning("Cell overlap checking is ON.") + end if end subroutine read_input_xml + subroutine finalize_geometry(material_temps, nuc_temps, sab_temps) + real(8), intent(in) :: material_temps(:) + type(VectorReal), allocatable, intent(out) :: nuc_temps(:) + type(VectorReal), optional, allocatable, intent(out) :: sab_temps(:) + + ! Perform some final operations to set up the geometry + call adjust_indices() + call count_instance(universes(root_universe)) + + ! After reading input and basic geometry setup is complete, build lists of + ! neighboring cells for efficient tracking + call neighbor_lists() + + ! Assign temperatures to cells that don't have temperatures already assigned + call assign_temperatures(material_temps) + + ! Determine desired txemperatures for each nuclide and S(a,b) table + call get_temperatures(nuc_temps, sab_temps) + + ! Check to make sure there are not too many nested coordinate levels in the + ! geometry since the coordinate list is statically allocated for performance + ! reasons + if (maximum_levels(universes(root_universe)) > MAX_COORD) then + call fatal_error("Too many nested coordinate levels in the geometry. & + &Try increasing the maximum number of coordinate levels by & + &providing the CMake -Dmaxcoord= option.") + end if + + end subroutine finalize_geometry + !=============================================================================== ! READ_SETTINGS_XML reads data from a settings.xml file and parses it, checking ! for errors and placing properly-formatted data in the right data structures @@ -75,8 +151,10 @@ contains integer :: n integer :: temp_int integer :: temp_int_array3(3) + integer(C_INT32_T) :: i_start, i_end + integer(C_INT64_T) :: seed + integer(C_INT) :: err integer, allocatable :: temp_int_array(:) - real(8), allocatable :: temp_real(:) integer :: n_tracks logical :: file_exists character(MAX_WORD_LEN) :: type @@ -84,10 +162,6 @@ contains type(XMLDocument) :: doc type(XMLNode) :: root type(XMLNode) :: node_mode - type(XMLNode) :: node_source - type(XMLNode) :: node_space - type(XMLNode) :: node_angle - type(XMLNode) :: node_dist type(XMLNode) :: node_cutoff type(XMLNode) :: node_entropy type(XMLNode) :: node_ufs @@ -97,6 +171,7 @@ contains type(XMLNode) :: node_trigger type(XMLNode) :: node_vol type(XMLNode) :: node_tab_leg + type(XMLNode), allocatable :: node_mesh_list(:) type(XMLNode), allocatable :: node_source_list(:) type(XMLNode), allocatable :: node_vol_list(:) @@ -255,7 +330,7 @@ contains call get_run_parameters(node_mode) ! Check number of active batches, inactive batches, and particles - if (n_active <= 0) then + if (n_batches <= n_inactive) then call fatal_error("Number of active batches must be greater than zero.") elseif (n_inactive < 0) then call fatal_error("Number of inactive batches must be non-negative.") @@ -265,7 +340,10 @@ contains end if ! Copy random number seed if specified - if (check_for_node(root, "seed")) call get_node_value(root, "seed", seed) + if (check_for_node(root, "seed")) then + call get_node_value(root, "seed", seed) + err = openmc_set_seed(seed) + end if ! Check for electron treatment if (check_for_node(root, "electron_treatment")) then @@ -350,257 +428,14 @@ contains allocate(external_source(n)) end if + ! Check if we want to write out source + if (check_for_node(root, "write_initial_source")) then + call get_node_value(root, "write_initial_source", write_initial_source) + end if + ! Read each source do i = 1, n - ! Get pointer to source - node_source = node_source_list(i) - - ! Check if we want to write out source - if (check_for_node(node_source, "write_initial")) then - call get_node_value(node_source, "write_initial", write_initial_source) - end if - - ! Check for particle type - if (check_for_node(node_source, "particle")) then - call get_node_value(node_source, "particle", temp_str) - select case (to_lower(temp_str)) - case ('neutron') - external_source(i) % particle = NEUTRON - case ('photon') - external_source(i) % particle = PHOTON - photon_transport = .true. - case default - call fatal_error('Unknown source particle type: ' // trim(temp_str)) - end select - else - external_source(i) % particle = NEUTRON - end if - - ! Check for source strength - if (check_for_node(node_source, "strength")) then - call get_node_value(node_source, "strength", external_source(i)%strength) - else - external_source(i)%strength = ONE - end if - - ! Check for external source file - if (check_for_node(node_source, "file")) then - ! Copy path of source file - call get_node_value(node_source, "file", path_source) - - ! Check if source file exists - inquire(FILE=path_source, EXIST=file_exists) - if (.not. file_exists) then - call fatal_error("Source file '" // trim(path_source) & - // "' does not exist!") - end if - - else - - ! Spatial distribution for external source - if (check_for_node(node_source, "space")) then - - ! Get pointer to spatial distribution - node_space = node_source % child("space") - - ! Check for type of spatial distribution - type = '' - if (check_for_node(node_space, "type")) & - call get_node_value(node_space, "type", type) - select case (to_lower(type)) - case ('cartesian') - allocate(CartesianIndependent :: external_source(i)%space) - - case ('box') - allocate(SpatialBox :: external_source(i)%space) - - case ('fission') - allocate(SpatialBox :: external_source(i)%space) - select type(space => external_source(i)%space) - type is (SpatialBox) - space%only_fissionable = .true. - end select - - case ('point') - allocate(SpatialPoint :: external_source(i)%space) - - case default - call fatal_error("Invalid spatial distribution for external source: "& - // trim(type)) - end select - - select type (space => external_source(i)%space) - type is (CartesianIndependent) - ! Read distribution for x coordinate - if (check_for_node(node_space, "x")) then - node_dist = node_space % child("x") - call distribution_from_xml(space%x, node_dist) - else - allocate(Discrete :: space%x) - select type (dist => space%x) - type is (Discrete) - allocate(dist%x(1), dist%p(1)) - dist%x(1) = ZERO - dist%p(1) = ONE - end select - end if - - ! Read distribution for y coordinate - if (check_for_node(node_space, "y")) then - node_dist = node_space % child("y") - call distribution_from_xml(space%y, node_dist) - else - allocate(Discrete :: space%y) - select type (dist => space%y) - type is (Discrete) - allocate(dist%x(1), dist%p(1)) - dist%x(1) = ZERO - dist%p(1) = ONE - end select - end if - - if (check_for_node(node_space, "z")) then - node_dist = node_space % child("z") - call distribution_from_xml(space%z, node_dist) - else - allocate(Discrete :: space%z) - select type (dist => space%z) - type is (Discrete) - allocate(dist%x(1), dist%p(1)) - dist%x(1) = ZERO - dist%p(1) = ONE - end select - end if - - type is (SpatialBox) - ! Make sure correct number of parameters are given - if (node_word_count(node_space, "parameters") /= 6) then - call fatal_error('Box/fission spatial source must have & - &six parameters specified.') - end if - - ! Read lower-right/upper-left coordinates - allocate(temp_real(6)) - call get_node_array(node_space, "parameters", temp_real) - space%lower_left(:) = temp_real(1:3) - space%upper_right(:) = temp_real(4:6) - deallocate(temp_real) - - type is (SpatialPoint) - ! Make sure correct number of parameters are given - if (node_word_count(node_space, "parameters") /= 3) then - call fatal_error('Point spatial source must have & - &three parameters specified.') - end if - - ! Read location of point source - allocate(temp_real(3)) - call get_node_array(node_space, "parameters", temp_real) - space%xyz(:) = temp_real - deallocate(temp_real) - - end select - - else - ! If no spatial distribution specified, make it a point source - allocate(SpatialPoint :: external_source(i) % space) - select type (space => external_source(i) % space) - type is (SpatialPoint) - space % xyz(:) = [ZERO, ZERO, ZERO] - end select - end if - - ! Determine external source angular distribution - if (check_for_node(node_source, "angle")) then - - ! Get pointer to angular distribution - node_angle = node_source % child("angle") - - ! Check for type of angular distribution - type = '' - if (check_for_node(node_angle, "type")) & - call get_node_value(node_angle, "type", type) - select case (to_lower(type)) - case ('isotropic') - allocate(Isotropic :: external_source(i)%angle) - - case ('monodirectional') - allocate(Monodirectional :: external_source(i)%angle) - - case ('mu-phi') - allocate(PolarAzimuthal :: external_source(i)%angle) - - case default - call fatal_error("Invalid angular distribution for external source: "& - // trim(type)) - end select - - ! Read reference directional unit vector - if (check_for_node(node_angle, "reference_uvw")) then - n = node_word_count(node_angle, "reference_uvw") - if (n /= 3) then - call fatal_error('Angular distribution reference direction must have & - &three parameters specified.') - end if - call get_node_array(node_angle, "reference_uvw", & - external_source(i)%angle%reference_uvw) - else - ! By default, set reference unit vector to be positive z-direction - external_source(i)%angle%reference_uvw(:) = [ZERO, ZERO, ONE] - end if - - ! Read parameters for angle distribution - select type (angle => external_source(i)%angle) - type is (Monodirectional) - call get_node_array(node_angle, "reference_uvw", & - external_source(i)%angle%reference_uvw) - - type is (PolarAzimuthal) - if (check_for_node(node_angle, "mu")) then - node_dist = node_angle % child("mu") - call distribution_from_xml(angle%mu, node_dist) - else - allocate(Uniform :: angle%mu) - select type (mu => angle%mu) - type is (Uniform) - mu%a = -ONE - mu%b = ONE - end select - end if - - if (check_for_node(node_angle, "phi")) then - node_dist = node_angle % child("phi") - call distribution_from_xml(angle%phi, node_dist) - else - allocate(Uniform :: angle%phi) - select type (phi => angle%phi) - type is (Uniform) - phi%a = ZERO - phi%b = TWO*PI - end select - end if - end select - - else - ! Set default angular distribution isotropic - allocate(Isotropic :: external_source(i)%angle) - external_source(i)%angle%reference_uvw(:) = [ZERO, ZERO, ONE] - end if - - ! Determine external source energy distribution - if (check_for_node(node_source, "energy")) then - node_dist = node_source % child("energy") - call distribution_from_xml(external_source(i)%energy, node_dist) - else - ! Default to a Watt spectrum with parameters 0.988 MeV and 2.249 MeV^-1 - allocate(Watt :: external_source(i)%energy) - select type(energy => external_source(i)%energy) - type is (Watt) - energy%a = 0.988e6_8 - energy%b = 2.249e-6_8 - end select - end if - end if + call external_source(i) % from_xml(node_source_list(i), path_source) end do ! Survival biasing @@ -667,121 +502,108 @@ contains track_identifiers = reshape(temp_int_array, [3, n_tracks/3]) end if + ! Read meshes + call get_node_list(root, "mesh", node_mesh_list) + + ! Check for user meshes and allocate + n = size(node_mesh_list) + if (n > 0) then + err = openmc_extend_meshes(n, i_start, i_end) + end if + + do i = 1, n + associate (m => meshes(i_start + i - 1)) + ! Instantiate mesh from XML node + call m % from_xml(node_mesh_list(i)) + + ! Add mesh to dictionary + call mesh_dict % set(m % id, i_start + i - 1) + end associate + end do + ! Shannon Entropy mesh - if (check_for_node(root, "entropy")) then + if (check_for_node(root, "entropy_mesh")) then + call get_node_value(root, "entropy_mesh", temp_int) + if (mesh_dict % has(temp_int)) then + index_entropy_mesh = mesh_dict % get(temp_int) + else + call fatal_error("Mesh " // to_str(temp_int) // " specified for & + &Shannon entropy does not exist.") + end if + elseif (check_for_node(root, "entropy")) then + call warning("Specifying a Shannon entropy mesh via the element & + &is deprecated. Please create a mesh using and then reference & + &it by specifying its ID in an element.") ! Get pointer to entropy node node_entropy = root % child("entropy") - ! Check to make sure enough values were supplied - if (node_word_count(node_entropy, "lower_left") /= 3) then - call fatal_error("Need to specify (x,y,z) coordinates of lower-left & - &corner of Shannon entropy mesh.") - elseif (node_word_count(node_entropy, "upper_right") /= 3) then - call fatal_error("Need to specify (x,y,z) coordinates of upper-right & - &corner of Shannon entropy mesh.") - end if + err = openmc_extend_meshes(1, index_entropy_mesh) - ! Allocate mesh object and coordinates on mesh - allocate(entropy_mesh) - allocate(entropy_mesh % lower_left(3)) - allocate(entropy_mesh % upper_right(3)) - allocate(entropy_mesh % width(3)) + associate (m => meshes(index_entropy_mesh)) + ! Assign ID + m % id = 10000 - ! Copy values - call get_node_array(node_entropy, "lower_left", & - entropy_mesh % lower_left) - call get_node_array(node_entropy, "upper_right", & - entropy_mesh % upper_right) + call m % from_xml(node_entropy) + end associate + end if - ! Check on values provided - if (.not. all(entropy_mesh % upper_right > entropy_mesh % lower_left)) & - &then - call fatal_error("Upper-right coordinate must be greater than & - &lower-left coordinate for Shannon entropy mesh.") - end if + if (index_entropy_mesh > 0) then + associate(m => meshes(index_entropy_mesh)) + if (.not. allocated(m % dimension)) then + ! If the user did not specify how many mesh cells are to be used in + ! each direction, we automatically determine an appropriate number of + ! cells + m % n_dimension = 3 + allocate(m % dimension(3)) + m % dimension = ceiling((n_particles/20)**(ONE/THREE)) - ! Check if dimensions were specified -- if not, they will be calculated - ! automatically upon first entry into shannon_entropy - if (check_for_node(node_entropy, "dimension")) then - - ! If so, make sure proper number of values were given - if (node_word_count(node_entropy, "dimension") /= 3) then - call fatal_error("Dimension of entropy mesh must be given as three & - &integers.") + ! Calculate width + m % width = (m % upper_right - m % lower_left) / m % dimension end if - ! Allocate dimensions - entropy_mesh % n_dimension = 3 - allocate(entropy_mesh % dimension(3)) - - ! Copy dimensions - call get_node_array(node_entropy, "dimension", entropy_mesh % dimension) - - ! Calculate width - entropy_mesh % width = (entropy_mesh % upper_right - & - entropy_mesh % lower_left) / entropy_mesh % dimension - - end if + ! Allocate space for storing number of fission sites in each mesh cell + allocate(entropy_p(1, product(m % dimension))) + end associate ! Turn on Shannon entropy calculation entropy_on = .true. end if ! Uniform fission source weighting mesh - if (check_for_node(root, "uniform_fs")) then + if (check_for_node(root, "ufs_mesh")) then + call get_node_value(root, "ufs_mesh", temp_int) + if (mesh_dict % has(temp_int)) then + index_ufs_mesh = mesh_dict % get(temp_int) + else + call fatal_error("Mesh " // to_str(temp_int) // " specified for & + &uniform fission site method does not exist.") + end if + elseif (check_for_node(root, "uniform_fs")) then + call warning("Specifying a UFS mesh via the element & + &is deprecated. Please create a mesh using and then reference & + &it by specifying its ID in a element.") ! Get pointer to ufs node node_ufs = root % child("uniform_fs") - ! Check to make sure enough values were supplied - if (node_word_count(node_ufs, "lower_left") /= 3) then - call fatal_error("Need to specify (x,y,z) coordinates of lower-left & - &corner of UFS mesh.") - elseif (node_word_count(node_ufs, "upper_right") /= 3) then - call fatal_error("Need to specify (x,y,z) coordinates of upper-right & - &corner of UFS mesh.") - elseif (node_word_count(node_ufs, "dimension") /= 3) then - call fatal_error("Dimension of UFS mesh must be given as three & - &integers.") - end if + err = openmc_extend_meshes(1, index_ufs_mesh) ! Allocate mesh object and coordinates on mesh - allocate(ufs_mesh) - allocate(ufs_mesh % lower_left(3)) - allocate(ufs_mesh % upper_right(3)) - allocate(ufs_mesh % width(3)) + associate (m => meshes(index_ufs_mesh)) + ! Assign ID + m % id = 10001 - ! Allocate dimensions - ufs_mesh % n_dimension = 3 - allocate(ufs_mesh % dimension(3)) + call m % from_xml(node_ufs) + end associate + end if - ! Copy dimensions - call get_node_array(node_ufs, "dimension", ufs_mesh % dimension) - - ! Copy values - call get_node_array(node_ufs, "lower_left", ufs_mesh % lower_left) - call get_node_array(node_ufs, "upper_right", ufs_mesh % upper_right) - - ! Check on values provided - if (.not. all(ufs_mesh % upper_right > ufs_mesh % lower_left)) then - call fatal_error("Upper-right coordinate must be greater than & - &lower-left coordinate for UFS mesh.") - end if - - ! Calculate width - ufs_mesh % width = (ufs_mesh % upper_right - & - ufs_mesh % lower_left) / ufs_mesh % dimension - - ! Calculate volume fraction of each cell - ufs_mesh % volume_frac = ONE/real(product(ufs_mesh % dimension),8) + if (index_ufs_mesh > 0) then + ! Allocate array to store source fraction for UFS + allocate(source_frac(1, product(meshes(index_ufs_mesh) % dimension))) ! Turn on uniform fission source weighting ufs = .true. - - ! Allocate source_frac - allocate(source_frac(1, ufs_mesh % dimension(1), & - ufs_mesh % dimension(2), ufs_mesh % dimension(3))) end if ! Check if the user has specified to write state points @@ -1115,9 +937,6 @@ contains end if end if - ! Determine number of active batches - n_active = n_batches - n_inactive - end subroutine get_run_parameters !=============================================================================== @@ -1129,6 +948,7 @@ contains integer :: i, j, k, m, i_x, i_a, input_index integer :: n, n_mats, n_x, n_y, n_z, n_rings, n_rlats, n_hlats + integer :: id integer :: univ_id integer :: n_cells_in_univ integer :: coeffs_reqd @@ -1162,13 +982,9 @@ contains type(DictIntInt) :: cells_in_univ_dict ! Used to count how many cells each ! universe contains - ! Display output message call write_message("Reading geometry XML file...", 5) - ! ========================================================================== - ! READ CELLS FROM GEOMETRY.XML - ! Check if geometry.xml exists filename = trim(path_input) // "geometry.xml" inquire(FILE=filename, EXIST=file_exists) @@ -1181,265 +997,6 @@ contains call doc % load_file(filename) root = doc % document_element() - ! Get pointer to list of XML - call get_node_list(root, "cell", node_cell_list) - - ! Get number of tags - n_cells = size(node_cell_list) - - ! Check for no cells - if (n_cells == 0) then - call fatal_error("No cells found in geometry.xml!") - end if - - ! Allocate cells array - allocate(cells(n_cells)) - - if (check_overlaps) then - allocate(overlap_check_cnt(n_cells)) - overlap_check_cnt = 0 - end if - - n_universes = 0 - do i = 1, n_cells - c => cells(i) - - ! Initialize distribcell instances and distribcell index - c % instances = 0 - c % distribcell_index = NONE - - ! Get pointer to i-th cell node - node_cell = node_cell_list(i) - - ! Copy data into cells - if (check_for_node(node_cell, "id")) then - call get_node_value(node_cell, "id", c % id) - else - call fatal_error("Must specify id of cell in geometry XML file.") - end if - - ! Copy cell name - if (check_for_node(node_cell, "name")) then - call get_node_value(node_cell, "name", c % name) - end if - - if (check_for_node(node_cell, "universe")) then - call get_node_value(node_cell, "universe", c % universe) - else - c % universe = 0 - end if - if (check_for_node(node_cell, "fill")) then - call get_node_value(node_cell, "fill", c % fill) - if (find(fill_univ_ids, c % fill) == -1) & - call fill_univ_ids % push_back(c % fill) - else - c % fill = NONE - end if - - ! Check to make sure 'id' hasn't been used - if (cell_dict % has_key(c % id)) then - call fatal_error("Two or more cells use the same unique ID: " & - // to_str(c % id)) - end if - - ! Read material - if (check_for_node(node_cell, "material")) then - n_mats = node_word_count(node_cell, "material") - - if (n_mats > 0) then - allocate(sarray(n_mats)) - call get_node_array(node_cell, "material", sarray) - - allocate(c % material(n_mats)) - do j = 1, n_mats - select case(trim(to_lower(sarray(j)))) - case ('void') - c % material(j) = MATERIAL_VOID - case default - c % material(j) = int(str_to_int(sarray(j)), 4) - - ! Check for error - if (c % material(j) == ERROR_INT) then - call fatal_error("Invalid material specified on cell " & - // to_str(c % id)) - end if - end select - end do - - deallocate(sarray) - - else - allocate(c % material(1)) - c % material(1) = NONE - end if - - else - allocate(c % material(1)) - c % material(1) = NONE - end if - - ! Check to make sure that either material or fill was specified - if (c % material(1) == NONE .and. c % fill == NONE) then - call fatal_error("Neither material nor fill was specified for cell " & - // trim(to_str(c % id))) - end if - - ! Check to make sure that both material and fill haven't been - ! specified simultaneously - if (c % material(1) /= NONE .and. c % fill /= NONE) then - call fatal_error("Cannot specify material and fill simultaneously") - end if - - ! Check for region specification (also under deprecated name surfaces) - if (check_for_node(node_cell, "surfaces")) then - call warning("The use of 'surfaces' is deprecated and will be & - &disallowed in a future release. Use 'region' instead. The & - &openmc-update-inputs utility can be used to automatically & - &update geometry.xml files.") - region_spec = node_value_string(node_cell, "surfaces") - call get_node_value(node_cell, "surfaces", region_spec) - elseif (check_for_node(node_cell, "region")) then - region_spec = node_value_string(node_cell, "region") - else - region_spec = '' - end if - - if (len_trim(region_spec) > 0) then - ! Create surfaces array from string - call tokenize(region_spec, tokens) - - ! Use shunting-yard algorithm to determine RPN for surface algorithm - call generate_rpn(c%id, tokens, rpn) - - ! Copy region spec and RPN form to cell arrays - allocate(c % region(tokens%size())) - allocate(c % rpn(rpn%size())) - c % region(:) = tokens%data(1:tokens%size()) - c % rpn(:) = rpn%data(1:rpn%size()) - - call tokens%clear() - call rpn%clear() - end if - if (.not. allocated(c%region)) allocate(c%region(0)) - if (.not. allocated(c%rpn)) allocate(c%rpn(0)) - - ! Check if this is a simple cell - if (any(c%rpn == OP_COMPLEMENT) .or. any(c%rpn == OP_UNION)) then - c%simple = .false. - else - c%simple = .true. - end if - - ! Rotation matrix - if (check_for_node(node_cell, "rotation")) then - ! Rotations can only be applied to cells that are being filled with - ! another universe - if (c % fill == NONE) then - call fatal_error("Cannot apply a rotation to cell " // trim(to_str(& - &c % id)) // " because it is not filled with another universe") - end if - - ! Read number of rotation parameters - n = node_word_count(node_cell, "rotation") - if (n /= 3) then - call fatal_error("Incorrect number of rotation parameters on cell " & - // to_str(c % id)) - end if - - ! Copy rotation angles in x,y,z directions - allocate(c % rotation(3)) - call get_node_array(node_cell, "rotation", c % rotation) - phi = -c % rotation(1) * PI/180.0_8 - theta = -c % rotation(2) * PI/180.0_8 - psi = -c % rotation(3) * PI/180.0_8 - - ! Calculate rotation matrix based on angles given - allocate(c % rotation_matrix(3,3)) - c % rotation_matrix = reshape((/ & - cos(theta)*cos(psi), cos(theta)*sin(psi), -sin(theta), & - -cos(phi)*sin(psi) + sin(phi)*sin(theta)*cos(psi), & - cos(phi)*cos(psi) + sin(phi)*sin(theta)*sin(psi), & - sin(phi)*cos(theta), & - sin(phi)*sin(psi) + cos(phi)*sin(theta)*cos(psi), & - -sin(phi)*cos(psi) + cos(phi)*sin(theta)*sin(psi), & - cos(phi)*cos(theta) /), (/ 3,3 /)) - end if - - ! Translation vector - if (check_for_node(node_cell, "translation")) then - ! Translations can only be applied to cells that are being filled with - ! another universe - if (c % fill == NONE) then - call fatal_error("Cannot apply a translation to cell " & - // trim(to_str(c % id)) // " because it is not filled with & - &another universe") - end if - - ! Read number of translation parameters - n = node_word_count(node_cell, "translation") - if (n /= 3) then - call fatal_error("Incorrect number of translation parameters on & - &cell " // to_str(c % id)) - end if - - ! Copy translation vector - allocate(c % translation(3)) - call get_node_array(node_cell, "translation", c % translation) - end if - - ! Read cell temperatures. If the temperature is not specified, set it to - ! ERROR_REAL for now. During initialization we'll replace ERROR_REAL with - ! the temperature from the material data. - if (check_for_node(node_cell, "temperature")) then - n = node_word_count(node_cell, "temperature") - if (n > 0) then - ! Make sure this is a "normal" cell. - if (c % material(1) == NONE) call fatal_error("Cell " & - // trim(to_str(c % id)) // " was specified with a temperature & - &but no material. Temperature specification is only valid for & - &cells filled with a material.") - - ! Copy in temperatures - allocate(c % sqrtkT(n)) - call get_node_array(node_cell, "temperature", c % sqrtkT) - - ! Make sure all temperatues are positive - do j = 1, size(c % sqrtkT) - if (c % sqrtkT(j) < ZERO) call fatal_error("Cell " & - // trim(to_str(c % id)) // " was specified with a negative & - &temperature. All cell temperatures must be non-negative.") - end do - - ! Convert to sqrt(kT) - c % sqrtkT(:) = sqrt(K_BOLTZMANN * c % sqrtkT(:)) - else - allocate(c % sqrtkT(1)) - c % sqrtkT(1) = ERROR_REAL - end if - else - allocate(c % sqrtkT(1)) - c % sqrtkT = ERROR_REAL - end if - - ! Add cell to dictionary - call cell_dict % add_key(c % id, i) - - ! For cells, we also need to check if there's a new universe -- - ! also for every cell add 1 to the count of cells for the - ! specified universe - univ_id = c % universe - if (.not. cells_in_univ_dict % has_key(univ_id)) then - n_universes = n_universes + 1 - n_cells_in_univ = 1 - call universe_dict % add_key(univ_id, n_universes) - call univ_ids % push_back(univ_id) - else - n_cells_in_univ = 1 + cells_in_univ_dict % get_key(univ_id) - end if - call cells_in_univ_dict % add_key(univ_id, n_cells_in_univ) - - end do - ! ========================================================================== ! READ SURFACES FROM GEOMETRY.XML @@ -1527,7 +1084,7 @@ contains end if ! Check to make sure 'id' hasn't been used - if (surface_dict % has_key(s%id)) then + if (surface_dict % has(s%id)) then call fatal_error("Two or more surfaces use the same unique ID: " & // to_str(s%id)) end if @@ -1658,7 +1215,7 @@ contains &"' specified on surface " // trim(to_str(s%id))) end select ! Add surface to dictionary - call surface_dict % add_key(s%id, i) + call surface_dict % set(s%id, i) end do ! Check to make sure a boundary condition was applied to at least one @@ -1684,7 +1241,7 @@ contains &interior surface.") end if else - surf % i_periodic = surface_dict % get_key(surf % i_periodic) + surf % i_periodic = surface_dict % get(surf % i_periodic) end if type is (SurfaceYPlane) @@ -1698,7 +1255,7 @@ contains &interior surface.") end if else - surf % i_periodic = surface_dict % get_key(surf % i_periodic) + surf % i_periodic = surface_dict % get(surf % i_periodic) end if type is (SurfaceZPlane) @@ -1712,7 +1269,7 @@ contains &interior surface.") end if else - surf % i_periodic = surface_dict % get_key(surf % i_periodic) + surf % i_periodic = surface_dict % get(surf % i_periodic) end if type is (SurfacePlane) @@ -1721,7 +1278,7 @@ contains &periodic boundary condition on surface " // & trim(to_str(surf % id)) // ".") else - surf % i_periodic = surface_dict % get_key(surf % i_periodic) + surf % i_periodic = surface_dict % get(surf % i_periodic) end if class default @@ -1739,6 +1296,279 @@ contains end if end do + ! ========================================================================== + ! READ CELLS FROM GEOMETRY.XML + + ! Get pointer to list of XML + call get_node_list(root, "cell", node_cell_list) + + ! Get number of tags + n_cells = size(node_cell_list) + + ! Check for no cells + if (n_cells == 0) then + call fatal_error("No cells found in geometry.xml!") + end if + + ! Allocate cells array + allocate(cells(n_cells)) + + if (check_overlaps) then + allocate(overlap_check_cnt(n_cells)) + overlap_check_cnt = 0 + end if + + n_universes = 0 + do i = 1, n_cells + c => cells(i) + + ! Initialize distribcell instances and distribcell index + c % instances = 0 + c % distribcell_index = NONE + + ! Get pointer to i-th cell node + node_cell = node_cell_list(i) + + ! Copy data into cells + if (check_for_node(node_cell, "id")) then + call get_node_value(node_cell, "id", c % id) + else + call fatal_error("Must specify id of cell in geometry XML file.") + end if + + ! Copy cell name + if (check_for_node(node_cell, "name")) then + call get_node_value(node_cell, "name", c % name) + end if + + if (check_for_node(node_cell, "universe")) then + call get_node_value(node_cell, "universe", c % universe) + else + c % universe = 0 + end if + if (check_for_node(node_cell, "fill")) then + call get_node_value(node_cell, "fill", c % fill) + if (find(fill_univ_ids, c % fill) == -1) & + call fill_univ_ids % push_back(c % fill) + else + c % fill = NONE + end if + + ! Check to make sure 'id' hasn't been used + if (cell_dict % has(c % id)) then + call fatal_error("Two or more cells use the same unique ID: " & + // to_str(c % id)) + end if + + ! Read material + if (check_for_node(node_cell, "material")) then + n_mats = node_word_count(node_cell, "material") + + if (n_mats > 0) then + allocate(sarray(n_mats)) + call get_node_array(node_cell, "material", sarray) + + allocate(c % material(n_mats)) + do j = 1, n_mats + select case(trim(to_lower(sarray(j)))) + case ('void') + c % material(j) = MATERIAL_VOID + case default + c % material(j) = int(str_to_int(sarray(j)), 4) + + ! Check for error + if (c % material(j) == ERROR_INT) then + call fatal_error("Invalid material specified on cell " & + // to_str(c % id)) + end if + end select + end do + + deallocate(sarray) + + else + allocate(c % material(1)) + c % material(1) = NONE + end if + + else + allocate(c % material(1)) + c % material(1) = NONE + end if + + ! Check to make sure that either material or fill was specified + if (c % material(1) == NONE .and. c % fill == NONE) then + call fatal_error("Neither material nor fill was specified for cell " & + // trim(to_str(c % id))) + end if + + ! Check to make sure that both material and fill haven't been + ! specified simultaneously + if (c % material(1) /= NONE .and. c % fill /= NONE) then + call fatal_error("Cannot specify material and fill simultaneously") + end if + + ! Check for region specification (also under deprecated name surfaces) + if (check_for_node(node_cell, "surfaces")) then + call warning("The use of 'surfaces' is deprecated and will be & + &disallowed in a future release. Use 'region' instead. The & + &openmc-update-inputs utility can be used to automatically & + &update geometry.xml files.") + region_spec = node_value_string(node_cell, "surfaces") + call get_node_value(node_cell, "surfaces", region_spec) + elseif (check_for_node(node_cell, "region")) then + region_spec = node_value_string(node_cell, "region") + else + region_spec = '' + end if + + if (len_trim(region_spec) > 0) then + ! Create surfaces array from string + call tokenize(region_spec, tokens) + + ! Convert user IDs to surface indices + do j = 1, tokens % size() + id = tokens % data(j) + if (id < OP_UNION) then + if (surface_dict % has(abs(id))) then + k = surface_dict % get(abs(id)) + tokens % data(j) = sign(k, id) + end if + end if + end do + + ! Use shunting-yard algorithm to determine RPN for surface algorithm + call generate_rpn(c%id, tokens, rpn) + + ! Copy region spec and RPN form to cell arrays + allocate(c % region(tokens%size())) + allocate(c % rpn(rpn%size())) + c % region(:) = tokens%data(1:tokens%size()) + c % rpn(:) = rpn%data(1:rpn%size()) + + call tokens%clear() + call rpn%clear() + end if + if (.not. allocated(c%region)) allocate(c%region(0)) + if (.not. allocated(c%rpn)) allocate(c%rpn(0)) + + ! Check if this is a simple cell + if (any(c%rpn == OP_COMPLEMENT) .or. any(c%rpn == OP_UNION)) then + c%simple = .false. + else + c%simple = .true. + end if + + ! Rotation matrix + if (check_for_node(node_cell, "rotation")) then + ! Rotations can only be applied to cells that are being filled with + ! another universe + if (c % fill == NONE) then + call fatal_error("Cannot apply a rotation to cell " // trim(to_str(& + &c % id)) // " because it is not filled with another universe") + end if + + ! Read number of rotation parameters + n = node_word_count(node_cell, "rotation") + if (n /= 3) then + call fatal_error("Incorrect number of rotation parameters on cell " & + // to_str(c % id)) + end if + + ! Copy rotation angles in x,y,z directions + allocate(c % rotation(3)) + call get_node_array(node_cell, "rotation", c % rotation) + phi = -c % rotation(1) * PI/180.0_8 + theta = -c % rotation(2) * PI/180.0_8 + psi = -c % rotation(3) * PI/180.0_8 + + ! Calculate rotation matrix based on angles given + allocate(c % rotation_matrix(3,3)) + c % rotation_matrix = reshape((/ & + cos(theta)*cos(psi), cos(theta)*sin(psi), -sin(theta), & + -cos(phi)*sin(psi) + sin(phi)*sin(theta)*cos(psi), & + cos(phi)*cos(psi) + sin(phi)*sin(theta)*sin(psi), & + sin(phi)*cos(theta), & + sin(phi)*sin(psi) + cos(phi)*sin(theta)*cos(psi), & + -sin(phi)*cos(psi) + cos(phi)*sin(theta)*sin(psi), & + cos(phi)*cos(theta) /), (/ 3,3 /)) + end if + + ! Translation vector + if (check_for_node(node_cell, "translation")) then + ! Translations can only be applied to cells that are being filled with + ! another universe + if (c % fill == NONE) then + call fatal_error("Cannot apply a translation to cell " & + // trim(to_str(c % id)) // " because it is not filled with & + &another universe") + end if + + ! Read number of translation parameters + n = node_word_count(node_cell, "translation") + if (n /= 3) then + call fatal_error("Incorrect number of translation parameters on & + &cell " // to_str(c % id)) + end if + + ! Copy translation vector + allocate(c % translation(3)) + call get_node_array(node_cell, "translation", c % translation) + end if + + ! Read cell temperatures. If the temperature is not specified, set it to + ! ERROR_REAL for now. During initialization we'll replace ERROR_REAL with + ! the temperature from the material data. + if (check_for_node(node_cell, "temperature")) then + n = node_word_count(node_cell, "temperature") + if (n > 0) then + ! Make sure this is a "normal" cell. + if (c % material(1) == NONE) call fatal_error("Cell " & + // trim(to_str(c % id)) // " was specified with a temperature & + &but no material. Temperature specification is only valid for & + &cells filled with a material.") + + ! Copy in temperatures + allocate(c % sqrtkT(n)) + call get_node_array(node_cell, "temperature", c % sqrtkT) + + ! Make sure all temperatues are positive + do j = 1, size(c % sqrtkT) + if (c % sqrtkT(j) < ZERO) call fatal_error("Cell " & + // trim(to_str(c % id)) // " was specified with a negative & + &temperature. All cell temperatures must be non-negative.") + end do + + ! Convert to sqrt(kT) + c % sqrtkT(:) = sqrt(K_BOLTZMANN * c % sqrtkT(:)) + else + allocate(c % sqrtkT(1)) + c % sqrtkT(1) = ERROR_REAL + end if + else + allocate(c % sqrtkT(1)) + c % sqrtkT = ERROR_REAL + end if + + ! Add cell to dictionary + call cell_dict % set(c % id, i) + + ! For cells, we also need to check if there's a new universe -- + ! also for every cell add 1 to the count of cells for the + ! specified universe + univ_id = c % universe + if (.not. cells_in_univ_dict % has(univ_id)) then + n_universes = n_universes + 1 + n_cells_in_univ = 1 + call universe_dict % set(univ_id, n_universes) + call univ_ids % push_back(univ_id) + else + n_cells_in_univ = 1 + cells_in_univ_dict % get(univ_id) + end if + call cells_in_univ_dict % set(univ_id, n_cells_in_univ) + + end do + ! ========================================================================== ! READ LATTICES FROM GEOMETRY.XML @@ -1769,7 +1599,7 @@ contains end if ! Check to make sure 'id' hasn't been used - if (lattice_dict % has_key(lat % id)) then + if (lattice_dict % has(lat % id)) then call fatal_error("Two or more lattices use the same unique ID: " & // to_str(lat % id)) end if @@ -1879,7 +1709,7 @@ contains end if ! Add lattice to dictionary - call lattice_dict % add_key(lat % id, i) + call lattice_dict % set(lat % id, i) end select end do RECT_LATTICES @@ -1901,7 +1731,7 @@ contains end if ! Check to make sure 'id' hasn't been used - if (lattice_dict % has_key(lat % id)) then + if (lattice_dict % has(lat % id)) then call fatal_error("Two or more lattices use the same unique ID: " & // to_str(lat % id)) end if @@ -2066,7 +1896,7 @@ contains end if ! Add lattice to dictionary - call lattice_dict % add_key(lat % id, n_rlats + i) + call lattice_dict % set(lat % id, n_rlats + i) end select end do HEX_LATTICES @@ -2081,7 +1911,7 @@ contains u % id = univ_ids % data(i) ! Allocate cell list - n_cells_in_univ = cells_in_univ_dict % get_key(u % id) + n_cells_in_univ = cells_in_univ_dict % get(u % id) allocate(u % cells(n_cells_in_univ)) u % cells(:) = 0 @@ -2100,7 +1930,7 @@ contains do i = 1, n_cells ! Get index in universes array - j = universe_dict % get_key(cells(i) % universe) + j = universe_dict % get(cells(i) % universe) ! Set the first zero entry in the universe cells array to the index in the ! global cells array @@ -2122,11 +1952,8 @@ contains ! for errors and placing properly-formatted data in the right data structures !=============================================================================== - subroutine read_materials() + subroutine read_cross_sections_xml() integer :: i, j - type(VectorReal), allocatable :: nuc_temps(:) ! List of T to read for each nuclide - type(VectorReal), allocatable :: sab_temps(:) ! List of T to read for each S(a,b) - real(8), allocatable :: material_temps(:) logical :: file_exists character(MAX_FILE_LEN) :: env_variable character(MAX_LINE_LEN) :: filename @@ -2220,38 +2047,21 @@ contains ! Creating dictionary that maps the name of the material to the entry do i = 1, size(libraries) do j = 1, size(libraries(i) % materials) - call library_dict % add_key(to_lower(libraries(i) % materials(j)), i) + call library_dict % set(to_lower(libraries(i) % materials(j)), i) end do end do ! Check that 0K nuclides are listed in the cross_sections.xml file if (allocated(res_scat_nuclides)) then do i = 1, size(res_scat_nuclides) - if (.not. library_dict % has_key(to_lower(res_scat_nuclides(i)))) then + if (.not. library_dict % has(to_lower(res_scat_nuclides(i)))) then call fatal_error("Could not find resonant scatterer " & // trim(res_scat_nuclides(i)) // " in cross_sections.xml file!") end if end do end if - ! Parse data from materials.xml - call read_materials_xml(material_temps) - - ! Assign temperatures to cells that don't have temperatures already assigned - call assign_temperatures(material_temps) - - ! Determine desired temperatures for each nuclide and S(a,b) table - call get_temperatures(cells, materials, material_dict, nuclide_dict, & - n_nuclides_total, nuc_temps, sab_dict, & - n_sab_tables, sab_temps) - - ! Read continuous-energy cross sections - if (run_CE .and. run_mode /= MODE_PLOTTING) then - call time_read_xs % start() - call read_ce_cross_sections(nuc_temps, sab_temps) - call time_read_xs % stop() - end if - end subroutine read_materials + end subroutine read_cross_sections_xml subroutine read_materials_xml(material_temps) real(8), allocatable, intent(out) :: material_temps(:) @@ -2335,7 +2145,7 @@ contains end if ! Check to make sure 'id' hasn't been used - if (material_dict % has_key(mat % id)) then + if (material_dict % has(mat % id)) then call fatal_error("Two or more materials use the same unique ID: " & // to_str(mat % id)) end if @@ -2545,11 +2355,11 @@ contains ALL_NUCLIDES: do j = 1, mat % n_nuclides ! Check that this nuclide is listed in the cross_sections.xml file name = trim(names % data(j)) - if (.not. library_dict % has_key(to_lower(name))) then + if (.not. library_dict % has(to_lower(name))) then call fatal_error("Could not find nuclide " // trim(name) & // " in cross_sections data file!") end if - i_library = library_dict % get_key(to_lower(name)) + i_library = library_dict % get(to_lower(name)) if (run_CE) then ! Check to make sure cross-section is continuous energy neutron table @@ -2561,13 +2371,13 @@ contains ! If this nuclide hasn't been encountered yet, we need to add its name ! and alias to the nuclide_dict - if (.not. nuclide_dict % has_key(to_lower(name))) then + if (.not. nuclide_dict % has(to_lower(name))) then index_nuclide = index_nuclide + 1 mat % nuclide(j) = index_nuclide - call nuclide_dict % add_key(to_lower(name), index_nuclide) + call nuclide_dict % set(to_lower(name), index_nuclide) else - mat % nuclide(j) = nuclide_dict % get_key(to_lower(name)) + mat % nuclide(j) = nuclide_dict % get(to_lower(name)) end if ! If the corresponding element hasn't been encountered yet and photon @@ -2576,18 +2386,18 @@ contains element = name(1:scan(name, '0123456789') - 1) ! Make sure photon cross section data is available - if (.not. library_dict % has_key(to_lower(element))) then + if (.not. library_dict % has(to_lower(element))) then call fatal_error("Could not find element " // trim(element) & // " in cross_sections data file!") end if - if (.not. element_dict % has_key(element)) then + if (.not. element_dict % has(element)) then index_element = index_element + 1 mat % element(j) = index_element - call element_dict % add_key(element, index_element) + call element_dict % set(element, index_element) else - mat % element(j) = element_dict % get_key(element) + mat % element(j) = element_dict % get(element) end if end if @@ -2661,14 +2471,14 @@ contains end if ! Check that this nuclide is listed in the cross_sections.xml file - if (.not. library_dict % has_key(to_lower(name))) then + if (.not. library_dict % has(to_lower(name))) then call fatal_error("Could not find S(a,b) table " // trim(name) & // " in cross_sections.xml file!") end if ! Find index in xs_listing and set the name and alias according to the ! listing - i_library = library_dict % get_key(to_lower(name)) + i_library = library_dict % get(to_lower(name)) if (run_CE) then ! Check to make sure cross-section is continuous energy neutron table @@ -2680,25 +2490,25 @@ contains ! If this S(a,b) table hasn't been encountered yet, we need to add its ! name and alias to the sab_dict - if (.not. sab_dict % has_key(to_lower(name))) then + if (.not. sab_dict % has(to_lower(name))) then index_sab = index_sab + 1 mat % i_sab_tables(j) = index_sab - call sab_dict % add_key(to_lower(name), index_sab) + call sab_dict % set(to_lower(name), index_sab) else - mat % i_sab_tables(j) = sab_dict % get_key(to_lower(name)) + mat % i_sab_tables(j) = sab_dict % get(to_lower(name)) end if end do end if end if ! Add material to dictionary - call material_dict % add_key(mat % id, i) + call material_dict % set(mat % id, i) end do ! Set total number of nuclides and S(a,b) tables - n_nuclides_total = index_nuclide - n_elements = index_element - n_sab_tables = index_sab + n_nuclides = index_nuclide + n_elements = index_element + n_sab_tables = index_sab ! Close materials XML file call doc % clear() @@ -2712,16 +2522,14 @@ contains subroutine read_tallies_xml() - integer :: d ! delayed group index integer :: i ! loop over user-specified tallies integer :: j ! loop over words integer :: k ! another loop index integer :: l ! another loop index - integer :: id ! user-specified identifier integer :: filter_id ! user-specified identifier for filter - integer :: i_mesh ! index in meshes array integer :: i_filt ! index in filters array integer :: i_filter_mesh ! index of mesh filter + integer :: i_elem ! index of entry in dictionary integer :: n ! size of arrays in mesh specification integer :: n_words ! number of words read integer :: n_filter ! number of filters @@ -2731,17 +2539,15 @@ contains integer :: n_user_trig ! number of user-specified tally triggers integer :: trig_ind ! index of triggers array for each tally integer :: user_trig_ind ! index of user-specified triggers for each tally + integer :: i_start, i_end + integer :: i_filt_start, i_filt_end + integer(C_INT) :: err real(8) :: threshold ! trigger convergence threshold integer :: n_order ! moment order requested integer :: n_order_pos ! oosition of Scattering order in score name string integer :: MT ! user-specified MT for score - integer :: iarray3(3) ! temporary integer array integer :: imomstr ! Index of MOMENT_STRS & MOMENT_N_STRS logical :: file_exists ! does tallies.xml file exist? - real(8) :: rarray3(3) ! temporary double prec. array - integer :: Nangle ! Number of angular bins - real(8) :: dangle ! Mu spacing if using automatic allocation - integer :: iangle ! Loop counter for building mu filter bins integer, allocatable :: temp_filter(:) ! temporary filter indices character(MAX_LINE_LEN) :: filename character(MAX_WORD_LEN) :: word @@ -2749,24 +2555,19 @@ contains character(MAX_WORD_LEN) :: temp_str character(MAX_WORD_LEN), allocatable :: sarray(:) type(DictCharInt) :: trigger_scores - type(ElemKeyValueCI), pointer :: pair_list - type(TallyObject), pointer :: t type(TallyFilterContainer), pointer :: f type(RegularMesh), pointer :: m type(XMLDocument) :: doc type(XMLNode) :: root - type(XMLNode) :: node_mesh type(XMLNode) :: node_tal type(XMLNode) :: node_filt type(XMLNode) :: node_trigger - type(XMLNode) :: node_deriv type(XMLNode), allocatable :: node_mesh_list(:) type(XMLNode), allocatable :: node_tal_list(:) type(XMLNode), allocatable :: node_filt_list(:) type(XMLNode), allocatable :: node_trigger_list(:) type(XMLNode), allocatable :: node_deriv_list(:) - type(ElemKeyValueCI), pointer :: scores - type(ElemKeyValueCI), pointer :: next + type(DictEntryCI) :: elem ! Check if tallies.xml exists filename = trim(path_input) // "tallies.xml" @@ -2801,34 +2602,6 @@ contains ! Get pointer list to XML call get_node_list(root, "tally", node_tal_list) - ! Check for user meshes - n_user_meshes = size(node_mesh_list) - if (cmfd_run) then - n_meshes = n_user_meshes + n_cmfd_meshes - else - n_meshes = n_user_meshes - end if - - ! Allocate mesh array - if (n_meshes > 0) allocate(meshes(n_meshes)) - - ! Check for user filters - n_user_filters = size(node_filt_list) - - ! Allocate filters array - if (n_user_filters > 0) call add_filters(n_user_filters) - - ! Check for user tallies - n_user_tallies = size(node_tal_list) - if (n_user_tallies == 0) then - if (master) call warning("No tallies present in tallies.xml file!") - end if - - ! Allocate tally array - if (n_user_tallies > 0 .and. run_mode /= MODE_PLOTTING) then - call add_tallies("user", n_user_tallies) - end if - ! Check for setting if (check_for_node(root, "assume_separate")) then call get_node_value(root, "assume_separate", assume_separate) @@ -2837,131 +2610,27 @@ contains ! ========================================================================== ! READ MESH DATA - do i = 1, n_user_meshes - m => meshes(i) + ! Check for user meshes and allocate + n = size(node_mesh_list) + if (n > 0) then + err = openmc_extend_meshes(n, i_start, i_end) + end if - ! Get pointer to mesh node - node_mesh = node_mesh_list(i) + do i = 1, n + m => meshes(i_start + i - 1) - ! Copy mesh id - if (check_for_node(node_mesh, "id")) then - call get_node_value(node_mesh, "id", m % id) - else - call fatal_error("Must specify id for mesh in tally XML file.") - end if - - ! Check to make sure 'id' hasn't been used - if (mesh_dict % has_key(m % id)) then - call fatal_error("Two or more meshes use the same unique ID: " & - // to_str(m % id)) - end if - - ! Read mesh type - temp_str = '' - if (check_for_node(node_mesh, "type")) & - call get_node_value(node_mesh, "type", temp_str) - select case (to_lower(temp_str)) - case ('rect', 'rectangle', 'rectangular') - call warning("Mesh type '" // trim(temp_str) // "' is deprecated. & - &Please use 'regular' instead.") - m % type = MESH_REGULAR - case ('regular') - m % type = MESH_REGULAR - case default - call fatal_error("Invalid mesh type: " // trim(temp_str)) - end select - - ! Determine number of dimensions for mesh - n = node_word_count(node_mesh, "dimension") - if (n /= 1 .and. n /= 2 .and. n /= 3) then - call fatal_error("Mesh must be one, two, or three dimensions.") - end if - m % n_dimension = n - - ! Allocate attribute arrays - allocate(m % dimension(n)) - allocate(m % lower_left(n)) - allocate(m % width(n)) - allocate(m % upper_right(n)) - - ! Check that dimensions are all greater than zero - call get_node_array(node_mesh, "dimension", iarray3(1:n)) - if (any(iarray3(1:n) <= 0)) then - call fatal_error("All entries on the element for a tally & - &mesh must be positive.") - end if - - ! Read dimensions in each direction - m % dimension = iarray3(1:n) - - ! Read mesh lower-left corner location - if (m % n_dimension /= node_word_count(node_mesh, "lower_left")) then - call fatal_error("Number of entries on must be the same & - &as the number of entries on .") - end if - call get_node_array(node_mesh, "lower_left", m % lower_left) - - ! Make sure both upper-right or width were specified - if (check_for_node(node_mesh, "upper_right") .and. & - check_for_node(node_mesh, "width")) then - call fatal_error("Cannot specify both and on a & - &tally mesh.") - end if - - ! Make sure either upper-right or width was specified - if (.not. check_for_node(node_mesh, "upper_right") .and. & - .not. check_for_node(node_mesh, "width")) then - call fatal_error("Must specify either and on a & - &tally mesh.") - end if - - if (check_for_node(node_mesh, "width")) then - ! Check to ensure width has same dimensions - if (node_word_count(node_mesh, "width") /= & - node_word_count(node_mesh, "lower_left")) then - call fatal_error("Number of entries on must be the same as & - &the number of entries on .") - end if - - ! Check for negative widths - call get_node_array(node_mesh, "width", rarray3(1:n)) - if (any(rarray3(1:n) < ZERO)) then - call fatal_error("Cannot have a negative on a tally mesh.") - end if - - ! Set width and upper right coordinate - m % width = rarray3(1:n) - m % upper_right = m % lower_left + m % dimension * m % width - - elseif (check_for_node(node_mesh, "upper_right")) then - ! Check to ensure width has same dimensions - if (node_word_count(node_mesh, "upper_right") /= & - node_word_count(node_mesh, "lower_left")) then - call fatal_error("Number of entries on must be the & - &same as the number of entries on .") - end if - - ! Check that upper-right is above lower-left - call get_node_array(node_mesh, "upper_right", rarray3(1:n)) - if (any(rarray3(1:n) < m % lower_left)) then - call fatal_error("The coordinates must be greater than & - &the coordinates on a tally mesh.") - end if - - ! Set width and upper right coordinate - m % upper_right = rarray3(1:n) - m % width = (m % upper_right - m % lower_left) / m % dimension - end if - - ! Set volume fraction - m % volume_frac = ONE/real(product(m % dimension),8) + ! Instantiate mesh from XML node + call m % from_xml(node_mesh_list(i)) ! Add mesh to dictionary - call mesh_dict % add_key(m % id, i) + call mesh_dict % set(m % id, i_start + i - 1) end do ! We only need the mesh info for plotting - if (run_mode == MODE_PLOTTING) return + if (run_mode == MODE_PLOTTING) then + call doc % clear() + return + end if ! ========================================================================== ! READ DATA FOR DERIVATIVES @@ -2980,82 +2649,23 @@ contains ! Read derivative attributes. do i = 1, size(node_deriv_list) - associate(deriv => tally_derivs(i)) - ! Get pointer to derivative node. - node_deriv = node_deriv_list(i) + call tally_derivs(i) % from_xml(node_deriv_list(i)) - ! Copy the derivative id. - if (check_for_node(node_deriv, "id")) then - call get_node_value(node_deriv, "id", deriv % id) - else - call fatal_error("Must specify an ID for elements in the& - & tally XML file") - end if - - ! Make sure the id is > 0. - if (deriv % id <= 0) then - call fatal_error(" IDs must be an integer greater than & - &zero") - end if - - ! Make sure this id has not already been used. - do j = 1, i-1 - if (tally_derivs(j) % id == deriv % id) then - call fatal_error("Two or more 's use the same unique & - &ID: " // trim(to_str(deriv % id))) - end if - end do - - ! Read the independent variable name. - temp_str = "" - call get_node_value(node_deriv, "variable", temp_str) - temp_str = to_lower(temp_str) - - select case(temp_str) - - case("density") - deriv % variable = DIFF_DENSITY - call get_node_value(node_deriv, "material", deriv % diff_material) - - case("nuclide_density") - deriv % variable = DIFF_NUCLIDE_DENSITY - call get_node_value(node_deriv, "material", deriv % diff_material) - - call get_node_value(node_deriv, "nuclide", word) - word = trim(to_lower(word)) - pair_list => nuclide_dict % keys() - do while (associated(pair_list)) - if (starts_with(pair_list % key, word)) then - word = pair_list % key(1:150) - exit - end if - - ! Advance to next - pair_list => pair_list % next - end do - - ! Check if no nuclide was found - if (.not. associated(pair_list)) then - call fatal_error("Could not find the nuclide " & - // trim(word) // " specified in derivative " & - // trim(to_str(deriv % id)) // " in any material.") - end if - deallocate(pair_list) - - deriv % diff_nuclide = nuclide_dict % get_key(word) - - case("temperature") - deriv % variable = DIFF_TEMPERATURE - call get_node_value(node_deriv, "material", deriv % diff_material) - end select - end associate + ! Update tally derivative dictionary + call tally_deriv_dict % set(tally_derivs(i) % id, i) end do ! ========================================================================== ! READ FILTER DATA - READ_FILTERS: do i = 1, n_user_filters - f => filters(i) + ! Check for user filters and allocate + n = size(node_filt_list) + if (n > 0) then + err = openmc_extend_filters(n, i_start, i_end) + end if + + READ_FILTERS: do i = 1, n + f => filters(i_start + i - 1) ! Get pointer to filter xml node node_filt = node_filt_list(i) @@ -3068,7 +2678,7 @@ contains end if ! Check to make sure 'id' hasn't been used - if (filter_dict % has_key(filter_id)) then + if (filter_dict % has(filter_id)) then call fatal_error("Two or more filters use the same unique ID: " & // to_str(filter_id)) end if @@ -3081,357 +2691,55 @@ contains ! Determine number of bins select case(temp_str) - case ("energy", "energyout", "mu", "polar", "azimuthal", & - "mesh", "universe", "material", "cell", "distribcell", & - "cellborn", "surface", "delayedgroup", "particle") + case ("energy", "energyout", "mu", "polar", "azimuthal") + if (.not. check_for_node(node_filt, "bins")) then + call fatal_error("Bins not set in filter " // trim(to_str(filter_id))) + end if + case ("mesh", "universe", "material", "cell", "distribcell", & + "cellborn", "cellfrom", "surface", "delayedgroup") if (.not. check_for_node(node_filt, "bins")) then call fatal_error("Bins not set in filter " // trim(to_str(filter_id))) end if - n_words = node_word_count(node_filt, "bins") end select - ! Determine type of filter - select case (temp_str) + ! Allocate according to the filter type + err = openmc_filter_set_type(i_start + i - 1, to_c_string(temp_str)) - case ('distribcell') - ! Allocate and declare the filter type - allocate(DistribcellFilter :: f % obj) - select type (filt => f % obj) - type is (DistribcellFilter) - if (n_words /= 1) call fatal_error("Only one cell can be & - &specified per distribcell filter.") - ! Store bins - call get_node_value(node_filt, "bins", filt % cell) - end select - - case ('cell') - ! Allocate and declare the filter type - allocate(CellFilter :: f % obj) - select type (filt => f % obj) - type is (CellFilter) - ! Allocate and store bins - filt % n_bins = n_words - allocate(filt % cells(n_words)) - call get_node_array(node_filt, "bins", filt % cells) - end select - - case ('cellborn') - ! Allocate and declare the filter type - allocate(CellbornFilter :: f % obj) - select type (filt => f % obj) - type is (CellbornFilter) - ! Allocate and store bins - filt % n_bins = n_words - allocate(filt % cells(n_words)) - call get_node_array(node_filt, "bins", filt % cells) - end select - - case ('material') - ! Allocate and declare the filter type - allocate(MaterialFilter :: f % obj) - select type (filt => f % obj) - type is (MaterialFilter) - ! Allocate and store bins - filt % n_bins = n_words - allocate(filt % materials(n_words)) - call get_node_array(node_filt, "bins", filt % materials) - end select - - case ('particle') - ! Allocate and declare the filter type - allocate(ParticleFilter :: f % obj) - select type (filt => f % obj) - type is (ParticleFilter) - ! Allocate and store bins - filt % n_bins = n_words - allocate(filt % particles(n_words)) - call get_node_array(node_filt, "bins", filt % particles) - end select - - case ('universe') - ! Allocate and declare the filter type - allocate(UniverseFilter :: f % obj) - select type (filt => f % obj) - type is (UniverseFilter) - ! Allocate and store bins - filt % n_bins = n_words - allocate(filt % universes(n_words)) - call get_node_array(node_filt, "bins", filt % universes) - end select - - case ('surface') - call fatal_error("Surface filter is not yet supported!") - ! Allocate and declare the filter type - allocate(SurfaceFilter :: f % obj) - select type (filt => f % obj) - type is (SurfaceFilter) - ! Allocate and store bins - filt % n_bins = n_words - allocate(filt % surfaces(n_words)) - call get_node_array(node_filt, "bins", filt % surfaces) - end select - - case ('mesh') - ! Allocate and declare the filter type - allocate(MeshFilter :: f % obj) - select type (filt => f % obj) - type is (MeshFilter) - if (n_words /= 1) call fatal_error("Only one mesh can be & - &specified per mesh filter.") - - ! Determine id of mesh - call get_node_value(node_filt, "bins", id) - - ! Get pointer to mesh - if (mesh_dict % has_key(id)) then - i_mesh = mesh_dict % get_key(id) - m => meshes(i_mesh) - else - call fatal_error("Could not find mesh " // trim(to_str(id)) & - // " specified on filter " // trim(to_str(filter_id))) - end if - - ! Determine number of bins - filt % n_bins = product(m % dimension) - - ! Store the index of the mesh - filt % mesh = i_mesh - end select - - case ('energy') - - ! Allocate and declare the filter type - allocate(EnergyFilter :: f % obj) - select type (filt => f % obj) - type is (EnergyFilter) - ! Allocate and store bins - filt % n_bins = n_words - 1 - allocate(filt % bins(n_words)) - call get_node_array(node_filt, "bins", filt % bins) - - ! We can save tallying time if we know that the tally bins match - ! the energy group structure. In that case, the matching bin - ! index is simply the group (after flipping for the different - ! ordering of the library and tallying systems). - if (.not. run_CE) then - if (n_words == num_energy_groups + 1) then - if (all(filt % bins == energy_bins(num_energy_groups + 1:1:-1))) & - then - filt % matches_transport_groups = .true. - end if - end if - end if - end select - - case ('energyout') - ! Allocate and declare the filter type - allocate(EnergyoutFilter :: f % obj) - select type (filt => f % obj) - type is (EnergyoutFilter) - ! Allocate and store bins - filt % n_bins = n_words - 1 - allocate(filt % bins(n_words)) - call get_node_array(node_filt, "bins", filt % bins) - - ! We can save tallying time if we know that the tally bins match - ! the energy group structure. In that case, the matching bin - ! index is simply the group (after flipping for the different - ! ordering of the library and tallying systems). - if (.not. run_CE) then - if (n_words == num_energy_groups + 1) then - if (all(filt % bins == energy_bins(num_energy_groups + 1:1:-1))) & - then - filt % matches_transport_groups = .true. - end if - end if - end if - end select - - case ('delayedgroup') - - ! Allocate and declare the filter type - allocate(DelayedGroupFilter :: f % obj) - select type (filt => f % obj) - type is (DelayedGroupFilter) - ! Allocate and store bins - filt % n_bins = n_words - allocate(filt % groups(n_words)) - call get_node_array(node_filt, "bins", filt % groups) - - ! Check that bins are all are between 1 and MAX_DELAYED_GROUPS - do d = 1, n_words - if (filt % groups(d) < 1 .or. & - filt % groups(d) > MAX_DELAYED_GROUPS) then - call fatal_error("Encountered delayedgroup bin with index " & - // trim(to_str(filt % groups(d))) // " that is outside & - &the range of 1 to MAX_DELAYED_GROUPS ( " & - // trim(to_str(MAX_DELAYED_GROUPS)) // ")") - end if - end do - end select - - case ('mu') - ! Allocate and declare the filter type - allocate(MuFilter :: f % obj) - select type (filt => f % obj) - type is (MuFilter) - ! Allocate and store bins - filt % n_bins = n_words - 1 - allocate(filt % bins(n_words)) - call get_node_array(node_filt, "bins", filt % bins) - - ! Allow a user to input a lone number which will mean that you - ! subdivide [-1,1] evenly with the input being the number of bins - if (n_words == 1) then - Nangle = int(filt % bins(1)) - if (Nangle > 1) then - filt % n_bins = Nangle - dangle = TWO / real(Nangle,8) - deallocate(filt % bins) - allocate(filt % bins(Nangle + 1)) - do iangle = 1, Nangle - filt % bins(iangle) = -ONE + (iangle - 1) * dangle - end do - filt % bins(Nangle + 1) = ONE - else - call fatal_error("Number of bins for mu filter must be& - & greater than 1 on filter " & - // trim(to_str(filter_id)) // ".") - end if - end if - end select - - case ('polar') - ! Allocate and declare the filter type - allocate(PolarFilter :: f % obj) - select type (filt => f % obj) - type is (PolarFilter) - ! Allocate and store bins - filt % n_bins = n_words - 1 - allocate(filt % bins(n_words)) - call get_node_array(node_filt, "bins", filt % bins) - - ! Allow a user to input a lone number which will mean that you - ! subdivide [0,pi] evenly with the input being the number of bins - if (n_words == 1) then - Nangle = int(filt % bins(1)) - if (Nangle > 1) then - filt % n_bins = Nangle - dangle = PI / real(Nangle,8) - deallocate(filt % bins) - allocate(filt % bins(Nangle + 1)) - do iangle = 1, Nangle - filt % bins(iangle) = (iangle - 1) * dangle - end do - filt % bins(Nangle + 1) = PI - else - call fatal_error("Number of bins for polar filter must be& - & greater than 1 on filter " & - // trim(to_str(filter_id)) // ".") - end if - end if - end select - - case ('azimuthal') - ! Allocate and declare the filter type - allocate(AzimuthalFilter :: f % obj) - select type (filt => f % obj) - type is (AzimuthalFilter) - ! Allocate and store bins - filt % n_bins = n_words - 1 - allocate(filt % bins(n_words)) - call get_node_array(node_filt, "bins", filt % bins) - - ! Allow a user to input a lone number which will mean that you - ! subdivide [-pi,pi) evenly with the input being the number of - ! bins - if (n_words == 1) then - Nangle = int(filt % bins(1)) - if (Nangle > 1) then - filt % n_bins = Nangle - dangle = TWO * PI / real(Nangle,8) - deallocate(filt % bins) - allocate(filt % bins(Nangle + 1)) - do iangle = 1, Nangle - filt % bins(iangle) = -PI + (iangle - 1) * dangle - end do - filt % bins(Nangle + 1) = PI - else - call fatal_error("Number of bins for azimuthal filter must be& - & greater than 1 on filter " & - // trim(to_str(filter_id)) // ".") - end if - end if - end select - - case ('energyfunction') - ! Allocate and declare the filter type. - allocate(EnergyFunctionFilter :: f % obj) - select type (filt => f % obj) - type is (EnergyFunctionFilter) - filt % n_bins = 1 - ! Make sure this is continuous-energy mode. - if (.not. run_CE) then - call fatal_error("EnergyFunction filters are only supported for & - &continuous-energy transport calculations") - end if - - ! Allocate and store energy grid. - if (.not. check_for_node(node_filt, "energy")) then - call fatal_error("Energy grid not specified for EnergyFunction & - &filter on filter " // trim(to_str(filter_id))) - end if - n_words = node_word_count(node_filt, "energy") - allocate(filt % energy(n_words)) - call get_node_array(node_filt, "energy", filt % energy) - - ! Allocate and store interpolant values. - if (.not. check_for_node(node_filt, "y")) then - call fatal_error("y values not specified for EnergyFunction & - &filter on filter " // trim(to_str(filter_id))) - end if - n_words = node_word_count(node_filt, "y") - allocate(filt % y(n_words)) - call get_node_array(node_filt, "y", filt % y) - end select - - case default - ! Specified filter is invalid, raise error - call fatal_error("Unknown filter type '" & - // trim(temp_str) // "' on filter " & - // trim(to_str(filter_id)) // ".") - - end select + ! Read filter data from XML + call f % obj % from_xml(node_filt) ! Set filter id - f % obj % id = filter_id - - ! Add filter to dictionary - call filter_dict % add_key(filter_id, i) + err = openmc_filter_set_id(i_start + i - 1, filter_id) + ! Initialize filter + call f % obj % initialize() end do READ_FILTERS ! ========================================================================== ! READ TALLY DATA - READ_TALLIES: do i = 1, n_user_tallies + ! Check for user tallies + n = size(node_tal_list) + if (n == 0) then + if (master) call warning("No tallies present in tallies.xml file!") + end if + + ! Allocate user tallies + if (n > 0 .and. run_mode /= MODE_PLOTTING) then + err = openmc_extend_tallies(n, i_start, i_end) + end if + + READ_TALLIES: do i = 1, n + ! Allocate tally + err = openmc_tally_set_type(i_start + i - 1, & + C_CHAR_'generic' // C_NULL_CHAR) + ! Get pointer to tally - t => tallies(i) + associate (t => tallies(i_start + i - 1) % obj) ! Get pointer to tally xml node node_tal = node_tal_list(i) - ! Set tally type to volume by default - t % type = TALLY_VOLUME - - ! It's desirable to use a track-length esimator for tallies since - ! generally more events will score to the tally, reducing the - ! variance. However, for tallies that require information on - ! post-collision parameters (e.g. tally with an energyout filter) the - ! analog esimator must be used. - - t % estimator = ESTIMATOR_TRACKLENGTH - ! Copy tally id if (check_for_node(node_tal, "id")) then call get_node_value(node_tal, "id", t % id) @@ -3440,7 +2748,7 @@ contains end if ! Check to make sure 'id' hasn't been used - if (tally_dict % has_key(t % id)) then + if (tally_dict % has(t % id)) then call fatal_error("Two or more tallies use the same unique ID: " & // to_str(t % id)) end if @@ -3472,70 +2780,26 @@ contains allocate(temp_filter(n_filter)) if (n_filter > 0) then call get_node_array(node_tal, "filters", temp_filter) + + do j = 1, n_filter + ! Get pointer to filter + if (filter_dict % has(temp_filter(j))) then + i_filt = filter_dict % get(temp_filter(j)) + f => filters(i_filt) + else + call fatal_error("Could not find filter " & + // trim(to_str(temp_filter(j))) // " specified on tally " & + // trim(to_str(t % id))) + end if + + ! Store the index of the filter + temp_filter(j) = i_filt + end do + + ! Set the filters + err = openmc_tally_set_filters(i_start + i - 1, n_filter, temp_filter) end if - - do j = 1, n_filter - ! Get pointer to filter - if (filter_dict % has_key(temp_filter(j))) then - i_filt = filter_dict % get_key(temp_filter(j)) - f => filters(i_filt) - else - call fatal_error("Could not find filter " & - // trim(to_str(temp_filter(j))) // " specified on tally " & - // trim(to_str(t % id))) - end if - - ! Set the filter index in the tally find_filter array - select type (filt => f % obj) - type is (DistribcellFilter) - t % find_filter(FILTER_DISTRIBCELL) = j - type is (CellFilter) - t % find_filter(FILTER_CELL) = j - type is (CellbornFilter) - t % find_filter(FILTER_CELLBORN) = j - type is (MaterialFilter) - t % find_filter(FILTER_MATERIAL) = j - type is (ParticleFilter) - t % find_filter(FILTER_PARTICLE) = j - type is (UniverseFilter) - t % find_filter(FILTER_UNIVERSE) = j - type is (SurfaceFilter) - t % find_filter(FILTER_SURFACE) = j - type is (MeshFilter) - t % find_filter(FILTER_MESH) = j - type is (EnergyFilter) - t % find_filter(FILTER_ENERGYIN) = j - type is (EnergyoutFilter) - t % find_filter(FILTER_ENERGYOUT) = j - ! Set to analog estimator - t % estimator = ESTIMATOR_ANALOG - type is (DelayedGroupFilter) - t % find_filter(FILTER_DELAYEDGROUP) = j - type is (MuFilter) - t % find_filter(FILTER_MU) = j - ! Set to analog estimator - t % estimator = ESTIMATOR_ANALOG - type is (PolarFilter) - t % find_filter(FILTER_POLAR) = j - type is (AzimuthalFilter) - t % find_filter(FILTER_AZIMUTHAL) = j - type is (EnergyFunctionFilter) - t % find_filter(FILTER_ENERGYFUNCTION) = j - end select - - ! Store the index of the filter - temp_filter(j) = i_filt - end do - - ! Store the filter indices - call move_alloc(FROM=temp_filter, TO=t % filter) - - ! Check that both cell and surface weren't specified - if (t % find_filter(FILTER_CELL) > 0 .and. & - t % find_filter(FILTER_SURFACE) > 0) then - call fatal_error("Cannot specify both cell and surface filters for & - &tally " // trim(to_str(t % id))) - end if + deallocate(temp_filter) ! ======================================================================= ! READ DATA FOR NUCLIDES @@ -3548,15 +2812,15 @@ contains if (trim(sarray(1)) == 'all') then ! Handle special case all - allocate(t % nuclide_bins(n_nuclides_total + 1)) + allocate(t % nuclide_bins(n_nuclides + 1)) - ! Set bins to 1, 2, 3, ..., n_nuclides_total, -1 - t % nuclide_bins(1:n_nuclides_total) = & - (/ (j, j=1, n_nuclides_total) /) - t % nuclide_bins(n_nuclides_total + 1) = -1 + ! Set bins to 1, 2, 3, ..., n_nuclides, -1 + t % nuclide_bins(1:n_nuclides) = & + (/ (j, j=1, n_nuclides) /) + t % nuclide_bins(n_nuclides + 1) = -1 ! Set number of nuclide bins - t % n_nuclide_bins = n_nuclides_total + 1 + t % n_nuclide_bins = n_nuclides + 1 ! Set flag so we can treat this case specially t % all_nuclides = .true. @@ -3576,27 +2840,14 @@ contains word = to_lower(sarray(j)) ! Search through nuclides - pair_list => nuclide_dict % keys() - do while (associated(pair_list)) - if (trim(pair_list % key) == trim(word)) then - word = pair_list % key(1:150) - exit - end if - - ! Advance to next - pair_list => pair_list % next - end do - - ! Check if no nuclide was found - if (.not. associated(pair_list)) then + if (.not. nuclide_dict % has(word)) then call fatal_error("Could not find the nuclide " & // trim(word) // " specified in tally " & // trim(to_str(t % id)) // " in any material.") end if - deallocate(pair_list) ! Set bin to index in nuclides array - t % nuclide_bins(j) = nuclide_dict % get_key(word) + t % nuclide_bins(j) = nuclide_dict % get(word) end do ! Set number of nuclide bins @@ -3634,7 +2885,7 @@ contains score_name = trim(sarray(j)) ! Append the score to the list of possible trigger scores - if (trigger_on) call trigger_scores % add_key(trim(score_name), j) + if (trigger_on) call trigger_scores % set(trim(score_name), j) do imomstr = 1, size(MOMENT_STRS) if (starts_with(score_name,trim(MOMENT_STRS(imomstr)))) then @@ -3905,95 +3156,102 @@ contains case ('fission-q-recoverable') t % score_bins(j) = SCORE_FISS_Q_RECOV case ('current') - t % score_bins(j) = SCORE_CURRENT - t % type = TALLY_SURFACE_CURRENT - ! Check to make sure that current is the only desired response - ! for this tally - if (n_words > 1) then - call fatal_error("Cannot tally other scores in the & - &same tally as surface currents") - end if + ! Check which type of current is desired: mesh currents or + ! surface currents + if (t % find_filter(FILTER_SURFACE) > 0 .or. & + &t % find_filter(FILTER_CELL) > 0 .or. & + &t % find_filter(FILTER_CELLFROM) > 0) then - ! Get index of mesh filter - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) + ! Check to make sure that mesh currents are not desired as well + if (t % find_filter(FILTER_MESH) > 0) then + call fatal_error("Cannot tally other mesh currents & + &in the same tally as surface currents") + end if - ! Check to make sure mesh filter was specified - if (i_filter_mesh == 0) then - call fatal_error("Cannot tally surface current without a mesh & - &filter.") - end if + t % type = TALLY_SURFACE + t % score_bins(j) = SCORE_CURRENT - ! Get pointer to mesh - select type(filt => filters(i_filter_mesh) % obj) - type is (MeshFilter) - i_mesh = filt % mesh - m => meshes(i_mesh) - end select + else if (t % find_filter(FILTER_MESH) > 0) then + t % score_bins(j) = SCORE_CURRENT + t % type = TALLY_MESH_CURRENT - ! Copy filter indices to temporary array - allocate(temp_filter(size(t % filter) + 1)) - temp_filter(1:size(t % filter)) = t % filter + ! Check to make sure that current is the only desired response + ! for this tally + if (n_words > 1) then + call fatal_error("Cannot tally other scores in the & + &same tally as surface currents") + end if - ! Move allocation back -- temp_filter becomes deallocated during - ! this call - call move_alloc(FROM=temp_filter, TO=t % filter) - n_filter = size(t % filter) + ! Get index of mesh filter + i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - ! Extend the filters array so we can add a surface filter and mesh - ! filter - call add_filters(2) + ! Check to make sure mesh filter was specified + if (i_filter_mesh == 0) then + call fatal_error("Cannot tally surface current without a mesh & + &filter.") + end if - ! Increment number of user filters - n_user_filters = n_user_filters + 2 + ! Get pointer to mesh + select type(filt => filters(i_filter_mesh) % obj) + type is (MeshFilter) + m => meshes(filt % mesh) + end select - ! Get index of the new mesh filter - i_filt = n_user_filters - 1 + ! Extend the filters array so we can add a surface filter and + ! mesh filter + err = openmc_extend_filters(2, i_filt_start, i_filt_end) - ! Duplicate the mesh filter since other tallies might use this - ! filter and we need to change the dimension - allocate(MeshFilter :: filters(i_filt) % obj) - select type(filt => filters(i_filt) % obj) - type is (MeshFilter) - filt % id = i_filt - filt % mesh = i_mesh + ! Duplicate the mesh filter since other tallies might use this + ! filter and we need to change the dimension + filters(i_filt_start) = filters(i_filter_mesh) ! We need to increase the dimension by one since we also need ! currents coming into and out of the boundary mesh cells. - filt % n_bins = product(m % dimension + 1) + filters(i_filt_start) % obj % n_bins = product(m % dimension + 1) - ! Add filter to dictionary - call filter_dict % add_key(filt % id, i_filt) - end select - t % filter(t % find_filter(FILTER_MESH)) = i_filt + ! Set ID + call openmc_get_filter_next_id(filter_id) + err = openmc_filter_set_id(i_filt_start, filter_id) - ! Get index of the new surface filter - i_filt = n_user_filters - ! Add surface filter - allocate(SurfaceFilter :: filters(i_filt) % obj) - select type (filt => filters(i_filt) % obj) - type is (SurfaceFilter) - filt % id = i_filt - filt % n_bins = 4 * m % n_dimension - allocate(filt % surfaces(4 * m % n_dimension)) - if (m % n_dimension == 1) then - filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT /) - elseif (m % n_dimension == 2) then - filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT, & - OUT_BACK, IN_BACK, OUT_FRONT, IN_FRONT /) - elseif (m % n_dimension == 3) then - filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT, & - OUT_BACK, IN_BACK, OUT_FRONT, IN_FRONT, OUT_BOTTOM, & - IN_BOTTOM, OUT_TOP, IN_TOP /) - end if - filt % current = .true. + ! Add surface filter + allocate(SurfaceFilter :: filters(i_filt_end) % obj) + select type (filt => filters(i_filt_end) % obj) + type is (SurfaceFilter) + filt % n_bins = 4 * m % n_dimension + allocate(filt % surfaces(4 * m % n_dimension)) + if (m % n_dimension == 1) then + filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT /) + elseif (m % n_dimension == 2) then + filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT, & + OUT_BACK, IN_BACK, OUT_FRONT, IN_FRONT /) + elseif (m % n_dimension == 3) then + filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT, & + OUT_BACK, IN_BACK, OUT_FRONT, IN_FRONT, OUT_BOTTOM, & + IN_BOTTOM, OUT_TOP, IN_TOP /) + end if + filt % current = .true. - ! Add filter to dictionary - call filter_dict % add_key(filt % id, i_filt) - end select - t % find_filter(FILTER_SURFACE) = n_filter - t % filter(n_filter) = i_filt + ! Set ID + call openmc_get_filter_next_id(filter_id) + err = openmc_filter_set_id(i_filt_end, filter_id) + end select + + ! Copy filter indices to resized array + n_filter = size(t % filter) + allocate(temp_filter(n_filter + 1)) + temp_filter(1:size(t % filter)) = t % filter + n_filter = n_filter + 1 + + ! Set mesh and surface filters + temp_filter(t % find_filter(FILTER_MESH)) = i_filt_start + temp_filter(n_filter) = i_filt_end + + ! Set filters + err = openmc_tally_set_filters(i_start + i - 1, n_filter, temp_filter) + deallocate(temp_filter) + end if case ('events') t % score_bins(j) = SCORE_EVENTS @@ -4253,15 +3511,7 @@ contains score_name = trim(to_lower(sarray(j))) if (score_name == "all") then - scores => trigger_scores % keys() - - do while (associated(scores)) - next => scores % next - deallocate(scores) - scores => next - t % n_triggers = t % n_triggers + 1 - end do - + t % n_triggers = t % n_triggers + trigger_scores % size() else t % n_triggers = t % n_triggers + 1 end if @@ -4320,16 +3570,19 @@ contains ! Expand "all" to include TriggerObjects for each score in tally if (score_name == "all") then - scores => trigger_scores % keys() ! Loop over all tally scores - do while (associated(scores)) - score_name = trim(scores % key) + i_elem = 0 + do + ! Move to next score + call trigger_scores % next_entry(elem, i_elem) + if (i_elem == 0) exit + + score_name = trim(elem % key) ! Store the score name and index in the trigger - t % triggers(trig_ind) % score_name = trim(score_name) - t % triggers(trig_ind) % score_index = & - trigger_scores % get_key(trim(score_name)) + t % triggers(trig_ind) % score_name = score_name + t % triggers(trig_ind) % score_index = elem % value ! Set the trigger convergence threshold type select case (temp_str) @@ -4347,11 +3600,6 @@ contains ! Store the trigger convergence threshold t % triggers(trig_ind) % threshold = threshold - ! Move to next score - next => scores % next - deallocate(scores) - scores => next - ! Increment the overall trigger index trig_ind = trig_ind + 1 end do @@ -4362,7 +3610,7 @@ contains ! Store the score name and index t % triggers(trig_ind) % score_name = trim(score_name) t % triggers(trig_ind) % score_index = & - trigger_scores % get_key(trim(score_name)) + trigger_scores % get(trim(score_name)) ! Check if an invalid score was set for the trigger if (t % triggers(trig_ind) % score_index == 0) then @@ -4439,8 +3687,9 @@ contains end if ! Add tally to dictionary - call tally_dict % add_key(t % id, i) + call tally_dict % set(t % id, i) + end associate end do READ_TALLIES ! Close XML document @@ -4457,7 +3706,6 @@ contains integer :: i, j integer :: n_cols, col_id, n_comp, n_masks, n_meshlines integer :: meshid - integer :: i_mesh integer, allocatable :: iarray(:) logical :: file_exists ! does plots.xml file exist? character(MAX_LINE_LEN) :: filename ! absolute path to plots.xml @@ -4511,7 +3759,7 @@ contains end if ! Check to make sure 'id' hasn't been used - if (plot_dict % has_key(pl % id)) then + if (plot_dict % has(pl % id)) then call fatal_error("Two or more plots use the same unique ID: " & // to_str(pl % id)) end if @@ -4696,8 +3944,8 @@ contains ! Add RGB if (pl % color_by == PLOT_COLOR_CELLS) then - if (cell_dict % has_key(col_id)) then - col_id = cell_dict % get_key(col_id) + if (cell_dict % has(col_id)) then + col_id = cell_dict % get(col_id) call get_node_array(node_col, "rgb", pl % colors(col_id) % rgb) else call fatal_error("Could not find cell " // trim(to_str(col_id)) & @@ -4706,8 +3954,8 @@ contains else if (pl % color_by == PLOT_COLOR_MATS) then - if (material_dict % has_key(col_id)) then - col_id = material_dict % get_key(col_id) + if (material_dict % has(col_id)) then + col_id = material_dict % get(col_id) call get_node_array(node_col, "rgb", pl % colors(col_id) % rgb) else call fatal_error("Could not find material " & @@ -4775,12 +4023,12 @@ contains select case (trim(meshtype)) case ('ufs') - if (.not. associated(ufs_mesh)) then + if (index_ufs_mesh < 0) then call fatal_error("No UFS mesh for meshlines on plot " & // trim(to_str(pl % id))) end if - pl % meshlines_mesh => ufs_mesh + pl % meshlines_mesh => meshes(index_ufs_mesh) case ('cmfd') @@ -4789,26 +4037,16 @@ contains &meshlines on plot " // trim(to_str(pl % id))) end if - select type(filt => filters(cmfd_tallies(1) % & - filter(cmfd_tallies(1) % find_filter(FILTER_MESH))) % obj) - type is (MeshFilter) - i_mesh = filt % mesh - end select - pl % meshlines_mesh => meshes(i_mesh) + pl % meshlines_mesh => cmfd_mesh case ('entropy') - if (.not. associated(entropy_mesh)) then + if (index_entropy_mesh < 0) then call fatal_error("No entropy mesh for meshlines on plot " & // trim(to_str(pl % id))) end if - if (.not. allocated(entropy_mesh % dimension)) then - call fatal_error("No dimension specified on entropy mesh & - &for meshlines on plot " // trim(to_str(pl % id))) - end if - - pl % meshlines_mesh => entropy_mesh + pl % meshlines_mesh => meshes(index_entropy_mesh) case ('tally') @@ -4821,8 +4059,8 @@ contains end if ! Check if the specified tally mesh exists - if (mesh_dict % has_key(meshid)) then - pl % meshlines_mesh => meshes(mesh_dict % get_key(meshid)) + if (mesh_dict % has(meshid)) then + pl % meshlines_mesh => meshes(mesh_dict % get(meshid)) if (meshes(meshid) % type /= LATTICE_RECT) then call fatal_error("Non-rectangular mesh specified in & &meshlines for plot " // trim(to_str(pl % id))) @@ -4881,8 +4119,8 @@ contains if (pl % color_by == PLOT_COLOR_CELLS) then - if (cell_dict % has_key(col_id)) then - iarray(j) = cell_dict % get_key(col_id) + if (cell_dict % has(col_id)) then + iarray(j) = cell_dict % get(col_id) else call fatal_error("Could not find cell " & // trim(to_str(col_id)) // " specified in the mask in & @@ -4891,8 +4129,8 @@ contains else if (pl % color_by == PLOT_COLOR_MATS) then - if (material_dict % has_key(col_id)) then - iarray(j) = material_dict % get_key(col_id) + if (material_dict % has(col_id)) then + iarray(j) = material_dict % get(col_id) else call fatal_error("Could not find material " & // trim(to_str(col_id)) // " specified in the mask in & @@ -4920,7 +4158,7 @@ contains end if ! Add plot to dictionary - call plot_dict % add_key(pl % id, i) + call plot_dict % set(pl % id, i) end do READ_PLOTS @@ -5280,123 +4518,6 @@ contains end subroutine normalize_ao -!=============================================================================== -! ASSIGN_SAB_TABLES assigns S(alpha,beta) tables to specific nuclides within -! materials so the code knows when to apply bound thermal scattering data -!=============================================================================== - - subroutine assign_sab_tables() - integer :: i ! index in materials array - integer :: j ! index over nuclides in material - integer :: k ! index over S(a,b) tables in material - integer :: m ! position for sorting - integer :: temp_nuclide ! temporary value for sorting - integer :: temp_table ! temporary value for sorting - real(8) :: temp_frac ! temporary value for sorting - logical :: found - type(VectorInt) :: i_sab_tables - type(VectorInt) :: i_sab_nuclides - type(VectorReal) :: sab_fracs - - do i = 1, size(materials) - ! Skip materials with no S(a,b) tables - if (.not. allocated(materials(i) % i_sab_tables)) cycle - - associate (mat => materials(i)) - - ASSIGN_SAB: do k = 1, size(mat % i_sab_tables) - ! In order to know which nuclide the S(a,b) table applies to, we need - ! to search through the list of nuclides for one which has a matching - ! name - found = .false. - associate (sab => sab_tables(mat % i_sab_tables(k))) - FIND_NUCLIDE: do j = 1, size(mat % nuclide) - if (any(sab % nuclides == nuclides(mat % nuclide(j)) % name)) then - call i_sab_tables % push_back(mat % i_sab_tables(k)) - call i_sab_nuclides % push_back(j) - call sab_fracs % push_back(mat % sab_fracs(k)) - found = .true. - end if - end do FIND_NUCLIDE - end associate - - ! Check to make sure S(a,b) table matched a nuclide - if (.not. found) then - call fatal_error("S(a,b) table " // trim(mat % & - sab_names(k)) // " did not match any nuclide on material " & - // trim(to_str(mat % id))) - end if - end do ASSIGN_SAB - - ! Make sure each nuclide only appears in one table. - do j = 1, i_sab_nuclides % size() - do k = j+1, i_sab_nuclides % size() - if (i_sab_nuclides % data(j) == i_sab_nuclides % data(k)) then - call fatal_error(trim( & - nuclides(mat % nuclide(i_sab_nuclides % data(j))) % name) & - // " in material " // trim(to_str(mat % id)) // " was found & - &in multiple S(a,b) tables. Each nuclide can only appear in & - &one S(a,b) table per material.") - end if - end do - end do - - ! Update i_sab_tables and i_sab_nuclides - deallocate(mat % i_sab_tables) - deallocate(mat % sab_fracs) - m = i_sab_tables % size() - allocate(mat % i_sab_tables(m)) - allocate(mat % i_sab_nuclides(m)) - allocate(mat % sab_fracs(m)) - mat % i_sab_tables(:) = i_sab_tables % data(1:m) - mat % i_sab_nuclides(:) = i_sab_nuclides % data(1:m) - mat % sab_fracs(:) = sab_fracs % data(1:m) - - ! Clear entries in vectors for next material - call i_sab_tables % clear() - call i_sab_nuclides % clear() - call sab_fracs % clear() - - ! If there are multiple S(a,b) tables, we need to make sure that the - ! entries in i_sab_nuclides are sorted or else they won't be applied - ! correctly in the cross_section module. The algorithm here is a simple - ! insertion sort -- don't need anything fancy! - - if (size(mat % i_sab_tables) > 1) then - SORT_SAB: do k = 2, size(mat % i_sab_tables) - ! Save value to move - m = k - temp_nuclide = mat % i_sab_nuclides(k) - temp_table = mat % i_sab_tables(k) - temp_frac = mat % i_sab_tables(k) - - MOVE_OVER: do - ! Check if insertion value is greater than (m-1)th value - if (temp_nuclide >= mat % i_sab_nuclides(m-1)) exit - - ! Move values over until hitting one that's not larger - mat % i_sab_nuclides(m) = mat % i_sab_nuclides(m-1) - mat % i_sab_tables(m) = mat % i_sab_tables(m-1) - mat % sab_fracs(m) = mat % sab_fracs(m-1) - m = m - 1 - - ! Exit if we've reached the beginning of the list - if (m == 1) exit - end do MOVE_OVER - - ! Put the original value into its new position - mat % i_sab_nuclides(m) = temp_nuclide - mat % i_sab_tables(m) = temp_table - mat % sab_fracs(m) = temp_frac - end do SORT_SAB - end if - - ! Deallocate temporary arrays for names of nuclides and S(a,b) tables - if (allocated(mat % names)) deallocate(mat % names) - end associate - end do - end subroutine assign_sab_tables - subroutine read_ce_cross_sections(nuc_temps, sab_temps) type(VectorReal), intent(in) :: nuc_temps(:) type(VectorReal), intent(in) :: sab_temps(:) @@ -5414,7 +4535,7 @@ contains type(SetChar) :: already_read type(SetChar) :: element_already_read - allocate(nuclides(n_nuclides_total)) + allocate(nuclides(n_nuclides)) allocate(elements(n_elements)) allocate(sab_tables(n_sab_tables)) @@ -5424,8 +4545,8 @@ contains name = materials(i) % names(j) if (.not. already_read % contains(name)) then - i_library = library_dict % get_key(to_lower(name)) - i_nuclide = nuclide_dict % get_key(to_lower(name)) + i_library = library_dict % get(to_lower(name)) + i_nuclide = nuclide_dict % get(to_lower(name)) call write_message('Reading ' // trim(name) // ' from ' // & trim(libraries(i_library) % path), 6) @@ -5443,7 +4564,7 @@ contains call file_close(file_id) ! Assign resonant scattering data - if (res_scat_on) call assign_0K_elastic_scattering(nuclides(i_nuclide)) + if (res_scat_on) call nuclides(i_nuclide) % assign_0K_elastic_scattering() ! Determine if minimum/maximum energy for this nuclide is greater/less ! than the previous @@ -5462,8 +4583,8 @@ contains if (photon_transport) then if (.not. element_already_read % contains(element)) then ! Read photon interaction data from HDF5 photon library - i_library = library_dict % get_key(to_lower(element)) - i_element = element_dict % get_key(element) + i_library = library_dict % get(to_lower(element)) + i_element = element_dict % get(element) call write_message('Reading ' // trim(element) // ' from ' // & trim(libraries(i_library) % path), 6) @@ -5509,8 +4630,8 @@ contains name = materials(i) % sab_names(j) if (.not. already_read % contains(name)) then - i_library = library_dict % get_key(to_lower(name)) - i_sab = sab_dict % get_key(to_lower(name)) + i_library = library_dict % get(to_lower(name)) + i_sab = sab_dict % get(to_lower(name)) call write_message('Reading ' // trim(name) // ' from ' // & trim(libraries(i_library) % path), 6) @@ -5530,10 +4651,10 @@ contains call already_read % add(name) end if end do - end do - ! Associate S(a,b) tables with specific nuclides - call assign_sab_tables() + ! Associate S(a,b) tables with specific nuclides + call materials(i) % assign_sab_tables() + end do ! Show which nuclide results in lowest energy for neutron transport do i = 1, size(nuclides) @@ -5592,7 +4713,7 @@ contains end if ! Use material default or global default temperature - i_material = material_dict % get_key(cells(i) % material(j)) + i_material = cells(i) % material(j) if (material_temps(i_material) /= ERROR_REAL) then cells(i) % sqrtkT(j) = sqrt(K_BOLTZMANN * & material_temps(i_material)) @@ -5603,60 +4724,6 @@ contains end do end subroutine assign_temperatures -!=============================================================================== -! ASSIGN_0K_ELASTIC_SCATTERING -!=============================================================================== - - subroutine assign_0K_elastic_scattering(nuc) - type(Nuclide), intent(inout) :: nuc - - integer :: i - real(8) :: xs_cdf_sum - - nuc % resonant = .false. - if (allocated(res_scat_nuclides)) then - ! If resonant nuclides were specified, check the list explicitly - do i = 1, size(res_scat_nuclides) - if (nuc % name == res_scat_nuclides(i)) then - nuc % resonant = .true. - - ! Make sure nuclide has 0K data - if (.not. allocated(nuc % energy_0K)) then - call fatal_error("Cannot treat " // trim(nuc % name) // " as a & - &resonant scatterer because 0 K elastic scattering data is & - ¬ present.") - end if - - exit - end if - end do - else - ! Otherwise, assume that any that have 0 K elastic scattering data are - ! resonant - nuc % resonant = allocated(nuc % energy_0K) - end if - - if (nuc % resonant) then - ! Build CDF for 0K elastic scattering - xs_cdf_sum = ZERO - allocate(nuc % xs_cdf(0:size(nuc % energy_0K))) - nuc % xs_cdf(0) = ZERO - - associate (E => nuc % energy_0K, xs => nuc % elastic_0K) - do i = 1, size(E) - 1 - ! Negative cross sections result in a CDF that is not monotonically - ! increasing. Set all negative xs values to zero. - if (xs(i) < ZERO) xs(i) = ZERO - - ! build xs cdf - xs_cdf_sum = xs_cdf_sum + (sqrt(E(i))*xs(i) + sqrt(E(i+1))*xs(i+1))& - / TWO * (E(i+1) - E(i)) - nuc % xs_cdf(i) = xs_cdf_sum - end do - end associate - end if - end subroutine assign_0K_elastic_scattering - !=============================================================================== ! READ_MULTIPOLE_DATA checks for the existence of a multipole library in the ! directory and loads it using multipole_read @@ -5707,28 +4774,303 @@ contains end subroutine read_multipole_data !=============================================================================== -! CHECK_DATA_VERSION checks for the right version of nuclear data within HDF5 -! files +! ADJUST_INDICES changes the values for 'surfaces' for each cell and the +! material index assigned to each to the indices in the surfaces and material +! array rather than the unique IDs assigned to each surface and material. Also +! assigns boundary conditions to surfaces based on those read into the bc_dict +! dictionary !=============================================================================== - subroutine check_data_version(file_id) - integer(HID_T), intent(in) :: file_id + subroutine adjust_indices() - integer, allocatable :: version(:) + integer :: i ! index for various purposes + integer :: j ! index for various purposes + integer :: k ! loop index for lattices + integer :: m ! loop index for lattices + integer :: lid ! lattice IDs + integer :: id ! user-specified id + class(Lattice), pointer :: lat => null() - if (attribute_exists(file_id, 'version')) then - call read_attribute(version, file_id, 'version') - if (version(1) /= HDF5_VERSION(1)) then - call fatal_error("HDF5 data format uses version " // trim(to_str(& - version(1))) // "." // trim(to_str(version(2))) // " whereas & - &your installation of OpenMC expects version " // trim(to_str(& - HDF5_VERSION(1))) // ".x data.") + do i = 1, n_cells + ! ======================================================================= + ! ADJUST UNIVERSE INDEX FOR EACH CELL + associate (c => cells(i)) + + id = c % universe + if (universe_dict % has(id)) then + c % universe = universe_dict % get(id) + else + call fatal_error("Could not find universe " // trim(to_str(id)) & + &// " specified on cell " // trim(to_str(c % id))) end if - else - call fatal_error("HDF5 data does not indicate a version. Your & - &installation of OpenMC expects version " // trim(to_str(& - HDF5_VERSION(1))) // ".x data.") + + ! ======================================================================= + ! ADJUST MATERIAL/FILL POINTERS FOR EACH CELL + + if (c % material(1) == NONE) then + id = c % fill + if (universe_dict % has(id)) then + c % type = FILL_UNIVERSE + c % fill = universe_dict % get(id) + elseif (lattice_dict % has(id)) then + lid = lattice_dict % get(id) + c % type = FILL_LATTICE + c % fill = lid + else + call fatal_error("Specified fill " // trim(to_str(id)) // " on cell "& + // trim(to_str(c % id)) // " is neither a universe nor a & + &lattice.") + end if + else + do j = 1, size(c % material) + id = c % material(j) + if (id == MATERIAL_VOID) then + c % type = FILL_MATERIAL + else if (material_dict % has(id)) then + c % type = FILL_MATERIAL + c % material(j) = material_dict % get(id) + else + call fatal_error("Could not find material " // trim(to_str(id)) & + // " specified on cell " // trim(to_str(c % id))) + end if + end do + end if + end associate + end do + + ! ========================================================================== + ! ADJUST UNIVERSE INDICES FOR EACH LATTICE + + do i = 1, n_lattices + lat => lattices(i) % obj + select type (lat) + + type is (RectLattice) + do m = 1, lat % n_cells(3) + do k = 1, lat % n_cells(2) + do j = 1, lat % n_cells(1) + id = lat % universes(j,k,m) + if (universe_dict % has(id)) then + lat % universes(j,k,m) = universe_dict % get(id) + else + call fatal_error("Invalid universe number " & + &// trim(to_str(id)) // " specified on lattice " & + &// trim(to_str(lat % id))) + end if + end do + end do + end do + + type is (HexLattice) + do m = 1, lat % n_axial + do k = 1, 2*lat % n_rings - 1 + do j = 1, 2*lat % n_rings - 1 + if (j + k < lat % n_rings + 1) then + cycle + else if (j + k > 3*lat % n_rings - 1) then + cycle + end if + id = lat % universes(j, k, m) + if (universe_dict % has(id)) then + lat % universes(j, k, m) = universe_dict % get(id) + else + call fatal_error("Invalid universe number " & + &// trim(to_str(id)) // " specified on lattice " & + &// trim(to_str(lat % id))) + end if + end do + end do + end do + + end select + + if (lat % outer /= NO_OUTER_UNIVERSE) then + if (universe_dict % has(lat % outer)) then + lat % outer = universe_dict % get(lat % outer) + else + call fatal_error("Invalid universe number " & + &// trim(to_str(lat % outer)) & + &// " specified on lattice " // trim(to_str(lat % id))) + end if + end if + + end do + + end subroutine adjust_indices + +!=============================================================================== +! PREPARE_DISTRIBCELL initializes any distribcell filters present and sets the +! offsets for distribcells +!=============================================================================== + + subroutine prepare_distribcell() + + integer :: i, j ! Tally, filter loop counters + logical :: distribcell_active ! Does simulation use distribcell? + integer, allocatable :: univ_list(:) ! Target offsets + integer, allocatable :: counts(:,:) ! Target count + logical, allocatable :: found(:,:) ! Target found + + ! Assume distribcell is not needed until proven otherwise. + distribcell_active = .false. + + ! We need distribcell if any tallies have distribcell filters. + do i = 1, n_tallies + do j = 1, size(tallies(i) % obj % filter) + select type(filt => filters(tallies(i) % obj % filter(j)) % obj) + type is (DistribcellFilter) + distribcell_active = .true. + end select + end do + end do + + ! We also need distribcell if any distributed materials or distributed + ! temperatues are present. + if (.not. distribcell_active) then + do i = 1, n_cells + if (size(cells(i) % material) > 1 .or. size(cells(i) % sqrtkT) > 1) then + distribcell_active = .true. + exit + end if + end do end if - end subroutine check_data_version + + ! If distribcell isn't used in this simulation then no more work left to do. + if (.not. distribcell_active) return + + ! Make sure the number of materials and temperatures matches the number of + ! cell instances. + do i = 1, n_cells + associate (c => cells(i)) + if (size(c % material) > 1) then + if (size(c % material) /= c % instances) then + call fatal_error("Cell " // trim(to_str(c % id)) // " was & + &specified with " // trim(to_str(size(c % material))) & + // " materials but has " // trim(to_str(c % instances)) & + // " distributed instances. The number of materials must & + &equal one or the number of instances.") + end if + end if + if (size(c % sqrtkT) > 1) then + if (size(c % sqrtkT) /= c % instances) then + call fatal_error("Cell " // trim(to_str(c % id)) // " was & + &specified with " // trim(to_str(size(c % sqrtkT))) & + // " temperatures but has " // trim(to_str(c % instances)) & + // " distributed instances. The number of temperatures must & + &equal one or the number of instances.") + end if + end if + end associate + end do + + ! Allocate offset maps at each level in the geometry + call allocate_offsets(univ_list, counts, found) + + ! Calculate offsets for each target distribcell + do i = 1, n_maps + do j = 1, n_universes + call calc_offsets(univ_list(i), i, universes(j), counts, found) + end do + end do + + end subroutine prepare_distribcell + +!=============================================================================== +! ALLOCATE_OFFSETS determines the number of maps needed and allocates required +! memory for distribcell offset tables +!=============================================================================== + + recursive subroutine allocate_offsets(univ_list, counts, found) + + integer, intent(out), allocatable :: univ_list(:) ! Target offsets + integer, intent(out), allocatable :: counts(:,:) ! Target count + logical, intent(out), allocatable :: found(:,:) ! Target found + + integer :: i, j, k ! Loop counters + type(SetInt) :: cell_list ! distribells to track + + ! Begin gathering list of cells in distribcell tallies + n_maps = 0 + + ! List all cells referenced in distribcell filters. + do i = 1, n_tallies + do j = 1, size(tallies(i) % obj % filter) + select type(filt => filters(tallies(i) % obj % filter(j)) % obj) + type is (DistribcellFilter) + call cell_list % add(filt % cell) + end select + end do + end do + + ! List all cells with multiple (distributed) materials or temperatures. + do i = 1, n_cells + if (size(cells(i) % material) > 1 .or. size(cells(i) % sqrtkT) > 1) then + call cell_list % add(i) + end if + end do + + ! Compute the number of unique universes containing these distribcells + ! to determine the number of offset tables to allocate + do i = 1, n_universes + do j = 1, size(universes(i) % cells) + if (cell_list % contains(universes(i) % cells(j))) then + n_maps = n_maps + 1 + end if + end do + end do + + ! Allocate the list of offset tables for each unique universe + allocate(univ_list(n_maps)) + + ! Allocate list to accumulate target distribcell counts in each universe + allocate(counts(n_universes, n_maps)) + counts(:,:) = 0 + + ! Allocate list to track if target distribcells are found in each universe + allocate(found(n_universes, n_maps)) + found(:,:) = .false. + + + ! Search through universes for distributed cells and assign each one a + ! unique distribcell array index. + k = 1 + do i = 1, n_universes + do j = 1, size(universes(i) % cells) + if (cell_list % contains(universes(i) % cells(j))) then + cells(universes(i) % cells(j)) % distribcell_index = k + univ_list(k) = universes(i) % id + k = k + 1 + end if + end do + end do + + ! Allocate the offset tables for lattices + do i = 1, n_lattices + associate(lat => lattices(i) % obj) + select type(lat) + + type is (RectLattice) + allocate(lat % offset(n_maps, lat % n_cells(1), lat % n_cells(2), & + lat % n_cells(3))) + type is (HexLattice) + allocate(lat % offset(n_maps, 2 * lat % n_rings - 1, & + 2 * lat % n_rings - 1, lat % n_axial)) + end select + + lat % offset(:, :, :, :) = 0 + end associate + end do + + ! Allocate offset table for fill cells + do i = 1, n_cells + if (cells(i) % type /= FILL_MATERIAL) then + allocate(cells(i) % offset(n_maps)) + end if + end do + + ! Free up memory + call cell_list % clear() + + end subroutine allocate_offsets end module input_xml diff --git a/src/main.F90 b/src/main.F90 index 723c87df9..d8e52643c 100644 --- a/src/main.F90 +++ b/src/main.F90 @@ -1,17 +1,18 @@ program main use constants - use finalize, only: openmc_finalize - use global - use initialize, only: openmc_init use message_passing - use particle_restart, only: run_particle_restart - use plot, only: openmc_plot_geometry - use simulation, only: openmc_run - use volume_calc, only: openmc_calculate_volumes + use openmc_api, only: openmc_init, openmc_finalize, openmc_run, & + openmc_plot_geometry, openmc_calculate_volumes + use particle_restart, only: run_particle_restart + use settings, only: run_mode implicit none +#ifdef MPI + integer :: mpi_err ! MPI error code +#endif + ! Initialize run -- when run with MPI, pass communicator #ifdef MPI #ifdef MPIF08 diff --git a/src/material_header.F90 b/src/material_header.F90 index 4ab59b4da..24014f572 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -1,18 +1,36 @@ module material_header + use, intrinsic :: ISO_C_BINDING + use constants - use nuclide_header, only: Nuclide + use dict_header, only: DictIntInt + use error + use nuclide_header + use sab_header + use stl_vector, only: VectorReal, VectorInt + use string, only: to_str implicit none + private + public :: free_memory_material + public :: openmc_extend_materials + public :: openmc_get_material_index + public :: openmc_material_add_nuclide + public :: openmc_material_get_id + public :: openmc_material_get_densities + public :: openmc_material_set_density + public :: openmc_material_set_densities + public :: openmc_material_set_id + !=============================================================================== ! MATERIAL describes a material by its constituent nuclides !=============================================================================== - type Material + type, public :: Material integer :: id ! unique identifier character(len=104) :: name = "" ! User-defined name - integer :: n_nuclides ! number of nuclides + integer :: n_nuclides = 0 ! number of nuclides integer, allocatable :: nuclide(:) ! index in nuclides array integer, allocatable :: element(:) ! index in elements array real(8) :: density ! total atom density in atom/b-cm @@ -45,25 +63,31 @@ module material_header contains procedure :: set_density => material_set_density + procedure :: assign_sab_tables => material_assign_sab_tables end type Material + integer(C_INT32_T), public, bind(C) :: n_materials ! # of materials + + type(Material), public, allocatable, target :: materials(:) + + ! Dictionary that maps user IDs to indices in 'materials' + type(DictIntInt), public :: material_dict + contains !=============================================================================== ! MATERIAL_SET_DENSITY sets the total density of a material in atom/b-cm. !=============================================================================== - function material_set_density(m, density, nuclides) result(err) + function material_set_density(m, density) result(err) class(Material), intent(inout) :: m real(8), intent(in) :: density - type(Nuclide), intent(in) :: nuclides(:) integer :: err integer :: i real(8) :: sum_percent real(8) :: awr - err = -1 if (allocated(m % atom_density)) then ! Set total density based on value provided m % density = density @@ -83,7 +107,393 @@ contains + m % atom_density(i) * awr * MASS_NEUTRON / N_AVOGADRO end do err = 0 + else + err = E_ALLOCATE + call set_errmsg("Material atom density array hasn't been allocated.") end if end function material_set_density +!=============================================================================== +! ASSIGN_SAB_TABLES assigns S(alpha,beta) tables to specific nuclides within +! materials so the code knows when to apply bound thermal scattering data +!=============================================================================== + + subroutine material_assign_sab_tables(this) + class(Material), intent(inout) :: this + + integer :: j ! index over nuclides in material + integer :: k ! index over S(a,b) tables in material + integer :: m ! position for sorting + integer :: temp_nuclide ! temporary value for sorting + integer :: temp_table ! temporary value for sorting + real(8) :: temp_frac ! temporary value for sorting + logical :: found + type(VectorInt) :: i_sab_tables + type(VectorInt) :: i_sab_nuclides + type(VectorReal) :: sab_fracs + + if (.not. allocated(this % i_sab_tables)) return + + ASSIGN_SAB: do k = 1, size(this % i_sab_tables) + ! In order to know which nuclide the S(a,b) table applies to, we need + ! to search through the list of nuclides for one which has a matching + ! name + found = .false. + associate (sab => sab_tables(this % i_sab_tables(k))) + FIND_NUCLIDE: do j = 1, size(this % nuclide) + if (any(sab % nuclides == nuclides(this % nuclide(j)) % name)) then + call i_sab_tables % push_back(this % i_sab_tables(k)) + call i_sab_nuclides % push_back(j) + call sab_fracs % push_back(this % sab_fracs(k)) + found = .true. + end if + end do FIND_NUCLIDE + end associate + + ! Check to make sure S(a,b) table matched a nuclide + if (.not. found) then + call fatal_error("S(a,b) table " // trim(this % & + sab_names(k)) // " did not match any nuclide on material " & + // trim(to_str(this % id))) + end if + end do ASSIGN_SAB + + ! Make sure each nuclide only appears in one table. + do j = 1, i_sab_nuclides % size() + do k = j+1, i_sab_nuclides % size() + if (i_sab_nuclides % data(j) == i_sab_nuclides % data(k)) then + call fatal_error(trim( & + nuclides(this % nuclide(i_sab_nuclides % data(j))) % name) & + // " in material " // trim(to_str(this % id)) // " was found & + &in multiple S(a,b) tables. Each nuclide can only appear in & + &one S(a,b) table per material.") + end if + end do + end do + + ! Update i_sab_tables and i_sab_nuclides + deallocate(this % i_sab_tables) + deallocate(this % sab_fracs) + if (allocated(this % i_sab_nuclides)) deallocate(this % i_sab_nuclides) + m = i_sab_tables % size() + allocate(this % i_sab_tables(m)) + allocate(this % i_sab_nuclides(m)) + allocate(this % sab_fracs(m)) + this % i_sab_tables(:) = i_sab_tables % data(1:m) + this % i_sab_nuclides(:) = i_sab_nuclides % data(1:m) + this % sab_fracs(:) = sab_fracs % data(1:m) + + ! Clear entries in vectors for next material + call i_sab_tables % clear() + call i_sab_nuclides % clear() + call sab_fracs % clear() + + ! If there are multiple S(a,b) tables, we need to make sure that the + ! entries in i_sab_nuclides are sorted or else they won't be applied + ! correctly in the cross_section module. The algorithm here is a simple + ! insertion sort -- don't need anything fancy! + + if (size(this % i_sab_tables) > 1) then + SORT_SAB: do k = 2, size(this % i_sab_tables) + ! Save value to move + m = k + temp_nuclide = this % i_sab_nuclides(k) + temp_table = this % i_sab_tables(k) + temp_frac = this % i_sab_tables(k) + + MOVE_OVER: do + ! Check if insertion value is greater than (m-1)th value + if (temp_nuclide >= this % i_sab_nuclides(m-1)) exit + + ! Move values over until hitting one that's not larger + this % i_sab_nuclides(m) = this % i_sab_nuclides(m-1) + this % i_sab_tables(m) = this % i_sab_tables(m-1) + this % sab_fracs(m) = this % sab_fracs(m-1) + m = m - 1 + + ! Exit if we've reached the beginning of the list + if (m == 1) exit + end do MOVE_OVER + + ! Put the original value into its new position + this % i_sab_nuclides(m) = temp_nuclide + this % i_sab_tables(m) = temp_table + this % sab_fracs(m) = temp_frac + end do SORT_SAB + end if + + ! Deallocate temporary arrays for names of nuclides and S(a,b) tables + if (allocated(this % names)) deallocate(this % names) + end subroutine material_assign_sab_tables + +!=============================================================================== +! FREE_MEMORY_MATERIAL deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_material() + n_materials = 0 + if (allocated(materials)) deallocate(materials) + call material_dict % clear() + end subroutine free_memory_material + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_extend_materials(n, index_start, index_end) result(err) bind(C) + ! Extend the materials array by n elements + integer(C_INT32_T), value, intent(in) :: n + integer(C_INT32_T), optional, intent(out) :: index_start + integer(C_INT32_T), optional, intent(out) :: index_end + integer(C_INT) :: err + + type(Material), allocatable :: temp(:) ! temporary materials array + + if (n_materials == 0) then + ! Allocate materials array + allocate(materials(n)) + else + ! Allocate materials array with increased size + allocate(temp(n_materials + n)) + + ! Move original materials to temporary array + temp(1:n_materials) = materials(:) + + ! Move allocation from temporary array + call move_alloc(FROM=temp, TO=materials) + end if + + ! Return indices in materials array + if (present(index_start)) index_start = n_materials + 1 + if (present(index_end)) index_end = n_materials + n + n_materials = n_materials + n + + err = 0 + end function openmc_extend_materials + + function openmc_get_material_index(id, index) result(err) bind(C) + ! Returns the index in the materials array of a material with a given ID + integer(C_INT32_T), value :: id + integer(C_INT32_T), intent(out) :: index + integer(C_INT) :: err + + if (allocated(materials)) then + if (material_dict % has(id)) then + index = material_dict % get(id) + err = 0 + else + err = E_INVALID_ID + call set_errmsg("No material exists with ID=" // trim(to_str(id)) // ".") + end if + else + err = E_ALLOCATE + call set_errmsg("Memory has not been allocated for materials.") + end if + end function openmc_get_material_index + + + function openmc_material_add_nuclide(index, name, density) result(err) bind(C) + ! Add a nuclide at a specified density in atom/b-cm to a material + integer(C_INT32_T), value, intent(in) :: index + character(kind=C_CHAR) :: name(*) + real(C_DOUBLE), value, intent(in) :: density + integer(C_INT) :: err + + integer :: j, k, n + real(8) :: awr + integer, allocatable :: new_nuclide(:) + real(8), allocatable :: new_density(:) + logical, allocatable :: new_p0(:) + character(:), allocatable :: name_ + + name_ = to_f_string(name) + + err = E_UNASSIGNED + if (index >= 1 .and. index <= size(materials)) then + associate (m => materials(index)) + ! Check if nuclide is already in material + do j = 1, m % n_nuclides + k = m % nuclide(j) + if (nuclides(k) % name == name_) then + awr = nuclides(k) % awr + m % density = m % density + density - m % atom_density(j) + m % density_gpcc = m % density_gpcc + (density - & + m % atom_density(j)) * awr * MASS_NEUTRON / N_AVOGADRO + m % atom_density(j) = density + err = 0 + end if + end do + + ! If nuclide wasn't found, extend nuclide/density arrays + if (err /= 0) then + ! If nuclide hasn't been loaded, load it now + err = openmc_load_nuclide(name) + + if (err == 0) then + ! Extend arrays + n = m % n_nuclides + allocate(new_nuclide(n + 1)) + if (n > 0) new_nuclide(1:n) = m % nuclide + call move_alloc(FROM=new_nuclide, TO=m % nuclide) + + allocate(new_density(n + 1)) + if (n > 0) new_density(1:n) = m % atom_density + call move_alloc(FROM=new_density, TO=m % atom_density) + + allocate(new_p0(n + 1)) + if (n > 0) new_p0(1:n) = m % p0 + new_p0(n + 1) = .false. + call move_alloc(FROM=new_p0, TO=m % p0) + + ! Append new nuclide/density + k = nuclide_dict % get(to_lower(name_)) + m % nuclide(n + 1) = k + m % atom_density(n + 1) = density + m % density = m % density + density + m % density_gpcc = m % density_gpcc + & + density * nuclides(k) % awr * MASS_NEUTRON / N_AVOGADRO + m % n_nuclides = n + 1 + end if + end if + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in materials array is out of bounds.") + end if + + end function openmc_material_add_nuclide + + + function openmc_material_get_densities(index, nuclides, densities, n) & + result(err) bind(C) + ! returns an array of nuclide densities in a material + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: nuclides + type(C_PTR), intent(out) :: densities + integer(C_INT), intent(out) :: n + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(materials)) then + associate (m => materials(index)) + if (allocated(m % atom_density)) then + nuclides = C_LOC(m % nuclide(1)) + densities = C_LOC(m % atom_density(1)) + n = size(m % atom_density) + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Material atom density array has not been allocated.") + end if + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in materials array is out of bounds.") + end if + end function openmc_material_get_densities + + + function openmc_material_get_id(index, id) result(err) bind(C) + ! returns the ID of a material + integer(C_INT32_T), value :: index + integer(C_INT32_T), intent(out) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(materials)) then + id = materials(index) % id + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in materials array is out of bounds.") + end if + end function openmc_material_get_id + + + function openmc_material_set_id(index, id) result(err) bind(C) + ! Set the ID of a material + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_materials) then + materials(index) % id = id + call material_dict % set(id, index) + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in materials array is out of bounds.") + end if + end function openmc_material_set_id + + + function openmc_material_set_density(index, density) result(err) bind(C) + ! Set the total density of a material in atom/b-cm + integer(C_INT32_T), value, intent(in) :: index + real(C_DOUBLE), value, intent(in) :: density + integer(C_INT) :: err + + err = E_UNASSIGNED + if (index >= 1 .and. index <= size(materials)) then + associate (m => materials(index)) + err = m % set_density(density) + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in materials array is out of bounds.") + end if + end function openmc_material_set_density + + + function openmc_material_set_densities(index, n, name, density) result(err) bind(C) + ! Sets the densities for a list of nuclides in a material. If the nuclides + ! don't already exist in the material, they will be added + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT), value, intent(in) :: n + type(C_PTR), intent(in) :: name(n) + real(C_DOUBLE), intent(in) :: density(n) + integer(C_INT) :: err + + integer :: i + integer :: stat + character(C_CHAR), pointer :: string(:) + character(len=:, kind=C_CHAR), allocatable :: name_ + + if (index >= 1 .and. index <= size(materials)) then + associate (m => materials(index)) + ! If nuclide/density arrays are not correct size, reallocate + if (n /= m % n_nuclides) then + deallocate(m % nuclide, m % atom_density, m % p0, STAT=stat) + allocate(m % nuclide(n), m % atom_density(n), m % p0(n)) + end if + + do i = 1, n + ! Convert C string to Fortran string + call c_f_pointer(name(i), string, [10]) + name_ = to_lower(to_f_string(string)) + + if (.not. nuclide_dict % has(name_)) then + err = openmc_load_nuclide(string) + if (err < 0) return + end if + + m % nuclide(i) = nuclide_dict % get(name_) + m % atom_density(i) = density(i) + end do + m % n_nuclides = n + + ! Set isotropic flags to flags + m % p0(:) = .false. + + ! Set total density to the sum of the vector + err = m % set_density(sum(density)) + + ! Assign S(a,b) tables + call m % assign_sab_tables() + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in materials array is out of bounds.") + end if + + end function openmc_material_set_densities + end module material_header diff --git a/src/mesh.F90 b/src/mesh.F90 index b778cbdf0..e997890a4 100644 --- a/src/mesh.F90 +++ b/src/mesh.F90 @@ -1,8 +1,8 @@ module mesh use algorithm, only: binary_search + use bank_header, only: bank use constants - use global use mesh_header use message_passing @@ -10,116 +10,6 @@ module mesh contains -!=============================================================================== -! GET_MESH_BIN determines the tally bin for a particle in a structured mesh -!=============================================================================== - - pure subroutine get_mesh_bin(m, xyz, bin) - type(RegularMesh), intent(in) :: m ! mesh pointer - real(8), intent(in) :: xyz(:) ! coordinates - integer, intent(out) :: bin ! tally bin - - integer :: n ! size of mesh - integer :: d ! mesh dimension index - integer :: ijk(3) ! indices in mesh - logical :: in_mesh ! was given coordinate in mesh at all? - - ! Get number of dimensions - n = m % n_dimension - - ! Loop over the dimensions of the mesh - do d = 1, n - - ! Check for cases where particle is outside of mesh - if (xyz(d) < m % lower_left(d)) then - bin = NO_BIN_FOUND - return - elseif (xyz(d) > m % upper_right(d)) then - bin = NO_BIN_FOUND - return - end if - end do - - ! Determine indices - call get_mesh_indices(m, xyz, ijk, in_mesh) - - ! Convert indices to bin - if (in_mesh) then - bin = mesh_indices_to_bin(m, ijk) - else - bin = NO_BIN_FOUND - end if - - end subroutine get_mesh_bin - -!=============================================================================== -! GET_MESH_INDICES determines the indices of a particle in a structured mesh -!=============================================================================== - - pure subroutine get_mesh_indices(m, xyz, ijk, in_mesh) - type(RegularMesh), intent(in) :: m - real(8), intent(in) :: xyz(:) ! coordinates to check - integer, intent(out) :: ijk(:) ! indices in mesh - logical, intent(out) :: in_mesh ! were given coords in mesh? - - ! Find particle in mesh - ijk(:m % n_dimension) = ceiling((xyz(:m % n_dimension) - m % lower_left)/m % width) - - ! Determine if particle is in mesh - if (any(ijk(:m % n_dimension) < 1) .or. & - any(ijk(:m % n_dimension) > m % dimension)) then - in_mesh = .false. - else - in_mesh = .true. - end if - - end subroutine get_mesh_indices - -!=============================================================================== -! MESH_INDICES_TO_BIN maps (i), (i,j), or (i,j,k) indices to a single bin number -! for use in a TallyObject results array -!=============================================================================== - - pure function mesh_indices_to_bin(m, ijk) result(bin) - type(RegularMesh), intent(in) :: m - integer, intent(in) :: ijk(:) - integer :: bin - - if (m % n_dimension == 1) then - bin = ijk(1) - elseif (m % n_dimension == 2) then - bin = (ijk(2) - 1) * m % dimension(1) + ijk(1) - elseif (m % n_dimension == 3) then - bin = ((ijk(3) - 1) * m % dimension(2) + (ijk(2) - 1)) & - * m % dimension(1) + ijk(1) - end if - - end function mesh_indices_to_bin - -!=============================================================================== -! BIN_TO_MESH_INDICES maps a single mesh bin from a TallyObject results array to -! (i), (i,j), or (i,j,k) indices -!=============================================================================== - - pure subroutine bin_to_mesh_indices(m, bin, ijk) - type(RegularMesh), intent(in) :: m - integer, intent(in) :: bin - integer, intent(out) :: ijk(:) - - if (m % n_dimension == 1) then - ijk(1) = bin - else if (m % n_dimension == 2) then - ijk(1) = mod(bin - 1, m % dimension(1)) + 1 - ijk(2) = (bin - 1)/m % dimension(1) + 1 - else if (m % n_dimension == 3) then - ijk(1) = mod(bin - 1, m % dimension(1)) + 1 - ijk(2) = mod(bin - 1, m % dimension(1) * m % dimension(2)) & - / m % dimension(1) + 1 - ijk(3) = (bin - 1)/(m % dimension(1) * m % dimension(2)) + 1 - end if - - end subroutine bin_to_mesh_indices - !=============================================================================== ! COUNT_BANK_SITES determines the number of fission bank sites in each cell of a ! given mesh as well as an optional energy group structure. This can be used for @@ -130,28 +20,28 @@ contains subroutine count_bank_sites(m, bank_array, cnt, energies, size_bank, & sites_outside) - type(RegularMesh), pointer :: m ! mesh to count sites + type(RegularMesh), intent(in) :: m ! mesh to count sites type(Bank), intent(in) :: bank_array(:) ! fission or source bank - real(8), intent(out) :: cnt(:,:,:,:) ! weight of sites in each + real(8), intent(out) :: cnt(:,:) ! weight of sites in each ! cell and energy group real(8), intent(in), optional :: energies(:) ! energy grid to search integer(8), intent(in), optional :: size_bank ! # of bank sites (on each proc) logical, intent(inout), optional :: sites_outside ! were there sites outside mesh? + real(8), allocatable :: cnt_(:,:) integer :: i ! loop index for local fission sites integer :: n_sites ! size of bank array - integer :: ijk(3) ! indices on mesh - integer :: n_groups ! number of groups in energies - integer :: e_bin ! energy_bin - logical :: in_mesh ! was single site outside mesh? - logical :: outside ! was any site outside mesh? + integer :: n ! number of energy groups / size + integer :: mesh_bin ! mesh bin + integer :: e_bin ! energy bin #ifdef MPI - integer :: n ! total size of count variable - real(8) :: dummy ! temporary receive buffer for non-root reductions + integer :: mpi_err ! MPI error code #endif + logical :: outside ! was any site outside mesh? ! initialize variables - cnt = ZERO + allocate(cnt_(size(cnt,1), size(cnt,2))) + cnt_ = ZERO outside = .false. ! Set size of bank @@ -163,18 +53,18 @@ contains ! Determine number of energies in group structure if (present(energies)) then - n_groups = size(energies) - 1 + n = size(energies) - 1 else - n_groups = 1 + n = 1 end if ! loop over fission sites and count how many are in each mesh box FISSION_SITES: do i = 1, n_sites ! determine scoring bin for entropy mesh - call get_mesh_indices(m, bank_array(i) % xyz, ijk, in_mesh) + call m % get_bin(bank_array(i) % xyz, mesh_bin) ! if outside mesh, skip particle - if (.not. in_mesh) then + if (mesh_bin == NO_BIN_FOUND) then outside = .true. cycle end if @@ -183,266 +73,34 @@ contains if (present(energies)) then if (bank_array(i) % E < energies(1)) then e_bin = 1 - elseif (bank_array(i) % E > energies(n_groups+1)) then - e_bin = n_groups + elseif (bank_array(i) % E > energies(n + 1)) then + e_bin = n else - e_bin = binary_search(energies, n_groups + 1, bank_array(i) % E) + e_bin = binary_search(energies, n + 1, bank_array(i) % E) end if else e_bin = 1 end if ! add to appropriate mesh box - cnt(e_bin,ijk(1),ijk(2),ijk(3)) = cnt(e_bin,ijk(1),ijk(2),ijk(3)) + & - bank_array(i) % wgt + cnt_(e_bin, mesh_bin) = cnt_(e_bin, mesh_bin) + bank_array(i) % wgt end do FISSION_SITES #ifdef MPI - ! determine total number of mesh cells - n = n_groups * size(cnt,2) * size(cnt,3) * size(cnt,4) - ! collect values from all processors - if (master) then - call MPI_REDUCE(MPI_IN_PLACE, cnt, n, MPI_REAL8, MPI_SUM, 0, & - mpi_intracomm, mpi_err) - else - ! Receive buffer not significant at other processors - call MPI_REDUCE(cnt, dummy, n, MPI_REAL8, MPI_SUM, 0, & - mpi_intracomm, mpi_err) - end if + n = size(cnt_) + call MPI_REDUCE(cnt_, cnt, n, MPI_REAL8, MPI_SUM, 0, mpi_intracomm, mpi_err) ! Check if there were sites outside the mesh for any processor if (present(sites_outside)) then call MPI_REDUCE(outside, sites_outside, 1, MPI_LOGICAL, MPI_LOR, 0, & mpi_intracomm, mpi_err) end if - #else sites_outside = outside + cnt = cnt_ #endif end subroutine count_bank_sites -!=============================================================================== -! MESH_INTERSECTS determines if a line between xyz0 and xyz1 intersects the -! outer boundary of the given mesh. This is important for determining whether a -! track will score to a mesh tally. -!=============================================================================== - - pure function mesh_intersects_1d(m, xyz0, xyz1) result(intersects) - type(RegularMesh), intent(in) :: m - real(8), intent(in) :: xyz0(1) - real(8), intent(in) :: xyz1(1) - logical :: intersects - - real(8) :: x0 ! track start point - real(8) :: x1 ! track end point - real(8) :: xm0 ! lower-left coordinates of mesh - real(8) :: xm1 ! upper-right coordinates of mesh - - ! Copy coordinates of starting point - x0 = xyz0(1) - - ! Copy coordinates of ending point - x1 = xyz1(1) - - ! Copy coordinates of mesh lower_left - xm0 = m % lower_left(1) - - ! Copy coordinates of mesh upper_right - xm1 = m % upper_right(1) - - ! Set default value for intersects - intersects = .false. - - ! Check if line intersects left surface - if ((x0 < xm0 .and. x1 > xm0) .or. (x0 > xm0 .and. x1 < xm0)) then - intersects = .true. - return - end if - - ! Check if line intersects right surface - if ((x0 < xm1 .and. x1 > xm1) .or. (x0 > xm1 .and. x1 < xm1)) then - intersects = .true. - return - end if - - end function mesh_intersects_1d - - pure function mesh_intersects_2d(m, xyz0, xyz1) result(intersects) - type(RegularMesh), intent(in) :: m - real(8), intent(in) :: xyz0(2) - real(8), intent(in) :: xyz1(2) - logical :: intersects - - real(8) :: x0, y0 ! track start point - real(8) :: x1, y1 ! track end point - real(8) :: xi, yi ! track intersection point with mesh - real(8) :: xm0, ym0 ! lower-left coordinates of mesh - real(8) :: xm1, ym1 ! upper-right coordinates of mesh - - ! Copy coordinates of starting point - x0 = xyz0(1) - y0 = xyz0(2) - - ! Copy coordinates of ending point - x1 = xyz1(1) - y1 = xyz1(2) - - ! Copy coordinates of mesh lower_left - xm0 = m % lower_left(1) - ym0 = m % lower_left(2) - - ! Copy coordinates of mesh upper_right - xm1 = m % upper_right(1) - ym1 = m % upper_right(2) - - ! Set default value for intersects - intersects = .false. - - ! Check if line intersects left surface -- calculate the intersection point - ! y - if ((x0 < xm0 .and. x1 > xm0) .or. (x0 > xm0 .and. x1 < xm0)) then - yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0) - if (yi >= ym0 .and. yi < ym1) then - intersects = .true. - return - end if - end if - - ! Check if line intersects back surface -- calculate the intersection point - ! x - if ((y0 < ym0 .and. y1 > ym0) .or. (y0 > ym0 .and. y1 < ym0)) then - xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0) - if (xi >= xm0 .and. xi < xm1) then - intersects = .true. - return - end if - end if - - ! Check if line intersects right surface -- calculate the intersection - ! point y - if ((x0 < xm1 .and. x1 > xm1) .or. (x0 > xm1 .and. x1 < xm1)) then - yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0) - if (yi >= ym0 .and. yi < ym1) then - intersects = .true. - return - end if - end if - - ! Check if line intersects front surface -- calculate the intersection point - ! x - if ((y0 < ym1 .and. y1 > ym1) .or. (y0 > ym1 .and. y1 < ym1)) then - xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0) - if (xi >= xm0 .and. xi < xm1) then - intersects = .true. - return - end if - end if - - end function mesh_intersects_2d - - pure function mesh_intersects_3d(m, xyz0, xyz1) result(intersects) - type(RegularMesh), intent(in) :: m - real(8), intent(in) :: xyz0(3) - real(8), intent(in) :: xyz1(3) - logical :: intersects - - real(8) :: x0, y0, z0 ! track start point - real(8) :: x1, y1, z1 ! track end point - real(8) :: xi, yi, zi ! track intersection point with mesh - real(8) :: xm0, ym0, zm0 ! lower-left coordinates of mesh - real(8) :: xm1, ym1, zm1 ! upper-right coordinates of mesh - - ! Copy coordinates of starting point - x0 = xyz0(1) - y0 = xyz0(2) - z0 = xyz0(3) - - ! Copy coordinates of ending point - x1 = xyz1(1) - y1 = xyz1(2) - z1 = xyz1(3) - - ! Copy coordinates of mesh lower_left - xm0 = m % lower_left(1) - ym0 = m % lower_left(2) - zm0 = m % lower_left(3) - - ! Copy coordinates of mesh upper_right - xm1 = m % upper_right(1) - ym1 = m % upper_right(2) - zm1 = m % upper_right(3) - - ! Set default value for intersects - intersects = .false. - - ! Check if line intersects left surface -- calculate the intersection point - ! (y,z) - if ((x0 < xm0 .and. x1 > xm0) .or. (x0 > xm0 .and. x1 < xm0)) then - yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0) - zi = z0 + (xm0 - x0) * (z1 - z0) / (x1 - x0) - if (yi >= ym0 .and. yi < ym1 .and. zi >= zm0 .and. zi < zm1) then - intersects = .true. - return - end if - end if - - ! Check if line intersects back surface -- calculate the intersection point - ! (x,z) - if ((y0 < ym0 .and. y1 > ym0) .or. (y0 > ym0 .and. y1 < ym0)) then - xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0) - zi = z0 + (ym0 - y0) * (z1 - z0) / (y1 - y0) - if (xi >= xm0 .and. xi < xm1 .and. zi >= zm0 .and. zi < zm1) then - intersects = .true. - return - end if - end if - - ! Check if line intersects bottom surface -- calculate the intersection - ! point (x,y) - if ((z0 < zm0 .and. z1 > zm0) .or. (z0 > zm0 .and. z1 < zm0)) then - xi = x0 + (zm0 - z0) * (x1 - x0) / (z1 - z0) - yi = y0 + (zm0 - z0) * (y1 - y0) / (z1 - z0) - if (xi >= xm0 .and. xi < xm1 .and. yi >= ym0 .and. yi < ym1) then - intersects = .true. - return - end if - end if - - ! Check if line intersects right surface -- calculate the intersection point - ! (y,z) - if ((x0 < xm1 .and. x1 > xm1) .or. (x0 > xm1 .and. x1 < xm1)) then - yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0) - zi = z0 + (xm1 - x0) * (z1 - z0) / (x1 - x0) - if (yi >= ym0 .and. yi < ym1 .and. zi >= zm0 .and. zi < zm1) then - intersects = .true. - return - end if - end if - - ! Check if line intersects front surface -- calculate the intersection point - ! (x,z) - if ((y0 < ym1 .and. y1 > ym1) .or. (y0 > ym1 .and. y1 < ym1)) then - xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0) - zi = z0 + (ym1 - y0) * (z1 - z0) / (y1 - y0) - if (xi >= xm0 .and. xi < xm1 .and. zi >= zm0 .and. zi < zm1) then - intersects = .true. - return - end if - end if - - ! Check if line intersects top surface -- calculate the intersection point - ! (x,y) - if ((z0 < zm1 .and. z1 > zm1) .or. (z0 > zm1 .and. z1 < zm1)) then - xi = x0 + (zm1 - z0) * (x1 - x0) / (z1 - z0) - yi = y0 + (zm1 - z0) * (y1 - y0) / (z1 - z0) - if (xi >= xm0 .and. xi < xm1 .and. yi >= ym0 .and. yi < ym1) then - intersects = .true. - return - end if - end if - - end function mesh_intersects_3d - end module mesh diff --git a/src/mesh_header.F90 b/src/mesh_header.F90 index 9da06f813..8a2d61fd9 100644 --- a/src/mesh_header.F90 +++ b/src/mesh_header.F90 @@ -1,14 +1,28 @@ module mesh_header + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants + use dict_header, only: DictIntInt + use error, only: warning, fatal_error + use hdf5_interface + use string, only: to_str, to_lower + use xml_interface + implicit none + private + public :: free_memory_mesh + public :: openmc_extend_meshes !=============================================================================== ! STRUCTUREDMESH represents a tessellation of n-dimensional Euclidean space by ! congruent squares or cubes !=============================================================================== - type RegularMesh - integer :: id ! user-specified id + type, public :: RegularMesh + integer :: id = -1 ! user-specified id integer :: type ! rectangular, hexagonal integer :: n_dimension ! rank of mesh real(8) :: volume_frac ! volume fraction of each cell @@ -16,6 +30,566 @@ module mesh_header real(8), allocatable :: lower_left(:) ! lower-left corner of mesh real(8), allocatable :: upper_right(:) ! upper-right corner of mesh real(8), allocatable :: width(:) ! width of each mesh cell + contains + procedure :: from_xml => regular_from_xml + procedure :: get_bin => regular_get_bin + procedure :: get_indices => regular_get_indices + procedure :: get_bin_from_indices => regular_get_bin_from_indices + procedure :: get_indices_from_bin => regular_get_indices_from_bin + procedure :: intersects => regular_intersects + procedure :: to_hdf5 => regular_to_hdf5 end type RegularMesh + integer(C_INT32_T), public, bind(C) :: n_meshes = 0 ! # of structured meshes + + type(RegularMesh), public, allocatable, target :: meshes(:) + + ! Dictionary that maps user IDs to indices in 'meshes' + type(DictIntInt), public :: mesh_dict + +contains + + subroutine regular_from_xml(this, node) + class(RegularMesh), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + character(MAX_LINE_LEN) :: temp_str + + ! Copy mesh id + if (check_for_node(node, "id")) then + call get_node_value(node, "id", this % id) + + ! Check to make sure 'id' hasn't been used + if (mesh_dict % has(this % id)) then + call fatal_error("Two or more meshes use the same unique ID: " & + // to_str(this % id)) + end if + end if + + ! Read mesh type + if (check_for_node(node, "type")) then + call get_node_value(node, "type", temp_str) + select case (to_lower(temp_str)) + case ('rect', 'rectangle', 'rectangular') + call warning("Mesh type '" // trim(temp_str) // "' is deprecated. & + &Please use 'regular' instead.") + this % type = MESH_REGULAR + case ('regular') + this % type = MESH_REGULAR + case default + call fatal_error("Invalid mesh type: " // trim(temp_str)) + end select + else + this % type = MESH_REGULAR + end if + + ! Determine number of dimensions for mesh + if (check_for_node(node, "dimension")) then + n = node_word_count(node, "dimension") + if (n /= 1 .and. n /= 2 .and. n /= 3) then + call fatal_error("Mesh must be one, two, or three dimensions.") + end if + this % n_dimension = n + + ! Allocate attribute arrays + allocate(this % dimension(n)) + + ! Check that dimensions are all greater than zero + call get_node_array(node, "dimension", this % dimension) + if (any(this % dimension <= 0)) then + call fatal_error("All entries on the element for a tally & + &mesh must be positive.") + end if + end if + + ! Check for lower-left coordinates + if (check_for_node(node, "lower_left")) then + n = node_word_count(node, "lower_left") + allocate(this % lower_left(n)) + + ! Read mesh lower-left corner location + call get_node_array(node, "lower_left", this % lower_left) + else + call fatal_error("Must specify on a mesh.") + end if + + if (check_for_node(node, "width")) then + ! Make sure both upper-right or width were specified + if (check_for_node(node, "upper_right")) then + call fatal_error("Cannot specify both and on a & + &mesh.") + end if + + n = node_word_count(node, "width") + allocate(this % width(n)) + allocate(this % upper_right(n)) + + ! Check to ensure width has same dimensions + if (n /= size(this % lower_left)) then + call fatal_error("Number of entries on must be the same as & + &the number of entries on .") + end if + + ! Check for negative widths + call get_node_array(node, "width", this % width) + if (any(this % width < ZERO)) then + call fatal_error("Cannot have a negative on a tally mesh.") + end if + + ! Set width and upper right coordinate + this % upper_right = this % lower_left + this % dimension * this % width + + elseif (check_for_node(node, "upper_right")) then + n = node_word_count(node, "upper_right") + allocate(this % upper_right(n)) + allocate(this % width(n)) + + ! Check to ensure width has same dimensions + if (n /= size(this % lower_left)) then + call fatal_error("Number of entries on must be the & + &same as the number of entries on .") + end if + + ! Check that upper-right is above lower-left + call get_node_array(node, "upper_right", this % upper_right) + if (any(this % upper_right < this % lower_left)) then + call fatal_error("The coordinates must be greater than & + &the coordinates on a tally mesh.") + end if + + ! Set width and upper right coordinate + this % width = (this % upper_right - this % lower_left) / this % dimension + else + call fatal_error("Must specify either and on a & + &mesh.") + end if + + if (allocated(this % dimension)) then + if (size(this % dimension) /= size(this % lower_left)) then + call fatal_error("Number of entries on must be the same & + &as the number of entries on .") + end if + + ! Set volume fraction + this % volume_frac = ONE/real(product(this % dimension),8) + end if + + end subroutine regular_from_xml + +!=============================================================================== +! GET_MESH_BIN determines the tally bin for a particle in a structured mesh +!=============================================================================== + + pure subroutine regular_get_bin(this, xyz, bin) + class(RegularMesh), intent(in) :: this + real(8), intent(in) :: xyz(:) ! coordinates + integer, intent(out) :: bin ! tally bin + + integer :: n ! size of mesh + integer :: d ! mesh dimension index + integer :: ijk(3) ! indices in mesh + logical :: in_mesh ! was given coordinate in mesh at all? + + ! Get number of dimensions + n = this % n_dimension + + ! Loop over the dimensions of the mesh + do d = 1, n + + ! Check for cases where particle is outside of mesh + if (xyz(d) < this % lower_left(d)) then + bin = NO_BIN_FOUND + return + elseif (xyz(d) > this % upper_right(d)) then + bin = NO_BIN_FOUND + return + end if + end do + + ! Determine indices + call this % get_indices(xyz, ijk, in_mesh) + + ! Convert indices to bin + if (in_mesh) then + bin = this % get_bin_from_indices(ijk) + else + bin = NO_BIN_FOUND + end if + + end subroutine regular_get_bin + +!=============================================================================== +! GET_MESH_INDICES determines the indices of a particle in a structured mesh +!=============================================================================== + + pure subroutine regular_get_indices(this, xyz, ijk, in_mesh) + class(RegularMesh), intent(in) :: this + real(8), intent(in) :: xyz(:) ! coordinates to check + integer, intent(out) :: ijk(:) ! indices in mesh + logical, intent(out) :: in_mesh ! were given coords in mesh? + + ! Find particle in mesh + ijk(:this % n_dimension) = ceiling((xyz(:this % n_dimension) - & + this % lower_left)/this % width) + + ! Determine if particle is in mesh + if (any(ijk(:this % n_dimension) < 1) .or. & + any(ijk(:this % n_dimension) > this % dimension)) then + in_mesh = .false. + else + in_mesh = .true. + end if + + end subroutine regular_get_indices + +!=============================================================================== +! MESH_INDICES_TO_BIN maps (i), (i,j), or (i,j,k) indices to a single bin number +! for use in a TallyObject results array +!=============================================================================== + + pure function regular_get_bin_from_indices(this, ijk) result(bin) + class(RegularMesh), intent(in) :: this + integer, intent(in) :: ijk(:) + integer :: bin + + if (this % n_dimension == 1) then + bin = ijk(1) + elseif (this % n_dimension == 2) then + bin = (ijk(2) - 1) * this % dimension(1) + ijk(1) + elseif (this % n_dimension == 3) then + bin = ((ijk(3) - 1) * this % dimension(2) + (ijk(2) - 1)) & + * this % dimension(1) + ijk(1) + end if + + end function regular_get_bin_from_indices + +!=============================================================================== +! BIN_TO_MESH_INDICES maps a single mesh bin from a TallyObject results array to +! (i), (i,j), or (i,j,k) indices +!=============================================================================== + + pure subroutine regular_get_indices_from_bin(this, bin, ijk) + class(RegularMesh), intent(in) :: this + integer, intent(in) :: bin + integer, intent(out) :: ijk(:) + + if (this % n_dimension == 1) then + ijk(1) = bin + else if (this % n_dimension == 2) then + ijk(1) = mod(bin - 1, this % dimension(1)) + 1 + ijk(2) = (bin - 1)/this % dimension(1) + 1 + else if (this % n_dimension == 3) then + ijk(1) = mod(bin - 1, this % dimension(1)) + 1 + ijk(2) = mod(bin - 1, this % dimension(1) * this % dimension(2)) & + / this % dimension(1) + 1 + ijk(3) = (bin - 1)/(this % dimension(1) * this % dimension(2)) + 1 + end if + + end subroutine regular_get_indices_from_bin + +!=============================================================================== +! MESH_INTERSECTS determines if a line between xyz0 and xyz1 intersects the +! outer boundary of the given mesh. This is important for determining whether a +! track will score to a mesh tally. +!=============================================================================== + + pure function regular_intersects(this, xyz0, xyz1) result(intersects) + class(RegularMesh), intent(in) :: this + real(8), intent(in) :: xyz0(:) + real(8), intent(in) :: xyz1(:) + logical :: intersects + + select case(this % n_dimension) + case (1) + intersects = mesh_intersects_1d(this, xyz0, xyz1) + case (2) + intersects = mesh_intersects_2d(this, xyz0, xyz1) + case (3) + intersects = mesh_intersects_3d(this, xyz0, xyz1) + end select + end function regular_intersects + + pure function mesh_intersects_1d(m, xyz0, xyz1) result(intersects) + type(RegularMesh), intent(in) :: m + real(8), intent(in) :: xyz0(:) + real(8), intent(in) :: xyz1(:) + logical :: intersects + + real(8) :: x0 ! track start point + real(8) :: x1 ! track end point + real(8) :: xm0 ! lower-left coordinates of mesh + real(8) :: xm1 ! upper-right coordinates of mesh + + ! Copy coordinates of starting point + x0 = xyz0(1) + + ! Copy coordinates of ending point + x1 = xyz1(1) + + ! Copy coordinates of mesh lower_left + xm0 = m % lower_left(1) + + ! Copy coordinates of mesh upper_right + xm1 = m % upper_right(1) + + ! Set default value for intersects + intersects = .false. + + ! Check if line intersects left surface + if ((x0 < xm0 .and. x1 > xm0) .or. (x0 > xm0 .and. x1 < xm0)) then + intersects = .true. + return + end if + + ! Check if line intersects right surface + if ((x0 < xm1 .and. x1 > xm1) .or. (x0 > xm1 .and. x1 < xm1)) then + intersects = .true. + return + end if + + end function mesh_intersects_1d + + pure function mesh_intersects_2d(m, xyz0, xyz1) result(intersects) + type(RegularMesh), intent(in) :: m + real(8), intent(in) :: xyz0(:) + real(8), intent(in) :: xyz1(:) + logical :: intersects + + real(8) :: x0, y0 ! track start point + real(8) :: x1, y1 ! track end point + real(8) :: xi, yi ! track intersection point with mesh + real(8) :: xm0, ym0 ! lower-left coordinates of mesh + real(8) :: xm1, ym1 ! upper-right coordinates of mesh + + ! Copy coordinates of starting point + x0 = xyz0(1) + y0 = xyz0(2) + + ! Copy coordinates of ending point + x1 = xyz1(1) + y1 = xyz1(2) + + ! Copy coordinates of mesh lower_left + xm0 = m % lower_left(1) + ym0 = m % lower_left(2) + + ! Copy coordinates of mesh upper_right + xm1 = m % upper_right(1) + ym1 = m % upper_right(2) + + ! Set default value for intersects + intersects = .false. + + ! Check if line intersects left surface -- calculate the intersection point + ! y + if ((x0 < xm0 .and. x1 > xm0) .or. (x0 > xm0 .and. x1 < xm0)) then + yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0) + if (yi >= ym0 .and. yi < ym1) then + intersects = .true. + return + end if + end if + + ! Check if line intersects back surface -- calculate the intersection point + ! x + if ((y0 < ym0 .and. y1 > ym0) .or. (y0 > ym0 .and. y1 < ym0)) then + xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0) + if (xi >= xm0 .and. xi < xm1) then + intersects = .true. + return + end if + end if + + ! Check if line intersects right surface -- calculate the intersection + ! point y + if ((x0 < xm1 .and. x1 > xm1) .or. (x0 > xm1 .and. x1 < xm1)) then + yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0) + if (yi >= ym0 .and. yi < ym1) then + intersects = .true. + return + end if + end if + + ! Check if line intersects front surface -- calculate the intersection point + ! x + if ((y0 < ym1 .and. y1 > ym1) .or. (y0 > ym1 .and. y1 < ym1)) then + xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0) + if (xi >= xm0 .and. xi < xm1) then + intersects = .true. + return + end if + end if + + end function mesh_intersects_2d + + pure function mesh_intersects_3d(m, xyz0, xyz1) result(intersects) + type(RegularMesh), intent(in) :: m + real(8), intent(in) :: xyz0(:) + real(8), intent(in) :: xyz1(:) + logical :: intersects + + real(8) :: x0, y0, z0 ! track start point + real(8) :: x1, y1, z1 ! track end point + real(8) :: xi, yi, zi ! track intersection point with mesh + real(8) :: xm0, ym0, zm0 ! lower-left coordinates of mesh + real(8) :: xm1, ym1, zm1 ! upper-right coordinates of mesh + + ! Copy coordinates of starting point + x0 = xyz0(1) + y0 = xyz0(2) + z0 = xyz0(3) + + ! Copy coordinates of ending point + x1 = xyz1(1) + y1 = xyz1(2) + z1 = xyz1(3) + + ! Copy coordinates of mesh lower_left + xm0 = m % lower_left(1) + ym0 = m % lower_left(2) + zm0 = m % lower_left(3) + + ! Copy coordinates of mesh upper_right + xm1 = m % upper_right(1) + ym1 = m % upper_right(2) + zm1 = m % upper_right(3) + + ! Set default value for intersects + intersects = .false. + + ! Check if line intersects left surface -- calculate the intersection point + ! (y,z) + if ((x0 < xm0 .and. x1 > xm0) .or. (x0 > xm0 .and. x1 < xm0)) then + yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0) + zi = z0 + (xm0 - x0) * (z1 - z0) / (x1 - x0) + if (yi >= ym0 .and. yi < ym1 .and. zi >= zm0 .and. zi < zm1) then + intersects = .true. + return + end if + end if + + ! Check if line intersects back surface -- calculate the intersection point + ! (x,z) + if ((y0 < ym0 .and. y1 > ym0) .or. (y0 > ym0 .and. y1 < ym0)) then + xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0) + zi = z0 + (ym0 - y0) * (z1 - z0) / (y1 - y0) + if (xi >= xm0 .and. xi < xm1 .and. zi >= zm0 .and. zi < zm1) then + intersects = .true. + return + end if + end if + + ! Check if line intersects bottom surface -- calculate the intersection + ! point (x,y) + if ((z0 < zm0 .and. z1 > zm0) .or. (z0 > zm0 .and. z1 < zm0)) then + xi = x0 + (zm0 - z0) * (x1 - x0) / (z1 - z0) + yi = y0 + (zm0 - z0) * (y1 - y0) / (z1 - z0) + if (xi >= xm0 .and. xi < xm1 .and. yi >= ym0 .and. yi < ym1) then + intersects = .true. + return + end if + end if + + ! Check if line intersects right surface -- calculate the intersection point + ! (y,z) + if ((x0 < xm1 .and. x1 > xm1) .or. (x0 > xm1 .and. x1 < xm1)) then + yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0) + zi = z0 + (xm1 - x0) * (z1 - z0) / (x1 - x0) + if (yi >= ym0 .and. yi < ym1 .and. zi >= zm0 .and. zi < zm1) then + intersects = .true. + return + end if + end if + + ! Check if line intersects front surface -- calculate the intersection point + ! (x,z) + if ((y0 < ym1 .and. y1 > ym1) .or. (y0 > ym1 .and. y1 < ym1)) then + xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0) + zi = z0 + (ym1 - y0) * (z1 - z0) / (y1 - y0) + if (xi >= xm0 .and. xi < xm1 .and. zi >= zm0 .and. zi < zm1) then + intersects = .true. + return + end if + end if + + ! Check if line intersects top surface -- calculate the intersection point + ! (x,y) + if ((z0 < zm1 .and. z1 > zm1) .or. (z0 > zm1 .and. z1 < zm1)) then + xi = x0 + (zm1 - z0) * (x1 - x0) / (z1 - z0) + yi = y0 + (zm1 - z0) * (y1 - y0) / (z1 - z0) + if (xi >= xm0 .and. xi < xm1 .and. yi >= ym0 .and. yi < ym1) then + intersects = .true. + return + end if + end if + + end function mesh_intersects_3d + +!=============================================================================== +! TO_HDF5 writes the mesh data to an HDF5 group +!=============================================================================== + + subroutine regular_to_hdf5(this, group) + class(RegularMesh), intent(in) :: this + integer(HID_T), intent(in) :: group + + integer(HID_T) :: mesh_group + + mesh_group = create_group(group, "mesh " // trim(to_str(this % id))) + + call write_dataset(mesh_group, "type", "regular") + call write_dataset(mesh_group, "dimension", this % dimension) + call write_dataset(mesh_group, "lower_left", this % lower_left) + call write_dataset(mesh_group, "upper_right", this % upper_right) + call write_dataset(mesh_group, "width", this % width) + + call close_group(mesh_group) + end subroutine regular_to_hdf5 + +!=============================================================================== +! FREE_MEMORY_MESH deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_mesh() + n_meshes = 0 + if (allocated(meshes)) deallocate(meshes) + call mesh_dict % clear() + end subroutine free_memory_mesh + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_extend_meshes(n, index_start, index_end) result(err) bind(C) + ! Extend the meshes array by n elements + integer(C_INT32_T), value, intent(in) :: n + integer(C_INT32_T), optional, intent(out) :: index_start + integer(C_INT32_T), optional, intent(out) :: index_end + integer(C_INT) :: err + + type(RegularMesh), allocatable :: temp(:) ! temporary meshes array + + if (n_meshes == 0) then + ! Allocate meshes array + allocate(meshes(n)) + else + ! Allocate meshes array with increased size + allocate(temp(n_meshes + n)) + + ! Copy original meshes to temporary array + temp(1:n_meshes) = meshes + + ! Move allocation from temporary array + call move_alloc(FROM=temp, TO=meshes) + end if + + ! Return indices in meshes array + if (present(index_start)) index_start = n_meshes + 1 + if (present(index_end)) index_end = n_meshes + n + n_meshes = n_meshes + n + + err = 0 + end function openmc_extend_meshes + end module mesh_header diff --git a/src/message_passing.F90 b/src/message_passing.F90 index 6c13b00e8..0f2b94d1a 100644 --- a/src/message_passing.F90 +++ b/src/message_passing.F90 @@ -16,7 +16,6 @@ module message_passing integer :: rank = 0 ! rank of process logical :: master = .true. ! master process? logical :: mpi_enabled = .false. ! is MPI in use and initialized? - integer :: mpi_err ! MPI error code #ifdef MPIF08 type(MPI_Datatype) :: MPI_BANK ! MPI datatype for fission bank type(MPI_Comm) :: mpi_intracomm ! MPI intra-communicator diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index ad20aca83..f2aaa00de 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -2,14 +2,16 @@ module mgxs_data use constants use algorithm, only: find + use dict_header, only: DictCharInt use error, only: fatal_error - use geometry_header, only: get_temperatures - use global + use geometry_header, only: get_temperatures, cells use hdf5_interface - use material_header, only: Material + use material_header, only: Material, materials, n_materials use mgxs_header + use nuclide_header, only: n_nuclides use output, only: write_message use set_header, only: SetChar + use settings use stl_vector, only: VectorReal use string, only: to_lower implicit none @@ -24,7 +26,6 @@ contains subroutine read_mgxs() integer :: i ! index in materials array integer :: j ! index over nuclides in material - integer :: i_xsdata ! index in list integer :: i_nuclide ! index in nuclides array character(20) :: name ! name of library to load integer :: representation ! Data representation @@ -34,7 +35,6 @@ contains integer(HID_T) :: file_id integer(HID_T) :: xsdata_group logical :: file_exists - type(DictCharInt) :: xsdata_dict type(VectorReal), allocatable :: temps(:) character(MAX_WORD_LEN) :: word integer, allocatable :: array(:) @@ -51,8 +51,7 @@ contains call write_message("Loading cross section data...", 5) ! Get temperatures - call get_temperatures(cells, materials, material_dict, nuclide_dict, & - n_nuclides_total, temps) + call get_temperatures(temps) ! Open file for reading file_id = file_open(path_cross_sections, 'r', parallel=.true.) @@ -72,7 +71,7 @@ contains end if ! allocate arrays for MGXS storage and cross section cache - allocate(nuclides_MG(n_nuclides_total)) + allocate(nuclides_MG(n_nuclides)) ! ========================================================================== ! READ ALL MGXS CROSS SECTION TABLES @@ -85,7 +84,6 @@ contains name = mat % names(j) if (.not. already_read % contains(name)) then - i_xsdata = xsdata_dict % get_key(to_lower(name)) i_nuclide = mat % nuclide(j) call write_message("Loading " // trim(name) // " data...", 6) @@ -234,7 +232,7 @@ contains kT = cells(i) % sqrtkT(1)**2 end if - i_material = material_dict % get_key(cells(i) % material(j)) + i_material = cells(i) % material(j) ! Add temperature if it hasn't already been added if (find(kTs(i_material), kT) == -1) then diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index faf0fbaff..b28239724 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -209,7 +209,25 @@ module mgxs_header procedure :: calculate_xs => mgxsang_calculate_xs end type MgxsAngle - contains + ! 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 + real(8), allocatable :: energy_bins(:) + + ! Midpoint of the energy group structure + real(8), allocatable :: energy_bin_avg(:) + +contains !=============================================================================== ! MGXS*_FROM_HDF5 reads in the data from the HDF5 Library. At the point of entry @@ -3533,4 +3551,15 @@ module mgxs_header 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/multipole.F90 b/src/multipole.F90 index c59f94727..0282adb7d 100644 --- a/src/multipole.F90 +++ b/src/multipole.F90 @@ -3,10 +3,11 @@ module multipole use hdf5 use constants - use global + use error, only: fatal_error use hdf5_interface use multipole_header, only: MultipoleArray, FIT_T, FIT_A, FIT_F, & MP_FISS, FORM_MLBW, FORM_RM + use nuclide_header, only: nuclides implicit none diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index d2a21bfd9..3309b0ede 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -7,19 +7,19 @@ module nuclide_header use algorithm, only: sort, find use constants - use dict_header, only: DictIntInt + use dict_header, only: DictIntInt, DictCharInt use endf, only: reaction_name, is_fission, is_disappearance use endf_header, only: Function1D, Polynomial, Tabulated1D - use error, only: fatal_error, warning - use hdf5_interface, only: read_attribute, open_group, close_group, & - open_dataset, read_dataset, close_dataset, get_shape, get_datasets, & - object_exists, get_name, get_groups + use error + use hdf5_interface use list_header, only: ListInt use math, only: evaluate_legendre + use message_passing use multipole_header, only: MultipoleArray use product_header, only: AngleEnergyContainer use reaction_header, only: Reaction use secondary_uncorrelated, only: UncorrelatedAngleEnergy + use settings use stl_vector, only: VectorInt, VectorReal use string use urr_header, only: UrrData @@ -96,6 +96,7 @@ module nuclide_header class(Function1D), allocatable :: fission_q_recov ! fragments, neutrons, gammas, betas contains + procedure :: assign_0K_elastic_scattering procedure :: clear => nuclide_clear procedure :: from_hdf5 => nuclide_from_hdf5 procedure :: init_grid => nuclide_init_grid @@ -166,8 +167,80 @@ module nuclide_header character(MAX_FILE_LEN) :: path end type Library + ! Cross section libraries + type(Library), allocatable :: libraries(:) + type(DictCharInt) :: library_dict + + ! Nuclear data for each nuclide + type(Nuclide), allocatable, target :: nuclides(:) + integer(C_INT), bind(C) :: n_nuclides + type(DictCharInt) :: nuclide_dict + + ! Cross section caches + type(NuclideMicroXS), allocatable :: micro_xs(:) ! Cache for each nuclide + type(MaterialMacroXS) :: material_xs ! Cache for current material +!$omp threadprivate(micro_xs, material_xs) + + ! Minimum/maximum energies + real(8) :: energy_min_neutron = ZERO + real(8) :: energy_max_neutron = INFINITY + contains +!=============================================================================== +! ASSIGN_0K_ELASTIC_SCATTERING +!=============================================================================== + + subroutine assign_0K_elastic_scattering(this) + class(Nuclide), intent(inout) :: this + + integer :: i + real(8) :: xs_cdf_sum + + this % resonant = .false. + if (allocated(res_scat_nuclides)) then + ! If resonant nuclides were specified, check the list explicitly + do i = 1, size(res_scat_nuclides) + if (this % name == res_scat_nuclides(i)) then + this % resonant = .true. + + ! Make sure nuclide has 0K data + if (.not. allocated(this % energy_0K)) then + call fatal_error("Cannot treat " // trim(this % name) // " as a & + &resonant scatterer because 0 K elastic scattering data is & + ¬ present.") + end if + + exit + end if + end do + else + ! Otherwise, assume that any that have 0 K elastic scattering data are + ! resonant + this % resonant = allocated(this % energy_0K) + end if + + if (this % resonant) then + ! Build CDF for 0K elastic scattering + xs_cdf_sum = ZERO + allocate(this % xs_cdf(0:size(this % energy_0K))) + this % xs_cdf(0) = ZERO + + associate (E => this % energy_0K, xs => this % elastic_0K) + do i = 1, size(E) - 1 + ! Negative cross sections result in a CDF that is not monotonically + ! increasing. Set all negative xs values to zero. + if (xs(i) < ZERO) xs(i) = ZERO + + ! build xs cdf + xs_cdf_sum = xs_cdf_sum + (sqrt(E(i))*xs(i) + sqrt(E(i+1))*xs(i+1))& + / TWO * (E(i+1) - E(i)) + this % xs_cdf(i) = xs_cdf_sum + end do + end associate + end if + end subroutine assign_0K_elastic_scattering + !=============================================================================== ! NUCLIDE_CLEAR resets and deallocates data in Nuclide !=============================================================================== @@ -521,7 +594,7 @@ contains do i = 1, size(this % reactions) call MTs % push_back(this % reactions(i) % MT) - call this % reaction_index % add_key(this % reactions(i) % MT, i) + call this % reaction_index % set(this % reactions(i) % MT, i) associate (rx => this % reactions(i)) ! Skip total inelastic level scattering, gas production cross sections @@ -745,4 +818,169 @@ contains end subroutine nuclide_init_grid +!=============================================================================== +! CHECK_DATA_VERSION checks for the right version of nuclear data within HDF5 +! files +!=============================================================================== + + subroutine check_data_version(file_id) + integer(HID_T), intent(in) :: file_id + + integer, allocatable :: version(:) + + if (attribute_exists(file_id, 'version')) then + call read_attribute(version, file_id, 'version') + if (version(1) /= HDF5_VERSION(1)) then + call fatal_error("HDF5 data format uses version " // trim(to_str(& + version(1))) // "." // trim(to_str(version(2))) // " whereas & + &your installation of OpenMC expects version " // trim(to_str(& + HDF5_VERSION(1))) // ".x data.") + end if + else + call fatal_error("HDF5 data does not indicate a version. Your & + &installation of OpenMC expects version " // trim(to_str(& + HDF5_VERSION(1))) // ".x data.") + end if + end subroutine check_data_version + +!=============================================================================== +! FREE_MEMORY_NUCLIDE deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_nuclide() + integer :: i + + ! Deallocate cross section data, listings, and cache + if (allocated(nuclides)) then + ! First call the clear routines + do i = 1, size(nuclides) + call nuclides(i) % clear() + end do + deallocate(nuclides) + end if + n_nuclides = 0 + + if (allocated(libraries)) deallocate(libraries) + + call nuclide_dict % clear() + call library_dict % clear() + + end subroutine free_memory_nuclide + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_get_nuclide_index(name, index) result(err) bind(C) + ! Return the index in the nuclides array of a nuclide with a given name + character(kind=C_CHAR), intent(in) :: name(*) + integer(C_INT), intent(out) :: index + integer(C_INT) :: err + + character(:), allocatable :: name_ + + ! Copy array of C_CHARs to normal Fortran string + name_ = to_f_string(name) + + if (allocated(nuclides)) then + if (nuclide_dict % has(to_lower(name_))) then + index = nuclide_dict % get(to_lower(name_)) + err = 0 + else + err = E_DATA + call set_errmsg("No nuclide named '" // trim(name_) // & + "' has been loaded.") + end if + else + err = E_ALLOCATE + call set_errmsg("Memory for nuclides has not been allocated.") + end if + end function openmc_get_nuclide_index + + + function openmc_load_nuclide(name) result(err) bind(C) + ! Load a nuclide from the cross section library + character(kind=C_CHAR), intent(in) :: name(*) + integer(C_INT) :: err + + integer :: i_library + integer :: n + integer(HID_T) :: file_id + integer(HID_T) :: group_id + character(:), allocatable :: name_ + real(8) :: minmax(2) = [ZERO, INFINITY] + type(VectorReal) :: temperature + type(Nuclide), allocatable :: new_nuclides(:) + + ! Copy array of C_CHARs to normal Fortran string + name_ = to_f_string(name) + + err = 0 + if (.not. nuclide_dict % has(to_lower(name_))) then + if (library_dict % has(to_lower(name_))) then + ! allocate extra space in nuclides array + n = n_nuclides + allocate(new_nuclides(n + 1)) + new_nuclides(1:n) = nuclides(:) + call move_alloc(FROM=new_nuclides, TO=nuclides) + n = n + 1 + + i_library = library_dict % get(to_lower(name_)) + + ! Open file and make sure version is sufficient + file_id = file_open(libraries(i_library) % path, 'r') + call check_data_version(file_id) + + ! Read nuclide data from HDF5 + group_id = open_group(file_id, name_) + call nuclides(n) % from_hdf5(group_id, temperature, & + temperature_method, temperature_tolerance, minmax, & + master) + call close_group(group_id) + call file_close(file_id) + + ! Add entry to nuclide dictionary + call nuclide_dict % set(to_lower(name_), n) + n_nuclides = n + + ! Assign resonant scattering data + if (res_scat_on) call nuclides(n) % assign_0K_elastic_scattering() + + ! Initialize nuclide grid + call nuclides(n) % init_grid(energy_min_neutron, & + energy_max_neutron, n_log_bins) + else + err = E_DATA + call set_errmsg("Nuclide '" // trim(name_) // "' is not present & + &in library.") + end if + end if + + end function openmc_load_nuclide + + + function openmc_nuclide_name(index, name) result(err) bind(C) + ! Return the name of a nuclide with a given index + integer(C_INT), value, intent(in) :: index + type(c_ptr), intent(out) :: name + integer(C_INT) :: err + + character(C_CHAR), pointer :: name_ + + err = E_UNASSIGNED + if (allocated(nuclides)) then + if (index >= 1 .and. index <= size(nuclides)) then + name_ => nuclides(index) % name(1:1) + name = C_LOC(name_) + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in nuclides array is out of bounds.") + end if + else + err = E_ALLOCATE + call set_errmsg("Memory for nuclides has not been allocated yet.") + end if + end function openmc_nuclide_name + end module nuclide_header diff --git a/src/output.F90 b/src/output.F90 index 828af717d..f820ce4b9 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -1,24 +1,32 @@ module output + use, intrinsic :: ISO_C_BINDING use, intrinsic :: ISO_FORTRAN_ENV + use cmfd_header use constants + use eigenvalue, only: openmc_get_keff use endf, only: reaction_name use error, only: fatal_error, warning - use geometry_header, only: Cell, Universe, Lattice, RectLattice, & - HexLattice - use global + use geometry_header use math, only: t_percentile - use mesh_header, only: RegularMesh - use mesh, only: mesh_indices_to_bin, bin_to_mesh_indices + use mesh_header, only: RegularMesh, meshes use message_passing, only: master, n_procs + use mgxs_header, only: nuclides_MG use nuclide_header use particle_header, only: LocalCoord, Particle use plot_header use sab_header, only: SAlphaBeta + use settings + use simulation_header + use surface_header, only: surfaces use string, only: to_upper, to_str - use tally_header, only: TallyObject + use tally_header + use tally_derivative_header use tally_filter + use tally_filter_mesh, only: MeshFilter + use tally_filter_header, only: TallyFilterMatch + use timer_header implicit none @@ -529,6 +537,7 @@ contains subroutine print_runtime() + integer :: n_active real(8) :: speed_inactive ! # of neutrons/second in inactive batches real(8) :: speed_active ! # of neutrons/second in active batches character(15) :: string @@ -561,6 +570,7 @@ contains write(ou,100) "Total time elapsed", time_total % elapsed ! Calculate particle rate in active/inactive batches + n_active = n_batches - n_inactive if (restart_run) then if (restart_batch < n_inactive) then speed_inactive = real(n_particles * (n_inactive - restart_batch) * & @@ -608,6 +618,8 @@ contains real(8) :: t_n1 ! t-value with N-1 degrees of freedom real(8) :: t_n3 ! t-value with N-3 degrees of freedom real(8) :: x(2) ! mean and standard deviation + real(C_DOUBLE) :: k_combined(2) + integer(C_INT) :: err ! display header block for results call header("Results", 4) @@ -634,10 +646,13 @@ contains write(ou,102) "k-effective (Track-length)", x(1), t_n1 * x(2) x(:) = mean_stdev(r(:, K_ABSORPTION), n) write(ou,102) "k-effective (Absorption)", x(1), t_n1 * x(2) - if (n > 3) write(ou,102) "Combined k-effective", k_combined(1), & - t_n3 * k_combined(2) + if (n > 3) then + err = openmc_get_keff(k_combined) + write(ou,102) "Combined k-effective", k_combined(1), & + t_n3 * k_combined(2) + end if end if - x(:) = mean_stdev(global_tallies(:, LEAKAGE), n) + x(:) = mean_stdev(r(:, LEAKAGE), n) write(ou,102) "Leakage Fraction", x(1), t_n1 * x(2) end associate else @@ -722,11 +737,13 @@ contains character(36) :: score_names(N_SCORE_TYPES) ! names of scoring function character(36) :: score_name ! names of scoring function ! to be applied at write-time - type(TallyObject), pointer :: t + type(TallyFilterMatch), allocatable :: matches(:) ! Skip if there are no tallies if (n_tallies == 0) return + allocate(matches(n_filters)) + ! Initialize names for scores score_names(abs(SCORE_FLUX)) = "Flux" score_names(abs(SCORE_TOTAL)) = "Total Reaction Rate" @@ -751,6 +768,7 @@ contains score_names(abs(SCORE_INVERSE_VELOCITY)) = "Flux-Weighted Inverse Velocity" score_names(abs(SCORE_FISS_Q_PROMPT)) = "Prompt fission power" score_names(abs(SCORE_FISS_Q_RECOV)) = "Recoverable fission power" + score_names(abs(SCORE_CURRENT)) = "Current" ! Create filename for tally output filename = trim(path_output) // "tallies.out" @@ -767,7 +785,7 @@ contains end if TALLY_LOOP: do i = 1, n_tallies - t => tallies(i) + associate (t => tallies(i) % obj) nr = t % n_realizations if (confidence_intervals) then @@ -809,7 +827,7 @@ contains end if ! Handle surface current tallies separately - if (t % type == TALLY_SURFACE_CURRENT) then + if (t % type == TALLY_MESH_CURRENT) then call write_surface_current(t, unit_tally) cycle end if @@ -822,8 +840,8 @@ contains ! Initialize bins, filter level, and indentation do h = 1, size(t % filter) - call filter_matches(t % filter(h)) % bins % clear() - call filter_matches(t % filter(h)) % bins % push_back(0) + call matches(t % filter(h)) % bins % clear() + call matches(t % filter(h)) % bins % push_back(0) end do j = 1 indent = 0 @@ -834,18 +852,18 @@ contains if (size(t % filter) == 0) exit find_bin ! Increment bin combination - filter_matches(t % filter(j)) % bins % data(1) = & - filter_matches(t % filter(j)) % bins % data(1) + 1 + matches(t % filter(j)) % bins % data(1) = & + matches(t % filter(j)) % bins % data(1) + 1 ! ================================================================= ! REACHED END OF BINS FOR THIS FILTER, MOVE TO NEXT FILTER - if (filter_matches(t % filter(j)) % bins % data(1) > & + if (matches(t % filter(j)) % bins % data(1) > & filters(t % filter(j)) % obj % n_bins) then ! If this is the first filter, then exit if (j == 1) exit print_bin - filter_matches(t % filter(j)) % bins % data(1) = 0 + matches(t % filter(j)) % bins % data(1) = 0 j = j - 1 indent = indent - 2 @@ -859,7 +877,7 @@ contains ! Print current filter information write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & trim(filters(t % filter(j)) % obj % & - text_label(filter_matches(t % filter(j)) % bins % data(1))) + text_label(matches(t % filter(j)) % bins % data(1))) indent = indent + 2 j = j + 1 end if @@ -870,7 +888,7 @@ contains if (size(t % filter) > 0) then write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & trim(filters(t % filter(j)) % obj % & - text_label(filter_matches(t % filter(j)) % bins % data(1))) + text_label(matches(t % filter(j)) % bins % data(1))) end if ! Determine scoring index for this bin combination -- note that unlike @@ -879,7 +897,7 @@ contains filter_index = 1 do h = 1, size(t % filter) - filter_index = filter_index + (max(filter_matches(t % filter(h)) & + filter_index = filter_index + (max(matches(t % filter(h)) & % bins % data(1),1) - 1) * t % stride(h) end do @@ -968,6 +986,7 @@ contains end do print_bin + end associate end do TALLY_LOOP close(UNIT=unit_tally) @@ -1001,6 +1020,9 @@ contains logical :: energy_filters ! energy filters present character(MAX_LINE_LEN) :: string type(RegularMesh), pointer :: m + type(TallyFilterMatch), allocatable :: matches(:) + + allocate(matches(n_filters)) nr = t % n_realizations @@ -1017,8 +1039,8 @@ contains ! initialize bins array do j = 1, size(t % filter) - call filter_matches(t % filter(j)) % bins % clear() - call filter_matches(t % filter(j)) % bins % push_back(1) + call matches(t % filter(j)) % bins % clear() + call matches(t % filter(j)) % bins % push_back(1) end do ! determine how many energy in bins there are @@ -1040,8 +1062,8 @@ contains do i = 1, n_cells ! Get the indices for this cell - call bin_to_mesh_indices(m, i, ijk) - filter_matches(i_filter_mesh) % bins % data(1) = i + call m % get_indices_from_bin(i, ijk) + matches(i_filter_mesh) % bins % data(1) = i ! Write the header for this cell if (n_dim == 1) then @@ -1059,18 +1081,18 @@ contains do l = 1, n if (print_ebin) then ! Set incoming energy bin - filter_matches(i_filter_ein) % bins % data(1) = l + matches(i_filter_ein) % bins % data(1) = l ! Write incoming energy bin write(UNIT=unit_tally, FMT='(3X,A)') & trim(filters(i_filter_ein) % obj % text_label( & - filter_matches(i_filter_ein) % bins % data(1))) + matches(i_filter_ein) % bins % data(1))) end if filter_index = 1 do j = 1, size(t % filter) if (t % filter(j) == i_filter_surf) cycle - filter_index = filter_index + (filter_matches(t % filter(j)) & + filter_index = filter_index + (matches(t % filter(j)) & % bins % data(1) - 1) * t % stride(j) end do diff --git a/src/particle_header.F90 b/src/particle_header.F90 index f0942fc41..b3c0343b3 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -15,6 +15,7 @@ module particle_header !=============================================================================== type LocalCoord + ! Indices in various arrays for this level integer :: cell = NONE integer :: universe = NONE @@ -48,6 +49,10 @@ module particle_header integer :: cell_instance ! offset for distributed properties type(LocalCoord) :: coord(MAX_COORD) ! coordinates for all levels + ! Particle coordinates before crossing a surface + integer :: last_n_coord ! number of current coordinates + integer :: last_cell(MAX_COORD) ! coordinates for all levels + ! Energy Data real(8) :: E ! post-collision energy real(8) :: last_E ! pre-collision energy @@ -142,11 +147,12 @@ contains this % fission = .false. this % delayed_group = 0 this % n_delayed_bank(:) = 0 - this % g = 1 + this % g = NONE ! Set up base level coordinates this % coord(1) % universe = root_universe this % n_coord = 1 + this % last_n_coord = 1 end subroutine initialize_particle @@ -209,6 +215,7 @@ contains this % last_uvw = src % uvw if (run_CE) then this % E = src % E + this % g = NONE else this % g = int(src % E) this % last_g = int(src % E) diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index 4438c48c0..53332c794 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -4,11 +4,15 @@ module particle_restart use bank_header, only: Bank use constants - use global use hdf5_interface, only: file_open, file_close, read_dataset + use mgxs_header, only: energy_bin_avg + use nuclide_header, only: micro_xs, n_nuclides use output, only: write_message, print_particle use particle_header, only: Particle use random_lcg, only: set_particle_seed + use settings + use simulation_header + use tally_header, only: n_tallies use tracking, only: transport use hdf5, only: HID_T @@ -32,7 +36,7 @@ contains ! Set verbosity high verbosity = 10 - allocate(micro_xs(n_nuclides_total)) + allocate(micro_xs(n_nuclides)) ! Initialize the particle to be tracked call p % initialize() diff --git a/src/particle_restart_write.F90 b/src/particle_restart_write.F90 index 08e80ea1b..5addf6056 100644 --- a/src/particle_restart_write.F90 +++ b/src/particle_restart_write.F90 @@ -1,9 +1,10 @@ module particle_restart_write - use bank_header, only: Bank - use global + use bank_header, only: Bank, source_bank use hdf5_interface use particle_header, only: Particle + use settings + use simulation_header use string, only: to_str use hdf5 diff --git a/src/photon_header.F90 b/src/photon_header.F90 index a5cdd9791..1c6f7b714 100644 --- a/src/photon_header.F90 +++ b/src/photon_header.F90 @@ -3,7 +3,7 @@ module photon_header use hdf5, only: HID_T, HSIZE_T, SIZE_T use constants, only: ZERO, HALF, SUBSHELLS - use dict_header, only: DictIntInt + use dict_header, only: DictIntInt, DictCharInt use endf_header, only: Tabulated1D use hdf5_interface @@ -57,6 +57,11 @@ module photon_header procedure :: from_hdf5 => photon_from_hdf5 end type PhotonInteraction + type(PhotonInteraction), allocatable :: elements(:) ! Photon cross sections + integer :: n_elements ! Number of photon cross section tables + + type(DictCharInt) :: element_dict + !=============================================================================== ! ELEMENTMICROXS contains cached microscopic photon cross sections for a ! particular element at the current energy @@ -73,6 +78,9 @@ module photon_header real(8) :: pair_production ! microscopic pair production xs end type ElementMicroXS + type(ElementMicroXS), allocatable :: micro_photon_xs(:) ! Cache for each element +!$omp threadprivate(micro_photon_xs) + contains subroutine photon_from_hdf5(this, group_id) @@ -167,7 +175,7 @@ contains ! Create mapping from designator to index do j = 1, size(SUBSHELLS) if (designators(i) == SUBSHELLS(j)) then - call this % shell_dict % add_key(j, i) + call this % shell_dict % set(j, i) this % shells(i) % index_subshell = j exit end if diff --git a/src/photon_physics.F90 b/src/photon_physics.F90 index 7d4809803..5a5bd55e2 100644 --- a/src/photon_physics.F90 +++ b/src/photon_physics.F90 @@ -352,7 +352,7 @@ contains call p % create_secondary(uvw, E, ELECTRON, run_ce=.true.) ! Fill hole left by emitted auger electron - i_hole = elm % shell_dict % get_key(secondary) + i_hole = elm % shell_dict % get(secondary) call atomic_relaxation(p, elm, i_hole) else ! Radiative transition -- get X-ray energy @@ -363,7 +363,7 @@ contains end if ! Fill hole created by electron transitioning to the photoelectron hole - i_hole = elm % shell_dict % get_key(primary) + i_hole = elm % shell_dict % get(primary) call atomic_relaxation(p, elm, i_hole) end subroutine atomic_relaxation diff --git a/src/physics.F90 b/src/physics.F90 index 46a9dad7d..57bb48968 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -5,22 +5,26 @@ module physics use cross_section, only: elastic_xs_0K use endf, only: reaction_name use error, only: fatal_error, warning - use global - use material_header, only: Material + use material_header, only: Material, materials use math - use mesh, only: get_mesh_indices + use mesh_header, only: meshes use message_passing use nuclide_header use output, only: write_message use particle_header, only: Particle use particle_restart_write, only: write_particle_restart + use photon_header use photon_physics, only: rayleigh_scatter, compton_scatter, & atomic_relaxation use physics_common use random_lcg, only: prn, advance_prn_seed, prn_set_stream use reaction_header, only: Reaction + use sab_header, only: sab_tables use secondary_uncorrelated, only: UncorrelatedAngleEnergy + use settings + use simulation_header use string, only: to_str + use tally_header implicit none @@ -35,11 +39,6 @@ contains type(Particle), intent(inout) :: p - ! Store pre-collision particle properties - p % last_wgt = p % wgt - p % last_E = p % E - p % last_uvw = p % coord(1) % uvw - ! Add to collision counter for particle p % n_collision = p % n_collision + 1 @@ -1399,10 +1398,9 @@ contains integer :: nu_d(MAX_DELAYED_GROUPS) ! number of delayed neutrons born integer :: i ! loop index integer :: nu ! actual number of neutrons produced - integer :: ijk(3) ! indices in ufs mesh + integer :: mesh_bin ! mesh bin for source site real(8) :: nu_t ! total nu real(8) :: weight ! weight adjustment for ufs method - logical :: in_mesh ! source site in ufs mesh? type(Nuclide), pointer :: nuc ! Get pointers @@ -1414,18 +1412,20 @@ contains ! the expected number of fission sites produced if (ufs) then - ! Determine indices on ufs mesh for current location - call get_mesh_indices(ufs_mesh, p % coord(1) % xyz, ijk, in_mesh) - if (.not. in_mesh) then - call write_particle_restart(p) - call fatal_error("Source site outside UFS mesh!") - end if + associate (m => meshes(index_ufs_mesh)) + ! Determine indices on ufs mesh for current location + call m % get_bin(p % coord(1) % xyz, mesh_bin) + if (mesh_bin == NO_BIN_FOUND) then + call write_particle_restart(p) + call fatal_error("Source site outside UFS mesh!") + end if - if (source_frac(1,ijk(1),ijk(2),ijk(3)) /= ZERO) then - weight = ufs_mesh % volume_frac / source_frac(1,ijk(1),ijk(2),ijk(3)) - else - weight = ONE - end if + if (source_frac(1, mesh_bin) /= ZERO) then + weight = m % volume_frac / source_frac(1, mesh_bin) + else + weight = ONE + end if + end associate else weight = ONE end if diff --git a/src/physics_common.F90 b/src/physics_common.F90 index 98d240c07..63bf62ce6 100644 --- a/src/physics_common.F90 +++ b/src/physics_common.F90 @@ -1,9 +1,9 @@ module physics_common use constants - use global, only: weight_cutoff, weight_survive use particle_header, only: Particle use random_lcg, only: prn + use settings, only: weight_cutoff, weight_survive implicit none diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index 198809994..cf382c5b9 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -2,21 +2,25 @@ module physics_mg ! This module contains the multi-group specific physics routines so as to not ! hinder performance of the CE versions with multiple if-thens. + use bank_header use constants use error, only: fatal_error, warning - use global - use material_header, only: Material + use material_header, only: Material, materials use math, only: rotate_angle - use mgxs_header, only: Mgxs, MgxsContainer - use mesh, only: get_mesh_indices + use mesh_header, only: meshes + use mgxs_header use message_passing + use nuclide_header, only: material_xs use output, only: write_message use particle_header, only: Particle use particle_restart_write, only: write_particle_restart use physics_common use random_lcg, only: prn use scattdata_header + use settings + use simulation_header use string, only: to_str + use tally_header implicit none @@ -31,12 +35,6 @@ contains type(Particle), intent(inout) :: p - ! Store pre-collision particle properties - p % last_wgt = p % wgt - p % last_g = p % g - p % last_E = p % E - p % last_uvw = p % coord(1) % uvw - ! Add to collision counter for particle p % n_collision = p % n_collision + 1 @@ -177,12 +175,11 @@ contains integer :: dg ! delayed group integer :: gout ! group out integer :: nu ! actual number of neutrons produced - integer :: ijk(3) ! indices in ufs mesh + integer :: mesh_bin ! mesh bin for source site real(8) :: nu_t ! total nu real(8) :: mu ! fission neutron angular cosine real(8) :: phi ! fission neutron azimuthal angle real(8) :: weight ! weight adjustment for ufs method - logical :: in_mesh ! source site in ufs mesh? class(Mgxs), pointer :: xs ! Get Pointers @@ -194,20 +191,21 @@ contains ! the expected number of fission sites produced if (ufs) then + associate (m => meshes(index_ufs_mesh)) + ! Determine indices on ufs mesh for current location + call m % get_bin(p % coord(1) % xyz, mesh_bin) - ! Determine indices on ufs mesh for current location - call get_mesh_indices(ufs_mesh, p % coord(1) % xyz, ijk, in_mesh) + if (mesh_bin == NO_BIN_FOUND) then + call write_particle_restart(p) + call fatal_error("Source site outside UFS mesh!") + end if - if (.not. in_mesh) then - call write_particle_restart(p) - call fatal_error("Source site outside UFS mesh!") - end if - - if (source_frac(1,ijk(1),ijk(2),ijk(3)) /= ZERO) then - weight = ufs_mesh % volume_frac / source_frac(1,ijk(1),ijk(2),ijk(3)) - else - weight = ONE - end if + if (source_frac(1, mesh_bin) /= ZERO) then + weight = m % volume_frac / source_frac(1, mesh_bin) + else + weight = ONE + end if + end associate else weight = ONE end if diff --git a/src/plot.F90 b/src/plot.F90 index 067d0c56f..fe2ddd13b 100644 --- a/src/plot.F90 +++ b/src/plot.F90 @@ -7,15 +7,14 @@ module plot use constants use error, only: fatal_error use geometry, only: find_cell, check_cell_overlap - use geometry_header, only: Cell, root_universe - use global + use geometry_header, only: Cell, root_universe, cells use hdf5_interface - use mesh, only: get_mesh_indices - use mesh_header, only: RegularMesh use output, only: write_message, time_stamp + use material_header, only: materials use particle_header, only: LocalCoord, Particle use plot_header use progress_header, only: ProgressBar + use settings, only: check_overlaps use string, only: to_str implicit none @@ -241,8 +240,8 @@ contains width = xyz_ur_plot - xyz_ll_plot associate (m => pl % meshlines_mesh) - call get_mesh_indices(m, xyz_ll_plot, ijk_ll(:m % n_dimension), in_mesh) - call get_mesh_indices(m, xyz_ur_plot, ijk_ur(:m % n_dimension), in_mesh) + call m % get_indices(xyz_ll_plot, ijk_ll(:m % n_dimension), in_mesh) + call m % get_indices(xyz_ur_plot, ijk_ur(:m % n_dimension), in_mesh) ! sweep through all meshbins on this plane and draw borders do i = ijk_ll(outer), ijk_ur(outer) diff --git a/src/plot_header.F90 b/src/plot_header.F90 index a6ea9a580..1091bd73a 100644 --- a/src/plot_header.F90 +++ b/src/plot_header.F90 @@ -1,7 +1,10 @@ module plot_header + use, intrinsic :: ISO_C_BINDING + use constants - use mesh_header, only: RegularMesh + use dict_header, only: DictIntInt + use mesh_header, only: RegularMesh implicit none @@ -50,4 +53,23 @@ module plot_header integer, parameter :: PLOT_COLOR_CELLS = 1 integer, parameter :: PLOT_COLOR_MATS = 2 + integer(C_INT32_T), bind(C) :: n_plots ! # of plots + + type(ObjectPlot), allocatable, target :: plots(:) + + ! Dictionary that maps user IDs to indices in 'plots' + type(DictIntInt) :: plot_dict + +contains + +!=============================================================================== +! FREE_MEMORY_PLOT deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_plot() + n_plots = 0 + if (allocated(plots)) deallocate(plots) + call plot_dict % clear() + end subroutine free_memory_plot + end module plot_header diff --git a/src/random_lcg.F90 b/src/random_lcg.F90 index 287bbba6d..685557f07 100644 --- a/src/random_lcg.F90 +++ b/src/random_lcg.F90 @@ -1,5 +1,7 @@ module random_lcg + use, intrinsic :: ISO_C_BINDING + use constants implicit none @@ -7,28 +9,29 @@ module random_lcg private save - ! Random number seed - integer(8), public :: seed = 1_8 + ! Starting seed + integer(C_INT64_T), public, bind(C) :: seed = 1_8 - integer(8) :: prn_seed0 ! original seed - integer(8) :: prn_seed(N_STREAMS) ! current seed - integer(8) :: prn_mult ! multiplication factor, g - integer(8) :: prn_add ! additive factor, c - integer :: prn_bits ! number of bits, M - integer(8) :: prn_mod ! 2^M - integer(8) :: prn_mask ! 2^M - 1 - integer(8) :: prn_stride ! stride between particles - real(8) :: prn_norm ! 2^(-M) - integer :: stream ! current RNG stream + ! LCG parameters + integer(C_INT64_T), parameter :: prn_mult = 2806196910506780709_8 ! multiplication factor, g + integer(C_INT64_T), parameter :: prn_add = 1_8 ! additive factor, c + integer, parameter :: prn_bits = 63 ! number of bits, M + integer(C_INT64_T), parameter :: prn_mod = ibset(0_8, prn_bits) ! 2^M + integer(C_INT64_T), parameter :: prn_mask = not(prn_mod) ! 2^M - 1 + integer(C_INT64_T), parameter :: prn_stride = 152917_8 ! stride between particles + real(C_DOUBLE), parameter :: prn_norm = 2._8**(-prn_bits) ! 2^(-M) + ! Current PRNG state + integer(C_INT64_T) :: prn_seed(N_STREAMS) ! current seed + integer :: stream ! current RNG stream !$omp threadprivate(prn_seed, stream) public :: prn public :: future_prn - public :: initialize_prng public :: set_particle_seed public :: advance_prn_seed public :: prn_set_stream + public :: openmc_set_seed contains @@ -38,10 +41,10 @@ contains function prn() result(pseudo_rn) - real(8) :: pseudo_rn + real(C_DOUBLE) :: pseudo_rn - ! This algorithm uses bit-masking to find the next integer(8) value to be - ! used to calculate the random number + ! This algorithm uses bit-masking to find the next integer(C_INT64_T) value + ! to be used to calculate the random number prn_seed(stream) = iand(prn_mult*prn_seed(stream) + prn_add, prn_mask) @@ -59,40 +62,14 @@ contains function future_prn(n) result(pseudo_rn) - integer(8), intent(in) :: n ! number of prns to skip + integer(C_INT64_T), intent(in) :: n ! number of prns to skip - real(8) :: pseudo_rn + real(C_DOUBLE) :: pseudo_rn pseudo_rn = future_seed(n, prn_seed(stream)) * prn_norm end function future_prn -!=============================================================================== -! INITIALIZE_PRNG sets up the random number generator, determining the seed and -! values for g, c, and m. -!=============================================================================== - - subroutine initialize_prng() - - integer :: i - - prn_seed0 = seed -!$omp parallel - do i = 1, N_STREAMS - prn_seed(i) = prn_seed0 + i - 1 - end do - stream = STREAM_TRACKING -!$omp end parallel - prn_mult = 2806196910506780709_8 - prn_add = 1_8 - prn_bits = 63 - prn_mod = ibset(0_8, prn_bits) ! clever way of calculating 2**bits - prn_mask = prn_mod - 1_8 - prn_stride = 152917_8 - prn_norm = 2._8**(-prn_bits) - - end subroutine initialize_prng - !=============================================================================== ! SET_PARTICLE_SEED sets the seed to a unique value based on the ID of the ! particle @@ -100,12 +77,12 @@ contains subroutine set_particle_seed(id) - integer(8), intent(in) :: id + integer(C_INT64_T), intent(in) :: id integer :: i do i = 1, N_STREAMS - prn_seed(i) = future_seed(id*prn_stride, prn_seed0 + i - 1) + prn_seed(i) = future_seed(id*prn_stride, seed + i - 1) end do end subroutine set_particle_seed @@ -117,7 +94,7 @@ contains subroutine advance_prn_seed(n) - integer(8), intent(in) :: n ! number of seeds to skip + integer(C_INT64_T), intent(in) :: n ! number of seeds to skip prn_seed(stream) = future_seed(n, prn_seed(stream)) @@ -132,15 +109,15 @@ contains function future_seed(n, seed) result(new_seed) - integer(8), intent(in) :: n ! number of seeds to skip - integer(8), intent(in) :: seed ! original seed - integer(8) :: new_seed ! new seed + integer(C_INT64_T), intent(in) :: n ! number of seeds to skip + integer(C_INT64_T), intent(in) :: seed ! original seed + integer(C_INT64_T) :: new_seed ! new seed - integer(8) :: nskip ! positive number of seeds to skip - integer(8) :: g ! original multiplicative constant - integer(8) :: c ! original additive constnat - integer(8) :: g_new ! new effective multiplicative constant - integer(8) :: c_new ! new effective additive constant + integer(C_INT64_T) :: nskip ! positive number of seeds to skip + integer(C_INT64_T) :: g ! original multiplicative constant + integer(C_INT64_T) :: c ! original additive constnat + integer(C_INT64_T) :: g_new ! new effective multiplicative constant + integer(C_INT64_T) :: c_new ! new effective additive constant ! In cases where we want to skip backwards, we add the period of the random ! number generator until the number of PRNs to skip is positive since @@ -198,4 +175,26 @@ contains end subroutine prn_set_stream +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_set_seed(new_seed) result(err) bind(C) + ! Saves the starting seed and sets up the PRNG thread state + integer(C_INT64_T), value, intent(in) :: new_seed + integer(C_INT) :: err + + integer :: i + + err = 0 + seed = new_seed +!$omp parallel + do i = 1, N_STREAMS + prn_seed(i) = seed + i - 1 + end do + stream = STREAM_TRACKING +!$omp end parallel + + end function openmc_set_seed + end module random_lcg diff --git a/src/relaxng/settings.rnc b/src/relaxng/settings.rnc index 62caeed95..21b0016a6 100644 --- a/src/relaxng/settings.rnc +++ b/src/relaxng/settings.rnc @@ -12,14 +12,7 @@ element settings { element energy_mode { ( "continuous-energy" | "ce" | "CE" | "multi-group" | "mg" | "MG" ) }? & - element entropy { - (element dimension { list { xsd:int+ } } | - attribute dimension { list { xsd:int+ } })? & - (element lower_left { list { xsd:double+ } } | - attribute lower_left { list { xsd:double+ } }) & - (element upper_right { list { xsd:double+ } } | - attribute upper_right { list { xsd:double+ } }) - }? & + element entropy_mesh { xsd:positiveInteger }? & element generations_per_batch { xsd:positiveInteger }? & @@ -34,6 +27,22 @@ element settings { element max_order { xsd:nonNegativeInteger }? & + element mesh { + (element id { xsd:int } | attribute id { xsd:int }) & + (element type { ( "regular" ) } | + attribute type { ( "regular" ) })? & + (element dimension { list { xsd:positiveInteger+ } } | + attribute dimension { list { xsd:positiveInteger+ } }) & + (element lower_left { list { xsd:double+ } } | + attribute lower_left { list { xsd:double+ } }) & + ( + (element upper_right { list { xsd:double+ } } | + attribute upper_right { list { xsd:double+ } }) | + (element width { list { xsd:double+ } } | + attribute width { list { xsd:double+ } }) + ) + }* & + element no_reduce { xsd:boolean }? & element output { @@ -133,6 +142,8 @@ element settings { (element batch_interval { xsd:positiveInteger } | attribute batch_interval { xsd:positiveInteger })? }? & + element ufs_mesh { xsd:positiveInteger }? & + element verbosity { xsd:positiveInteger }? & element volume_calc { @@ -148,15 +159,6 @@ element settings { attribute upper_right { list { xsd:double+ } }) }* & - element uniform_fs{ - (element dimension { list { xsd:positiveInteger+ } } | - attribute dimension { list { xsd:positiveInteger+ } }) & - (element lower_left { list { xsd:double+ } } | - attribute lower_left { list { xsd:double+ } }) & - (element upper_right { list { xsd:double+ } } | - attribute upper_right { list { xsd:double+ } }) - }? & - element resonance_scattering { (element enable { xsd:boolean } | attribute enable { xsd:boolean })? & (element method { xsd:string } | attribute method { xsd:string })? & diff --git a/src/relaxng/settings.rng b/src/relaxng/settings.rng index c478bc5f2..36fb16b79 100644 --- a/src/relaxng/settings.rng +++ b/src/relaxng/settings.rng @@ -62,59 +62,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -159,6 +108,96 @@ + + + + + + + + + + + + + + + regular + + + regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -595,6 +634,11 @@ + + + + + @@ -670,60 +714,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/relaxng/tallies.rnc b/src/relaxng/tallies.rnc index 4674fd4c0..5ab8971e6 100644 --- a/src/relaxng/tallies.rnc +++ b/src/relaxng/tallies.rnc @@ -35,12 +35,14 @@ element tallies { element filter { (element id { xsd:int } | attribute id { xsd:int }) & ( - ( (element type { ( "cell" | "cellborn" | "material" | "universe" | - "surface" | "distribcell" | "mesh" | "energy" | "energyout" | "mu" | - "polar" | "azimuthal" | "delayedgroup" | "energyfunction") } | - attribute type { ( "cell" | "cellborn" | "material" | "universe" | - "surface" | "distribcell" | "mesh" | "energy" | "energyout" | "mu" | - "polar" | "azimuthal" | "delayedgroup" | "energyfunction") }) & + ( (element type { ( "cell" | "cellfrom" | "cellborn" | "material" | + "universe" | "surface" | "distribcell" | "mesh" | "energy" | + "energyout" | "mu" | "polar" | "azimuthal" | "delayedgroup" | + "energyfunction") } | + attribute type { ( "cell" | "cellfrom" | "cellborn" | "material" | + "universe" | "surface" | "distribcell" | "mesh" | "energy" | + "energyout" | "mu" | "polar" | "azimuthal" | "delayedgroup" | + "energyfunction") }) & (element bins { list { xsd:double+ } } | attribute bins { list { xsd:double+ } }) ) | diff --git a/src/relaxng/tallies.rng b/src/relaxng/tallies.rng index 1c68e57e8..bad2fdce9 100644 --- a/src/relaxng/tallies.rng +++ b/src/relaxng/tallies.rng @@ -168,6 +168,7 @@ cell + cellfrom cellborn material universe @@ -186,6 +187,7 @@ cell + cellfrom cellborn material universe diff --git a/src/sab_header.F90 b/src/sab_header.F90 index e0b79f248..6dd617790 100644 --- a/src/sab_header.F90 +++ b/src/sab_header.F90 @@ -1,10 +1,11 @@ module sab_header + use, intrinsic :: ISO_C_BINDING use, intrinsic :: ISO_FORTRAN_ENV use algorithm, only: find, sort use constants - use dict_header, only: DictIntInt + use dict_header, only: DictIntInt, DictCharInt use distribution_univariate, only: Tabular use error, only: warning, fatal_error use hdf5, only: HID_T, HSIZE_T, SIZE_T @@ -78,6 +79,11 @@ module sab_header procedure :: from_hdf5 => salphabeta_from_hdf5 end type SAlphaBeta + ! S(a,b) tables + type(SAlphaBeta), allocatable, target :: sab_tables(:) + integer(C_INT), bind(C) :: n_sab_tables + type(DictCharInt) :: sab_dict + contains subroutine salphabeta_from_hdf5(this, group_id, temperature, method, & @@ -354,4 +360,14 @@ contains call close_group(kT_group) end subroutine salphabeta_from_hdf5 +!=============================================================================== +! FREE_MEMORY_SAB deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_sab() + n_sab_tables = 0 + if (allocated(sab_tables)) deallocate(sab_tables) + call sab_dict % clear() + end subroutine free_memory_sab + end module sab_header diff --git a/src/settings.F90 b/src/settings.F90 new file mode 100644 index 000000000..4fcb3f5b4 --- /dev/null +++ b/src/settings.F90 @@ -0,0 +1,158 @@ +module settings + + use, intrinsic :: ISO_C_BINDING + + use constants + use set_header, only: SetInt + + implicit none + + ! ============================================================================ + ! ENERGY TREATMENT RELATED VARIABLES + logical :: run_CE = .true. ! Run in CE mode? + + ! ============================================================================ + ! CONTINUOUS-ENERGY CROSS SECTION RELATED VARIABLES + + ! Unreoslved resonance probablity tables + logical :: urr_ptables_on = .true. + + ! Default temperature and method for choosing temperatures + integer :: temperature_method = TEMPERATURE_NEAREST + logical :: temperature_multipole = .false. + real(8) :: temperature_tolerance = 10.0_8 + real(8) :: temperature_default = 293.6_8 + real(8) :: temperature_range(2) = [ZERO, ZERO] + + integer :: n_log_bins ! number of bins for logarithmic grid + + logical :: photon_transport = .false. + integer :: electron_treatment = ELECTRON_LED + + ! ============================================================================ + ! MULTI-GROUP CROSS SECTION RELATED VARIABLES + + ! Maximum Data Order + integer :: 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 + + ! Assume all tallies are spatially distinct + logical :: assume_separate = .false. + + ! 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 + integer(C_INT32_T), bind(C) :: gen_per_batch = 1 ! # of generations per batch + + integer :: n_max_batches ! max # of batches + integer :: n_batch_interval = 1 ! batch interval for triggers + logical :: pred_batches = .false. ! predict batches for triggers + logical :: trigger_on = .false. ! flag for turning triggers on/off + + logical :: entropy_on = .false. + integer :: index_entropy_mesh = -1 + + logical :: ufs = .false. + integer :: index_ufs_mesh = -1 + + ! Write source at end of simulation + logical :: source_separate = .false. + logical :: source_write = .true. + logical :: source_latest = .false. + + ! Variance reduction settins + logical :: survival_biasing = .false. + real(8) :: weight_cutoff = 0.25_8 + real(8) :: energy_cutoff(4) = [ZERO, 1000.0_8, ZERO, ZERO] + real(8) :: weight_survive = ONE + + ! Mode to run in (fixed source, eigenvalue, plotting, etc) + integer(C_INT), bind(C) :: run_mode = NONE + + ! Restart run + logical :: restart_run = .false. + + ! The verbosity controls how much information will be printed to the screen + ! and in logs + integer(C_INT), bind(C) :: verbosity = 7 + + logical :: check_overlaps = .false. + + ! Trace for single particle + integer :: trace_batch + integer :: trace_gen + integer(8) :: trace_particle + + ! Particle tracks + logical :: write_all_tracks = .false. + integer, allocatable :: track_identifiers(:,:) + + ! Particle restart run + logical :: particle_restart_run = .false. + + ! Write out initial source + logical :: write_initial_source = .false. + + ! Whether create fission neutrons or not. Only applied for MODE_FIXEDSOURCE + logical :: create_fission_neutrons = .true. + + ! Information about state points to be written + integer :: n_state_points = 0 + type(SetInt) :: statepoint_batch + + ! Information about source points to be written + integer :: n_source_points = 0 + type(SetInt) :: sourcepoint_batch + + character(MAX_FILE_LEN) :: path_input ! Path to input file + character(MAX_FILE_LEN) :: path_cross_sections = '' ! Path to cross_sections.xml + character(MAX_FILE_LEN) :: path_multipole ! Path to wmp library + character(MAX_FILE_LEN) :: path_source = '' ! Path to binary source + character(MAX_FILE_LEN) :: path_state_point ! Path to binary state point + character(MAX_FILE_LEN) :: path_source_point ! Path to binary source point + character(MAX_FILE_LEN) :: path_particle_restart ! Path to particle restart + character(MAX_FILE_LEN) :: path_output = '' ! Path to output directory + + ! Various output options + logical :: output_summary = .true. + logical :: output_tallies = .true. + + ! Resonance scattering settings + logical :: res_scat_on = .false. ! is resonance scattering treated? + integer :: res_scat_method = RES_SCAT_ARES ! resonance scattering method + real(8) :: res_scat_energy_min = 0.01_8 + real(8) :: res_scat_energy_max = 1000.0_8 + character(10), allocatable :: res_scat_nuclides(:) + + ! Is CMFD active + logical :: cmfd_run = .false. + + ! No reduction at end of batch + logical :: reduce_tallies = .true. + +contains + +!=============================================================================== +! FREE_MEMORY_SETTINGS deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_settings() + if (allocated(res_scat_nuclides)) deallocate(res_scat_nuclides) + if (allocated(track_identifiers)) deallocate(track_identifiers) + + call statepoint_batch % clear() + call sourcepoint_batch % clear() + end subroutine free_memory_settings + +end module settings diff --git a/src/simulation.F90 b/src/simulation.F90 index 2c26bdc31..78141f310 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -2,26 +2,42 @@ module simulation use, intrinsic :: ISO_C_BINDING +#ifdef _OPENMP + use omp_lib +#endif + + use bank_header, only: source_bank use cmfd_execute, only: cmfd_init_batch, execute_cmfd + use cmfd_header, only: cmfd_on use constants, only: ZERO use eigenvalue, only: count_source_for_ufs, calculate_average_keff, & - calculate_combined_keff, calculate_generation_keff, & - shannon_entropy, synchronize_bank, keff_generation, & - k_sum + calculate_generation_keff, shannon_entropy, & + synchronize_bank, keff_generation, k_sum #ifdef _OPENMP use eigenvalue, only: join_bank_from_threads #endif - use global + use error, only: fatal_error + use geometry_header, only: n_cells use message_passing + use mgxs_header, only: energy_bins, energy_bin_avg + use nuclide_header, only: micro_xs, n_nuclides use output, only: write_message, header, print_columns, & print_batch_keff, print_generation, print_runtime, & print_results, print_overlap_check, write_tallies use particle_header, only: Particle + use photon_header, only: micro_photon_xs, n_elements use random_lcg, only: set_particle_seed + use settings + use simulation_header use source, only: initialize_source, sample_external_source - use state_point, only: write_state_point, write_source_point + use state_point, only: write_state_point, write_source_point, load_state_point use string, only: to_str - use tally, only: synchronize_tallies, setup_active_usertallies + use tally, only: accumulate_tallies, setup_active_tallies, & + init_tally_routines + use tally_header + use tally_filter_header, only: filter_matches, n_filters + use tally_derivative_header, only: tally_derivs + use timer_header use trigger, only: check_triggers use tracking, only: transport @@ -159,6 +175,8 @@ contains subroutine initialize_batch() + integer :: i + if (run_mode == MODE_FIXEDSOURCE) then call write_message("Simulating batch " // trim(to_str(current_batch)) & // "...", 6) @@ -172,12 +190,9 @@ contains call time_inactive % stop() call time_active % start() - ! Enable active batches (and tallies_on if it hasn't been enabled) - active_batches = .true. - tallies_on = .true. - - ! Add user tallies to active tallies list - call setup_active_usertallies() + do i = 1, n_tallies + tallies(i) % obj % active = .true. + end do end if ! check CMFD initialize batch @@ -185,6 +200,9 @@ contains if (cmfd_run) call cmfd_init_batch() end if + ! Add user tallies to active tallies list + call setup_active_tallies() + end subroutine initialize_batch !=============================================================================== @@ -286,13 +304,17 @@ contains subroutine finalize_batch() - ! Collect tallies +#ifdef MPI + integer :: mpi_err ! MPI error code +#endif + + ! Reduce tallies onto master process and accumulate call time_tallies % start() - call synchronize_tallies() + call accumulate_tallies() call time_tallies % stop() ! Reset global tally results - if (.not. active_batches) then + if (current_batch <= n_inactive) then global_tallies(:,:) = ZERO n_realizations = 0 end if @@ -300,9 +322,6 @@ contains if (run_mode == MODE_EIGENVALUE) then ! Perform CMFD calculation if on if (cmfd_on) call execute_cmfd() - - ! Calculate combined estimate of k-effective - if (master) call calculate_combined_keff() end if ! Check_triggers @@ -327,13 +346,6 @@ contains call write_source_point() end if - if (master .and. current_batch == n_max_batches .and. & - run_mode == MODE_EIGENVALUE) then - ! Make sure combined estimate of k-effective is calculated at the last - ! batch in case no state point is written - call calculate_combined_keff() - end if - end subroutine finalize_batch !=============================================================================== @@ -376,12 +388,35 @@ contains subroutine initialize_simulation() + ! Set up tally procedure pointers + call init_tally_routines() + + ! Determine how much work each processor should do + call calculate_work() + + ! Allocate source bank, and for eigenvalue simulations also allocate the + ! fission bank + call allocate_banks() + + ! Allocate tally results arrays if they're not allocated yet + call configure_tallies() + !$omp parallel - allocate(micro_xs(n_nuclides_total)) + ! Allocate array for microscopic cross section cache + allocate(micro_xs(n_nuclides)) allocate(micro_photon_xs(n_elements)) + + ! Allocate array for matching filter bins + allocate(filter_matches(n_filters)) !$omp end parallel - if (.not. restart_run) call initialize_source() + ! If this is a restart run, load the state point data and binary source + ! file + if (restart_run) then + call load_state_point() + else + call initialize_source() + end if ! Display header if (master) then @@ -402,9 +437,16 @@ contains subroutine finalize_simulation() +#ifdef MPI + integer :: i ! loop index for tallies + integer :: n ! size of arrays + integer :: mpi_err ! MPI error code + integer(8) :: temp + real(8) :: tempr(3) ! temporary array for communication +#endif + !$omp parallel - deallocate(micro_xs) - deallocate(micro_photon_xs) + deallocate(micro_xs, micro_photon_xs, filter_matches) !$omp end parallel ! Increment total number of generations @@ -413,9 +455,39 @@ contains ! Start finalization timer call time_finalize % start() +#ifdef MPI + ! Broadcast tally results so that each process has access to results + if (allocated(tallies)) then + do i = 1, size(tallies) + n = size(tallies(i) % obj % results) + call MPI_BCAST(tallies(i) % obj % results, n, MPI_DOUBLE, 0, & + mpi_intracomm, mpi_err) + end do + end if + + ! Also broadcast global tally results + n = size(global_tallies) + call MPI_BCAST(global_tallies, n, MPI_DOUBLE, 0, mpi_intracomm, mpi_err) + + ! These guys are needed so that non-master processes can calculate the + ! combined estimate of k-effective + tempr(1) = k_col_abs + tempr(2) = k_col_tra + tempr(3) = k_abs_tra + call MPI_BCAST(tempr, 3, MPI_REAL8, 0, mpi_intracomm, mpi_err) + k_col_abs = tempr(1) + k_col_tra = tempr(2) + k_abs_tra = tempr(3) + + if (check_overlaps) then + call MPI_REDUCE(overlap_check_cnt, temp, n_cells, MPI_INTEGER8, & + MPI_SUM, 0, mpi_intracomm, mpi_err) + overlap_check_cnt = temp + end if +#endif + ! Write tally results to tallies.out if (output_tallies .and. master) call write_tallies() - if (check_overlaps) call reduce_overlap_count() ! Stop timers and show timing statistics call time_finalize%stop() @@ -429,21 +501,95 @@ contains end subroutine finalize_simulation !=============================================================================== -! REDUCE_OVERLAP_COUNT accumulates cell overlap check counts to master +! CALCULATE_WORK determines how many particles each processor should simulate !=============================================================================== - subroutine reduce_overlap_count() + subroutine calculate_work() -#ifdef MPI - if (master) then - call MPI_REDUCE(MPI_IN_PLACE, overlap_check_cnt, n_cells, & - MPI_INTEGER8, MPI_SUM, 0, mpi_intracomm, mpi_err) - else - call MPI_REDUCE(overlap_check_cnt, overlap_check_cnt, n_cells, & - MPI_INTEGER8, MPI_SUM, 0, mpi_intracomm, mpi_err) + integer :: i ! loop index + integer :: remainder ! Number of processors with one extra particle + integer(8) :: i_bank ! Running count of number of particles + integer(8) :: min_work ! Minimum number of particles on each proc + integer(8) :: work_i ! Number of particles on rank i + + if (.not. allocated(work_index)) allocate(work_index(0:n_procs)) + + ! Determine minimum amount of particles to simulate on each processor + min_work = n_particles/n_procs + + ! Determine number of processors that have one extra particle + remainder = int(mod(n_particles, int(n_procs,8)), 4) + + i_bank = 0 + work_index(0) = 0 + do i = 0, n_procs - 1 + ! Number of particles for rank i + if (i < remainder) then + work_i = min_work + 1 + else + work_i = min_work + end if + + ! Set number of particles + if (rank == i) work = work_i + + ! Set index into source bank for rank i + i_bank = i_bank + work_i + work_index(i+1) = i_bank + end do + + end subroutine calculate_work + +!=============================================================================== +! ALLOCATE_BANKS allocates memory for the fission and source banks +!=============================================================================== + + subroutine allocate_banks() + + integer :: alloc_err ! allocation error code + + ! Allocate source bank + if (allocated(source_bank)) deallocate(source_bank) + allocate(source_bank(work), STAT=alloc_err) + + ! Check for allocation errors + if (alloc_err /= 0) then + call fatal_error("Failed to allocate source bank.") end if + + if (run_mode == MODE_EIGENVALUE) then + +#ifdef _OPENMP + ! If OpenMP is being used, each thread needs its own private fission + ! bank. Since the private fission banks need to be combined at the end of + ! a generation, there is also a 'master_fission_bank' that is used to + ! collect the sites from each thread. + + n_threads = omp_get_max_threads() + +!$omp parallel + thread_id = omp_get_thread_num() + + if (allocated(fission_bank)) deallocate(fission_bank) + if (thread_id == 0) then + allocate(fission_bank(3*work)) + else + allocate(fission_bank(3*work/n_threads)) + end if +!$omp end parallel + if (allocated(master_fission_bank)) deallocate(master_fission_bank) + allocate(master_fission_bank(3*work), STAT=alloc_err) +#else + if (allocated(fission_bank)) deallocate(fission_bank) + allocate(fission_bank(3*work), STAT=alloc_err) #endif - end subroutine reduce_overlap_count + ! Check for allocation errors + if (alloc_err /= 0) then + call fatal_error("Failed to allocate fission bank.") + end if + end if + + end subroutine allocate_banks end module simulation diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 new file mode 100644 index 000000000..cfb75b449 --- /dev/null +++ b/src/simulation_header.F90 @@ -0,0 +1,95 @@ +module simulation_header + + use bank_header + use constants + use settings, only: gen_per_batch + + implicit none + + ! ============================================================================ + ! GEOMETRY-RELATED VARIABLES + + ! Number of lost particles + integer :: n_lost_particles + + real(8) :: log_spacing ! spacing on logarithmic grid + + ! ============================================================================ + ! EIGENVALUE SIMULATION VARIABLES + + integer :: current_batch ! current batch + integer :: current_gen ! current generation within a batch + integer :: total_gen = 0 ! total number of generations simulated + + ! ============================================================================ + ! TALLY PRECISION TRIGGER VARIABLES + + logical :: satisfy_triggers = .false. ! whether triggers are satisfied + + integer(8) :: work ! number of particles per processor + integer(8), allocatable :: work_index(:) ! starting index in source bank for each process + integer(8) :: current_work ! index in source bank of current history simulated + + ! Temporary k-effective values + real(8), allocatable :: k_generation(:) ! single-generation estimates of k + real(8) :: keff = ONE ! average k over active batches + real(8) :: keff_std ! standard deviation of average k + real(8) :: k_col_abs = ZERO ! sum over batches of k_collision * k_absorption + real(8) :: k_col_tra = ZERO ! sum over batches of k_collision * k_tracklength + real(8) :: k_abs_tra = ZERO ! sum over batches of k_absorption * k_tracklength + + ! Shannon entropy + real(8), allocatable :: entropy(:) ! shannon entropy at each generation + real(8), allocatable :: entropy_p(:,:) ! % of source sites in each cell + + ! Uniform fission source weighting + real(8), allocatable :: source_frac(:,:) + + ! ============================================================================ + ! PARALLEL PROCESSING VARIABLES + +#ifdef _OPENMP + integer :: n_threads = NONE ! number of OpenMP threads + integer :: thread_id ! ID of a given thread +#endif + + ! ============================================================================ + ! MISCELLANEOUS VARIABLES + + integer :: restart_batch + + ! Flag for enabling cell overlap checking during transport + integer(8), allocatable :: overlap_check_cnt(:) + + logical :: trace + + ! Number of distribcell maps + integer :: n_maps + +!$omp threadprivate(trace, thread_id, current_work) + +contains + +!=============================================================================== +! OVERALL_GENERATION determines the overall generation number +!=============================================================================== + + pure function overall_generation() result(gen) + integer :: gen + gen = gen_per_batch*(current_batch - 1) + current_gen + end function overall_generation + +!=============================================================================== +! FREE_MEMORY_SIMULATION deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_simulation() + if (allocated(overlap_check_cnt)) deallocate(overlap_check_cnt) + if (allocated(k_generation)) deallocate(k_generation) + if (allocated(entropy)) deallocate(entropy) + if (allocated(entropy_p)) deallocate(entropy_p) + if (allocated(source_frac)) deallocate(source_frac) + if (allocated(work_index)) deallocate(work_index) + end subroutine free_memory_simulation + +end module simulation_header diff --git a/src/source.F90 b/src/source.F90 index cf7cd5b32..0689cb403 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -6,20 +6,23 @@ module source #endif use algorithm, only: binary_search - use bank_header, only: Bank + use bank_header, only: Bank, source_bank use constants use distribution_univariate, only: Discrete use distribution_multivariate, only: SpatialBox use error, only: fatal_error use geometry, only: find_cell - use global use hdf5_interface, only: file_create, file_open, file_close, read_dataset + use math use message_passing, only: rank + use mgxs_header, only: 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 + use settings + use simulation_header + use source_header, only: external_source use string, only: to_str - use math use state_point, only: read_source_bank, write_source_bank implicit none @@ -103,14 +106,7 @@ contains integer :: i ! dummy loop index integer :: n_source ! number of source distributions real(8) :: c ! cumulative frequency - real(8) :: r(3) ! sampled coordinates - logical :: found ! Does the source particle exist within geometry? - type(Particle) :: p ! Temporary particle for using find_cell - integer, save :: n_accept = 0 ! Number of samples accepted - integer, save :: n_reject = 0 ! Number of samples rejected - - ! Set weight to one by default - site % wgt = ONE + real(8) :: xi ! Set the random number generator to the source stream. call prn_set_stream(STREAM_SOURCE) @@ -118,87 +114,18 @@ contains ! Sample from among multiple source distributions n_source = size(external_source) if (n_source > 1) then - r(1) = prn()*sum(external_source(:) % strength) + xi = prn()*sum(external_source(:) % strength) c = ZERO do i = 1, n_source c = c + external_source(i) % strength - if (r(1) < c) exit + if (xi < c) exit end do else i = 1 end if - ! Repeat sampling source location until a good site has been found - found = .false. - do while (.not. found) - ! Set particle defaults - call p % initialize() - - ! Set particle type - site % particle = external_source(i) % particle - - ! Sample spatial distribution - site % xyz(:) = external_source(i) % space % sample() - - ! Fill p with needed data - p % coord(1) % xyz(:) = site % xyz - p % coord(1) % uvw(:) = [ ONE, ZERO, ZERO ] - - ! Now search to see if location exists in geometry - call find_cell(p, found) - - ! Check if spatial site is in fissionable material - if (found) then - select type (space => external_source(i) % space) - type is (SpatialBox) - if (space % only_fissionable) then - if (p % material == MATERIAL_VOID) then - found = .false. - elseif (.not. materials(p % material) % fissionable) then - found = .false. - end if - end if - end select - end if - - ! Check for rejection - if (.not. found) then - n_reject = n_reject + 1 - if (n_reject >= EXTSRC_REJECT_THRESHOLD .and. & - real(n_accept, 8)/n_reject <= EXTSRC_REJECT_FRACTION) then - call fatal_error("More than 95% of external source sites sampled & - &were rejected. Please check your external source definition.") - end if - end if - end do - - ! Increment number of accepted samples - n_accept = n_accept + 1 - - call p % clear() - - ! Sample angle - site % uvw(:) = external_source(i) % angle % sample() - - ! Check for monoenergetic source above maximum neutron energy - select type (energy => external_source(i) % energy) - type is (Discrete) - if (any(energy % x >= energy_max_neutron)) then - call fatal_error("Source energy above range of energies of at least & - &one cross section table") - end if - end select - - do - ! Sample energy spectrum - site % E = external_source(i) % energy % sample() - - ! resample if energy is greater than maximum neutron energy - if (site % E < energy_max_neutron) exit - end do - - ! Set delayed group - site % delayed_group = 0 + ! Sample source site from i-th source distribution + site = external_source(i) % sample() ! If running in MG, convert site % E to group if (.not. run_CE) then diff --git a/src/source_header.F90 b/src/source_header.F90 index 763d8a7e9..12595e468 100644 --- a/src/source_header.F90 +++ b/src/source_header.F90 @@ -1,21 +1,382 @@ module source_header - use distribution_univariate, only: Distribution - use distribution_multivariate, only: UnitSphereDistribution, SpatialDistribution + use, intrinsic :: ISO_C_BINDING + + use bank_header, only: Bank + use constants + use distribution_univariate + use distribution_multivariate + use error + use geometry, only: find_cell + use material_header, only: materials + use nuclide_header, only: energy_max_neutron + use particle_header, only: Particle + use settings, only: photon_transport + use string, only: to_lower + use xml_interface implicit none + private + public :: free_memory_source + + integer :: n_accept = 0 ! Number of samples accepted + integer :: n_reject = 0 ! Number of samples rejected !=============================================================================== ! SOURCEDISTRIBUTION describes an external source of particles for a ! fixed-source problem or for the starting source in a k eigenvalue problem !=============================================================================== - type SourceDistribution + type, public :: SourceDistribution integer :: particle ! particle type - real(8) :: strength ! source strength + real(8) :: strength = ONE ! source strength class(SpatialDistribution), allocatable :: space ! spatial distribution class(UnitSphereDistribution), allocatable :: angle ! angle distribution class(Distribution), allocatable :: energy ! energy distribution + contains + procedure :: from_xml + procedure :: sample end type SourceDistribution + ! Number of external source distributions + integer(C_INT32_T), public, bind(C) :: n_sources = 0 + + ! External source distributions + type(SourceDistribution), public, allocatable :: external_source(:) + +contains + + subroutine from_xml(this, node, path_source) + class(SourceDistribution), intent(inout) :: this + type(XMLNode), intent(in) :: node + character(MAX_FILE_LEN), intent(out) :: path_source + + integer :: n + logical :: file_exists + character(MAX_WORD_LEN) :: type, temp_str + type(XMLNode) :: node_space + type(XMLNode) :: node_angle + type(XMLNode) :: node_dist + + ! Check for particle type + if (check_for_node(node, "particle")) then + call get_node_value(node, "particle", temp_str) + select case (to_lower(temp_str)) + case ('neutron') + this % particle = NEUTRON + case ('photon') + this % particle = PHOTON + photon_transport = .true. + case default + call fatal_error('Unknown source particle type: ' // trim(temp_str)) + end select + else + this % particle = NEUTRON + end if + + ! Check for source strength + if (check_for_node(node, "strength")) then + call get_node_value(node, "strength", this % strength) + end if + + ! Check for external source file + if (check_for_node(node, "file")) then + ! Copy path of source file + call get_node_value(node, "file", path_source) + + ! Check if source file exists + inquire(FILE=path_source, EXIST=file_exists) + if (.not. file_exists) then + call fatal_error("Source file '" // trim(path_source) & + // "' does not exist!") + end if + + else + + ! Spatial distribution for external source + if (check_for_node(node, "space")) then + + ! Get pointer to spatial distribution + node_space = node % child("space") + + ! Check for type of spatial distribution + type = '' + if (check_for_node(node_space, "type")) & + call get_node_value(node_space, "type", type) + select case (to_lower(type)) + case ('cartesian') + allocate(CartesianIndependent :: this % space) + + case ('box') + allocate(SpatialBox :: this % space) + + case ('fission') + allocate(SpatialBox :: this % space) + select type(space => this % space) + type is (SpatialBox) + space % only_fissionable = .true. + end select + + case ('point') + allocate(SpatialPoint :: this % space) + + case default + call fatal_error("Invalid spatial distribution for external source: "& + // trim(type)) + end select + + ! Read spatial distribution from XML + call this % space % from_xml(node_space) + + else + ! If no spatial distribution specified, make it a point source + allocate(SpatialPoint :: this % space) + select type (space => this % space) + type is (SpatialPoint) + space % xyz(:) = [ZERO, ZERO, ZERO] + end select + end if + + ! Determine external source angular distribution + if (check_for_node(node, "angle")) then + + ! Get pointer to angular distribution + node_angle = node % child("angle") + + ! Check for type of angular distribution + type = '' + if (check_for_node(node_angle, "type")) & + call get_node_value(node_angle, "type", type) + select case (to_lower(type)) + case ('isotropic') + allocate(Isotropic :: this % angle) + + case ('monodirectional') + allocate(Monodirectional :: this % angle) + + case ('mu-phi') + allocate(PolarAzimuthal :: this % angle) + + case default + call fatal_error("Invalid angular distribution for external source: "& + // trim(type)) + end select + + ! Read reference directional unit vector + if (check_for_node(node_angle, "reference_uvw")) then + n = node_word_count(node_angle, "reference_uvw") + if (n /= 3) then + call fatal_error('Angular distribution reference direction must have & + &three parameters specified.') + end if + call get_node_array(node_angle, "reference_uvw", & + this % angle % reference_uvw) + else + ! By default, set reference unit vector to be positive z-direction + this % angle % reference_uvw(:) = [ZERO, ZERO, ONE] + end if + + ! Read parameters for angle distribution + select type (angle => this % angle) + type is (Monodirectional) + call get_node_array(node_angle, "reference_uvw", & + this % angle % reference_uvw) + + type is (PolarAzimuthal) + if (check_for_node(node_angle, "mu")) then + node_dist = node_angle % child("mu") + call distribution_from_xml(angle % mu, node_dist) + else + allocate(Uniform :: angle%mu) + select type (mu => angle%mu) + type is (Uniform) + mu % a = -ONE + mu % b = ONE + end select + end if + + if (check_for_node(node_angle, "phi")) then + node_dist = node_angle % child("phi") + call distribution_from_xml(angle % phi, node_dist) + else + allocate(Uniform :: angle%phi) + select type (phi => angle%phi) + type is (Uniform) + phi % a = ZERO + phi % b = TWO*PI + end select + end if + end select + + else + ! Set default angular distribution isotropic + allocate(Isotropic :: this % angle) + this % angle % reference_uvw(:) = [ZERO, ZERO, ONE] + end if + + ! Determine external source energy distribution + if (check_for_node(node, "energy")) then + node_dist = node % child("energy") + call distribution_from_xml(this % energy, node_dist) + else + ! Default to a Watt spectrum with parameters 0.988 MeV and 2.249 MeV^-1 + allocate(Watt :: this % energy) + select type(energy => this % energy) + type is (Watt) + energy % a = 0.988e6_8 + energy % b = 2.249e-6_8 + end select + end if + end if + + end subroutine from_xml + + function sample(this) result(site) + class(SourceDistribution), intent(in) :: this + type(Bank) :: site + + logical :: found ! Does the source particle exist within geometry? + type(Particle) :: p ! Temporary particle for using find_cell + + ! Set weight to one by default + site % wgt = ONE + + ! Repeat sampling source location until a good site has been found + found = .false. + do while (.not. found) + ! Set particle defaults + call p % initialize() + + ! Set particle type + site % particle = this % particle + + ! Sample spatial distribution + site % xyz(:) = this % space % sample() + + ! Fill p with needed data + p % coord(1) % xyz(:) = site % xyz + p % coord(1) % uvw(:) = [ ONE, ZERO, ZERO ] + + ! Now search to see if location exists in geometry + call find_cell(p, found) + + ! Check if spatial site is in fissionable material + if (found) then + select type (space => this % space) + type is (SpatialBox) + if (space % only_fissionable) then + if (p % material == MATERIAL_VOID) then + found = .false. + elseif (.not. materials(p % material) % fissionable) then + found = .false. + end if + end if + end select + end if + + ! Check for rejection + if (.not. found) then + n_reject = n_reject + 1 + if (n_reject >= EXTSRC_REJECT_THRESHOLD .and. & + real(n_accept, 8)/n_reject <= EXTSRC_REJECT_FRACTION) then + call fatal_error("More than 95% of external source sites sampled & + &were rejected. Please check your external source definition.") + end if + end if + end do + + ! Increment number of accepted samples + n_accept = n_accept + 1 + + call p % clear() + + ! Sample angle + site % uvw(:) = this % angle % sample() + + ! Check for monoenergetic source above maximum neutron energy + select type (energy => this % energy) + type is (Discrete) + if (any(energy % x >= energy_max_neutron)) then + call fatal_error("Source energy above range of energies of at least & + &one cross section table") + end if + end select + + do + ! Sample energy spectrum + site % E = this % energy % sample() + + ! resample if energy is greater than maximum neutron energy + if (site % E < energy_max_neutron) exit + end do + + ! Set delayed group + site % delayed_group = 0 + + end function sample + +!=============================================================================== +! FREE_MEMORY_SOURCE deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_source() + n_sources = 0 + if (allocated(external_source)) deallocate(external_source) + end subroutine free_memory_source + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_extend_sources(n, index_start, index_end) result(err) bind(C) + ! Extend the external_source array by n elements + integer(C_INT32_T), value, intent(in) :: n + integer(C_INT32_T), optional, intent(out) :: index_start + integer(C_INT32_T), optional, intent(out) :: index_end + integer(C_INT) :: err + + type(SourceDistribution), allocatable :: temp(:) ! temporary array + + if (n_sources == 0) then + ! Allocate external_source array + allocate(external_source(n)) + else + ! Allocate external_source array with increased size + allocate(temp(n_sources + n)) + + ! Copy original source array to temporary array + temp(1:n_sources) = external_source + + ! Move allocation from temporary array + call move_alloc(FROM=temp, TO=external_source) + end if + + ! Return indices in external_source array + if (present(index_start)) index_start = n_sources + 1 + if (present(index_end)) index_end = n_sources + n + n_sources = n_sources + n + + err = 0 + end function openmc_extend_sources + + + function openmc_source_set_strength(index, strength) result(err) bind(C) + integer(C_INT32_T), value, intent(in) :: index + real(C_DOUBLE), value, intent(in) :: strength + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_sources) then + if (strength > ZERO) then + external_source(index) % strength = strength + err = 0 + else + err = E_INVALID_ARGUMENT + call set_errmsg("Source strength must be positive.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in external source array is out of bounds.") + end if + end function openmc_source_set_strength + end module source_header diff --git a/src/state_point.F90 b/src/state_point.F90 index e4c9a43ae..5dde708aa 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -11,21 +11,29 @@ module state_point ! intervals, using the tag. !=============================================================================== - use, intrinsic :: ISO_C_BINDING, only: c_loc, c_ptr + use, intrinsic :: ISO_C_BINDING use hdf5 + use cmfd_header use constants + use eigenvalue, only: openmc_get_keff use endf, only: reaction_name use error, only: fatal_error, warning - use global use hdf5_interface - use mesh_header, only: RegularMesh + use mesh_header, only: RegularMesh, meshes, n_meshes use message_passing + use mgxs_header, only: nuclides_MG + use nuclide_header, only: nuclides use output, only: write_message, time_stamp use random_lcg, only: seed + use settings + use simulation_header use string, only: to_str, count_digits, zero_padded - use tally_header, only: TallyObject + use tally_header + use tally_filter_header + use tally_derivative_header, only: tally_derivs + use timer_header implicit none @@ -44,11 +52,12 @@ contains integer, allocatable :: id_array(:) integer(HID_T) :: file_id integer(HID_T) :: cmfd_group, tallies_group, tally_group, meshes_group, & - mesh_group, filters_group, filter_group, derivs_group, & + filters_group, filter_group, derivs_group, & deriv_group, runtime_group + integer(C_INT) :: err + real(C_DOUBLE) :: k_combined(2) character(MAX_WORD_LEN), allocatable :: str_array(:) character(MAX_FILE_LEN) :: filename - type(TallyObject), pointer :: tally ! Set filename for state point filename = trim(path_output) // 'statepoint.' // & @@ -117,6 +126,7 @@ contains call write_dataset(file_id, "k_col_abs", k_col_abs) call write_dataset(file_id, "k_col_tra", k_col_tra) call write_dataset(file_id, "k_abs_tra", k_abs_tra) + err = openmc_get_keff(k_combined) call write_dataset(file_id, "k_combined", k_combined) ! Write out CMFD info @@ -154,21 +164,7 @@ contains ! Write information for meshes MESH_LOOP: do i = 1, n_meshes - associate (m => meshes(i)) - mesh_group = create_group(meshes_group, "mesh " & - // trim(to_str(m % id))) - - select case (m % type) - case (MESH_REGULAR) - call write_dataset(mesh_group, "type", "regular") - end select - call write_dataset(mesh_group, "dimension", m % dimension) - call write_dataset(mesh_group, "lower_left", m % lower_left) - call write_dataset(mesh_group, "upper_right", m % upper_right) - call write_dataset(mesh_group, "width", m % width) - - call close_group(mesh_group) - end associate + call meshes(i) % to_hdf5(meshes_group) end do MESH_LOOP end if @@ -237,7 +233,7 @@ contains ! Write array of tally IDs allocate(id_array(n_tallies)) do i = 1, n_tallies - id_array(i) = tallies(i) % id + id_array(i) = tallies(i) % obj % id end do call write_attribute(tallies_group, "ids", id_array) deallocate(id_array) @@ -246,7 +242,7 @@ contains TALLY_METADATA: do i = 1, n_tallies ! Get pointer to tally - tally => tallies(i) + associate (tally => tallies(i) % obj) tally_group = create_group(tallies_group, "tally " // & trim(to_str(tally % id))) @@ -351,6 +347,7 @@ contains deallocate(str_array) call close_group(tally_group) + end associate end do TALLY_METADATA end if @@ -374,7 +371,7 @@ contains call write_dataset(file_id, "global_tallies", global_tallies) ! Write tallies - if (tallies_on) then + if (active_tallies % size() > 0) then ! Indicate that tallies are on call write_attribute(file_id, "tallies_present", 1) @@ -382,14 +379,13 @@ contains ! Write all tally results TALLY_RESULTS: do i = 1, n_tallies - ! Set point to current tally - tally => tallies(i) - - ! Write sum and sum_sq for each bin - tally_group = open_group(tallies_group, "tally " & - // to_str(tally % id)) - call tally % write_results_hdf5(tally_group) - call close_group(tally_group) + associate (tally => tallies(i) % obj) + ! Write sum and sum_sq for each bin + tally_group = open_group(tallies_group, "tally " & + // to_str(tally % id)) + call tally % write_results_hdf5(tally_group) + call close_group(tally_group) + end associate end do TALLY_RESULTS call close_group(tallies_group) @@ -518,7 +514,8 @@ contains real(8), allocatable :: tally_temp(:,:,:) ! contiguous array of results real(8), target :: global_temp(3,N_GLOBAL_TALLIES) #ifdef MPI - real(8) :: dummy ! temporary receive buffer for non-root reduces + integer :: mpi_err ! MPI error code + real(8) :: dummy ! temporary receive buffer for non-root reduces #endif type(TallyObject) :: dummy_tally @@ -535,18 +532,15 @@ contains tallies_group = open_group(file_id, "tallies") end if - ! Copy global tallies into temporary array for reducing - n_bins = 3 * N_GLOBAL_TALLIES - global_temp(:,:) = global_tallies(:,:) - if (master) then - ! The MPI_IN_PLACE specifier allows the master to copy values into a - ! receive buffer without having a temporary variable #ifdef MPI - call MPI_REDUCE(MPI_IN_PLACE, global_temp, n_bins, MPI_REAL8, MPI_SUM, & - 0, mpi_intracomm, mpi_err) + ! Reduce global tallies + n_bins = size(global_tallies) + call MPI_REDUCE(global_tallies, global_temp, n_bins, MPI_REAL8, MPI_SUM, & + 0, mpi_intracomm, mpi_err) #endif + if (master) then ! Transfer values to value on master if (current_batch == n_max_batches .or. satisfy_triggers) then global_tallies(:,:) = global_temp(:,:) @@ -554,15 +548,9 @@ contains ! Write out global tallies sum and sum_sq call write_dataset(file_id, "global_tallies", global_temp) - else - ! Receive buffer not significant at other processors -#ifdef MPI - call MPI_REDUCE(global_temp, dummy, n_bins, MPI_REAL8, MPI_SUM, & - 0, mpi_intracomm, mpi_err) -#endif end if - if (tallies_on) then + if (active_tallies % size() > 0) then ! Indicate that tallies are on if (master) then call write_attribute(file_id, "tallies_present", 1) @@ -570,7 +558,7 @@ contains ! Write all tally results TALLY_RESULTS: do i = 1, n_tallies - associate (t => tallies(i)) + associate (t => tallies(i) % obj) ! Determine size of tally results array m = size(t % results, 2) n = size(t % results, 3) @@ -647,7 +635,6 @@ contains integer(HID_T) :: cmfd_group integer(HID_T) :: tallies_group integer(HID_T) :: tally_group - real(8) :: real_array(3) logical :: source_present character(MAX_WORD_LEN) :: word @@ -727,7 +714,6 @@ contains call read_dataset(k_col_abs, file_id, "k_col_abs") call read_dataset(k_col_tra, file_id, "k_col_tra") call read_dataset(k_abs_tra, file_id, "k_abs_tra") - call read_dataset(real_array(1:2), file_id, "k_combined") ! Take maximum of statepoint n_inactive and input n_inactive n_inactive = max(n_inactive, int_array(1)) @@ -781,7 +767,7 @@ contains tallies_group = open_group(file_id, "tallies") TALLY_RESULTS: do i = 1, n_tallies - associate (t => tallies(i)) + associate (t => tallies(i) % obj) ! Read sum, sum_sq, and N for each bin tally_group = open_group(tallies_group, "tally " // & trim(to_str(t % id))) @@ -848,6 +834,7 @@ contains #else integer :: i #ifdef MPI + integer :: mpi_err ! MPI error code type(Bank), allocatable, target :: temp_source(:) #endif #endif diff --git a/src/string.F90 b/src/string.F90 index efb0dd982..71d9d6e96 100644 --- a/src/string.F90 +++ b/src/string.F90 @@ -514,4 +514,47 @@ contains if (inword) n = n + 1 end function word_count +!=============================================================================== +! TO_F_STRING takes a null-terminated array of C chars and turns it into a +! deferred-length character string. Yay Fortran 2003! +!=============================================================================== + + function to_f_string(c_string) result(f_string) + character(kind=C_CHAR), intent(in) :: c_string(*) + character(:), allocatable :: f_string + + integer :: i, n + + ! Determine length of original string + n = 0 + do while (c_string(n + 1) /= C_NULL_CHAR) + n = n + 1 + end do + + ! Copy C string character by character + allocate(character(len=n) :: f_string) + do i = 1, n + f_string(i:i) = c_string(i) + end do + end function to_f_string + +!=============================================================================== +! TO_C_STRING takes a space-padded Fortran character and turns it into a +! null-terminated C char array. Yay Fortran 2003! +!=============================================================================== + + function to_c_string(f_string) result(c_string) + character(*), intent(in) :: f_string + character(kind=C_CHAR) :: c_string(len_trim(f_string) + 1) + + integer :: i, n + + ! Copy Fortran string character by character + n = len_trim(f_string) + do i = 1, n + c_string(i) = f_string(i:i) + end do + c_string(n + 1) = C_NULL_CHAR + end function to_c_string + end module string diff --git a/src/summary.F90 b/src/summary.F90 index 9c952fe70..e271fbaba 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -4,19 +4,19 @@ module summary use constants use endf, only: reaction_name - use geometry_header, only: root_universe, Cell, Universe, Lattice, & - RectLattice, HexLattice - use global + use geometry_header use hdf5_interface - use material_header, only: Material + use material_header, only: Material, n_materials use mesh_header, only: RegularMesh use message_passing + use mgxs_header, only: nuclides_MG use nuclide_header use output, only: time_stamp + use settings, only: run_CE use surface_header use string, only: to_str use tally_header, only: TallyObject - use tally_filter, only: find_offset + use tally_filter_distribcell, only: find_offset implicit none private @@ -79,12 +79,12 @@ contains ! Write useful data from nuclide objects nuclide_group = create_group(file_id, "nuclides") - call write_attribute(nuclide_group, "n_nuclides", n_nuclides_total) + call write_attribute(nuclide_group, "n_nuclides", n_nuclides) ! Build array of nuclide names and awrs - allocate(nucnames(n_nuclides_total)) - allocate(awrs(n_nuclides_total)) - do i = 1, n_nuclides_total + allocate(nucnames(n_nuclides)) + allocate(awrs(n_nuclides)) + do i = 1, n_nuclides if (run_CE) then nucnames(i) = nuclides(i) % name awrs(i) = nuclides(i) % awr diff --git a/src/surface_header.F90 b/src/surface_header.F90 index 68e5144b7..ed4ef86e0 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -1,6 +1,9 @@ module surface_header + use, intrinsic :: ISO_C_BINDING + use constants, only: NONE, ONE, TWO, ZERO, HALF, INFINITY, FP_COINCIDENT + use dict_header, only: DictIntInt implicit none @@ -193,6 +196,13 @@ module surface_header procedure :: normal => quadric_normal end type SurfaceQuadric + integer(C_INT32_T), bind(C) :: n_surfaces ! # of surfaces + + type(SurfaceContainer), allocatable, target :: surfaces(:) + + ! Dictionary that maps user IDs to indices in 'surfaces' + type(DictIntInt) :: surface_dict + contains !=============================================================================== @@ -1050,4 +1060,14 @@ contains end associate end function quadric_normal +!=============================================================================== +! FREE_MEMORY_SURFACES deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_surfaces() + n_surfaces = 0 + if (allocated(surfaces)) deallocate(surfaces) + call surface_dict % clear() + end subroutine free_memory_surfaces + end module surface_header diff --git a/src/tally.F90 b/src/tallies/tally.F90 similarity index 93% rename from src/tally.F90 rename to src/tallies/tally.F90 index 3d7ee2cb8..7104eca81 100644 --- a/src/tally.F90 +++ b/src/tallies/tally.F90 @@ -5,20 +5,22 @@ module tally use algorithm, only: binary_search use constants use cross_section, only: multipole_deriv_eval + use dict_header, only: EMPTY use error, only: fatal_error use geometry_header - use global use math, only: t_percentile, calc_pn, calc_rn - use mesh, only: get_mesh_bin, bin_to_mesh_indices, & - get_mesh_indices, mesh_indices_to_bin, & - mesh_intersects_1d, mesh_intersects_2d, & - mesh_intersects_3d - use mesh_header, only: RegularMesh + use mesh_header, only: RegularMesh, meshes use message_passing + use mgxs_header + use nuclide_header use output, only: header use particle_header, only: LocalCoord, Particle + use settings + use simulation_header use string, only: to_str + use tally_derivative_header, only: tally_derivs use tally_filter + use tally_header implicit none @@ -101,6 +103,9 @@ contains real(8) :: E ! particle energy real(8) :: xs ! cross section + ! Pre-collision energy of particle + E = p % last_E + i = 0 SCORE_LOOP: do q = 1, t % n_user_score_bins i = i + 1 @@ -166,13 +171,6 @@ contains case (SCORE_INVERSE_VELOCITY) - ! make sure the correct energy is used - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - E = p % E - else - E = p % last_E - end if - if (t % estimator == ESTIMATOR_ANALOG) then ! All events score to an inverse velocity bin. We actually use a ! collision estimator in place of an analog one since there is no way @@ -256,13 +254,12 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p % event_nuclide) % reaction_index % & - get_key(p % event_MT) + m = nuclides(p % event_nuclide) % reaction_index % get(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p % event_nuclide) % reactions(m)) score = p % last_wgt * flux & - * rxn % products(1) % yield % evaluate(p % last_E) + * rxn % products(1) % yield % evaluate(E) end associate end if @@ -283,13 +280,12 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p%event_nuclide)%reaction_index% & - get_key(p % event_MT) + m = nuclides(p % event_nuclide) % reaction_index % get(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p % event_nuclide) % reactions(m)) score = p % last_wgt * flux & - * rxn % products(1) % yield % evaluate(p % last_E) + * rxn % products(1) % yield % evaluate(E) end associate end if @@ -310,13 +306,12 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p%event_nuclide)%reaction_index% & - get_key(p % event_MT) + m = nuclides(p % event_nuclide) % reaction_index % get(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p%event_nuclide)%reactions(m)) score = p % last_wgt * flux & - * rxn % products(1) % yield % evaluate(p % last_E) + * rxn % products(1) % yield % evaluate(E) end associate end if @@ -429,13 +424,6 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP - ! make sure the correct energy is used - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - E = p % E - else - E = p % last_E - end if - if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then if (t % find_filter(FILTER_ENERGYOUT) > 0) then @@ -497,18 +485,10 @@ contains end if end if - case (SCORE_DELAYED_NU_FISSION) if (material_xs % absorption == ZERO) cycle SCORE_LOOP - ! make sure the correct energy is used - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - E = p % E - else - E = p % last_E - end if - ! Set the delayedgroup filter index and the number of delayed group bins dg_filter = t % find_filter(FILTER_DELAYEDGROUP) @@ -703,13 +683,6 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP - ! make sure the correct energy is used - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - E = p % E - else - E = p % last_E - end if - ! Set the delayedgroup filter index dg_filter = t % find_filter(FILTER_DELAYEDGROUP) @@ -1125,12 +1098,6 @@ contains end if else - if (t % estimator == ESTIMATOR_COLLISION) then - E = p % last_E - else - E = p % E - end if - if (i_nuclide > 0) then associate (nuc => nuclides(i_nuclide)) if (allocated(nuc % fission_q_prompt)) then @@ -1185,9 +1152,8 @@ contains score = ZERO if (i_nuclide > 0) then - if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then - m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) - + m = nuclides(i_nuclide) % reaction_index % get(score_bin) + if (m /= EMPTY) then ! Retrieve temperature and energy grid index and interpolation ! factor i_temp = micro_xs(i_nuclide) % index_temp @@ -1225,9 +1191,8 @@ contains ! Get index in nuclides array i_nuc = materials(p % material) % nuclide(l) - if (nuclides(i_nuc)%reaction_index%has_key(score_bin)) then - m = nuclides(i_nuc)%reaction_index%get_key(score_bin) - + m = nuclides(i_nuc) % reaction_index % get(score_bin) + if (m /= EMPTY) then ! Retrieve temperature and energy grid index and ! interpolation factor i_temp = micro_xs(i_nuc) % index_temp @@ -2198,22 +2163,18 @@ contains ! the user requests all. !=============================================================================== - subroutine score_all_nuclides(p, i_tally, flux, filter_index) + subroutine score_all_nuclides(p, t, flux, filter_index) type(Particle), intent(in) :: p - integer, intent(in) :: i_tally + type(TallyObject), intent(inout) :: t real(8), intent(in) :: flux integer, intent(in) :: filter_index integer :: i ! loop index for nuclides in material integer :: i_nuclide ! index in nuclides array real(8) :: atom_density ! atom density of single nuclide in atom/b-cm - type(TallyObject), pointer :: t type(Material), pointer :: mat - ! Get pointer to tally - t => tallies(i_tally) - ! Get pointer to current material. We need this in order to determine what ! nuclides are in the material mat => materials(p % material) @@ -2241,7 +2202,7 @@ contains atom_density = ZERO ! Determine score for each bin - call score_general(p, t, n_nuclides_total*t % n_score_bins, filter_index, & + call score_general(p, t, n_nuclides*t % n_score_bins, filter_index, & i_nuclide, atom_density, flux) end subroutine score_all_nuclides @@ -2263,10 +2224,8 @@ contains integer :: k ! loop index for nuclide bins integer :: filter_index ! single index for single bin integer :: i_nuclide ! index in nuclides array - integer :: matching_bin ! next valid filter bin real(8) :: filter_weight ! combined weight of all filters logical :: finished ! found all valid bin combinations - type(TallyObject), pointer :: t ! A loop over all tallies is necessary because we need to simultaneously ! determine different filter bins for the same tally in order to score to it @@ -2274,7 +2233,7 @@ contains TALLY_LOOP: do i = 1, active_analog_tallies % size() ! Get index of tally and pointer to tally i_tally = active_analog_tallies % data(i) - t => tallies(i_tally) + associate (t => tallies(i_tally) % obj) ! Find all valid bins in each filter if they have not already been found ! for a previous tally. @@ -2283,14 +2242,8 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins % clear() call filter_matches(i_filt) % weights % clear() - matching_bin = NO_BIN_FOUND - do - call filters(i_filt) % obj % get_next_bin(p, t % estimator, & - matching_bin, matching_bin, filter_weight) - if (matching_bin == NO_BIN_FOUND) exit - call filter_matches(i_filt) % bins % push_back(matching_bin) - call filter_matches(i_filt) % weights % push_back(filter_weight) - end do + call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if ! If there are no valid bins for this filter, then there is nothing to @@ -2342,7 +2295,7 @@ contains elseif (k == p % event_nuclide + 1) then ! After we've tallied the individual nuclide bin, we also need ! to contribute to the total material bin which is the last bin - k = n_nuclides_total + 1 + k = n_nuclides + 1 else ! After we've tallied in both the individual nuclide bin and the ! total material bin, we're done @@ -2396,6 +2349,7 @@ contains if (assume_separate) exit TALLY_LOOP + end associate end do TALLY_LOOP ! Reset filter matches flag @@ -2414,11 +2368,9 @@ contains integer :: k ! loop index for nuclide bins integer :: filter_index ! single index for single bin integer :: i_nuclide ! index in nuclides array - integer :: matching_bin ! next valid filter bin real(8) :: filter_weight ! combined weight of all filters real(8) :: atom_density logical :: finished ! found all valid bin combinations - type(TallyObject), pointer :: t type(Material), pointer :: mat ! A loop over all tallies is necessary because we need to simultaneously @@ -2427,7 +2379,7 @@ contains TALLY_LOOP: do i = 1, active_analog_tallies % size() ! Get index of tally and pointer to tally i_tally = active_analog_tallies % data(i) - t => tallies(i_tally) + associate (t => tallies(i_tally) % obj) ! Get pointer to current material. We need this in order to determine what ! nuclides are in the material @@ -2440,14 +2392,8 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins % clear() call filter_matches(i_filt) % weights % clear() - matching_bin = NO_BIN_FOUND - do - call filters(i_filt) % obj % get_next_bin(p, t % estimator, & - matching_bin, matching_bin, filter_weight) - if (matching_bin == NO_BIN_FOUND) exit - call filter_matches(i_filt) % bins % push_back(matching_bin) - call filter_matches(i_filt) % weights % push_back(filter_weight) - end do + call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if ! If there are no valid bins for this filter, then there is nothing to @@ -2541,6 +2487,7 @@ contains if (assume_separate) exit TALLY_LOOP + end associate end do TALLY_LOOP ! Reset filter matches flag @@ -2796,12 +2743,10 @@ contains integer :: k ! loop index for nuclide bins integer :: filter_index ! single index for single bin integer :: i_nuclide ! index in nuclides array (from bins) - integer :: matching_bin ! next valid filter bin real(8) :: flux ! tracklength estimate of flux real(8) :: atom_density ! atom density of single nuclide in atom/b-cm real(8) :: filter_weight ! combined weight of all filters logical :: finished ! found all valid bin combinations - type(TallyObject), pointer :: t type(Material), pointer :: mat ! Determine track-length estimate of flux @@ -2813,7 +2758,7 @@ contains TALLY_LOOP: do i = 1, active_tracklength_tallies % size() ! Get index of tally and pointer to tally i_tally = active_tracklength_tallies % data(i) - t => tallies(i_tally) + associate (t => tallies(i_tally) % obj) ! Find all valid bins in each filter if they have not already been found ! for a previous tally. @@ -2822,14 +2767,8 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins % clear() call filter_matches(i_filt) % weights % clear() - matching_bin = NO_BIN_FOUND - do - call filters(i_filt) % obj % get_next_bin(p, t % estimator, & - matching_bin, matching_bin, filter_weight) - if (matching_bin == NO_BIN_FOUND) exit - call filter_matches(i_filt) % bins % push_back(matching_bin) - call filter_matches(i_filt) % weights % push_back(filter_weight) - end do + call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if ! If there are no valid bins for this filter, then there is nothing to @@ -2864,8 +2803,7 @@ contains if (t % all_nuclides) then if (p % material /= MATERIAL_VOID) then - call score_all_nuclides(p, i_tally, flux * filter_weight, & - filter_index) + call score_all_nuclides(p, t, flux * filter_weight, filter_index) end if else @@ -2936,6 +2874,7 @@ contains if (assume_separate) exit TALLY_LOOP + end associate end do TALLY_LOOP ! Reset filter matches flag @@ -2963,13 +2902,11 @@ contains integer :: k ! loop index for nuclide bins integer :: filter_index ! single index for single bin integer :: i_nuclide ! index in nuclides array (from bins) - integer :: matching_bin ! next valid filter bin real(8) :: flux ! collision estimate of flux real(8) :: atom_density ! atom density of single nuclide ! in atom/b-cm real(8) :: filter_weight ! combined weight of all filters logical :: finished ! found all valid bin combinations - type(TallyObject), pointer :: t type(Material), pointer :: mat ! Determine collision estimate of flux @@ -2986,7 +2923,7 @@ contains TALLY_LOOP: do i = 1, active_collision_tallies % size() ! Get index of tally and pointer to tally i_tally = active_collision_tallies % data(i) - t => tallies(i_tally) + associate (t => tallies(i_tally) % obj) ! Find all valid bins in each filter if they have not already been found ! for a previous tally. @@ -2995,14 +2932,8 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins % clear() call filter_matches(i_filt) % weights % clear() - matching_bin = NO_BIN_FOUND - do - call filters(i_filt) % obj % get_next_bin(p, t % estimator, & - matching_bin, matching_bin, filter_weight) - if (matching_bin == NO_BIN_FOUND) exit - call filter_matches(i_filt) % bins % push_back(matching_bin) - call filter_matches(i_filt) % weights % push_back(filter_weight) - end do + call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if ! If there are no valid bins for this filter, then there is nothing to @@ -3037,8 +2968,7 @@ contains if (t % all_nuclides) then if (p % material /= MATERIAL_VOID) then - call score_all_nuclides(p, i_tally, flux * filter_weight, & - filter_index) + call score_all_nuclides(p, t, flux * filter_weight, filter_index) end if else @@ -3109,6 +3039,7 @@ contains if (assume_separate) exit TALLY_LOOP + end associate end do TALLY_LOOP ! Reset filter matches flag @@ -3116,6 +3047,135 @@ contains end subroutine score_collision_tally +!=============================================================================== +! score_surface_tally is called at every surface crossing and can be used to +! tally total or partial currents between two cells +!=============================================================================== + + subroutine score_surface_tally(p) + + type(Particle), intent(in) :: p + + integer :: i + integer :: i_tally + integer :: i_filt + integer :: i_bin + integer :: q ! loop index for scoring bins + integer :: k ! working index for expand and score + integer :: score_bin ! scoring bin, e.g. SCORE_FLUX + integer :: score_index ! scoring bin index + integer :: j ! loop index for scoring bins + integer :: filter_index ! single index for single bin + real(8) :: flux ! collision estimate of flux + real(8) :: filter_weight ! combined weight of all filters + real(8) :: score ! analog tally score + logical :: finished ! found all valid bin combinations + + ! No collision, so no weight change when survival biasing + flux = p % wgt + + TALLY_LOOP: do i = 1, active_surface_tallies % size() + ! Get index of tally and pointer to tally + i_tally = active_surface_tallies % data(i) + associate (t => tallies(i_tally) % obj) + + ! Find all valid bins in each filter if they have not already been found + ! for a previous tally. + do j = 1, size(t % filter) + i_filt = t % filter(j) + if (.not. filter_matches(i_filt) % bins_present) then + call filter_matches(i_filt) % bins % clear() + call filter_matches(i_filt) % weights % clear() + call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + filter_matches(i_filt)) + filter_matches(i_filt) % bins_present = .true. + end if + ! If there are no valid bins for this filter, then there is nothing to + ! score and we can move on to the next tally. + if (filter_matches(i_filt) % bins % size() == 0) cycle TALLY_LOOP + + ! Set the index of the bin used in the first filter combination + filter_matches(i_filt) % i_bin = 1 + end do + + ! ======================================================================== + ! Loop until we've covered all valid bins on each of the filters. + + FILTER_LOOP: do + + ! Reset scoring index and weight + filter_index = 1 + filter_weight = ONE + + ! Determine scoring index and weight for this filter combination + do j = 1, size(t % filter) + i_filt = t % filter(j) + i_bin = filter_matches(i_filt) % i_bin + filter_index = filter_index + (filter_matches(i_filt) % bins % & + data(i_bin) - 1) * t % stride(j) + filter_weight = filter_weight * filter_matches(i_filt) % weights % & + data(i_bin) + end do + + ! Determine score + score = flux * filter_weight + + ! Currently only one score type + k = 0 + SCORE_LOOP: do q = 1, t % n_user_score_bins + k = k + 1 + + ! determine what type of score bin + score_bin = t % score_bins(q) + + ! determine scoring bin index, no offset from nuclide bins + score_index = q + + ! Expand score if necessary and add to tally results. + call expand_and_score(p, t, score_index, filter_index, score_bin, & + score, k) + end do SCORE_LOOP + + ! ====================================================================== + ! Filter logic + + ! Increment the filter bins, starting with the last filter to find the + ! next valid bin combination + finished = .true. + do j = size(t % filter), 1, -1 + i_filt = t % filter(j) + if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & + bins % size()) then + filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 + finished = .false. + exit + else + filter_matches(i_filt) % i_bin = 1 + end if + end do + + ! Once we have finished all valid bins for each of the filters, exit + ! the loop. + if (finished) exit FILTER_LOOP + + end do FILTER_LOOP + + end associate + + ! If the user has specified that we can assume all tallies are spatially + ! separate, this implies that once a tally has been scored to, we needn't + ! check the others. This cuts down on overhead when there are many + ! tallies specified + + if (assume_separate) exit TALLY_LOOP + + end do TALLY_LOOP + + ! Reset filter matches flag + filter_matches(:) % bins_present = .false. + + end subroutine score_surface_tally + !=============================================================================== ! SCORE_SURFACE_CURRENT tallies surface crossings in a mesh tally by manually ! determining which mesh surfaces were crossed @@ -3145,13 +3205,11 @@ contains real(8) :: xyz_cross(3) ! coordinates of bounding surfaces real(8) :: d(3) ! distance to each bounding surface real(8) :: distance ! actual distance traveled - real(8) :: filt_score ! score applied by filters integer :: matching_bin ! next valid filter bin logical :: start_in_mesh ! particle's starting xyz in mesh? logical :: end_in_mesh ! particle's ending xyz in mesh? logical :: cross_surface ! whether the particle crosses a surface logical :: energy_filter ! energy filter present - type(TallyObject), pointer :: t type(RegularMesh), pointer :: m TALLY_LOOP: do i = 1, active_current_tallies % size() @@ -3161,7 +3219,7 @@ contains ! Get pointer to tally i_tally = active_current_tallies % data(i) - t => tallies(i_tally) + associate (t => tallies(i_tally) % obj) ! Check for energy filter energy_filter = (t % find_filter(FILTER_ENERGYIN) > 0) @@ -3189,19 +3247,13 @@ contains n_dim = m % n_dimension ! Determine indices for starting and ending location - call get_mesh_indices(m, xyz0, ijk0, start_in_mesh) - call get_mesh_indices(m, xyz1, ijk1, end_in_mesh) + call m % get_indices(xyz0, ijk0, start_in_mesh) + call m % get_indices(xyz1, ijk1, end_in_mesh) ! Check to see if start or end is in mesh -- if not, check if track still ! intersects with mesh if ((.not. start_in_mesh) .and. (.not. end_in_mesh)) then - if (n_dim == 1) then - if (.not. mesh_intersects_1d(m, xyz0, xyz1)) cycle - else if (n_dim == 2) then - if (.not. mesh_intersects_2d(m, xyz0, xyz1)) cycle - else - if (.not. mesh_intersects_3d(m, xyz0, xyz1)) cycle - end if + if (.not. m % intersects(xyz0, xyz1)) cycle end if ! Calculate number of surface crossings @@ -3216,9 +3268,12 @@ contains ! Determine incoming energy bin. We need to tell the energy filter this ! is a tracklength tally so it uses the pre-collision energy. if (energy_filter) then - call filters(i_filter_energy) % obj % get_next_bin(p, & - ESTIMATOR_TRACKLENGTH, NO_BIN_FOUND, matching_bin, filt_score) - if (matching_bin == NO_BIN_FOUND) cycle + call filter_matches(i_filter_energy) % bins % clear() + call filter_matches(i_filter_energy) % weights % clear() + call filters(i_filter_energy) % obj % get_all_bins(p, & + ESTIMATOR_TRACKLENGTH, filter_matches(i_filter_energy)) + if (filter_matches(i_filter_energy) % bins % size() == 0) cycle + matching_bin = filter_matches(i_filter_energy) % bins % data(1) filter_matches(i_filter_energy) % bins % data(1) = matching_bin end if @@ -3277,7 +3332,7 @@ contains all(ijk0(:n_dim) <= m % dimension)) then filter_matches(i_filter_surf) % bins % data(1) = d1 * 4 - 1 filter_matches(i_filter_mesh) % bins % data(1) = & - mesh_indices_to_bin(m, ijk0) + m % get_bin_from_indices(ijk0) filter_index = 1 do k = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % & @@ -3316,7 +3371,7 @@ contains ijk0(d1) = ijk0(d1) + 1 filter_matches(i_filter_surf) % bins % data(1) = d1 * 4 - 2 filter_matches(i_filter_mesh) % bins % data(1) = & - mesh_indices_to_bin(m, ijk0) + m % get_bin_from_indices(ijk0) filter_index = 1 do k = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % & @@ -3339,7 +3394,7 @@ contains all(ijk0(:n_dim) <= m % dimension)) then filter_matches(i_filter_surf) % bins % data(1) = d1 * 4 - 3 filter_matches(i_filter_mesh) % bins % data(1) = & - mesh_indices_to_bin(m, ijk0) + m % get_bin_from_indices(ijk0) filter_index = 1 do k = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % & @@ -3378,7 +3433,7 @@ contains ijk0(d1) = ijk0(d1) - 1 filter_matches(i_filter_surf) % bins % data(1) = d1 * 4 filter_matches(i_filter_mesh) % bins % data(1) = & - mesh_indices_to_bin(m, ijk0) + m % get_bin_from_indices(ijk0) filter_index = 1 do k = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % & @@ -3400,6 +3455,7 @@ contains xyz0 = xyz0 + distance * uvw end do + end associate end do TALLY_LOOP end subroutine score_surface_current @@ -4177,11 +4233,11 @@ contains end subroutine zero_flux_derivs !=============================================================================== -! SYNCHRONIZE_TALLIES accumulates the sum of the contributions from each history +! ACCUMULATE_TALLIES accumulates the sum of the contributions from each history ! within the batch to a new random variable !=============================================================================== - subroutine synchronize_tallies() + subroutine accumulate_tallies() integer :: i real(C_DOUBLE) :: k_col ! Copy of batch collision estimate of keff @@ -4205,11 +4261,11 @@ contains if (master .or. (.not. reduce_tallies)) then ! Accumulate results for each tally do i = 1, active_tallies % size() - call accumulate_tally(tallies(active_tallies % data(i))) + call tallies(active_tallies % data(i)) % obj % accumulate() end do if (run_mode == MODE_EIGENVALUE) then - if (active_batches) then + if (current_batch > n_inactive) then ! Accumulate products of different estimators of k k_col = global_tallies(RESULT_VALUE, K_COLLISION) / total_weight k_abs = global_tallies(RESULT_VALUE, K_ABSORPTION) / total_weight @@ -4231,7 +4287,7 @@ contains end do end if - end subroutine synchronize_tallies + end subroutine accumulate_tallies !=============================================================================== ! REDUCE_TALLY_RESULTS collects all the results from tallies onto one processor @@ -4241,188 +4297,137 @@ contains subroutine reduce_tally_results() integer :: i - integer :: n ! number of filter bins - integer :: m ! number of score bins - integer :: n_bins ! total number of bins - real(8), allocatable :: tally_temp(:,:) ! contiguous array of results - real(8) :: global_temp(N_GLOBAL_TALLIES) - real(8) :: dummy ! temporary receive buffer for non-root reduces - type(TallyObject), pointer :: t + integer :: n ! number of filter bins + integer :: m ! number of score bins + integer :: n_bins ! total number of bins + integer :: mpi_err ! MPI error code + real(C_DOUBLE), allocatable :: tally_temp(:,:) ! contiguous array of results + real(C_DOUBLE), allocatable :: tally_temp2(:,:) ! reduced contiguous results + real(C_DOUBLE) :: temp(N_GLOBAL_TALLIES), temp2(N_GLOBAL_TALLIES) do i = 1, active_tallies % size() - t => tallies(active_tallies % data(i)) + associate (t => tallies(active_tallies % data(i)) % obj) - m = t % total_score_bins - n = t % total_filter_bins - n_bins = m*n + m = size(t % results, 2) + n = size(t % results, 3) + n_bins = m*n - allocate(tally_temp(m,n)) + allocate(tally_temp(m,n), tally_temp2(m,n)) - tally_temp = t % results(RESULT_VALUE,:,:) - - if (master) then - ! The MPI_IN_PLACE specifier allows the master to copy values into - ! a receive buffer without having a temporary variable - call MPI_REDUCE(MPI_IN_PLACE, tally_temp, n_bins, MPI_REAL8, & + ! Reduce contiguous set of tally results + tally_temp = t % results(RESULT_VALUE,:,:) + call MPI_REDUCE(tally_temp, tally_temp2, n_bins, MPI_DOUBLE, & MPI_SUM, 0, mpi_intracomm, mpi_err) - ! Transfer values to value on master - t % results(RESULT_VALUE,:,:) = tally_temp - else - ! Receive buffer not significant at other processors - call MPI_REDUCE(tally_temp, dummy, n_bins, MPI_REAL8, & - MPI_SUM, 0, mpi_intracomm, mpi_err) + if (master) then + ! Transfer values to value on master + t % results(RESULT_VALUE,:,:) = tally_temp2 + else + ! Reset value on other processors + t % results(RESULT_VALUE,:,:) = ZERO + end if - ! Reset value on other processors - t % results(RESULT_VALUE,:,:) = ZERO - end if - - deallocate(tally_temp) + deallocate(tally_temp, tally_temp2) + end associate end do - ! Copy global tallies into array to be reduced - global_temp = global_tallies(RESULT_VALUE, :) - + ! Reduce global tallies onto master + temp = global_tallies(RESULT_VALUE, :) + call MPI_REDUCE(temp, temp2, N_GLOBAL_TALLIES, MPI_DOUBLE, MPI_SUM, & + 0, mpi_intracomm, mpi_err) if (master) then - call MPI_REDUCE(MPI_IN_PLACE, global_temp, N_GLOBAL_TALLIES, & - MPI_REAL8, MPI_SUM, 0, mpi_intracomm, mpi_err) - - ! Transfer values back to global_tallies on master - global_tallies(RESULT_VALUE, :) = global_temp + global_tallies(RESULT_VALUE, :) = temp2 else - ! Receive buffer not significant at other processors - call MPI_REDUCE(global_temp, dummy, N_GLOBAL_TALLIES, & - MPI_REAL8, MPI_SUM, 0, mpi_intracomm, mpi_err) - - ! Reset value on other processors global_tallies(RESULT_VALUE, :) = ZERO end if ! We also need to determine the total starting weight of particles from the ! last realization - if (master) then - call MPI_REDUCE(MPI_IN_PLACE, total_weight, 1, MPI_REAL8, MPI_SUM, & - 0, mpi_intracomm, mpi_err) - else - ! Receive buffer not significant at other processors - call MPI_REDUCE(total_weight, dummy, 1, MPI_REAL8, MPI_SUM, & - 0, mpi_intracomm, mpi_err) - end if + temp(1) = total_weight + call MPI_REDUCE(temp, total_weight, 1, MPI_REAL8, MPI_SUM, & + 0, mpi_intracomm, mpi_err) end subroutine reduce_tally_results #endif !=============================================================================== -! ACCUMULATE_TALLY +! SETUP_ACTIVE_TALLIES !=============================================================================== - subroutine accumulate_tally(t) + subroutine setup_active_tallies() - type(TallyObject), intent(inout) :: t + integer :: i ! loop counter - integer :: i, j - real(C_DOUBLE) :: val + call active_tallies % clear() + call active_analog_tallies % clear() + call active_collision_tallies % clear() + call active_tracklength_tallies % clear() + call active_surface_tallies % clear() + call active_current_tallies % clear() - ! Increment number of realizations - if (reduce_tallies) then - t % n_realizations = t % n_realizations + 1 + do i = 1, n_tallies + associate (t => tallies(i) % obj) + if (t % active) then + ! Add tally to active tallies + call active_tallies % push_back(i) + + ! Check what type of tally this is and add it to the appropriate list + if (t % type == TALLY_VOLUME) then + if (t % estimator == ESTIMATOR_ANALOG) then + call active_analog_tallies % push_back(i) + elseif (t % estimator == ESTIMATOR_TRACKLENGTH) then + call active_tracklength_tallies % push_back(i) + elseif (t % estimator == ESTIMATOR_COLLISION) then + call active_collision_tallies % push_back(i) + end if + elseif (t % type == TALLY_MESH_CURRENT) then + call active_current_tallies % push_back(i) + elseif (t % type == TALLY_SURFACE) then + call active_surface_tallies % push_back(i) + end if + end if + end associate + end do + + end subroutine setup_active_tallies + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_tally_set_type(index, type) result(err) bind(C) + ! Set the type of the tally + integer(C_INT32_T), value, intent(in) :: index + character(kind=C_CHAR), intent(in) :: type(*) + integer(C_INT) :: err + + integer(C_INT32_T) :: empty(0) + character(:), allocatable :: type_ + + ! Convert C string to Fortran string + type_ = to_f_string(type) + + err = 0 + if (index >= 1 .and. index <= n_tallies) then + if (allocated(tallies(index) % obj)) then + err = E_ALLOCATE + call set_errmsg("Tally type has already been set.") + else + select case (type_) + case ('generic') + allocate(TallyObject :: tallies(index) % obj) + case default + err = E_UNASSIGNED + call set_errmsg("Unknown tally type: " // trim(type_)) + end select + + ! When a tally is allocated, set it to have 0 filters + err = tallies(index) % obj % set_filters(empty) + end if else - t % n_realizations = t % n_realizations + n_procs + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in tallies array is out of bounds.") end if - - ! Accumulate each result - do j = 1, size(t % results, 3) - do i = 1, size(t % results, 2) - val = t % results(RESULT_VALUE, i, j)/total_weight - t % results(RESULT_VALUE, i, j) = ZERO - - t % results(RESULT_SUM, i, j) = & - t % results(RESULT_SUM, i, j) + val - t % results(RESULT_SUM_SQ, i, j) = & - t % results(RESULT_SUM_SQ, i, j) + val*val - end do - end do - - end subroutine accumulate_tally - -!=============================================================================== -! SETUP_ACTIVE_USERTALLIES -!=============================================================================== - - subroutine setup_active_usertallies() - - integer :: i ! loop counter - - do i = 1, n_user_tallies - ! Add tally to active tallies - call active_tallies % push_back(i_user_tallies + i) - - ! Check what type of tally this is and add it to the appropriate list - if (user_tallies(i) % type == TALLY_VOLUME) then - if (user_tallies(i) % estimator == ESTIMATOR_ANALOG) then - call active_analog_tallies % push_back(i_user_tallies + i) - elseif (user_tallies(i) % estimator == ESTIMATOR_TRACKLENGTH) then - call active_tracklength_tallies % push_back(i_user_tallies + i) - elseif (user_tallies(i) % estimator == ESTIMATOR_COLLISION) then - call active_collision_tallies % push_back(i_user_tallies + i) - end if - elseif (user_tallies(i) % type == TALLY_SURFACE_CURRENT) then - call active_current_tallies % push_back(i_user_tallies + i) - end if - end do - - call active_tallies % shrink_to_fit() - call active_analog_tallies % shrink_to_fit() - call active_tracklength_tallies % shrink_to_fit() - call active_collision_tallies % shrink_to_fit() - call active_current_tallies % shrink_to_fit() - - end subroutine setup_active_usertallies - -!=============================================================================== -! SETUP_ACTIVE_CMFDTALLIES -!=============================================================================== - - subroutine setup_active_cmfdtallies() - - integer :: i ! loop counter - - ! check to see if any of the active tally lists has been allocated - if (active_tallies % size() > 0) then - call fatal_error("Active tallies should not exist before CMFD tallies!") - else if (active_analog_tallies % size() > 0) then - call fatal_error('Active analog tallies should not exist before CMFD & - &tallies!') - else if (active_tracklength_tallies % size() > 0) then - call fatal_error("Active tracklength tallies should not exist before & - &CMFD tallies!") - else if (active_current_tallies % size() > 0) then - call fatal_error("Active current tallies should not exist before CMFD & - &tallies!") - end if - - do i = 1, n_cmfd_tallies - ! Add CMFD tally to active tallies - call active_tallies % push_back(i_cmfd_tallies + i) - - ! Check what type of tally this is and add it to the appropriate list - if (cmfd_tallies(i) % type == TALLY_VOLUME) then - if (cmfd_tallies(i) % estimator == ESTIMATOR_ANALOG) then - call active_analog_tallies % push_back(i_cmfd_tallies + i) - elseif (cmfd_tallies(i) % estimator == ESTIMATOR_TRACKLENGTH) then - call active_tracklength_tallies % push_back(i_cmfd_tallies + i) - end if - elseif (cmfd_tallies(i) % type == TALLY_SURFACE_CURRENT) then - call active_current_tallies % push_back(i_cmfd_tallies + i) - end if - end do - - call active_tallies % shrink_to_fit() - call active_analog_tallies % shrink_to_fit() - call active_tracklength_tallies % shrink_to_fit() - call active_collision_tallies % shrink_to_fit() - call active_current_tallies % shrink_to_fit() - - end subroutine setup_active_cmfdtallies + end function openmc_tally_set_type end module tally diff --git a/src/tallies/tally_derivative_header.F90 b/src/tallies/tally_derivative_header.F90 new file mode 100644 index 000000000..304ae5e45 --- /dev/null +++ b/src/tallies/tally_derivative_header.F90 @@ -0,0 +1,104 @@ +module tally_derivative_header + + use constants + use dict_header, only: DictIntInt, EMPTY + use error, only: fatal_error + use nuclide_header, only: nuclide_dict + use string, only: to_str, to_lower + use xml_interface + + implicit none + private + public :: free_memory_tally_derivative + +!=============================================================================== +! TALLYDERIVATIVE describes a first-order derivative that can be applied to +! tallies. +!=============================================================================== + + type, public :: TallyDerivative + integer :: id + integer :: variable + integer :: diff_material + integer :: diff_nuclide + real(8) :: flux_deriv + contains + procedure :: from_xml + end type TallyDerivative + + type(TallyDerivative), public, allocatable :: tally_derivs(:) +!$omp threadprivate(tally_derivs) + + ! Dictionary that maps user IDs to indices in 'tally_derivs' + type(DictIntInt), public :: tally_deriv_dict + +contains + + subroutine from_xml(this, node) + class(TallyDerivative), intent(inout) :: this + type(XMLNode), intent(in) :: node + + character(MAX_WORD_LEN) :: temp_str + character(MAX_WORD_LEN) :: word + integer :: val + + ! Copy the derivative id. + if (check_for_node(node, "id")) then + call get_node_value(node, "id", this % id) + else + call fatal_error("Must specify an ID for elements in the& + & tally XML file") + end if + + ! Make sure the id is > 0. + if (this % id <= 0) then + call fatal_error(" IDs must be an integer greater than & + &zero") + end if + + ! Make sure this id has not already been used. + if (tally_deriv_dict % has(this % id)) then + call fatal_error("Two or more 's use the same unique & + &ID: " // trim(to_str(this % id))) + end if + + ! Read the independent variable name. + call get_node_value(node, "variable", temp_str) + temp_str = to_lower(temp_str) + + select case(temp_str) + case("density") + this % variable = DIFF_DENSITY + + case("nuclide_density") + this % variable = DIFF_NUCLIDE_DENSITY + + call get_node_value(node, "nuclide", word) + word = trim(to_lower(word)) + val = nuclide_dict % get(word) + if (val == EMPTY) then + call fatal_error("Could not find the nuclide " & + // trim(word) // " specified in derivative " & + // trim(to_str(this % id)) // " in any material.") + end if + this % diff_nuclide = val + + case("temperature") + this % variable = DIFF_TEMPERATURE + end select + + call get_node_value(node, "material", this % diff_material) + + end subroutine from_xml + +!=============================================================================== +! FREE_MEMORY_TALLY_DERIVATIVE deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_tally_derivative() +!$omp parallel + if (allocated(tally_derivs)) deallocate(tally_derivs) +!$omp end parallel + end subroutine free_memory_tally_derivative + +end module tally_derivative_header diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 new file mode 100644 index 000000000..e3d1d7082 --- /dev/null +++ b/src/tallies/tally_filter.F90 @@ -0,0 +1,163 @@ +module tally_filter + + use, intrinsic :: ISO_C_BINDING + + use hdf5, only: HID_T + + use error + use string, only: to_f_string + use tally_filter_header + + ! Inherit other filters + use tally_filter_azimuthal + use tally_filter_cell + use tally_filter_cellborn + use tally_filter_cellfrom + use tally_filter_delayedgroup + use tally_filter_distribcell + use tally_filter_energy + use tally_filter_energyfunc + use tally_filter_material + use tally_filter_mesh + use tally_filter_mu + use tally_filter_particle + use tally_filter_polar + use tally_filter_surface + use tally_filter_universe + + implicit none + +contains + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_filter_get_type(index, type) result(err) bind(C) + ! Get the type of a filter + integer(C_INT32_T), value, intent(in) :: index + character(kind=C_CHAR), intent(out) :: type(*) + integer(C_INT) :: err + + integer :: i + character(20) :: type_ + + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + ! Get type as a Fortran string + select type (f => filters(index) % obj) + type is (AzimuthalFilter) + type_ = 'azimuthal' + type is (CellFilter) + type_ = 'cell' + type is (CellbornFilter) + type_ = 'cellborn' + type is (CellfromFilter) + type_ = 'cellfrom' + type is (DelayedGroupFilter) + type_ = 'delayedgroup' + type is (DistribcellFilter) + type_ = 'distribcell' + type is (EnergyFilter) + type_ = 'energy' + type is (EnergyoutFilter) + type_ = 'energyout' + type is (EnergyFunctionFilter) + type_ = 'energyfunction' + type is (MaterialFilter) + type_ = 'material' + type is (MeshFilter) + type_ = 'mesh' + type is (MuFilter) + type_ = 'mu' + type is (ParticleFilter) + type_ = 'particle' + type is (PolarFilter) + type_ = 'polar' + type is (SurfaceFilter) + type_ = 'surface' + type is (UniverseFilter) + type_ = 'universe' + end select + + ! Convert Fortran string to null-terminated C string. We assume the + ! caller has allocated a char array buffer + do i = 1, len_trim(type_) + type(i) = type_(i:i) + end do + type(len_trim(type_) + 1) = C_NULL_CHAR + + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array is out of bounds.") + end if + end function openmc_filter_get_type + + + function openmc_filter_set_type(index, type) result(err) bind(C) + ! Set the type of a filter + integer(C_INT32_T), value, intent(in) :: index + character(kind=C_CHAR), intent(in) :: type(*) + integer(C_INT) :: err + + character(:), allocatable :: type_ + + ! Convert C string to Fortran string + type_ = to_f_string(type) + + err = 0 + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + err = E_ALLOCATE + call set_errmsg("Filter type has already been set.") + else + select case (type_) + case ('azimuthal') + allocate(AzimuthalFilter :: filters(index) % obj) + case ('cell') + allocate(CellFilter :: filters(index) % obj) + case ('cellborn') + allocate(CellbornFilter :: filters(index) % obj) + case ('cellfrom') + allocate(CellfromFilter :: filters(index) % obj) + case ('delayedgroup') + allocate(DelayedGroupFilter :: filters(index) % obj) + case ('distribcell') + allocate(DistribcellFilter :: filters(index) % obj) + case ('energy') + allocate(EnergyFilter :: filters(index) % obj) + case ('energyout') + allocate(EnergyoutFilter :: filters(index) % obj) + case ('energyfunction') + allocate(EnergyFunctionFilter :: filters(index) % obj) + case ('material') + allocate(MaterialFilter :: filters(index) % obj) + case ('mesh') + allocate(MeshFilter :: filters(index) % obj) + case ('mu') + allocate(MuFilter :: filters(index) % obj) + case ('particle') + allocate(ParticleFilter :: filters(index) % obj) + case ('polar') + allocate(PolarFilter :: filters(index) % obj) + case ('surface') + allocate(SurfaceFilter :: filters(index) % obj) + case ('universe') + allocate(UniverseFilter :: filters(index) % obj) + case default + err = E_UNASSIGNED + call set_errmsg("Unknown filter type: " // trim(type_)) + end select + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array is out of bounds.") + end if + end function openmc_filter_set_type + +end module tally_filter diff --git a/src/tallies/tally_filter_azimuthal.F90 b/src/tallies/tally_filter_azimuthal.F90 new file mode 100644 index 000000000..272d111d1 --- /dev/null +++ b/src/tallies/tally_filter_azimuthal.F90 @@ -0,0 +1,122 @@ +module tally_filter_azimuthal + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use algorithm, only: binary_search + use constants + use error, only: fatal_error + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! AZIMUTHALFILTER bins the incident neutron azimuthal angle (relative to the +! global xy-plane). +!=============================================================================== + + type, public, extends(TallyFilter) :: AzimuthalFilter + real(8), allocatable :: bins(:) + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_azimuthal + procedure :: to_statepoint => to_statepoint_azimuthal + procedure :: text_label => text_label_azimuthal + end type AzimuthalFilter + +contains + + subroutine from_xml(this, node) + class(AzimuthalFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: i + integer :: n + integer :: n_angle + real(8) :: d_angle + + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n - 1 + allocate(this % bins(n)) + call get_node_array(node, "bins", this % bins) + + ! Allow a user to input a lone number which will mean that you + ! subdivide [-pi,pi) evenly with the input being the number of + ! bins + if (n == 1) then + n_angle = int(this % bins(1)) + if (n_angle > 1) then + this % n_bins = n_angle + d_angle = TWO * PI / n_angle + deallocate(this % bins) + allocate(this % bins(n_angle + 1)) + do i = 1, n_angle + this % bins(i) = -PI + (i - 1) * d_angle + end do + this % bins(n_angle + 1) = PI + else + call fatal_error("Number of bins for azimuthal filter must be& + & greater than 1.") + end if + end if + end subroutine from_xml + + subroutine get_all_bins_azimuthal(this, p, estimator, match) + class(AzimuthalFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: n + integer :: bin + real(8) :: phi + + n = this % n_bins + + ! Make sure the correct direction vector is used. + if (estimator == ESTIMATOR_TRACKLENGTH) then + phi = atan2(p % coord(1) % uvw(2), p % coord(1) % uvw(1)) + else + phi = atan2(p % last_uvw(2), p % last_uvw(1)) + end if + + ! Search to find azimuthal angle bin. + bin = binary_search(this % bins, n + 1, phi) + if (bin /= NO_BIN_FOUND) then + call match % bins % push_back(bin) + call match % weights % push_back(ONE) + end if + + end subroutine get_all_bins_azimuthal + + subroutine to_statepoint_azimuthal(this, filter_group) + class(AzimuthalFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "azimuthal") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins) + end subroutine to_statepoint_azimuthal + + function text_label_azimuthal(this, bin) result(label) + class(AzimuthalFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Azimuthal Angle [" // trim(to_str(E0)) // ", " & + // trim(to_str(E1)) // ")" + end function text_label_azimuthal + +end module tally_filter_azimuthal diff --git a/src/tallies/tally_filter_cell.F90 b/src/tallies/tally_filter_cell.F90 new file mode 100644 index 000000000..8d2b93d28 --- /dev/null +++ b/src/tallies/tally_filter_cell.F90 @@ -0,0 +1,121 @@ +module tally_filter_cell + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants, only: ONE, MAX_LINE_LEN + use dict_header, only: EMPTY + use error, only: fatal_error + use hdf5_interface + use geometry_header + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! CELLFILTER specifies which geometric cells tally events reside in. +!=============================================================================== + + type, public, extends(TallyFilter) :: CellFilter + integer, allocatable :: cells(:) + type(DictIntInt) :: map + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_cell + procedure :: to_statepoint => to_statepoint_cell + procedure :: text_label => text_label_cell + procedure :: initialize => initialize_cell + end type CellFilter + +contains + + subroutine from_xml(this, node) + class(CellFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + ! Determine how many bins were given + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n + allocate(this % cells(n)) + call get_node_array(node, "bins", this % cells) + end subroutine from_xml + + subroutine get_all_bins_cell(this, p, estimator, match) + class(CellFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: i + integer :: val + + ! Iterate over coordinate levels to see with cells match + do i = 1, p % n_coord + val = this % map % get(p % coord(i) % cell) + if (val /= EMPTY) then + call match % bins % push_back(val) + call match % weights % push_back(ONE) + end if + end do + + end subroutine get_all_bins_cell + + subroutine to_statepoint_cell(this, filter_group) + class(CellFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + integer :: i + integer, allocatable :: cell_ids(:) + + call write_dataset(filter_group, "type", "cell") + call write_dataset(filter_group, "n_bins", this % n_bins) + + allocate(cell_ids(size(this % cells))) + do i = 1, size(this % cells) + cell_ids(i) = cells(this % cells(i)) % id + end do + call write_dataset(filter_group, "bins", cell_ids) + end subroutine to_statepoint_cell + + subroutine initialize_cell(this) + class(CellFilter), intent(inout) :: this + + integer :: i, id + integer :: val + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % cells(i) + val = cell_dict % get(id) + if (val /= EMPTY) then + this % cells(i) = val + else + call fatal_error("Could not find cell " // trim(to_str(id)) & + &// " specified on tally filter.") + end if + end do + + ! Generate mapping from cell indices to filter bins. + do i = 1, this % n_bins + call this % map % set(this % cells(i), i) + end do + end subroutine initialize_cell + + function text_label_cell(this, bin) result(label) + class(CellFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Cell " // to_str(cells(this % cells(bin)) % id) + end function text_label_cell + +end module tally_filter_cell diff --git a/src/tallies/tally_filter_cellborn.F90 b/src/tallies/tally_filter_cellborn.F90 new file mode 100644 index 000000000..450858b6b --- /dev/null +++ b/src/tallies/tally_filter_cellborn.F90 @@ -0,0 +1,116 @@ +module tally_filter_cellborn + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants, only: ONE, MAX_LINE_LEN + use dict_header, only: EMPTY + use error, only: fatal_error + use hdf5_interface + use geometry_header + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! CELLBORNFILTER specifies which cell the particle was born in. +!=============================================================================== + + type, public, extends(TallyFilter) :: CellbornFilter + integer, allocatable :: cells(:) + type(DictIntInt) :: map + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_cellborn + procedure :: to_statepoint => to_statepoint_cellborn + procedure :: text_label => text_label_cellborn + procedure :: initialize => initialize_cellborn + end type CellbornFilter + +contains + + subroutine from_xml(this, node) + class(CellbornFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + ! Determine number of bins + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n + allocate(this % cells(n)) + call get_node_array(node, "bins", this % cells) + end subroutine from_xml + + subroutine get_all_bins_cellborn(this, p, estimator, match) + class(CellbornFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: val + + val = this % map % get(p % cell_born) + if (val /= EMPTY) then + call match % bins % push_back(val) + call match % weights % push_back(ONE) + end if + + end subroutine get_all_bins_cellborn + + subroutine to_statepoint_cellborn(this, filter_group) + class(CellbornFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + integer :: i + integer, allocatable :: cell_ids(:) + + call write_dataset(filter_group, "type", "cellborn") + call write_dataset(filter_group, "n_bins", this % n_bins) + allocate(cell_ids(size(this % cells))) + do i = 1, size(this % cells) + cell_ids(i) = cells(this % cells(i)) % id + end do + call write_dataset(filter_group, "bins", cell_ids) + end subroutine to_statepoint_cellborn + + subroutine initialize_cellborn(this) + class(CellbornFilter), intent(inout) :: this + + integer :: i, id + integer :: val + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % cells(i) + val = cell_dict % get(id) + if (val /= EMPTY) then + this % cells(i) = val + else + call fatal_error("Could not find cell " // trim(to_str(id)) & + &// " specified on tally filter.") + end if + end do + + ! Generate mapping from cell indices to filter bins. + do i = 1, this % n_bins + call this % map % set(this % cells(i), i) + end do + end subroutine initialize_cellborn + + function text_label_cellborn(this, bin) result(label) + class(CellbornFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Birth Cell " // to_str(cells(this % cells(bin)) % id) + end function text_label_cellborn + +end module tally_filter_cellborn diff --git a/src/tallies/tally_filter_cellfrom.F90 b/src/tallies/tally_filter_cellfrom.F90 new file mode 100644 index 000000000..16cb294d5 --- /dev/null +++ b/src/tallies/tally_filter_cellfrom.F90 @@ -0,0 +1,82 @@ +module tally_filter_cellfrom + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants, only: ONE, MAX_LINE_LEN + use dict_header, only: EMPTY + use error, only: fatal_error + use hdf5_interface + use geometry_header + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use tally_filter_cell + use xml_interface + + implicit none + private + +!=============================================================================== +! CELLFROMFILTER specifies which geometric cells particles exit when crossing a +! surface. +!=============================================================================== + + type, public, extends(CellFilter) :: CellFromFilter + contains + ! Inherit from_xml from CellFilter + procedure :: get_all_bins => get_all_bins_cell_from + procedure :: to_statepoint => to_statepoint_cell_from + procedure :: text_label => text_label_cell_from + end type CellFromFilter + +contains + + subroutine get_all_bins_cell_from(this, p, estimator, match) + class(CellFromFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: i + integer :: val + + ! Starting one coordinate level deeper, find the next bin. + do i = 1, p % last_n_coord + val = this % map % get(p % last_cell(i)) + if (val /= EMPTY) then + call match % bins % push_back(val) + call match % weights % push_back(ONE) + exit + end if + end do + + end subroutine get_all_bins_cell_from + + subroutine to_statepoint_cell_from(this, filter_group) + class(CellFromFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + integer :: i + integer, allocatable :: cell_ids(:) + + call write_dataset(filter_group, "type", "cellfrom") + call write_dataset(filter_group, "n_bins", this % n_bins) + + allocate(cell_ids(size(this % cells))) + do i = 1, size(this % cells) + cell_ids(i) = cells(this % cells(i)) % id + end do + call write_dataset(filter_group, "bins", cell_ids) + end subroutine to_statepoint_cell_from + + function text_label_cell_from(this, bin) result(label) + class(CellFromFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Cell from " // to_str(cells(this % cells(bin)) % id) + end function text_label_cell_from + +end module tally_filter_cellfrom diff --git a/src/tallies/tally_filter_delayedgroup.F90 b/src/tallies/tally_filter_delayedgroup.F90 new file mode 100644 index 000000000..32010e815 --- /dev/null +++ b/src/tallies/tally_filter_delayedgroup.F90 @@ -0,0 +1,88 @@ +module tally_filter_delayedgroup + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants, only: ONE, MAX_LINE_LEN, MAX_DELAYED_GROUPS + use error, only: fatal_error + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! DELAYEDGROUPFILTER bins outgoing fission neutrons in their delayed groups. +! The get_all_bins functionality is not actually used. The bins are manually +! iterated over in the scoring subroutines. +!=============================================================================== + + type, public, extends(TallyFilter) :: DelayedGroupFilter + integer, allocatable :: groups(:) + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_dg + procedure :: to_statepoint => to_statepoint_dg + procedure :: text_label => text_label_dg + end type DelayedGroupFilter + +contains + + subroutine from_xml(this, node) + class(DelayedGroupFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: i + integer :: n + + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n + allocate(this % groups(n)) + call get_node_array(node, "bins", this % groups) + + ! Check that bins are all are between 1 and MAX_DELAYED_GROUPS + do i = 1, n + if (this % groups(i) < 1 .or. & + this % groups(i) > MAX_DELAYED_GROUPS) then + call fatal_error("Encountered delayedgroup bin with index " & + // trim(to_str(this % groups(i))) // " that is outside & + &the range of 1 to MAX_DELAYED_GROUPS ( " & + // trim(to_str(MAX_DELAYED_GROUPS)) // ")") + end if + end do + end subroutine from_xml + + subroutine get_all_bins_dg(this, p, estimator, match) + class(DelayedGroupFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + call match % bins % push_back(1) + call match % weights % push_back(ONE) + end subroutine get_all_bins_dg + + subroutine to_statepoint_dg(this, filter_group) + class(DelayedGroupFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "delayedgroup") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % groups) + end subroutine to_statepoint_dg + + function text_label_dg(this, bin) result(label) + class(DelayedGroupFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Delayed Group " // to_str(this % groups(bin)) + end function text_label_dg + +end module tally_filter_delayedgroup diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 new file mode 100644 index 000000000..3cb34efe4 --- /dev/null +++ b/src/tallies/tally_filter_distribcell.F90 @@ -0,0 +1,411 @@ +module tally_filter_distribcell + + use, intrinsic :: ISO_C_BINDING + + use hdf5, only: HID_T + + use constants + use dict_header, only: EMPTY + use error + use geometry_header + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + public :: find_offset + +!=============================================================================== +! DISTRIBCELLFILTER specifies which distributed geometric cells tally events +! reside in. +!=============================================================================== + + type, public, extends(TallyFilter) :: DistribcellFilter + integer :: cell + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_distribcell + procedure :: to_statepoint => to_statepoint_distribcell + procedure :: text_label => text_label_distribcell + procedure :: initialize => initialize_distribcell + end type DistribcellFilter + +contains + + subroutine from_xml(this, node) + class(DistribcellFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + n = node_word_count(node, "bins") + if (n /= 1) call fatal_error("Only one cell can be & + &specified per distribcell filter.") + + ! Store bins + call get_node_value(node, "bins", this % cell) + end subroutine from_xml + + subroutine get_all_bins_distribcell(this, p, estimator, match) + class(DistribcellFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: distribcell_index, offset, i + + distribcell_index = cells(this % cell) % distribcell_index + offset = 0 + do i = 1, p % n_coord + if (cells(p % coord(i) % cell) % type == FILL_UNIVERSE) then + offset = offset + cells(p % coord(i) % cell) % & + offset(distribcell_index) + elseif (cells(p % coord(i) % cell) % type == FILL_LATTICE) then + if (lattices(p % coord(i + 1) % lattice) % obj & + % are_valid_indices([& + p % coord(i + 1) % lattice_x, & + p % coord(i + 1) % lattice_y, & + p % coord(i + 1) % lattice_z])) then + offset = offset + lattices(p % coord(i + 1) % lattice) % obj % & + offset(distribcell_index, & + p % coord(i + 1) % lattice_x, & + p % coord(i + 1) % lattice_y, & + p % coord(i + 1) % lattice_z) + end if + end if + if (this % cell == p % coord(i) % cell) then + call match % bins % push_back(offset + 1) + call match % weights % push_back(ONE) + return + end if + end do + end subroutine get_all_bins_distribcell + + subroutine to_statepoint_distribcell(this, filter_group) + class(DistribcellFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "distribcell") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", cells(this % cell) % id) + end subroutine to_statepoint_distribcell + + subroutine initialize_distribcell(this) + class(DistribcellFilter), intent(inout) :: this + + integer :: id + integer :: val + + ! Convert id to index. + id = this % cell + val = cell_dict % get(id) + if (val /= EMPTY) then + this % cell = val + this % n_bins = cells(this % cell) % instances + else + call fatal_error("Could not find cell " // trim(to_str(id)) & + &// " specified on tally filter.") + end if + end subroutine initialize_distribcell + + function text_label_distribcell(this, bin) result(label) + class(DistribcellFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + integer :: offset + type(Universe), pointer :: univ + + univ => universes(root_universe) + offset = 0 + label = '' + call find_offset(this % cell, univ, bin-1, offset, label) + label = "Distributed Cell " // label + end function text_label_distribcell + +!=============================================================================== +! FIND_OFFSET (for distribcell) uses a given map number, a target cell ID, and +! a target offset to build a string which is the path from the base universe to +! the target cell with the given offset +!=============================================================================== + + recursive subroutine find_offset(i_cell, univ, target_offset, offset, path) + + integer, intent(in) :: i_cell ! The target cell index + type(Universe), intent(in) :: univ ! Universe to begin search + integer, intent(in) :: target_offset ! Target offset + integer, intent(inout) :: offset ! Current offset + character(*), intent(inout) :: path ! Path to offset + + integer :: map ! Index in maps vector + integer :: i, j ! Index over cells + integer :: k, l, m ! Indices in lattice + integer :: old_k, old_l, old_m ! Previous indices in lattice + integer :: n_x, n_y, n_z ! Lattice cell array dimensions + integer :: n ! Number of cells to search + integer :: cell_index ! Index in cells array + integer :: lat_offset ! Offset from lattice + integer :: temp_offset ! Looped sum of offsets + integer :: i_univ ! index in universes array + logical :: this_cell = .false. ! Advance in this cell? + logical :: later_cell = .false. ! Fill cells after this one? + type(Cell), pointer :: c ! Pointer to current cell + type(Universe), pointer :: next_univ ! Next universe to loop through + class(Lattice), pointer :: lat ! Pointer to current lattice + + ! Get the distribcell index for this cell + map = cells(i_cell) % distribcell_index + + n = size(univ % cells) + + ! Write to the geometry stack + i_univ = universe_dict % get(univ % id) + if (i_univ == root_universe) then + path = trim(path) // "u" // to_str(univ%id) + else + path = trim(path) // "->u" // to_str(univ%id) + end if + + ! Look through all cells in this universe + do i = 1, n + ! If the cell matches the goal and the offset matches final, write to the + ! geometry stack + if (univ % cells(i) == i_cell .and. offset == target_offset) then + c => cells(univ % cells(i)) + path = trim(path) // "->c" // to_str(c % id) + return + end if + end do + + ! Find the fill cell or lattice cell that we need to enter + do i = 1, n + + later_cell = .false. + + c => cells(univ % cells(i)) + + this_cell = .false. + + ! If we got here, we still think the target is in this universe + ! or further down, but it's not this exact cell. + ! Compare offset to next cell to see if we should enter this cell + if (i /= n) then + + do j = i+1, n + + c => cells(univ % cells(j)) + + ! Skip normal cells which do not have offsets + if (c % type == FILL_MATERIAL) cycle + + ! Break loop once we've found the next cell with an offset + exit + end do + + ! Ensure we didn't just end the loop by iteration + if (c % type /= FILL_MATERIAL) then + + ! There are more cells in this universe that it could be in + later_cell = .true. + + ! Two cases, lattice or fill cell + if (c % type == FILL_UNIVERSE) then + temp_offset = c % offset(map) + + ! Get the offset of the first lattice location + else + lat => lattices(c % fill) % obj + temp_offset = lat % offset(map, 1, 1, 1) + end if + + ! If the final offset is in the range of offset - temp_offset+offset + ! then the goal is in this cell + if (target_offset < temp_offset + offset) then + this_cell = .true. + end if + end if + end if + + if (n == 1 .and. c % type /= FILL_MATERIAL) then + this_cell = .true. + end if + + if (.not. later_cell) then + this_cell = .true. + end if + + ! Get pointer to THIS cell because target must be in this cell + if (this_cell) then + + cell_index = univ % cells(i) + c => cells(cell_index) + + path = trim(path) // "->c" // to_str(c%id) + + ! ==================================================================== + ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL + if (c % type == FILL_UNIVERSE) then + + ! Enter this cell to update the current offset + offset = c % offset(map) + offset + + next_univ => universes(c % fill) + call find_offset(i_cell, next_univ, target_offset, offset, path) + return + + ! ==================================================================== + ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL + elseif (c % type == FILL_LATTICE) then + + ! Set current lattice + lat => lattices(c % fill) % obj + + select type (lat) + + ! ================================================================== + ! RECTANGULAR LATTICES + type is (RectLattice) + + ! Write to the geometry stack + path = trim(path) // "->l" // to_str(lat%id) + + n_x = lat % n_cells(1) + n_y = lat % n_cells(2) + n_z = lat % n_cells(3) + old_m = 1 + old_l = 1 + old_k = 1 + + ! Loop over lattice coordinates + do k = 1, n_x + do l = 1, n_y + do m = 1, n_z + + if (target_offset >= lat % offset(map, k, l, m) + offset) then + if (k == n_x .and. l == n_y .and. m == n_z) then + ! This is last lattice cell, so target must be here + lat_offset = lat % offset(map, k, l, m) + offset = offset + lat_offset + next_univ => universes(lat % universes(k, l, m)) + if (lat % is_3d) then + path = trim(path) // "(" // trim(to_str(k-1)) // & + "," // trim(to_str(l-1)) // "," // & + trim(to_str(m-1)) // ")" + else + path = trim(path) // "(" // trim(to_str(k-1)) // & + "," // trim(to_str(l-1)) // ")" + end if + call find_offset(i_cell, next_univ, target_offset, offset, path) + return + else + old_m = m + old_l = l + old_k = k + cycle + end if + else + ! Target is at this lattice position + lat_offset = lat % offset(map, old_k, old_l, old_m) + offset = offset + lat_offset + next_univ => universes(lat % universes(old_k, old_l, old_m)) + if (lat % is_3d) then + path = trim(path) // "(" // trim(to_str(old_k-1)) // & + "," // trim(to_str(old_l-1)) // "," // & + trim(to_str(old_m-1)) // ")" + else + path = trim(path) // "(" // trim(to_str(old_k-1)) // & + "," // trim(to_str(old_l-1)) // ")" + end if + call find_offset(i_cell, next_univ, target_offset, offset, path) + return + end if + + end do + end do + end do + + ! ================================================================== + ! HEXAGONAL LATTICES + type is (HexLattice) + + ! Write to the geometry stack + path = trim(path) // "->l" // to_str(lat%id) + + n_z = lat % n_axial + n_y = 2 * lat % n_rings - 1 + n_x = 2 * lat % n_rings - 1 + old_m = 1 + old_l = 1 + old_k = 1 + + ! Loop over lattice coordinates + do m = 1, n_z + do l = 1, n_y + do k = 1, n_x + + ! This array position is never used + if (k + l < lat % n_rings + 1) then + cycle + ! This array position is never used + else if (k + l > 3*lat % n_rings - 1) then + cycle + end if + + if (target_offset >= lat % offset(map, k, l, m) + offset) then + if (k == lat % n_rings .and. l == n_y .and. m == n_z) then + ! This is last lattice cell, so target must be here + lat_offset = lat % offset(map, k, l, m) + offset = offset + lat_offset + next_univ => universes(lat % universes(k, l, m)) + if (lat % is_3d) then + path = trim(path) // "(" // & + trim(to_str(k - lat % n_rings)) // "," // & + trim(to_str(l - lat % n_rings)) // "," // & + trim(to_str(m - 1)) // ")" + else + path = trim(path) // "(" // & + trim(to_str(k - lat % n_rings)) // "," // & + trim(to_str(l - lat % n_rings)) // ")" + end if + call find_offset(i_cell, next_univ, target_offset, offset, path) + return + else + old_m = m + old_l = l + old_k = k + cycle + end if + else + ! Target is at this lattice position + lat_offset = lat % offset(map, old_k, old_l, old_m) + offset = offset + lat_offset + next_univ => universes(lat % universes(old_k, old_l, old_m)) + if (lat % is_3d) then + path = trim(path) // "(" // & + trim(to_str(old_k - lat % n_rings)) // "," // & + trim(to_str(old_l - lat % n_rings)) // "," // & + trim(to_str(old_m - 1)) // ")" + else + path = trim(path) // "(" // & + trim(to_str(old_k - lat % n_rings)) // "," // & + trim(to_str(old_l - lat % n_rings)) // ")" + end if + call find_offset(i_cell, next_univ, target_offset, offset, path) + return + end if + + end do + end do + end do + + end select + + end if + end if + end do + end subroutine find_offset + +end module tally_filter_distribcell diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 new file mode 100644 index 000000000..7f9655280 --- /dev/null +++ b/src/tallies/tally_filter_energy.F90 @@ -0,0 +1,259 @@ +module tally_filter_energy + + use, intrinsic :: ISO_C_BINDING + + use hdf5, only: HID_T + + use algorithm, only: binary_search + use constants + use error + use hdf5_interface + use mgxs_header, only: num_energy_groups, energy_bins + use particle_header, only: Particle + use settings, only: run_CE + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + public :: openmc_energy_filter_get_bins + public :: openmc_energy_filter_set_bins + +!=============================================================================== +! ENERGYFILTER bins the incident neutron energy. +!=============================================================================== + + type, public, extends(TallyFilter) :: EnergyFilter + real(8), allocatable :: bins(:) + + ! True if transport group number can be used directly to get bin number + logical :: matches_transport_groups = .false. + contains + procedure :: from_xml => from_xml_energy + procedure :: get_all_bins => get_all_bins_energy + procedure :: to_statepoint => to_statepoint_energy + procedure :: text_label => text_label_energy + end type EnergyFilter + +!=============================================================================== +! ENERGYOUTFILTER bins the outgoing neutron energy. Only scattering events use +! the get_all_bins functionality. Nu-fission tallies manually iterate over the +! filter bins. +!=============================================================================== + + type, public, extends(EnergyFilter) :: EnergyoutFilter + contains + ! Inherit from_xml from EnergyFilter + procedure :: get_all_bins => get_all_bins_energyout + procedure :: to_statepoint => to_statepoint_energyout + procedure :: text_label => text_label_energyout + end type EnergyoutFilter + +contains + +!=============================================================================== +! EnergyFilter methods +!=============================================================================== + + subroutine from_xml_energy(this, node) + class(EnergyFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n - 1 + allocate(this % bins(n)) + call get_node_array(node, "bins", this % bins) + + ! We can save tallying time if we know that the tally bins match + ! the energy group structure. In that case, the matching bin + ! index is simply the group (after flipping for the different + ! ordering of the library and tallying systems). + if (.not. run_CE) then + if (n == num_energy_groups + 1) then + if (all(this % bins == energy_bins(num_energy_groups + 1:1:-1))) & + then + this % matches_transport_groups = .true. + end if + end if + end if + end subroutine from_xml_energy + + subroutine get_all_bins_energy(this, p, estimator, match) + class(EnergyFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: n + integer :: bin + real(8) :: E + + n = this % n_bins + + if (p % g /= NONE .and. this % matches_transport_groups) then + if (estimator == ESTIMATOR_TRACKLENGTH) then + call match % bins % push_back(num_energy_groups - p % g + 1) + call match % weights % push_back(ONE) + else + call match % bins % push_back(num_energy_groups - p % last_g + 1) + call match % weights % push_back(ONE) + end if + + else + ! Pre-collision energy of particle + E = p % last_E + + ! Search to find incoming energy bin. + bin = binary_search(this % bins, n + 1, E) + if (bin /= NO_BIN_FOUND) then + call match % bins % push_back(bin) + call match % weights % push_back(ONE) + end if + end if + end subroutine get_all_bins_energy + + subroutine to_statepoint_energy(this, filter_group) + class(EnergyFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "energy") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins) + end subroutine to_statepoint_energy + + function text_label_energy(this, bin) result(label) + class(EnergyFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Incoming Energy [" // trim(to_str(E0)) // ", " & + // trim(to_str(E1)) // ")" + end function text_label_energy + +!=============================================================================== +! EnergyoutFilter methods +!=============================================================================== + + subroutine get_all_bins_energyout(this, p, estimator, match) + class(EnergyoutFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: n + integer :: bin + + n = this % n_bins + + if (p % g /= NONE .and. this % matches_transport_groups) then + ! Tallies are ordered in increasing groups, group indices + ! however are the opposite, so switch + call match % bins % push_back(num_energy_groups - p % g + 1) + call match % weights % push_back(ONE) + + else + + ! Search to find incoming energy bin. + bin = binary_search(this % bins, n + 1, p % E) + if (bin /= NO_BIN_FOUND) then + call match % bins % push_back(bin) + call match % weights % push_back(ONE) + end if + end if + end subroutine get_all_bins_energyout + + subroutine to_statepoint_energyout(this, filter_group) + class(EnergyoutFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "energyout") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins) + end subroutine to_statepoint_energyout + + function text_label_energyout(this, bin) result(label) + class(EnergyoutFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Outgoing Energy [" // trim(to_str(E0)) // ", " & + // trim(to_str(E1)) // ")" + end function text_label_energyout + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_energy_filter_get_bins(index, energies, n) result(err) bind(C) + ! Return the bounding energies for an energy filter + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: energies + integer(C_INT32_T), intent(out) :: n + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + select type (f => filters(index) % obj) + type is (EnergyFilter) + energies = C_LOC(f % bins) + n = size(f % bins) + err = 0 + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get energy bins on a non-energy filter.") + end select + else + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function openmc_energy_filter_get_bins + + + function openmc_energy_filter_set_bins(index, n, energies) result(err) bind(C) + ! Set the bounding energies for an energy filter + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: n + real(C_DOUBLE), intent(in) :: energies(n) + integer(C_INT) :: err + + err = 0 + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + select type (f => filters(index) % obj) + type is (EnergyFilter) + f % n_bins = n - 1 + if (allocated(f % bins)) deallocate(f % bins) + allocate(f % bins(n)) + f % bins(:) = energies + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get energy bins on a non-energy filter.") + end select + else + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function openmc_energy_filter_set_bins + +end module tally_filter_energy diff --git a/src/tallies/tally_filter_energyfunc.F90 b/src/tallies/tally_filter_energyfunc.F90 new file mode 100644 index 000000000..efaabae2e --- /dev/null +++ b/src/tallies/tally_filter_energyfunc.F90 @@ -0,0 +1,126 @@ +module tally_filter_energyfunc + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use algorithm, only: binary_search + use constants + use error, only: fatal_error + use hdf5_interface + use particle_header, only: Particle + use settings, only: run_CE + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! EnergyFunctionFilter multiplies tally scores by an arbitrary function of +! incident energy described by a piecewise linear-linear interpolation. +!=============================================================================== + + type, public, extends(TallyFilter) :: EnergyFunctionFilter + real(8), allocatable :: energy(:) + real(8), allocatable :: y(:) + + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_energyfunction + procedure :: to_statepoint => to_statepoint_energyfunction + procedure :: text_label => text_label_energyfunction + end type EnergyFunctionFilter + +contains + + subroutine from_xml(this, node) + class(EnergyFunctionFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + this % n_bins = 1 + ! Make sure this is continuous-energy mode. + if (.not. run_CE) then + call fatal_error("EnergyFunction filters are only supported for & + &continuous-energy transport calculations") + end if + + ! Allocate and store energy grid. + if (.not. check_for_node(node, "energy")) then + call fatal_error("Energy grid not specified for EnergyFunction & + &filter.") + end if + n = node_word_count(node, "energy") + allocate(this % energy(n)) + call get_node_array(node, "energy", this % energy) + + ! Allocate and store interpolant values. + if (.not. check_for_node(node, "y")) then + call fatal_error("y values not specified for EnergyFunction & + &filter.") + end if + n = node_word_count(node, "y") + allocate(this % y(n)) + call get_node_array(node, "y", this % y) + end subroutine from_xml + + subroutine get_all_bins_energyfunction(this, p, estimator, match) + class(EnergyFunctionFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: n, indx + real(8) :: E, f, weight + + select type(this) + type is (EnergyFunctionFilter) + n = size(this % energy) + + ! Get pre-collision energy of particle + E = p % last_E + + ! Search to find incoming energy bin. + indx = binary_search(this % energy, n, E) + + ! Compute an interpolation factor between nearest bins. + f = (E - this % energy(indx)) & + / (this % energy(indx+1) - this % energy(indx)) + + ! Interpolate on the lin-lin grid. + call match % bins % push_back(1) + weight = (ONE - f) * this % y(indx) + f * this % y(indx+1) + call match % weights % push_back(weight) + end select + end subroutine get_all_bins_energyfunction + + subroutine to_statepoint_energyfunction(this, filter_group) + class(EnergyFunctionFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + select type(this) + type is (EnergyFunctionFilter) + call write_dataset(filter_group, "type", "energyfunction") + call write_dataset(filter_group, "energy", this % energy) + call write_dataset(filter_group, "y", this % y) + end select + end subroutine to_statepoint_energyfunction + + function text_label_energyfunction(this, bin) result(label) + class(EnergyFunctionFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + select type(this) + type is (EnergyFunctionFilter) + write(label, FMT="(A, ES8.1, A, ES8.1, A, ES8.1, A, ES8.1, A)") & + "Energy Function f([", this % energy(1), ", ..., ", & + this % energy(size(this % energy)), "]) = [", this % y(1), & + ", ..., ", this % y(size(this % y)), "]" + end select + end function text_label_energyfunction + +end module tally_filter_energyfunc diff --git a/src/tallies/tally_filter_header.F90 b/src/tallies/tally_filter_header.F90 new file mode 100644 index 000000000..34ad75388 --- /dev/null +++ b/src/tallies/tally_filter_header.F90 @@ -0,0 +1,256 @@ +module tally_filter_header + + use, intrinsic :: ISO_C_BINDING + + use constants, only: MAX_LINE_LEN + use dict_header, only: DictIntInt + use error + use particle_header, only: Particle + use stl_vector, only: VectorInt, VectorReal + use string, only: to_str + use xml_interface, only: XMLNode + + use hdf5 + + implicit none + private + public :: free_memory_tally_filter + public :: openmc_extend_filters + public :: openmc_filter_get_id + public :: openmc_filter_set_id + public :: openmc_get_filter_index + public :: openmc_get_filter_next_id + +!=============================================================================== +! TALLYFILTERMATCH stores every valid bin and weight for a filter +!=============================================================================== + + type, public :: TallyFilterMatch + ! Index of the bin and weight being used in the current filter combination + integer :: i_bin + type(VectorInt) :: bins + type(VectorReal) :: weights + + ! Indicates whether all valid bins for this filter have been found + logical :: bins_present = .false. + end type TallyFilterMatch + +!=============================================================================== +! TALLYFILTER describes a filter that limits what events score to a tally. For +! example, a cell filter indicates that only particles in a specified cell +! should score to the tally. +!=============================================================================== + + type, public, abstract :: TallyFilter + integer :: id + integer :: n_bins = 0 + contains + procedure(from_xml_), deferred :: from_xml + procedure(get_all_bins_), deferred :: get_all_bins + procedure(to_statepoint_), deferred :: to_statepoint + procedure(text_label_), deferred :: text_label + procedure :: initialize => filter_initialize + end type TallyFilter + + abstract interface + + subroutine from_xml_(this, node) + import TallyFilter, XMLNode + class(TallyFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + end subroutine from_xml_ + +!=============================================================================== +! GET_NEXT_BIN gives the index for the next valid filter bin and a weight that +! will be applied to the flux. +! +! In principle, a filter can have multiple valid bins. If current_bin = +! NO_BIN_FOUND, then this method should give the first valid bin. Providing the +! first valid bin should then give the second valid bin, and so on. When there +! are no valid bins left, the next_bin should be NO_VALID_BIN. + + subroutine get_all_bins_(this, p, estimator, match) + import TallyFilter + import Particle + import TallyFilterMatch + class(TallyFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + end subroutine get_all_bins_ + +!=============================================================================== +! TO_STATEPOINT writes all the information needed to reconstruct the filter to +! the given filter_group. + + subroutine to_statepoint_(this, filter_group) + import TallyFilter + import HID_T + class(TallyFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + end subroutine to_statepoint_ + +!=============================================================================== +! TEXT_LABEL returns a string describing the given filter bin. For example, an +! energy filter might return the string "Incoming Energy [0.625E-6, 20.0)". +! This is used to write the tallies.out file. + + function text_label_(this, bin) result(label) + import TallyFilter + import MAX_LINE_LEN + class(TallyFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + end function text_label_ + + end interface + +!=============================================================================== +! TALLYFILTERCONTAINER contains an allocatable TallyFilter object for arrays of +! TallyFilters +!=============================================================================== + + type, public :: TallyFilterContainer + class(TallyFilter), allocatable :: obj + end type TallyFilterContainer + + integer(C_INT32_T), public, bind(C) :: n_filters = 0 ! # of filters + + type(TallyFilterContainer), public, allocatable, target :: filters(:) + type(TallyFilterMatch), public, allocatable :: filter_matches(:) +!$omp threadprivate(filter_matches) + + ! Dictionary that maps user IDs to indices in 'filters' + type(DictIntInt), public :: filter_dict + + ! The largest filter ID that has been specified in the system. This is useful + ! in case the code needs to find an ID for a new filter. + integer :: largest_filter_id + +contains + +!=============================================================================== +! INITIALIZE sets up any internal data, as necessary. If this procedure is not +! overriden by the derived class, then it will do nothing by default. + + subroutine filter_initialize(this) + class(TallyFilter), intent(inout) :: this + end subroutine filter_initialize + +!=============================================================================== +! FREE_MEMORY_TALLY_FILTER deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_tally_filter() + n_filters = 0 + if (allocated(filters)) deallocate(filters) + call filter_dict % clear() + largest_filter_id = 0 + end subroutine free_memory_tally_filter + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_extend_filters(n, index_start, index_end) result(err) bind(C) + ! Creates or extends the filters array + integer(C_INT32_T), value, intent(in) :: n + integer(C_INT32_T), optional, intent(out) :: index_start + integer(C_INT32_T), optional, intent(out) :: index_end + integer(C_INT) :: err + + integer :: i ! loop counter + type(TallyFilterContainer), allocatable :: temp(:) ! temporary filters + + if (n_filters == 0) then + ! Allocate filters array + allocate(filters(n)) + else + ! Move filters to temporary array + allocate(temp(n_filters + n)) + do i = 1, n_filters + call move_alloc(filters(i) % obj, temp(i) % obj) + end do + + ! Move filters back from temporary array to filters array + call move_alloc(temp, filters) + end if + + ! Return indices in filters array + if (present(index_start)) index_start = n_filters + 1 + if (present(index_end)) index_end = n_filters + n + n_filters = n_filters + n + + err = 0 + end function openmc_extend_filters + + + function openmc_filter_get_id(index, id) result(err) bind(C) + ! Return the ID of a filter + integer(C_INT32_T), value :: index + integer(C_INT32_T), intent(out) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_filters) then + id = filters(index) % obj % id + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function openmc_filter_get_id + + + function openmc_filter_set_id(index, id) result(err) bind(C) + ! Set the ID of a filter + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + filters(index) % obj % id = id + call filter_dict % set(id, index) + if (id > largest_filter_id) largest_filter_id = id + + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function openmc_filter_set_id + + + function openmc_get_filter_index(id, index) result(err) bind(C) + ! Returns the index in the filters array of a filter with a given ID + integer(C_INT32_T), value :: id + integer(C_INT32_T), intent(out) :: index + integer(C_INT) :: err + + if (allocated(filters)) then + if (filter_dict % has(id)) then + index = filter_dict % get(id) + err = 0 + else + err = E_INVALID_ID + call set_errmsg("No filter exists with ID=" // trim(to_str(id)) // ".") + end if + else + err = E_ALLOCATE + call set_errmsg("Memory has not been allocated for filters.") + end if + end function openmc_get_filter_index + + + subroutine openmc_get_filter_next_id(id) bind(C) + ! Returns an ID number that has not been used by any other filters. + integer(C_INT32_T), intent(out) :: id + + id = largest_filter_id + 1 + end subroutine openmc_get_filter_next_id + +end module tally_filter_header diff --git a/src/tallies/tally_filter_material.F90 b/src/tallies/tally_filter_material.F90 new file mode 100644 index 000000000..778fec4b6 --- /dev/null +++ b/src/tallies/tally_filter_material.F90 @@ -0,0 +1,192 @@ +module tally_filter_material + + use, intrinsic :: ISO_C_BINDING + + use hdf5, only: HID_T + + use constants + use dict_header, only: DictIntInt, EMPTY + use error + use hdf5_interface + use material_header, only: materials, material_dict + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + public :: openmc_material_filter_get_bins + public :: openmc_material_filter_set_bins + +!=============================================================================== +! MATERIAL specifies which material tally events reside in. +!=============================================================================== + + type, public, extends(TallyFilter) :: MaterialFilter + integer, allocatable :: materials(:) + type(DictIntInt) :: map + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_material + procedure :: to_statepoint => to_statepoint_material + procedure :: text_label => text_label_material + procedure :: initialize => initialize_material + end type MaterialFilter + +contains + + subroutine from_xml(this, node) + class(MaterialFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n + allocate(this % materials(n)) + call get_node_array(node, "bins", this % materials) + end subroutine from_xml + + subroutine get_all_bins_material(this, p, estimator, match) + class(MaterialFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: val + + val = this % map % get(p % material) + if (val /= EMPTY) then + call match % bins % push_back(val) + call match % weights % push_back(ONE) + end if + + end subroutine get_all_bins_material + + subroutine to_statepoint_material(this, filter_group) + class(MaterialFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + integer :: i + integer, allocatable :: material_ids(:) + + call write_dataset(filter_group, "type", "material") + call write_dataset(filter_group, "n_bins", this % n_bins) + + allocate(material_ids(size(this % materials))) + do i = 1, size(this % materials) + material_ids(i) = materials(this % materials(i)) % id + end do + call write_dataset(filter_group, "bins", material_ids) + end subroutine to_statepoint_material + + subroutine initialize_material(this) + class(MaterialFilter), intent(inout) :: this + + integer :: i, id + integer :: val + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % materials(i) + val = material_dict % get(id) + if (val /= EMPTY) then + this % materials(i) = val + else + call fatal_error("Could not find material " // trim(to_str(id)) & + &// " specified on a tally filter.") + end if + end do + + ! Generate mapping from material indices to filter bins. + do i = 1, this % n_bins + call this % map % set(this % materials(i), i) + end do + end subroutine initialize_material + + function text_label_material(this, bin) result(label) + class(MaterialFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Material " // to_str(materials(this % materials(bin)) % id) + end function text_label_material + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_material_filter_get_bins(index, bins, n) result(err) bind(C) + ! Return the bins for a material filter + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: bins + integer(C_INT32_T), intent(out) :: n + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + select type (f => filters(index) % obj) + type is (MaterialFilter) + bins = C_LOC(f % materials) + n = size(f % materials) + err = 0 + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get material filter bins on a & + &non-material filter.") + end select + else + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function openmc_material_filter_get_bins + + + function openmc_material_filter_set_bins(index, n, bins) result(err) bind(C) + ! Set the materials for the filter + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: n + integer(C_INT32_T), intent(in) :: bins(n) + integer(C_INT) :: err + + integer :: i + + err = 0 + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + select type (f => filters(index) % obj) + type is (MaterialFilter) + f % n_bins = n + if (allocated(f % materials)) deallocate(f % materials) + allocate(f % materials(n)) + f % materials(:) = bins + + ! Generate mapping from material indices to filter bins. + call f % map % clear() + do i = 1, n + call f % map % set(f % materials(i), i) + end do + + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set material filter bins on a & + &non-material filter.") + end select + else + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function openmc_material_filter_set_bins + +end module tally_filter_material diff --git a/src/tallies/tally_filter_mesh.F90 b/src/tallies/tally_filter_mesh.F90 new file mode 100644 index 000000000..2092e7e71 --- /dev/null +++ b/src/tallies/tally_filter_mesh.F90 @@ -0,0 +1,318 @@ +module tally_filter_mesh + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants + use dict_header, only: EMPTY + use error + use mesh_header, only: RegularMesh, meshes, n_meshes, mesh_dict + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + public :: openmc_mesh_filter_set_mesh + +!=============================================================================== +! MESHFILTER indexes the location of particle events to a regular mesh. For +! tracklength tallies, it will produce multiple valid bins and the bin weight +! will correspond to the fraction of the track length that lies in that bin. +!=============================================================================== + + type, public, extends(TallyFilter) :: MeshFilter + integer :: mesh + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_mesh + procedure :: to_statepoint => to_statepoint_mesh + procedure :: text_label => text_label_mesh + end type MeshFilter + +contains + + subroutine from_xml(this, node) + class(MeshFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: i_mesh + integer :: id + integer :: n + integer :: val + + n = node_word_count(node, "bins") + + if (n /= 1) call fatal_error("Only one mesh can be & + &specified per mesh filter.") + + ! Determine id of mesh + call get_node_value(node, "bins", id) + + ! Get pointer to mesh + val = mesh_dict % get(id) + if (val /= EMPTY) then + i_mesh = val + else + call fatal_error("Could not find mesh " // trim(to_str(id)) & + // " specified on filter.") + end if + + ! Determine number of bins + this % n_bins = product(meshes(i_mesh) % dimension) + + ! Store the index of the mesh + this % mesh = i_mesh + end subroutine from_xml + + subroutine get_all_bins_mesh(this, p, estimator, match) + class(MeshFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer, parameter :: MAX_SEARCH_ITER = 100 ! Maximum number of times we can + ! can loop while trying to find + ! the first intersection. + + integer :: j ! loop index for direction + integer :: n + integer :: ijk0(3) ! indices of starting coordinates + integer :: ijk1(3) ! indices of ending coordinates + integer :: search_iter ! loop count for intersection search + integer :: bin + real(8) :: weight ! weight to be pushed back + real(8) :: uvw(3) ! cosine of angle of particle + real(8) :: xyz0(3) ! starting/intermediate coordinates + real(8) :: xyz1(3) ! ending coordinates of particle + real(8) :: xyz_cross ! coordinates of next boundary + real(8) :: d(3) ! distance to each bounding surface + real(8) :: total_distance ! distance of entire particle track + real(8) :: distance ! distance traveled in mesh cell + logical :: start_in_mesh ! starting coordinates inside mesh? + logical :: end_in_mesh ! ending coordinates inside mesh? + type(RegularMesh), pointer :: m + + weight = ERROR_REAL + + ! Get a pointer to the mesh. + m => meshes(this % mesh) + n = m % n_dimension + + if (estimator /= ESTIMATOR_TRACKLENGTH) then + ! If this is an analog or collision tally, then there can only be one + ! valid mesh bin. + call m % get_bin(p % coord(1) % xyz, bin) + if (bin /= NO_BIN_FOUND) then + call match % bins % push_back(bin) + call match % weights % push_back(ONE) + end if + return + end if + + ! A track can span multiple mesh bins so we need to handle a lot of + ! intersection logic for tracklength tallies. + + ! ======================================================================== + ! Determine if the track intersects the tally mesh. + + ! Copy the starting and ending coordinates of the particle. Offset these + ! just a bit for the purposes of determining if there was an intersection + ! in case the mesh surfaces coincide with lattice/geometric surfaces which + ! might produce finite-precision errors. + xyz0 = p % last_xyz + TINY_BIT * p % coord(1) % uvw + xyz1 = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw + + ! Determine indices for starting and ending location. + call m % get_indices(xyz0, ijk0(:n), start_in_mesh) + call m % get_indices(xyz1, ijk1(:n), end_in_mesh) + + ! If this is the first iteration of the filter loop, check if the track + ! intersects any part of the mesh. + if ((.not. start_in_mesh) .and. (.not. end_in_mesh)) then + if (.not. m % intersects(xyz0, xyz1)) return + end if + + ! ======================================================================== + ! Figure out which mesh cell to tally. + + ! Copy the un-modified coordinates the particle direction. + xyz0 = p % last_xyz + xyz1 = p % coord(1) % xyz + uvw = p % coord(1) % uvw + + ! Compute the length of the entire track. + total_distance = sqrt(sum((xyz1 - xyz0)**2)) + + ! We are looking for the first valid mesh bin. Check to see if the + ! particle starts inside the mesh. + if (any(ijk0(:n) < 1) .or. any(ijk0(:n) > m % dimension)) then + ! The particle does not start in the mesh. Note that we nudged the + ! start and end coordinates by a TINY_BIT each so we will have + ! difficulty resolving tracks that are less than 2*TINY_BIT in length. + ! If the track is that short, it is also insignificant so we can + ! safely ignore it in the tallies. + if (total_distance < 2*TINY_BIT) return + + ! The particle does not start in the mesh so keep iterating the ijk0 + ! indices to cross the nearest mesh surface until we've found a valid + ! bin. MAX_SEARCH_ITER prevents an infinite loop. + search_iter = 0 + do while (any(ijk0(:n) < 1) .or. any(ijk0(:n) > m % dimension)) + if (search_iter == MAX_SEARCH_ITER) then + call warning("Failed to find a mesh intersection on a tally mesh & + &filter.") + return + end if + + do j = 1, n + if (abs(uvw(j)) < FP_PRECISION) then + d(j) = INFINITY + else if (uvw(j) > 0) then + xyz_cross = m % lower_left(j) + ijk0(j) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + else + xyz_cross = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + end if + end do + j = minloc(d(:n), 1) + if (uvw(j) > ZERO) then + ijk0(j) = ijk0(j) + 1 + else + ijk0(j) = ijk0(j) - 1 + end if + + search_iter = search_iter + 1 + end do + distance = d(j) + xyz0 = xyz0 + distance * uvw + end if + + do + ! ======================================================================== + ! Compute the length of the track segment in the appropiate mesh cell and + ! return. + + if (all(ijk0(:n) == ijk1(:n))) then + ! The track ends in this cell. Use the particle end location rather + ! than the mesh surface. + distance = sqrt(sum((xyz1 - xyz0)**2)) + else + ! The track exits this cell. Determine the distance to the closest mesh + ! surface. + do j = 1, n + if (abs(uvw(j)) < FP_PRECISION) then + d(j) = INFINITY + else if (uvw(j) > 0) then + xyz_cross = m % lower_left(j) + ijk0(j) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + else + xyz_cross = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + end if + end do + j = minloc(d(:n), 1) + distance = d(j) + end if + + ! Assign the next tally bin and the score. + bin = m % get_bin_from_indices(ijk0(:n)) + call match % bins % push_back(bin) + call match % weights % push_back(distance / total_distance) + + ! Find the next mesh cell that the particle enters. + + ! If the particle track ends in that bin, then we are done. + if (all(ijk0(:n) == ijk1(:n))) exit + + ! Translate the starting coordintes by the distance to that face. This + ! should be the xyz that we computed the distance to in the last + ! iteration of the filter loop. + xyz0 = xyz0 + distance * uvw + + ! Increment the indices into the next mesh cell. + if (uvw(j) > ZERO) then + ijk0(j) = ijk0(j) + 1 + else + ijk0(j) = ijk0(j) - 1 + end if + + ! If the next indices are invalid, then the track has left the mesh and + ! we are done. + if (any(ijk0(:n) < 1) .or. any(ijk0(:n) > m % dimension)) exit + end do + + end subroutine get_all_bins_mesh + + subroutine to_statepoint_mesh(this, filter_group) + class(MeshFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "mesh") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", meshes(this % mesh) % id) + end subroutine to_statepoint_mesh + + function text_label_mesh(this, bin) result(label) + class(MeshFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + integer, allocatable :: ijk(:) + + associate (m => meshes(this % mesh)) + allocate(ijk(m % n_dimension)) + call m % get_indices_from_bin(bin, ijk) + if (m % n_dimension == 1) then + label = "Mesh Index (" // trim(to_str(ijk(1))) // ")" + elseif (m % n_dimension == 2) then + label = "Mesh Index (" // trim(to_str(ijk(1))) // ", " // & + trim(to_str(ijk(2))) // ")" + elseif (m % n_dimension == 3) then + label = "Mesh Index (" // trim(to_str(ijk(1))) // ", " // & + trim(to_str(ijk(2))) // ", " // trim(to_str(ijk(3))) // ")" + end if + end associate + end function text_label_mesh + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_mesh_filter_set_mesh(index, index_mesh) result(err) bind(C) + ! Set the mesh for a mesh filter + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: index_mesh + integer(C_INT) :: err + + err = 0 + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + select type (f => filters(index) % obj) + type is (MeshFilter) + if (index_mesh >= 1 .and. index_mesh <= n_meshes) then + f % mesh = index_mesh + f % n_bins = product(meshes(index_mesh) % dimension) + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in 'meshes' array is out of bounds.") + end if + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set mesh on a non-mesh filter.") + end select + else + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function openmc_mesh_filter_set_mesh + +end module tally_filter_mesh diff --git a/src/tallies/tally_filter_mu.F90 b/src/tallies/tally_filter_mu.F90 new file mode 100644 index 000000000..c949289b6 --- /dev/null +++ b/src/tallies/tally_filter_mu.F90 @@ -0,0 +1,112 @@ +module tally_filter_mu + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use algorithm, only: binary_search + use constants, only: ONE, TWO, MAX_LINE_LEN, NO_BIN_FOUND + use error, only: fatal_error + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! MUFILTER bins the incoming-outgoing direction cosine. This is only used for +! scatter reactions. +!=============================================================================== + + type, public, extends(TallyFilter) :: MuFilter + real(8), allocatable :: bins(:) + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_mu + procedure :: to_statepoint => to_statepoint_mu + procedure :: text_label => text_label_mu + end type MuFilter + +contains + + subroutine from_xml(this, node) + class(MuFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: i + integer :: n_angle + integer :: n + real(8) :: d_angle + + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n - 1 + allocate(this % bins(n)) + call get_node_array(node, "bins", this % bins) + + ! Allow a user to input a lone number which will mean that you + ! subdivide [-1,1] evenly with the input being the number of bins + if (n == 1) then + n_angle = int(this % bins(1)) + if (n_angle > 1) then + this % n_bins = n_angle + d_angle = TWO / n_angle + deallocate(this % bins) + allocate(this % bins(n_angle + 1)) + do i = 1, n_angle + this % bins(i) = -ONE + (i - 1) * d_angle + end do + this % bins(n_angle + 1) = ONE + else + call fatal_error("Number of bins for mu filter must be& + & greater than 1.") + end if + end if + end subroutine from_xml + + subroutine get_all_bins_mu(this, p, estimator, match) + class(MuFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: n + integer :: bin + + n = this % n_bins + + ! Search to find incoming energy bin. + bin = binary_search(this % bins, n + 1, p % mu) + if (bin /= NO_BIN_FOUND) then + call match % bins % push_back(bin) + call match % weights % push_back(ONE) + end if + end subroutine get_all_bins_mu + + subroutine to_statepoint_mu(this, filter_group) + class(MuFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "mu") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins) + end subroutine to_statepoint_mu + + function text_label_mu(this, bin) result(label) + class(MuFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Change-in-Angle [" // trim(to_str(E0)) // ", " & + // trim(to_str(E1)) // ")" + end function text_label_mu + +end module tally_filter_mu diff --git a/src/tallies/tally_filter_particle.F90 b/src/tallies/tally_filter_particle.F90 new file mode 100644 index 000000000..86e201301 --- /dev/null +++ b/src/tallies/tally_filter_particle.F90 @@ -0,0 +1,80 @@ +module tally_filter_particle + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants, only: ONE, MAX_LINE_LEN + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! PARTICLEFILTER specifies what particles can score to a tally +!=============================================================================== + + type, public, extends(TallyFilter) :: ParticleFilter + integer, allocatable :: particles(:) + contains + procedure :: from_xml + procedure :: get_all_bins + procedure :: to_statepoint + procedure :: text_label + end type ParticleFilter + +contains + + subroutine from_xml(this, node) + class(ParticleFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + ! Determine how many bins were given + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n + allocate(this % particles(n)) + call get_node_array(node, "bins", this % particles) + end subroutine from_xml + + subroutine get_all_bins(this, p, estimator, match) + class(ParticleFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: i + + do i = 1, this % n_bins + if (this % particles(i) == p % type) then + call match % bins % push_back(i) + call match % weights % push_back(ONE) + end if + end do + end subroutine get_all_bins + + subroutine to_statepoint(this, filter_group) + class(ParticleFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "particle") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % particles) + end subroutine to_statepoint + + function text_label(this, bin) result(label) + class(ParticleFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Particle " // to_str(this % particles(bin)) + end function text_label + +end module tally_filter_particle diff --git a/src/tallies/tally_filter_polar.F90 b/src/tallies/tally_filter_polar.F90 new file mode 100644 index 000000000..89815c102 --- /dev/null +++ b/src/tallies/tally_filter_polar.F90 @@ -0,0 +1,120 @@ +module tally_filter_polar + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use algorithm, only: binary_search + use constants + use error, only: fatal_error + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! POLARFILTER bins the incident neutron polar angle (relative to the global +! z-axis). +!=============================================================================== + + type, public, extends(TallyFilter) :: PolarFilter + real(8), allocatable :: bins(:) + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_polar + procedure :: to_statepoint => to_statepoint_polar + procedure :: text_label => text_label_polar + end type PolarFilter + +contains + + subroutine from_xml(this, node) + class(PolarFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: i + integer :: n_angle + integer :: n + real(8) :: d_angle + + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n - 1 + allocate(this % bins(n)) + call get_node_array(node, "bins", this % bins) + + ! Allow a user to input a lone number which will mean that you + ! subdivide [0,pi] evenly with the input being the number of bins + if (n == 1) then + n_angle = int(this % bins(1)) + if (n_angle > 1) then + this % n_bins = n_angle + d_angle = PI / real(n_angle,8) + deallocate(this % bins) + allocate(this % bins(n_angle + 1)) + do i = 1, n_angle + this % bins(i) = (i - 1) * d_angle + end do + this % bins(n_angle + 1) = PI + else + call fatal_error("Number of bins for polar filter must be& + & greater than 1.") + end if + end if + end subroutine from_xml + + subroutine get_all_bins_polar(this, p, estimator, match) + class(PolarFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: n + integer :: bin + real(8) :: theta + + n = this % n_bins + + ! Make sure the correct direction vector is used. + if (estimator == ESTIMATOR_TRACKLENGTH) then + theta = acos(p % coord(1) % uvw(3)) + else + theta = acos(p % last_uvw(3)) + end if + + ! Search to find polar angle bin. + bin = binary_search(this % bins, n + 1, theta) + if (bin /= NO_BIN_FOUND) then + call match % bins % push_back(bin) + call match % weights % push_back(ONE) + end if + end subroutine get_all_bins_polar + + subroutine to_statepoint_polar(this, filter_group) + class(PolarFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "polar") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins) + end subroutine to_statepoint_polar + + function text_label_polar(this, bin) result(label) + class(PolarFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Polar Angle [" // trim(to_str(E0)) // ", " // trim(to_str(E1)) & + // ")" + end function text_label_polar + +end module tally_filter_polar diff --git a/src/tallies/tally_filter_surface.F90 b/src/tallies/tally_filter_surface.F90 new file mode 100644 index 000000000..daecd8891 --- /dev/null +++ b/src/tallies/tally_filter_surface.F90 @@ -0,0 +1,111 @@ +module tally_filter_surface + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants, only: ONE, MAX_LINE_LEN + use dict_header, only: EMPTY + use error, only: fatal_error + use hdf5_interface + use surface_header + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! SURFACEFILTER specifies which surface particles are crossing +!=============================================================================== + + type, public, extends(TallyFilter) :: SurfaceFilter + integer, allocatable :: surfaces(:) + + ! True if this filter is used for surface currents + logical :: current = .false. + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_surface + procedure :: to_statepoint => to_statepoint_surface + procedure :: text_label => text_label_surface + procedure :: initialize => initialize_surface + end type SurfaceFilter + +contains + + subroutine from_xml(this, node) + class(SurfaceFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n + allocate(this % surfaces(n)) + call get_node_array(node, "bins", this % surfaces) + end subroutine from_xml + + subroutine get_all_bins_surface(this, p, estimator, match) + class(SurfaceFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: i + + do i = 1, this % n_bins + if (abs(p % surface) == this % surfaces(i)) then + call match % bins % push_back(i) + if (p % surface < 0) then + call match % weights % push_back(-ONE) + else + call match % weights % push_back(ONE) + end if + exit + end if + end do + + end subroutine get_all_bins_surface + + subroutine to_statepoint_surface(this, filter_group) + class(SurfaceFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "surface") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % surfaces) + end subroutine to_statepoint_surface + + subroutine initialize_surface(this) + class(SurfaceFilter), intent(inout) :: this + + integer :: i, id + integer :: val + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % surfaces(i) + val = surface_dict % get(id) + if (val /= EMPTY) then + this % surfaces(i) = val + else + call fatal_error("Could not find surface " // trim(to_str(id)) & + &// " specified on tally filter.") + end if + end do + end subroutine initialize_surface + + function text_label_surface(this, bin) result(label) + class(SurfaceFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Surface " // to_str(surfaces(this % surfaces(bin)) % obj % id) + end function text_label_surface + +end module tally_filter_surface diff --git a/src/tallies/tally_filter_universe.F90 b/src/tallies/tally_filter_universe.F90 new file mode 100644 index 000000000..742152232 --- /dev/null +++ b/src/tallies/tally_filter_universe.F90 @@ -0,0 +1,120 @@ +module tally_filter_universe + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants, only: ONE, MAX_LINE_LEN + use dict_header, only: EMPTY + use error, only: fatal_error + use hdf5_interface + use geometry_header + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! UNIVERSEFILTER specifies which geometric universes tally events reside in. +!=============================================================================== + + type, public, extends(TallyFilter) :: UniverseFilter + integer, allocatable :: universes(:) + type(DictIntInt) :: map + contains + procedure :: from_xml + procedure :: get_all_bins => get_all_bins_universe + procedure :: to_statepoint => to_statepoint_universe + procedure :: text_label => text_label_universe + procedure :: initialize => initialize_universe + end type UniverseFilter + +contains + + subroutine from_xml(this, node) + class(UniverseFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + n = node_word_count(node, "bins") + + ! Allocate and store bins + this % n_bins = n + allocate(this % universes(n)) + call get_node_array(node, "bins", this % universes) + end subroutine from_xml + + subroutine get_all_bins_universe(this, p, estimator, match) + class(UniverseFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: i + integer :: val + + ! Iterate over coordinate levels to see which universes match + do i = 1, p % n_coord + val = this % map % get(p % coord(i) % universe) + if (val /= EMPTY) then + call match % bins % push_back(val) + call match % weights % push_back(ONE) + end if + end do + + end subroutine get_all_bins_universe + + subroutine to_statepoint_universe(this, filter_group) + class(UniverseFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + integer :: i + integer, allocatable :: universe_ids(:) + + call write_dataset(filter_group, "type", "universe") + call write_dataset(filter_group, "n_bins", this % n_bins) + + allocate(universe_ids(size(this % universes))) + do i = 1, size(this % universes) + universe_ids(i) = universes(this % universes(i)) % id + end do + call write_dataset(filter_group, "bins", universe_ids) + end subroutine to_statepoint_universe + + subroutine initialize_universe(this) + class(UniverseFilter), intent(inout) :: this + + integer :: i, id + integer :: val + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % universes(i) + val = universe_dict % get(id) + if (val /= EMPTY) then + this % universes(i) = val + else + call fatal_error("Could not find universe " // trim(to_str(id)) & + &// " specified on a tally filter.") + end if + end do + + ! Generate mapping from universe indices to filter bins. + do i = 1, this % n_bins + call this % map % set(this % universes(i), i) + end do + end subroutine initialize_universe + + function text_label_universe(this, bin) result(label) + class(UniverseFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Universe " // to_str(universes(this % universes(bin)) % id) + end function text_label_universe + +end module tally_filter_universe diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 new file mode 100644 index 000000000..df9d935e9 --- /dev/null +++ b/src/tallies/tally_header.F90 @@ -0,0 +1,821 @@ +module tally_header + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants + use error + use dict_header, only: DictIntInt + use message_passing, only: n_procs + use nuclide_header, only: nuclide_dict + use settings, only: reduce_tallies + use stl_vector, only: VectorInt + use string, only: to_lower, to_f_string, str_to_int, to_str + use tally_filter_header, only: TallyFilterContainer, filters, n_filters + use tally_filter + use trigger_header, only: TriggerObject + + implicit none + private + public :: configure_tallies + public :: free_memory_tally + public :: openmc_extend_tallies + public :: openmc_get_tally_index + public :: openmc_tally_get_id + public :: openmc_tally_get_filters + public :: openmc_tally_get_nuclides + public :: openmc_tally_results + public :: openmc_tally_set_filters + public :: openmc_tally_set_id + public :: openmc_tally_set_nuclides + public :: openmc_tally_set_scores + +!=============================================================================== +! TALLYOBJECT describes a user-specified tally. The region of phase space to +! tally in is given by the TallyFilters and the results are stored in a +! TallyResult array. +!=============================================================================== + + type, public :: TallyObject + ! Basic data + + integer :: id ! user-defined identifier + character(len=104) :: name = "" ! user-defined name + integer :: type = TALLY_VOLUME ! volume, surface current + integer :: estimator = ESTIMATOR_TRACKLENGTH ! collision, track-length + real(8) :: volume ! volume of region + logical :: active = .false. + integer, allocatable :: filter(:) ! index in filters array + + ! The stride attribute is used for determining the index in the results + ! array for a matching_bin combination. Since multiple dimensions are + ! mapped onto one dimension in the results array, the stride attribute gives + ! the stride for a given filter type within the results array + + integer, allocatable :: stride(:) + + ! This array provides a way to lookup what index in the filters array a + ! certain filter is. For example, if find_filter(FILTER_CELL) > 0, then the + ! value is the index in filters(:). + + integer :: find_filter(N_FILTER_TYPES) = 0 + + ! Individual nuclides to tally + integer :: n_nuclide_bins = 0 + integer, allocatable :: nuclide_bins(:) + logical :: all_nuclides = .false. + + ! Values to score, e.g. flux, absorption, etc. + ! scat_order is the scattering order for each score. + ! It is to be 0 if the scattering order is 0, or if the score is not a + ! scattering response. + integer :: n_score_bins = 0 + integer, allocatable :: score_bins(:) + integer, allocatable :: moment_order(:) + integer :: n_user_score_bins = 0 + + ! Results for each bin -- the first dimension of the array is for scores + ! (e.g. flux, total reaction rate, fission reaction rate, etc.) and the + ! second dimension of the array is for the combination of filters + ! (e.g. specific cell, specific energy group, etc.) + + integer :: n_filter_bins = 1 + integer :: total_score_bins + real(C_DOUBLE), allocatable :: results(:,:,:) + + ! reset property - allows a tally to be reset after every batch + logical :: reset = .false. + + ! Number of realizations of tally random variables + integer :: n_realizations = 0 + + ! Tally precision triggers + integer :: n_triggers = 0 ! # of triggers + type(TriggerObject), allocatable :: triggers(:) ! Array of triggers + + ! Index for the TallyDerivative for differential tallies. + integer :: deriv = NONE + + contains + procedure :: accumulate => tally_accumulate + procedure :: allocate_results => tally_allocate_results + procedure :: read_results_hdf5 => tally_read_results_hdf5 + procedure :: write_results_hdf5 => tally_write_results_hdf5 + procedure :: set_filters => tally_set_filters + end type TallyObject + + type, public :: TallyContainer + class(TallyObject), allocatable :: obj + end type TallyContainer + + integer(C_INT32_T), public, bind(C) :: n_tallies = 0 ! # of tallies + + type(TallyContainer), public, allocatable, target :: tallies(:) + + ! Dictionary that maps user IDs to indices in 'tallies' + type(DictIntInt), public :: tally_dict + + ! Global tallies + ! 1) collision estimate of k-eff + ! 2) absorption estimate of k-eff + ! 3) track-length estimate of k-eff + ! 4) leakage fraction + + real(C_DOUBLE), public, allocatable, target :: global_tallies(:,:) + + ! It is possible to protect accumulate operations on global tallies by using + ! an atomic update. However, when multiple threads accumulate to the same + ! global tally, it can cause a higher cache miss rate due to + ! invalidation. Thus, we use threadprivate variables to accumulate global + ! tallies and then reduce at the end of a generation. + real(C_DOUBLE), public :: global_tally_collision = ZERO + real(C_DOUBLE), public :: global_tally_absorption = ZERO + real(C_DOUBLE), public :: global_tally_tracklength = ZERO + real(C_DOUBLE), public :: global_tally_leakage = ZERO +!$omp threadprivate(global_tally_collision, global_tally_absorption, & +!$omp& global_tally_tracklength, global_tally_leakage) + + ! Active tally lists + type(VectorInt), public :: active_analog_tallies + type(VectorInt), public :: active_tracklength_tallies + type(VectorInt), public :: active_current_tallies + type(VectorInt), public :: active_collision_tallies + type(VectorInt), public :: active_tallies + type(VectorInt), public :: active_surface_tallies + + ! Normalization for statistics + integer, public :: n_realizations = 0 ! # of independent realizations + real(8), public :: total_weight ! total starting particle weight in realization + +contains + +!=============================================================================== +! ACCUMULATE_TALLY +!=============================================================================== + + subroutine tally_accumulate(this) + class(TallyObject), intent(inout) :: this + + integer :: i, j + real(C_DOUBLE) :: val + + ! Increment number of realizations + if (reduce_tallies) then + this % n_realizations = this % n_realizations + 1 + else + this % n_realizations = this % n_realizations + n_procs + end if + + ! Accumulate each result + do j = 1, size(this % results, 3) + do i = 1, size(this % results, 2) + val = this % results(RESULT_VALUE, i, j)/total_weight + this % results(RESULT_VALUE, i, j) = ZERO + + this % results(RESULT_SUM, i, j) = & + this % results(RESULT_SUM, i, j) + val + this % results(RESULT_SUM_SQ, i, j) = & + this % results(RESULT_SUM_SQ, i, j) + val*val + end do + end do + end subroutine tally_accumulate + + subroutine tally_write_results_hdf5(this, group_id) + class(TallyObject), intent(in) :: this + integer(HID_T), intent(in) :: group_id + + integer :: hdf5_err + integer(HID_T) :: dset, dspace + integer(HID_T) :: memspace + integer(HSIZE_T) :: dims(3) + integer(HSIZE_T) :: dims_slab(3) + integer(HSIZE_T) :: offset(3) = [1,0,0] + + ! Create file dataspace + dims_slab(:) = shape(this % results) + dims_slab(1) = 2 + call h5screate_simple_f(3, dims_slab, dspace, hdf5_err) + + ! Create memory dataspace that contains only SUM and SUM_SQ values + dims(:) = shape(this % results) + call h5screate_simple_f(3, dims, memspace, hdf5_err) + call h5sselect_hyperslab_f(memspace, H5S_SELECT_SET_F, offset, dims_slab, & + hdf5_err) + + ! Create and write to dataset + call h5dcreate_f(group_id, "results", H5T_NATIVE_DOUBLE, dspace, dset, & + hdf5_err) + call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, this % results, dims_slab, & + hdf5_err, mem_space_id=memspace) + + ! Close identifiers + call h5dclose_f(dset, hdf5_err) + call h5sclose_f(memspace, hdf5_err) + call h5sclose_f(dspace, hdf5_err) + end subroutine tally_write_results_hdf5 + + subroutine tally_read_results_hdf5(this, group_id) + class(TallyObject), intent(inout) :: this + integer(HID_T), intent(in) :: group_id + + integer :: hdf5_err + integer(HID_T) :: dset, dspace + integer(HID_T) :: memspace + integer(HSIZE_T) :: dims(3) + integer(HSIZE_T) :: dims_slab(3) + integer(HSIZE_T) :: offset(3) = [1,0,0] + + ! Create file dataspace + dims_slab(:) = shape(this % results) + dims_slab(1) = 2 + call h5screate_simple_f(3, dims_slab, dspace, hdf5_err) + + ! Create memory dataspace that contains only SUM and SUM_SQ values + dims(:) = shape(this % results) + call h5screate_simple_f(3, dims, memspace, hdf5_err) + call h5sselect_hyperslab_f(memspace, H5S_SELECT_SET_F, offset, dims_slab, & + hdf5_err) + + ! Create and write to dataset + call h5dopen_f(group_id, "results", dset, hdf5_err) + call h5dread_f(dset, H5T_NATIVE_DOUBLE, this % results, dims_slab, & + hdf5_err, mem_space_id=memspace) + + ! Close identifiers + call h5dclose_f(dset, hdf5_err) + call h5sclose_f(memspace, hdf5_err) + call h5sclose_f(dspace, hdf5_err) + end subroutine tally_read_results_hdf5 + +!=============================================================================== +! ALLOCATE_RESULTS allocates and initializes the results component of the +! TallyObject derived type +!=============================================================================== + + subroutine tally_allocate_results(this) + class(TallyObject), intent(inout) :: this + + ! If no nuclides were specified, add a single bin for total material + if (.not. allocated(this % nuclide_bins)) then + allocate(this % nuclide_bins(1)) + this % nuclide_bins(1) = -1 + this % n_nuclide_bins = 1 + end if + + ! Set total number of filter and scoring bins + this % total_score_bins = this % n_score_bins * this % n_nuclide_bins + + if (allocated(this % results)) then + ! If results was already allocated but shape is wrong, then reallocate it + ! to the correct shape + if (this % total_score_bins /= size(this % results, 2) .or. & + this % n_filter_bins /= size(this % results, 3)) then + deallocate(this % results) + allocate(this % results(3, this % total_score_bins, this % n_filter_bins)) + end if + else + allocate(this % results(3, this % total_score_bins, this % n_filter_bins)) + end if + + ! Initialize results array to zero + this % results(:,:,:) = ZERO + + end subroutine tally_allocate_results + + function tally_set_filters(this, filter_indices) result(err) + class(TallyObject), intent(inout) :: this + integer(C_INT32_T), intent(in) :: filter_indices(:) + integer(C_INT) :: err + + integer :: i ! index in this % filter/stride + integer :: j ! index in this % find_filter + integer :: k ! index in global filters array + integer :: n ! number of filters + integer :: stride ! filter stride + + err = 0 + this % find_filter(:) = 0 + n = size(filter_indices) + do i = 1, n + k = filter_indices(i) + if (k < 1 .or. k > n_filters) then + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in tally filter array out of bounds.") + exit + end if + + ! Set the filter index in the tally find_filter array + select type (filt => filters(k) % obj) + type is (DistribcellFilter) + j = FILTER_DISTRIBCELL + type is (CellFilter) + j = FILTER_CELL + type is (CellFromFilter) + j = FILTER_CELLFROM + type is (CellbornFilter) + j = FILTER_CELLBORN + type is (MaterialFilter) + j = FILTER_MATERIAL + type is (UniverseFilter) + j = FILTER_UNIVERSE + type is (SurfaceFilter) + j = FILTER_SURFACE + type is (MeshFilter) + j = FILTER_MESH + type is (EnergyFilter) + j = FILTER_ENERGYIN + type is (EnergyoutFilter) + j = FILTER_ENERGYOUT + this % estimator = ESTIMATOR_ANALOG + type is (DelayedGroupFilter) + j = FILTER_DELAYEDGROUP + type is (MuFilter) + j = FILTER_MU + this % estimator = ESTIMATOR_ANALOG + type is (PolarFilter) + j = FILTER_POLAR + type is (AzimuthalFilter) + j = FILTER_AZIMUTHAL + type is (EnergyFunctionFilter) + j = FILTER_ENERGYFUNCTION + end select + this % find_filter(j) = i + end do + + if (err == 0) then + if (allocated(this % filter)) deallocate(this % filter) + if (allocated(this % stride)) deallocate(this % stride) + allocate(this % filter(n), this % stride(n)) + + ! Filters are traversed in reverse so that the last filter has the + ! shortest stride in memory and the first filter has the largest stride + stride = 1 + do i = n, 1, -1 + ! Set filter and stride + k = filter_indices(i) + this % filter(i) = k + this % stride(i) = stride + + ! Multiply stride by number of bins in this filter + stride = stride * filters(k) % obj % n_bins + end do + + ! Set total number of filter bins + this % n_filter_bins = stride + end if + + end function tally_set_filters + +!=============================================================================== +! CONFIGURE_TALLIES initializes several data structures related to tallies. This +! is called after the basic tally data has already been read from the +! tallies.xml file. +!=============================================================================== + + subroutine configure_tallies() + + integer :: i + + ! Allocate and initialize global tallies + if (.not. allocated(global_tallies)) then + allocate(global_tallies(3, N_GLOBAL_TALLIES)) + end if + global_tallies(:,:) = ZERO + + do i = 1, n_tallies + call tallies(i) % obj % allocate_results() + end do + + end subroutine configure_tallies + +!=============================================================================== +! FREE_MEMORY_TALLY deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_tally() + n_tallies = 0 + if (allocated(tallies)) deallocate(tallies) + call tally_dict % clear() + + if (allocated(global_tallies)) deallocate(global_tallies) + + ! Deallocate tally node lists + call active_analog_tallies % clear() + call active_tracklength_tallies % clear() + call active_current_tallies % clear() + call active_collision_tallies % clear() + call active_surface_tallies % clear() + call active_tallies % clear() + end subroutine free_memory_tally + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_extend_tallies(n, index_start, index_end) result(err) bind(C) + ! Extend the tallies array by n elements + integer(C_INT32_T), value, intent(in) :: n + integer(C_INT32_T), optional, intent(out) :: index_start + integer(C_INT32_T), optional, intent(out) :: index_end + integer(C_INT) :: err + + integer :: i + type(TallyContainer), allocatable :: temp(:) ! temporary tallies array + + if (n_tallies == 0) then + ! Allocate tallies array + allocate(tallies(n)) + else + ! Allocate tallies array with increased size + allocate(temp(n_tallies + n)) + + ! Move original tallies to temporary array + do i = 1, n_tallies + call move_alloc(tallies(i) % obj, temp(i) % obj) + end do + + ! Move allocation from temporary array + call move_alloc(FROM=temp, TO=tallies) + end if + + ! Return indices in tallies array + if (present(index_start)) index_start = n_tallies + 1 + if (present(index_end)) index_end = n_tallies + n + n_tallies = n_tallies + n + + err = 0 + end function openmc_extend_tallies + + + function openmc_get_tally_index(id, index) result(err) bind(C) + ! Returns the index in the tallies array of a tally with a given ID + integer(C_INT32_T), value :: id + integer(C_INT32_T), intent(out) :: index + integer(C_INT) :: err + + if (allocated(tallies)) then + if (tally_dict % has(id)) then + index = tally_dict % get(id) + err = 0 + else + err = E_INVALID_ID + call set_errmsg("No tally exists with ID=" // trim(to_str(id)) // ".") + end if + else + err = E_ALLOCATE + call set_errmsg("Memory has not been allocated for tallies.") + end if + end function openmc_get_tally_index + + + function openmc_tally_get_id(index, id) result(err) bind(C) + ! Return the ID of a tally + integer(C_INT32_T), value :: index + integer(C_INT32_T), intent(out) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(tallies)) then + id = tallies(index) % obj % id + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_get_id + + + function openmc_tally_get_filters(index, filter_indices, n) result(err) bind(C) + ! Return the list of filters assigned to a tally + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: filter_indices + integer(C_INT), intent(out) :: n + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(tallies)) then + associate (t => tallies(index) % obj) + if (allocated(t % filter)) then + filter_indices = C_LOC(t % filter(1)) + n = size(t % filter) + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Tally filters have not been allocated yet.") + end if + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_get_filters + + + function openmc_tally_get_nuclides(index, nuclides, n) result(err) bind(C) + ! Return the list of nuclides assigned to a tally + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: nuclides + integer(C_INT), intent(out) :: n + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(tallies)) then + associate (t => tallies(index) % obj) + if (allocated(t % nuclide_bins)) then + nuclides = C_LOC(t % nuclide_bins(1)) + n = size(t % nuclide_bins) + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Tally nuclides have not been allocated yet.") + end if + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_get_nuclides + + + function openmc_tally_results(index, ptr, shape_) result(err) bind(C) + ! Returns a pointer to a tally results array along with its shape. This + ! allows a user to obtain in-memory tally results from Python directly. + integer(C_INT32_T), intent(in), value :: index + type(C_PTR), intent(out) :: ptr + integer(C_INT), intent(out) :: shape_(3) + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(tallies)) then + associate (t => tallies(index) % obj) + if (allocated(t % results)) then + ptr = C_LOC(t % results(1,1,1)) + shape_(:) = shape(t % results) + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Tally results have not been allocated yet.") + end if + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_results + + + function openmc_tally_set_filters(index, n, filter_indices) result(err) bind(C) + ! Set the list of filters for a tally + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT), value, intent(in) :: n + integer(C_INT32_T), intent(in) :: filter_indices(n) + integer(C_INT) :: err + + err = 0 + if (index >= 1 .and. index <= n_tallies) then + if (allocated(tallies(index) % obj)) then + err = tallies(index) % obj % set_filters(filter_indices) + else + err = E_ALLOCATE + call set_errmsg("Tally type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_set_filters + + + function openmc_tally_set_id(index, id) result(err) bind(C) + ! Set the ID of a tally + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_tallies) then + if (allocated(tallies(index) % obj)) then + tallies(index) % obj % id = id + call tally_dict % set(id, index) + + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Tally type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_set_id + + + function openmc_tally_set_nuclides(index, n, nuclides) result(err) bind(C) + ! Sets the nuclides in the tally which results should be scored for + integer(C_INT32_T), value :: index + integer(C_INT), value :: n + type(C_PTR), intent(in) :: nuclides(n) + integer(C_INT) :: err + + integer :: i + character(C_CHAR), pointer :: string(:) + character(len=:, kind=C_CHAR), allocatable :: nuclide_ + + err = E_UNASSIGNED + if (index >= 1 .and. index <= size(tallies)) then + associate (t => tallies(index) % obj) + if (allocated(t % nuclide_bins)) deallocate(t % nuclide_bins) + allocate(t % nuclide_bins(n)) + t % n_nuclide_bins = n + + do i = 1, n + ! Convert C string to Fortran string + call c_f_pointer(nuclides(i), string, [10]) + nuclide_ = to_lower(to_f_string(string)) + + select case (nuclide_) + case ('total') + t % nuclide_bins(i) = -1 + case default + if (nuclide_dict % has(nuclide_)) then + t % nuclide_bins(i) = nuclide_dict % get(nuclide_) + else + err = E_DATA + call set_errmsg("Nuclide '" // trim(to_f_string(string)) // & + "' has not been loaded yet.") + return + end if + end select + end do + + err = 0 + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_set_nuclides + + + function openmc_tally_set_scores(index, n, scores) result(err) bind(C) + ! Sets the scores in the tally + integer(C_INT32_T), value :: index + integer(C_INT), value :: n + type(C_PTR), intent(in) :: scores(n) + integer(C_INT) :: err + + integer :: i + integer :: MT + character(C_CHAR), pointer :: string(:) + character(len=:, kind=C_CHAR), allocatable :: score_ + + err = E_UNASSIGNED + if (index >= 1 .and. index <= size(tallies)) then + associate (t => tallies(index) % obj) + if (allocated(t % score_bins)) deallocate(t % score_bins) + allocate(t % score_bins(n)) + t % n_user_score_bins = n + t % n_score_bins = n + + do i = 1, n + ! Convert C string to Fortran string + call c_f_pointer(scores(i), string, [20]) + score_ = to_lower(to_f_string(string)) + + select case (score_) + case ('flux') + t % score_bins(i) = SCORE_FLUX + case ('total', '(n,total)') + t % score_bins(i) = SCORE_TOTAL + case ('scatter') + t % score_bins(i) = SCORE_SCATTER + case ('nu-scatter') + t % score_bins(i) = SCORE_NU_SCATTER + case ('(n,2n)') + t % score_bins(i) = N_2N + case ('(n,3n)') + t % score_bins(i) = N_3N + case ('(n,4n)') + t % score_bins(i) = N_4N + case ('absorption') + t % score_bins(i) = SCORE_ABSORPTION + case ('fission', '18') + t % score_bins(i) = SCORE_FISSION + case ('nu-fission') + t % score_bins(i) = SCORE_NU_FISSION + case ('decay-rate') + t % score_bins(i) = SCORE_DECAY_RATE + case ('delayed-nu-fission') + t % score_bins(i) = SCORE_DELAYED_NU_FISSION + case ('prompt-nu-fission') + t % score_bins(i) = SCORE_PROMPT_NU_FISSION + case ('kappa-fission') + t % score_bins(i) = SCORE_KAPPA_FISSION + case ('inverse-velocity') + t % score_bins(i) = SCORE_INVERSE_VELOCITY + case ('fission-q-prompt') + t % score_bins(i) = SCORE_FISS_Q_PROMPT + case ('fission-q-recoverable') + t % score_bins(i) = SCORE_FISS_Q_RECOV + case ('current') + t % score_bins(i) = SCORE_CURRENT + case ('events') + t % score_bins(i) = SCORE_EVENTS + case ('elastic', '(n,elastic)') + t % score_bins(i) = ELASTIC + case ('(n,2nd)') + t % score_bins(i) = N_2ND + case ('(n,na)') + t % score_bins(i) = N_2NA + case ('(n,n3a)') + t % score_bins(i) = N_N3A + case ('(n,2na)') + t % score_bins(i) = N_2NA + case ('(n,3na)') + t % score_bins(i) = N_3NA + case ('(n,np)') + t % score_bins(i) = N_NP + case ('(n,n2a)') + t % score_bins(i) = N_N2A + case ('(n,2n2a)') + t % score_bins(i) = N_2N2A + case ('(n,nd)') + t % score_bins(i) = N_ND + case ('(n,nt)') + t % score_bins(i) = N_NT + case ('(n,nHe-3)') + t % score_bins(i) = N_N3HE + case ('(n,nd2a)') + t % score_bins(i) = N_ND2A + case ('(n,nt2a)') + t % score_bins(i) = N_NT2A + case ('(n,3nf)') + t % score_bins(i) = N_3NF + case ('(n,2np)') + t % score_bins(i) = N_2NP + case ('(n,3np)') + t % score_bins(i) = N_3NP + case ('(n,n2p)') + t % score_bins(i) = N_N2P + case ('(n,npa)') + t % score_bins(i) = N_NPA + case ('(n,n1)') + t % score_bins(i) = N_N1 + case ('(n,nc)') + t % score_bins(i) = N_NC + case ('(n,gamma)') + t % score_bins(i) = N_GAMMA + case ('(n,p)') + t % score_bins(i) = N_P + case ('(n,d)') + t % score_bins(i) = N_D + case ('(n,t)') + t % score_bins(i) = N_T + case ('(n,3He)') + t % score_bins(i) = N_3HE + case ('(n,a)') + t % score_bins(i) = N_A + case ('(n,2a)') + t % score_bins(i) = N_2A + case ('(n,3a)') + t % score_bins(i) = N_3A + case ('(n,2p)') + t % score_bins(i) = N_2P + case ('(n,pa)') + t % score_bins(i) = N_PA + case ('(n,t2a)') + t % score_bins(i) = N_T2A + case ('(n,d2a)') + t % score_bins(i) = N_D2A + case ('(n,pd)') + t % score_bins(i) = N_PD + case ('(n,pt)') + t % score_bins(i) = N_PT + case ('(n,da)') + t % score_bins(i) = N_DA + case default + ! Assume that user has specified an MT number + MT = int(str_to_int(score_)) + + if (MT /= ERROR_INT) then + ! Specified score was an integer + if (MT > 1) then + t % score_bins(i) = MT + else + err = E_INVALID_ARGUMENT + call set_errmsg("Negative MT number cannot be used as a score.") + end if + + else + err = E_INVALID_ARGUMENT + call set_errmsg("Unknown score: " // trim(score_) // ".") + end if + + end select + end do + + err = 0 + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_set_scores + +end module tally_header diff --git a/src/trigger.F90 b/src/tallies/trigger.F90 similarity index 94% rename from src/trigger.F90 rename to src/tallies/trigger.F90 index 9eb39e9cc..772e739ba 100644 --- a/src/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -1,19 +1,24 @@ module trigger + use, intrinsic :: ISO_C_BINDING + #ifdef MPI use message_passing #endif use constants - use global + use eigenvalue, only: openmc_get_keff use string, only: to_str use output, only: warning, write_message - use mesh, only: bin_to_mesh_indices - use mesh_header, only: RegularMesh + use mesh_header, only: RegularMesh, meshes use message_passing, only: master - use trigger_header, only: TriggerObject + use settings + use simulation_header + use trigger_header use tally, only: TallyObject - use tally_filter, only: MeshFilter + use tally_filter_mesh, only: MeshFilter + use tally_filter_header, only: filter_matches, filters + use tally_header, only: tallies, n_tallies implicit none @@ -101,12 +106,12 @@ contains integer :: score_index ! scoring bin index integer :: n_order ! loop index for moment orders integer :: nm_order ! loop index for Ynm moment orders + integer(C_INT) :: err real(8) :: uncertainty ! trigger uncertainty real(8) :: std_dev = ZERO ! trigger standard deviation real(8) :: rel_err = ZERO ! trigger relative error real(8) :: ratio ! ratio of the uncertainty/trigger threshold - type(TallyObject), pointer :: t ! tally pointer - type(TriggerObject), pointer :: trigger ! tally trigger + real(C_DOUBLE) :: k_combined(2) ! Initialize tally trigger maximum uncertainty ratio to zero max_ratio = 0 @@ -119,6 +124,7 @@ contains ! Check eigenvalue trigger if (run_mode == MODE_EIGENVALUE) then if (keff_trigger % trigger_type /= 0) then + err = openmc_get_keff(k_combined) select case (keff_trigger % trigger_type) case(VARIANCE) uncertainty = k_combined(2) ** 2 @@ -146,7 +152,7 @@ contains ! Compute uncertainties for all tallies, scores with triggers TALLY_LOOP: do i = 1, n_tallies - t => tallies(i) + associate (t => tallies(i) % obj) ! Cycle through if only one batch has been simumlate if (t % n_realizations == 1) then @@ -154,15 +160,15 @@ contains end if TRIGGER_LOOP: do s = 1, t % n_triggers - trigger => t % triggers(s) + associate (trigger => t % triggers(s)) ! Initialize trigger uncertainties to zero trigger % std_dev = ZERO trigger % rel_err = ZERO trigger % variance = ZERO - ! Surface current tally triggers require special treatment - if (t % type == TALLY_SURFACE_CURRENT) then + ! Mesh current tally triggers require special treatment + if (t % type == TALLY_MESH_CURRENT) then call compute_tally_current(t, trigger) else @@ -173,7 +179,7 @@ contains call filter_matches(t % filter(j)) % bins % push_back(0) end do - FILTER_LOOP: do filter_index = 1, t % total_filter_bins + FILTER_LOOP: do filter_index = 1, t % n_filter_bins ! Initialize score index score_index = trigger % score_index @@ -274,18 +280,22 @@ contains if (size(t % filter) == 0) exit FILTER_LOOP end do FILTER_LOOP end if + end associate end do TRIGGER_LOOP + end associate end do TALLY_LOOP end if end subroutine check_tally_triggers !=============================================================================== -! COMPUTE_TALLY_CURRENT computes the current for a surface current tally with +! COMPUTE_TALLY_CURRENT computes the current for a mesh current tally with ! precision trigger(s). !=============================================================================== subroutine compute_tally_current(t, trigger) + type(TallyObject), intent(in) :: t ! mesh current tally + type(TriggerObject), intent(inout) :: trigger ! mesh current tally trigger integer :: i ! mesh index integer :: j ! loop index for tally filters @@ -301,8 +311,6 @@ contains logical :: print_ebin ! should incoming energy bin be displayed? real(8) :: rel_err = ZERO ! temporary relative error of result real(8) :: std_dev = ZERO ! temporary standard deviration of result - type(TallyObject), pointer :: t ! surface current tally - type(TriggerObject) :: trigger ! surface current tally trigger type(RegularMesh), pointer :: m ! surface current mesh ! Get pointer to mesh @@ -338,7 +346,7 @@ contains do i = 1, n_cells ! Get the indices for this cell - call bin_to_mesh_indices(m, i, ijk) + call m % get_indices_from_bin(i, ijk) filter_matches(i_filter_mesh) % bins % data(1) = i do l = 1, n diff --git a/src/trigger_header.F90 b/src/tallies/trigger_header.F90 similarity index 89% rename from src/trigger_header.F90 rename to src/tallies/trigger_header.F90 index 96421314c..87507224a 100644 --- a/src/trigger_header.F90 +++ b/src/tallies/trigger_header.F90 @@ -3,12 +3,13 @@ module trigger_header use constants, only: NONE, N_FILTER_TYPES, ZERO implicit none + private !=============================================================================== ! TRIGGEROBJECT stores the variance, relative error and standard deviation ! for some user-specified trigger. !=============================================================================== - type TriggerObject + type, public :: TriggerObject integer :: type ! "variance", "std_dev" or "rel_err" real(8) :: threshold ! a convergence threshold character(len=52) :: score_name ! the name of the score @@ -21,9 +22,11 @@ module trigger_header !=============================================================================== ! KTRIGGER describes a user-specified precision trigger for k-effective !=============================================================================== - type KTrigger + type, public :: KTrigger integer :: trigger_type = 0 real(8) :: threshold = ZERO end type KTrigger + type(KTrigger), public :: keff_trigger ! trigger for k-effective + end module trigger_header diff --git a/src/tally_filter.F90 b/src/tally_filter.F90 deleted file mode 100644 index dc8136b44..000000000 --- a/src/tally_filter.F90 +++ /dev/null @@ -1,1752 +0,0 @@ -module tally_filter - - use algorithm, only: binary_search - use constants, only: ONE, NO_BIN_FOUND, FP_PRECISION, ERROR_REAL - use dict_header, only: DictIntInt - use geometry_header, only: root_universe, RectLattice, HexLattice - use global - use hdf5_interface - use mesh_header, only: RegularMesh - use mesh, only: get_mesh_bin, bin_to_mesh_indices, & - get_mesh_indices, mesh_indices_to_bin, & - mesh_intersects_1d, mesh_intersects_2d, & - mesh_intersects_3d - use particle_header, only: Particle - use string, only: to_str - use tally_filter_header, only: TallyFilter, TallyFilterContainer - - use hdf5, only: HID_T - - implicit none - -!=============================================================================== -! MESHFILTER indexes the location of particle events to a regular mesh. For -! tracklength tallies, it will produce multiple valid bins and the bin weight -! will correspond to the fraction of the track length that lies in that bin. -!=============================================================================== - type, extends(TallyFilter) :: MeshFilter - integer :: mesh - contains - procedure :: get_next_bin => get_next_bin_mesh - procedure :: to_statepoint => to_statepoint_mesh - procedure :: text_label => text_label_mesh - end type MeshFilter - -!=============================================================================== -! UNIVERSEFILTER specifies which geometric universes tally events reside in. -!=============================================================================== - type, extends(TallyFilter) :: UniverseFilter - integer, allocatable :: universes(:) - type(DictIntInt) :: map - contains - procedure :: get_next_bin => get_next_bin_universe - procedure :: to_statepoint => to_statepoint_universe - procedure :: text_label => text_label_universe - procedure :: initialize => initialize_universe - end type UniverseFilter - -!=============================================================================== -! MATERIAL specifies which material tally events reside in. -!=============================================================================== - type, extends(TallyFilter) :: MaterialFilter - integer, allocatable :: materials(:) - type(DictIntInt) :: map - contains - procedure :: get_next_bin => get_next_bin_material - procedure :: to_statepoint => to_statepoint_material - procedure :: text_label => text_label_material - procedure :: initialize => initialize_material - end type MaterialFilter - -!=============================================================================== -! PARTICLE specifies which particle tally events reside in. -!=============================================================================== - type, extends(TallyFilter) :: ParticleFilter - integer, allocatable :: particles(:) - contains - procedure :: get_next_bin => get_next_bin_particle - procedure :: to_statepoint => to_statepoint_particle - procedure :: text_label => text_label_particle - procedure :: initialize => initialize_particle - end type ParticleFilter - -!=============================================================================== -! CELLFILTER specifies which geometric cells tally events reside in. -!=============================================================================== - type, extends(TallyFilter) :: CellFilter - integer, allocatable :: cells(:) - type(DictIntInt) :: map - contains - procedure :: get_next_bin => get_next_bin_cell - procedure :: to_statepoint => to_statepoint_cell - procedure :: text_label => text_label_cell - procedure :: initialize => initialize_cell - end type CellFilter - -!=============================================================================== -! DISTRIBCELLFILTER specifies which distributed geometric cells tally events -! reside in. -!=============================================================================== - type, extends(TallyFilter) :: DistribcellFilter - integer :: cell - contains - procedure :: get_next_bin => get_next_bin_distribcell - procedure :: to_statepoint => to_statepoint_distribcell - procedure :: text_label => text_label_distribcell - procedure :: initialize => initialize_distribcell - end type DistribcellFilter - -!=============================================================================== -! CELLBORNFILTER specifies which cell the particle was born in. -!=============================================================================== - type, extends(TallyFilter) :: CellbornFilter - integer, allocatable :: cells(:) - type(DictIntInt) :: map - contains - procedure :: get_next_bin => get_next_bin_cellborn - procedure :: to_statepoint => to_statepoint_cellborn - procedure :: text_label => text_label_cellborn - procedure :: initialize => initialize_cellborn - end type CellbornFilter - -!=============================================================================== -! SURFACEFILTER is currently not implemented for usual geometric surfaces, but -! it is used as a placeholder for mesh surfaces used in current tallies. -!=============================================================================== - type, extends(TallyFilter) :: SurfaceFilter - integer, allocatable :: surfaces(:) - - ! True if this filter is used for surface currents - logical :: current = .false. - contains - procedure :: get_next_bin => get_next_bin_surface - procedure :: to_statepoint => to_statepoint_surface - procedure :: text_label => text_label_surface - procedure :: initialize => initialize_surface - end type SurfaceFilter - -!=============================================================================== -! ENERGYFILTER bins the incident neutron energy. -!=============================================================================== - type, extends(TallyFilter) :: EnergyFilter - real(8), allocatable :: bins(:) - - ! True if transport group number can be used directly to get bin number - logical :: matches_transport_groups = .false. - - contains - procedure :: get_next_bin => get_next_bin_energy - procedure :: to_statepoint => to_statepoint_energy - procedure :: text_label => text_label_energy - end type EnergyFilter - -!=============================================================================== -! ENERGYOUTFILTER bins the outgoing neutron energy. Only scattering events use -! the get_next_bin functionality. Nu-fission tallies manually iterate over the -! filter bins. -!=============================================================================== - type, extends(TallyFilter) :: EnergyoutFilter - real(8), allocatable :: bins(:) - - ! True if transport group number can be used directly to get bin number - logical :: matches_transport_groups = .false. - - contains - procedure :: get_next_bin => get_next_bin_energyout - procedure :: to_statepoint => to_statepoint_energyout - procedure :: text_label => text_label_energyout - end type EnergyoutFilter - -!=============================================================================== -! DELAYEDGROUPFILTER bins outgoing fission neutrons in their delayed groups. -! The get_next_bin functionality is not actually used. The bins are manually -! iterated over in the scoring subroutines. -!=============================================================================== - type, extends(TallyFilter) :: DelayedGroupFilter - integer, allocatable :: groups(:) - contains - procedure :: get_next_bin => get_next_bin_dg - procedure :: to_statepoint => to_statepoint_dg - procedure :: text_label => text_label_dg - end type DelayedGroupFilter - -!=============================================================================== -! MUFILTER bins the incoming-outgoing direction cosine. This is only used for -! scatter reactions. -!=============================================================================== - type, extends(TallyFilter) :: MuFilter - real(8), allocatable :: bins(:) - contains - procedure :: get_next_bin => get_next_bin_mu - procedure :: to_statepoint => to_statepoint_mu - procedure :: text_label => text_label_mu - end type MuFilter - -!=============================================================================== -! POLARFILTER bins the incident neutron polar angle (relative to the global -! z-axis). -!=============================================================================== - type, extends(TallyFilter) :: PolarFilter - real(8), allocatable :: bins(:) - contains - procedure :: get_next_bin => get_next_bin_polar - procedure :: to_statepoint => to_statepoint_polar - procedure :: text_label => text_label_polar - end type PolarFilter - -!=============================================================================== -! AZIMUTHALFILTER bins the incident neutron azimuthal angle (relative to the -! global xy-plane). -!=============================================================================== - type, extends(TallyFilter) :: AzimuthalFilter - real(8), allocatable :: bins(:) - contains - procedure :: get_next_bin => get_next_bin_azimuthal - procedure :: to_statepoint => to_statepoint_azimuthal - procedure :: text_label => text_label_azimuthal - end type AzimuthalFilter - -!=============================================================================== -! EnergyFunctionFilter multiplies tally scores by an arbitrary function of -! incident energy described by a piecewise linear-linear interpolation. -!=============================================================================== - type, extends(TallyFilter) :: EnergyFunctionFilter - real(8), allocatable :: energy(:) - real(8), allocatable :: y(:) - - contains - procedure :: get_next_bin => get_next_bin_energyfunction - procedure :: to_statepoint => to_statepoint_energyfunction - procedure :: text_label => text_label_energyfunction - end type EnergyFunctionFilter - -contains - -!=============================================================================== -! METHODS: for a description of these methods, see their counterparts bound to -! the abstract TallyFilter class. -!=============================================================================== - -!=============================================================================== -! MeshFilter methods -!=============================================================================== - subroutine get_next_bin_mesh(this, p, estimator, current_bin, next_bin, & - weight) - class(MeshFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer, parameter :: MAX_SEARCH_ITER = 100 ! Maximum number of times we can - ! can loop while trying to find - ! the first intersection. - - integer :: j ! loop index for direction - integer :: ijk0(3) ! indices of starting coordinates - integer :: ijk1(3) ! indices of ending coordinates - integer :: search_iter ! loop count for intersection search - real(8) :: uvw(3) ! cosine of angle of particle - real(8) :: xyz0(3) ! starting/intermediate coordinates - real(8) :: xyz1(3) ! ending coordinates of particle - real(8) :: xyz_cross ! coordinates of next boundary - real(8) :: d(3) ! distance to each bounding surface - real(8) :: total_distance ! distance of entire particle track - real(8) :: distance ! distance traveled in mesh cell - logical :: start_in_mesh ! starting coordinates inside mesh? - logical :: end_in_mesh ! ending coordinates inside mesh? - type(RegularMesh), pointer :: m - - weight = ERROR_REAL - - ! Get a pointer to the mesh. - m => meshes(this % mesh) - - if (estimator /= ESTIMATOR_TRACKLENGTH) then - ! If this is an analog or collision tally, then there can only be one - ! valid mesh bin. - if (current_bin == NO_BIN_FOUND) then - call get_mesh_bin(m, p % coord(1) % xyz, next_bin) - weight = ONE - else - next_bin = NO_BIN_FOUND - end if - - else - ! A track can span multiple mesh bins so we need to handle a lot of - ! intersection logic for tracklength tallies. - - ! ======================================================================== - ! Determine if the track intersects the tally mesh. - - ! Copy the starting and ending coordinates of the particle. Offset these - ! just a bit for the purposes of determining if there was an intersection - ! in case the mesh surfaces coincide with lattice/geometric surfaces which - ! might produce finite-precision errors. - xyz0 = p % last_xyz + TINY_BIT * p % coord(1) % uvw - xyz1 = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw - - ! Determine indices for starting and ending location. - call get_mesh_indices(m, xyz0, ijk0(:m % n_dimension), start_in_mesh) - call get_mesh_indices(m, xyz1, ijk1(:m % n_dimension), end_in_mesh) - - ! If this is the first iteration of the filter loop, check if the track - ! intersects any part of the mesh. - if (current_bin == NO_BIN_FOUND) then - if ((.not. start_in_mesh) .and. (.not. end_in_mesh)) then - if (m % n_dimension == 1) then - if (.not. mesh_intersects_1d(m, xyz0, xyz1)) then - next_bin = NO_BIN_FOUND - return - end if - else if (m % n_dimension == 2) then - if (.not. mesh_intersects_2d(m, xyz0, xyz1)) then - next_bin = NO_BIN_FOUND - return - end if - else - if (.not. mesh_intersects_3d(m, xyz0, xyz1)) then - next_bin = NO_BIN_FOUND - return - end if - end if - end if - end if - - ! ======================================================================== - ! Figure out which mesh cell to tally. - - ! Copy the un-modified coordinates the particle direction. - xyz0 = p % last_xyz - xyz1 = p % coord(1) % xyz - uvw = p % coord(1) % uvw - - ! Compute the length of the entire track. - total_distance = sqrt(sum((xyz1 - xyz0)**2)) - - ! Check if particle has moved - if (total_distance == ZERO) then - next_bin = current_bin - return - end if - - if (current_bin == NO_BIN_FOUND) then - ! We are looking for the first valid mesh bin. Check to see if the - ! particle starts inside the mesh. - if (any(ijk0(:m % n_dimension) < 1) & - .or. any(ijk0(:m % n_dimension) > m % dimension)) then - ! The particle does not start in the mesh. Note that we nudged the - ! start and end coordinates by a TINY_BIT each so we will have - ! difficulty resolving tracks that are less than 2*TINY_BIT in length. - ! If the track is that short, it is also insignificant so we can - ! safely ignore it in the tallies. - if (total_distance < 2*TINY_BIT) then - next_bin = NO_BIN_FOUND - return - end if - - ! The particle does not start in the mesh so keep iterating the ijk0 - ! indices to cross the nearest mesh surface until we've found a valid - ! bin. MAX_SEARCH_ITER prevents an infinite loop. - search_iter = 0 - do while (any(ijk0(:m % n_dimension) < 1) & - .or. any(ijk0(:m % n_dimension) > m % dimension)) - if (search_iter == MAX_SEARCH_ITER) then - call warning("Failed to find a mesh intersection on a tally mesh & - &filter.") - next_bin = NO_BIN_FOUND - return - end if - - do j = 1, m % n_dimension - if (abs(uvw(j)) < FP_PRECISION) then - d(j) = INFINITY - else if (uvw(j) > 0) then - xyz_cross = m % lower_left(j) + ijk0(j) * m % width(j) - d(j) = (xyz_cross - xyz0(j)) / uvw(j) - else - xyz_cross = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) - d(j) = (xyz_cross - xyz0(j)) / uvw(j) - end if - end do - j = minloc(d(:m % n_dimension), 1) - if (uvw(j) > ZERO) then - ijk0(j) = ijk0(j) + 1 - else - ijk0(j) = ijk0(j) - 1 - end if - - search_iter = search_iter + 1 - end do - distance = d(j) - xyz0 = xyz0 + distance * uvw - - end if - - else - ! We have already scored some mesh bins for this track. Pick up where - ! we left off and find the next mesh cell that the particle enters. - - ! Get the indices to the last bin we scored. - call bin_to_mesh_indices(m, current_bin, ijk0(:m % n_dimension)) - - ! If the particle track ends in that bin, then we are done. - if (all(ijk0(:m % n_dimension) == ijk1(:m % n_dimension))) then - next_bin = NO_BIN_FOUND - return - end if - - ! Figure out which face of the previous mesh cell our track exits, i.e. - ! the closest surface of that cell for which - ! dot(p % uvw, face_normal) > 0. - do j = 1, m % n_dimension - if (abs(uvw(j)) < FP_PRECISION) then - d(j) = INFINITY - else if (uvw(j) > 0) then - xyz_cross = m % lower_left(j) + ijk0(j) * m % width(j) - d(j) = (xyz_cross - xyz0(j)) / uvw(j) - else - xyz_cross = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) - d(j) = (xyz_cross - xyz0(j)) / uvw(j) - end if - end do - j = minloc(d(:m % n_dimension), 1) - - ! Translate the starting coordintes by the distance to that face. This - ! should be the xyz that we computed the distance to in the last - ! iteration of the filter loop. - distance = d(j) - xyz0 = xyz0 + distance * uvw - - ! Increment the indices into the next mesh cell. - if (uvw(j) > ZERO) then - ijk0(j) = ijk0(j) + 1 - else - ijk0(j) = ijk0(j) - 1 - end if - - ! If the next indices are invalid, then the track has left the mesh and - ! we are done. - if (any(ijk0(:m % n_dimension) < 1) & - .or. any(ijk0(:m % n_dimension) > m % dimension)) then - next_bin = NO_BIN_FOUND - return - end if - end if - - ! ======================================================================== - ! Compute the length of the track segment in the appropiate mesh cell and - ! return. - - if (all(ijk0(:m % n_dimension) == ijk1(:m % n_dimension))) then - ! The track ends in this cell. Use the particle end location rather - ! than the mesh surface. - distance = sqrt(sum((xyz1 - xyz0)**2)) - else - ! The track exits this cell. Use the distance to the mesh surface. - do j = 1, m % n_dimension - if (abs(uvw(j)) < FP_PRECISION) then - d(j) = INFINITY - else if (uvw(j) > 0) then - xyz_cross = m % lower_left(j) + ijk0(j) * m % width(j) - d(j) = (xyz_cross - xyz0(j)) / uvw(j) - else - xyz_cross = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) - d(j) = (xyz_cross - xyz0(j)) / uvw(j) - end if - end do - distance = minval(d(:m % n_dimension)) - end if - - ! Assign the next tally bin and the score. - next_bin = mesh_indices_to_bin(m, ijk0(:m % n_dimension)) - weight = distance / total_distance - endif - end subroutine get_next_bin_mesh - - subroutine to_statepoint_mesh(this, filter_group) - class(MeshFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "mesh") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", meshes(this % mesh) % id) - end subroutine to_statepoint_mesh - - function text_label_mesh(this, bin) result(label) - class(MeshFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - integer, allocatable :: ijk(:) - type(RegularMesh), pointer :: m - - m => meshes(this % mesh) - allocate(ijk(m % n_dimension)) - call bin_to_mesh_indices(m, bin, ijk) - if (m % n_dimension == 1) then - label = "Mesh Index (" // trim(to_str(ijk(1))) // ")" - elseif (m % n_dimension == 2) then - label = "Mesh Index (" // trim(to_str(ijk(1))) // ", " // & - trim(to_str(ijk(2))) // ")" - elseif (m % n_dimension == 3) then - label = "Mesh Index (" // trim(to_str(ijk(1))) // ", " // & - trim(to_str(ijk(2))) // ", " // trim(to_str(ijk(3))) // ")" - end if - end function text_label_mesh - -!=============================================================================== -! UniverseFilter methods -!=============================================================================== - subroutine get_next_bin_universe(this, p, estimator, current_bin, next_bin, & - weight) - class(UniverseFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: i, start - - ! Find the coordinate level of the last bin we found. - if (current_bin == NO_BIN_FOUND) then - start = 1 - else - do i = 1, p % n_coord - if (p % coord(i) % universe == this % universes(current_bin)) then - start = i + 1 - exit - end if - end do - end if - - ! Starting one coordinate level deeper, find the next bin. - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - do i = start, p % n_coord - if (this % map % has_key(p % coord(i) % universe)) then - next_bin = this % map % get_key(p % coord(i) % universe) - weight = ONE - exit - end if - end do - end subroutine get_next_bin_universe - - subroutine to_statepoint_universe(this, filter_group) - class(UniverseFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - integer :: i - integer, allocatable :: universe_ids(:) - - call write_dataset(filter_group, "type", "universe") - call write_dataset(filter_group, "n_bins", this % n_bins) - - allocate(universe_ids(size(this % universes))) - do i = 1, size(this % universes) - universe_ids(i) = universes(this % universes(i)) % id - end do - call write_dataset(filter_group, "bins", universe_ids) - end subroutine to_statepoint_universe - - subroutine initialize_universe(this) - class(UniverseFilter), intent(inout) :: this - - integer :: i, id - - ! Convert ids to indices. - do i = 1, this % n_bins - id = this % universes(i) - if (universe_dict % has_key(id)) then - this % universes(i) = universe_dict % get_key(id) - else - call fatal_error("Could not find universe " // trim(to_str(id)) & - &// " specified on a tally filter.") - end if - end do - - ! Generate mapping from universe indices to filter bins. - do i = 1, this % n_bins - call this % map % add_key(this % universes(i), i) - end do - end subroutine initialize_universe - - function text_label_universe(this, bin) result(label) - class(UniverseFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Universe " // to_str(universes(this % universes(bin)) % id) - end function text_label_universe - -!=============================================================================== -! MaterialFilter methods -!=============================================================================== - subroutine get_next_bin_material(this, p, estimator, current_bin, next_bin, & - weight) - class(MaterialFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - if (current_bin == NO_BIN_FOUND) then - if (this % map % has_key(p % material)) then - next_bin = this % map % get_key(p % material) - weight = ONE - end if - end if - end subroutine get_next_bin_material - - subroutine to_statepoint_material(this, filter_group) - class(MaterialFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - integer :: i - integer, allocatable :: material_ids(:) - - call write_dataset(filter_group, "type", "material") - call write_dataset(filter_group, "n_bins", this % n_bins) - - allocate(material_ids(size(this % materials))) - do i = 1, size(this % materials) - material_ids(i) = materials(this % materials(i)) % id - end do - call write_dataset(filter_group, "bins", material_ids) - end subroutine to_statepoint_material - - subroutine initialize_material(this) - class(MaterialFilter), intent(inout) :: this - - integer :: i, id - - ! Convert ids to indices. - do i = 1, this % n_bins - id = this % materials(i) - if (material_dict % has_key(id)) then - this % materials(i) = material_dict % get_key(id) - else - call fatal_error("Could not find material " // trim(to_str(id)) & - &// " specified on a tally filter.") - end if - end do - - ! Generate mapping from material indices to filter bins. - do i = 1, this % n_bins - call this % map % add_key(this % materials(i), i) - end do - end subroutine initialize_material - - function text_label_material(this, bin) result(label) - class(MaterialFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Material " // to_str(materials(this % materials(bin)) % id) - end function text_label_material - -!=============================================================================== -! ParticleFilter methods -!=============================================================================== - subroutine get_next_bin_particle(this, p, estimator, current_bin, next_bin, & - weight) - class(ParticleFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: i - - weight = ERROR_REAL - next_bin = NO_BIN_FOUND - - if (current_bin == NO_BIN_FOUND) then - do i = 1, this % n_bins - if (this % particles(i) == p % type) then - next_bin = i - weight = ONE - end if - end do - end if - - end subroutine get_next_bin_particle - - subroutine to_statepoint_particle(this, filter_group) - class(ParticleFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - integer :: i - - call write_dataset(filter_group, "type", "particle") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", this % particles) - end subroutine to_statepoint_particle - - subroutine initialize_particle(this) - class(ParticleFilter), intent(inout) :: this - - end subroutine initialize_particle - - function text_label_particle(this, bin) result(label) - class(ParticleFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Particle " // to_str(this % particles(bin)) - end function text_label_particle - -!=============================================================================== -! CellFilter methods -!=============================================================================== - subroutine get_next_bin_cell(this, p, estimator, current_bin, next_bin, & - weight) - class(CellFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: i, start - - ! Find the coordinate level of the last bin we found. - if (current_bin == NO_BIN_FOUND) then - start = 1 - else - do i = 1, p % n_coord - if (p % coord(i) % cell == this % cells(current_bin)) then - start = i + 1 - exit - end if - end do - end if - - ! Starting one coordinate level deeper, find the next bin. - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - do i = start, p % n_coord - if (this % map % has_key(p % coord(i) % cell)) then - next_bin = this % map % get_key(p % coord(i) % cell) - weight = ONE - exit - end if - end do - end subroutine get_next_bin_cell - - subroutine to_statepoint_cell(this, filter_group) - class(CellFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - integer :: i - integer, allocatable :: cell_ids(:) - - call write_dataset(filter_group, "type", "cell") - call write_dataset(filter_group, "n_bins", this % n_bins) - - allocate(cell_ids(size(this % cells))) - do i = 1, size(this % cells) - cell_ids(i) = cells(this % cells(i)) % id - end do - call write_dataset(filter_group, "bins", cell_ids) - end subroutine to_statepoint_cell - - subroutine initialize_cell(this) - class(CellFilter), intent(inout) :: this - - integer :: i, id - - ! Convert ids to indices. - do i = 1, this % n_bins - id = this % cells(i) - if (cell_dict % has_key(id)) then - this % cells(i) = cell_dict % get_key(id) - else - call fatal_error("Could not find cell " // trim(to_str(id)) & - &// " specified on tally filter.") - end if - end do - - ! Generate mapping from cell indices to filter bins. - do i = 1, this % n_bins - call this % map % add_key(this % cells(i), i) - end do - end subroutine initialize_cell - - function text_label_cell(this, bin) result(label) - class(CellFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Cell " // to_str(cells(this % cells(bin)) % id) - end function text_label_cell - -!=============================================================================== -! DistribcellFilter methods -!=============================================================================== - subroutine get_next_bin_distribcell(this, p, estimator, current_bin, & - next_bin, weight) - class(DistribcellFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: distribcell_index, offset, i - - if (current_bin == NO_BIN_FOUND) then - distribcell_index = cells(this % cell) % distribcell_index - offset = 0 - do i = 1, p % n_coord - if (cells(p % coord(i) % cell) % type == FILL_UNIVERSE) then - offset = offset + cells(p % coord(i) % cell) % & - offset(distribcell_index) - elseif (cells(p % coord(i) % cell) % type == FILL_LATTICE) then - if (lattices(p % coord(i + 1) % lattice) % obj & - % are_valid_indices([& - p % coord(i + 1) % lattice_x, & - p % coord(i + 1) % lattice_y, & - p % coord(i + 1) % lattice_z])) then - offset = offset + lattices(p % coord(i + 1) % lattice) % obj % & - offset(distribcell_index, & - p % coord(i + 1) % lattice_x, & - p % coord(i + 1) % lattice_y, & - p % coord(i + 1) % lattice_z) - end if - end if - if (this % cell == p % coord(i) % cell) then - next_bin = offset + 1 - weight = ONE - return - end if - end do - end if - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - end subroutine get_next_bin_distribcell - - subroutine to_statepoint_distribcell(this, filter_group) - class(DistribcellFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "distribcell") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", cells(this % cell) % id) - end subroutine to_statepoint_distribcell - - subroutine initialize_distribcell(this) - class(DistribcellFilter), intent(inout) :: this - - integer :: id - - ! Convert id to index. - id = this % cell - if (cell_dict % has_key(id)) then - this % cell = cell_dict % get_key(id) - else - call fatal_error("Could not find cell " // trim(to_str(id)) & - &// " specified on tally filter.") - end if - end subroutine initialize_distribcell - - function text_label_distribcell(this, bin) result(label) - class(DistribcellFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - integer :: offset - type(Universe), pointer :: univ - - univ => universes(root_universe) - offset = 0 - label = '' - call find_offset(this % cell, univ, bin-1, offset, label) - label = "Distributed Cell " // label - end function text_label_distribcell - -!=============================================================================== -! CellbornFilter methods -!=============================================================================== - subroutine get_next_bin_cellborn(this, p, estimator, current_bin, next_bin, & - weight) - class(CellbornFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - if (current_bin == NO_BIN_FOUND) then - if (this % map % has_key(p % cell_born)) then - next_bin = this % map % get_key(p % cell_born) - weight = ONE - end if - end if - end subroutine get_next_bin_cellborn - - subroutine to_statepoint_cellborn(this, filter_group) - class(CellbornFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - integer :: i - integer, allocatable :: cell_ids(:) - - call write_dataset(filter_group, "type", "cellborn") - call write_dataset(filter_group, "n_bins", this % n_bins) - allocate(cell_ids(size(this % cells))) - do i = 1, size(this % cells) - cell_ids(i) = cells(this % cells(i)) % id - end do - call write_dataset(filter_group, "bins", cell_ids) - end subroutine to_statepoint_cellborn - - subroutine initialize_cellborn(this) - class(CellbornFilter), intent(inout) :: this - - integer :: i, id - - ! Convert ids to indices. - do i = 1, this % n_bins - id = this % cells(i) - if (cell_dict % has_key(id)) then - this % cells(i) = cell_dict % get_key(id) - else - call fatal_error("Could not find cell " // trim(to_str(id)) & - &// " specified on tally filter.") - end if - end do - - ! Generate mapping from cell indices to filter bins. - do i = 1, this % n_bins - call this % map % add_key(this % cells(i), i) - end do - end subroutine initialize_cellborn - - function text_label_cellborn(this, bin) result(label) - class(CellbornFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Birth Cell " // to_str(cells(this % cells(bin)) % id) - end function text_label_cellborn - -!=============================================================================== -! SurfaceFilter methods -!=============================================================================== - subroutine get_next_bin_surface(this, p, estimator, current_bin, next_bin, & - weight) - class(SurfaceFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: i - - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - if (current_bin == NO_BIN_FOUND) then - do i = 1, this % n_bins - if (p % surface == this % surfaces(i)) then - next_bin = i - weight = ONE - exit - end if - end do - end if - end subroutine get_next_bin_surface - - subroutine to_statepoint_surface(this, filter_group) - class(SurfaceFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "surface") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", this % surfaces) - end subroutine to_statepoint_surface - - subroutine initialize_surface(this) - class(SurfaceFilter), intent(inout) :: this - - integer :: i, id - - ! Convert ids to indices. - do i = 1, this % n_bins - id = this % surfaces(i) - if (surface_dict % has_key(id)) then - this % surfaces(i) = surface_dict % get_key(id) - else - call fatal_error("Could not find surface " // trim(to_str(id)) & - &// " specified on tally filter.") - end if - end do - end subroutine initialize_surface - - function text_label_surface(this, bin) result(label) - class(SurfaceFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Surface " // to_str(surfaces(this % surfaces(bin)) % obj % id) - end function text_label_surface - -!=============================================================================== -! EnergyFilter methods -!=============================================================================== - subroutine get_next_bin_energy(this, p, estimator, current_bin, next_bin, & - weight) - class(EnergyFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: n - real(8) :: E - - if (current_bin == NO_BIN_FOUND) then - n = this % n_bins - - if ((.not. run_CE) .and. this % matches_transport_groups) then - if (estimator == ESTIMATOR_TRACKLENGTH) then - next_bin = p % g - else - next_bin = p % last_g - end if - - ! Tallies are ordered in increasing groups, group indices - ! however are the opposite, so switch - next_bin = num_energy_groups - next_bin + 1 - weight = ONE - - else - ! Make sure the correct energy is used. - if (estimator == ESTIMATOR_TRACKLENGTH) then - E = p % E - else - E = p % last_E - end if - - ! Check if energy of the particle is within energy bins. - if (E < this % bins(1) .or. E > this % bins(n + 1)) then - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - else - ! Search to find incoming energy bin. - next_bin = binary_search(this % bins, n + 1, E) - weight = ONE - end if - end if - - else - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - end if - end subroutine get_next_bin_energy - - subroutine to_statepoint_energy(this, filter_group) - class(EnergyFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "energy") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", this % bins) - end subroutine to_statepoint_energy - - function text_label_energy(this, bin) result(label) - class(EnergyFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - real(8) :: E0, E1 - - E0 = this % bins(bin) - E1 = this % bins(bin + 1) - label = "Incoming Energy [" // trim(to_str(E0)) // ", " & - // trim(to_str(E1)) // ")" - end function text_label_energy - -!=============================================================================== -! EnergyoutFilter methods -!=============================================================================== - subroutine get_next_bin_energyout(this, p, estimator, current_bin, next_bin, & - weight) - class(EnergyoutFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: n - - if (current_bin == NO_BIN_FOUND) then - n = this % n_bins - - if ((.not. run_CE) .and. this % matches_transport_groups) then - next_bin = p % g - - ! Tallies are ordered in increasing groups, group indices - ! however are the opposite, so switch - next_bin = num_energy_groups - next_bin + 1 - weight = ONE - - else - ! Check if energy of the particle is within energy bins. - if (p % E < this % bins(1) .or. p % E > this % bins(n + 1)) then - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - else - ! Search to find incoming energy bin. - next_bin = binary_search(this % bins, n + 1, p % E) - weight = ONE - end if - end if - - else - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - end if - end subroutine get_next_bin_energyout - - subroutine to_statepoint_energyout(this, filter_group) - class(EnergyoutFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "energyout") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", this % bins) - end subroutine to_statepoint_energyout - - function text_label_energyout(this, bin) result(label) - class(EnergyoutFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - real(8) :: E0, E1 - - E0 = this % bins(bin) - E1 = this % bins(bin + 1) - label = "Outgoing Energy [" // trim(to_str(E0)) // ", " & - // trim(to_str(E1)) // ")" - end function text_label_energyout - -!=============================================================================== -! DelayedGroupFilter methods -!=============================================================================== - subroutine get_next_bin_dg(this, p, estimator, current_bin, next_bin, weight) - class(DelayedGroupFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - if (current_bin == NO_BIN_FOUND) then - next_bin = 1 - weight = ONE - else - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - end if - end subroutine get_next_bin_dg - - subroutine to_statepoint_dg(this, filter_group) - class(DelayedGroupFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "delayedgroup") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", this % groups) - end subroutine to_statepoint_dg - - function text_label_dg(this, bin) result(label) - class(DelayedGroupFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Delayed Group " // to_str(this % groups(bin)) - end function text_label_dg - -!=============================================================================== -! MuFilter methods -!=============================================================================== - subroutine get_next_bin_mu(this, p, estimator, current_bin, next_bin, weight) - class(MuFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: n - - if (current_bin == NO_BIN_FOUND) then - n = this % n_bins - - ! Check if energy of the particle is within energy bins. - if (p % mu < this % bins(1) .or. p % mu > this % bins(n + 1)) then - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - else - ! Search to find incoming energy bin. - next_bin = binary_search(this % bins, n + 1, p % mu) - weight = ONE - end if - - else - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - end if - end subroutine get_next_bin_mu - - subroutine to_statepoint_mu(this, filter_group) - class(MuFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "mu") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", this % bins) - end subroutine to_statepoint_mu - - function text_label_mu(this, bin) result(label) - class(MuFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - real(8) :: E0, E1 - - E0 = this % bins(bin) - E1 = this % bins(bin + 1) - label = "Change-in-Angle [" // trim(to_str(E0)) // ", " & - // trim(to_str(E1)) // ")" - end function text_label_mu - -!=============================================================================== -! PolarFilter methods -!=============================================================================== - subroutine get_next_bin_polar(this, p, estimator, current_bin, next_bin, & - weight) - class(PolarFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: n - real(8) :: theta - - if (current_bin == NO_BIN_FOUND) then - n = this % n_bins - - ! Make sure the correct direction vector is used. - if (estimator == ESTIMATOR_TRACKLENGTH) then - theta = acos(p % coord(1) % uvw(3)) - else - theta = acos(p % last_uvw(3)) - end if - - ! Check if particle is within polar angle bins. - if (theta < this % bins(1) .or. theta > this % bins(n + 1)) then - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - else - ! Search to find polar angle bin. - next_bin = binary_search(this % bins, n + 1, theta) - weight = ONE - end if - - else - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - end if - end subroutine get_next_bin_polar - - subroutine to_statepoint_polar(this, filter_group) - class(PolarFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "polar") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", this % bins) - end subroutine to_statepoint_polar - - function text_label_polar(this, bin) result(label) - class(PolarFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - real(8) :: E0, E1 - - E0 = this % bins(bin) - E1 = this % bins(bin + 1) - label = "Polar Angle [" // trim(to_str(E0)) // ", " // trim(to_str(E1)) & - // ")" - end function text_label_polar - -!=============================================================================== -! AzimuthalFilter methods -!=============================================================================== - subroutine get_next_bin_azimuthal(this, p, estimator, current_bin, next_bin, & - weight) - class(AzimuthalFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: n - real(8) :: phi - - if (current_bin == NO_BIN_FOUND) then - n = this % n_bins - - ! Make sure the correct direction vector is used. - if (estimator == ESTIMATOR_TRACKLENGTH) then - phi = atan2(p % coord(1) % uvw(2), p % coord(1) % uvw(1)) - else - phi = atan2(p % last_uvw(2), p % last_uvw(1)) - end if - - ! Check if particle is within azimuthal angle bins. - if (phi < this % bins(1) .or. phi > this % bins(n + 1)) then - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - else - ! Search to find azimuthal angle bin. - next_bin = binary_search(this % bins, n + 1, phi) - weight = ONE - end if - - else - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - end if - end subroutine get_next_bin_azimuthal - - subroutine to_statepoint_azimuthal(this, filter_group) - class(AzimuthalFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "azimuthal") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", this % bins) - end subroutine to_statepoint_azimuthal - - function text_label_azimuthal(this, bin) result(label) - class(AzimuthalFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - real(8) :: E0, E1 - - E0 = this % bins(bin) - E1 = this % bins(bin + 1) - label = "Azimuthal Angle [" // trim(to_str(E0)) // ", " & - // trim(to_str(E1)) // ")" - end function text_label_azimuthal - -!=============================================================================== -! EnergyFunctionFilter methods -!=============================================================================== - subroutine get_next_bin_energyfunction(this, p, estimator, current_bin, & - next_bin, weight) - class(EnergyFunctionFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - - integer :: n, indx - real(8) :: E, f - - select type(this) - type is (EnergyFunctionFilter) - if (current_bin == NO_BIN_FOUND) then - n = size(this % energy) - - ! Make sure the correct energy is used. - if (estimator == ESTIMATOR_TRACKLENGTH) then - E = p % E - else - E = p % last_E - end if - - ! Check if energy of the particle is within energy bins. - if (E < this % energy(1) .or. E > this % energy(n)) then - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - - else - ! Search to find incoming energy bin. - indx = binary_search(this % energy, n, E) - - ! Compute an interpolation factor between nearest bins. - f = (E - this % energy(indx)) & - / (this % energy(indx+1) - this % energy(indx)) - - ! Interpolate on the lin-lin grid. - next_bin = 1 - weight = (ONE - f) * this % y(indx) + f * this % y(indx+1) - end if - - else - next_bin = NO_BIN_FOUND - weight = ERROR_REAL - end if - end select - end subroutine get_next_bin_energyfunction - - subroutine to_statepoint_energyfunction(this, filter_group) - class(EnergyFunctionFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - select type(this) - type is (EnergyFunctionFilter) - call write_dataset(filter_group, "type", "energyfunction") - call write_dataset(filter_group, "energy", this % energy) - call write_dataset(filter_group, "y", this % y) - end select - end subroutine to_statepoint_energyfunction - - function text_label_energyfunction(this, bin) result(label) - class(EnergyFunctionFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - select type(this) - type is (EnergyFunctionFilter) - write(label, FMT="(A, ES8.1, A, ES8.1, A, ES8.1, A, ES8.1, A)") & - "Energy Function f([", this % energy(1), ", ..., ", & - this % energy(size(this % energy)), "]) = [", this % y(1), & - ", ..., ", this % y(size(this % y)), "]" - end select - end function text_label_energyfunction - -!=============================================================================== -! FIND_OFFSET (for distribcell) uses a given map number, a target cell ID, and -! a target offset to build a string which is the path from the base universe to -! the target cell with the given offset -!=============================================================================== - - recursive subroutine find_offset(i_cell, univ, target_offset, offset, path) - - integer, intent(in) :: i_cell ! The target cell index - type(Universe), intent(in) :: univ ! Universe to begin search - integer, intent(in) :: target_offset ! Target offset - integer, intent(inout) :: offset ! Current offset - character(*), intent(inout) :: path ! Path to offset - - integer :: map ! Index in maps vector - integer :: i, j ! Index over cells - integer :: k, l, m ! Indices in lattice - integer :: old_k, old_l, old_m ! Previous indices in lattice - integer :: n_x, n_y, n_z ! Lattice cell array dimensions - integer :: n ! Number of cells to search - integer :: cell_index ! Index in cells array - integer :: lat_offset ! Offset from lattice - integer :: temp_offset ! Looped sum of offsets - integer :: i_univ ! index in universes array - logical :: this_cell = .false. ! Advance in this cell? - logical :: later_cell = .false. ! Fill cells after this one? - type(Cell), pointer :: c ! Pointer to current cell - type(Universe), pointer :: next_univ ! Next universe to loop through - class(Lattice), pointer :: lat ! Pointer to current lattice - - ! Get the distribcell index for this cell - map = cells(i_cell) % distribcell_index - - n = size(univ % cells) - - ! Write to the geometry stack - i_univ = universe_dict % get_key(univ % id) - if (i_univ == root_universe) then - path = trim(path) // "u" // to_str(univ%id) - else - path = trim(path) // "->u" // to_str(univ%id) - end if - - ! Look through all cells in this universe - do i = 1, n - ! If the cell matches the goal and the offset matches final, write to the - ! geometry stack - if (univ % cells(i) == i_cell .and. offset == target_offset) then - c => cells(univ % cells(i)) - path = trim(path) // "->c" // to_str(c % id) - return - end if - end do - - ! Find the fill cell or lattice cell that we need to enter - do i = 1, n - - later_cell = .false. - - c => cells(univ % cells(i)) - - this_cell = .false. - - ! If we got here, we still think the target is in this universe - ! or further down, but it's not this exact cell. - ! Compare offset to next cell to see if we should enter this cell - if (i /= n) then - - do j = i+1, n - - c => cells(univ % cells(j)) - - ! Skip normal cells which do not have offsets - if (c % type == FILL_MATERIAL) cycle - - ! Break loop once we've found the next cell with an offset - exit - end do - - ! Ensure we didn't just end the loop by iteration - if (c % type /= FILL_MATERIAL) then - - ! There are more cells in this universe that it could be in - later_cell = .true. - - ! Two cases, lattice or fill cell - if (c % type == FILL_UNIVERSE) then - temp_offset = c % offset(map) - - ! Get the offset of the first lattice location - else - lat => lattices(c % fill) % obj - temp_offset = lat % offset(map, 1, 1, 1) - end if - - ! If the final offset is in the range of offset - temp_offset+offset - ! then the goal is in this cell - if (target_offset < temp_offset + offset) then - this_cell = .true. - end if - end if - end if - - if (n == 1 .and. c % type /= FILL_MATERIAL) then - this_cell = .true. - end if - - if (.not. later_cell) then - this_cell = .true. - end if - - ! Get pointer to THIS cell because target must be in this cell - if (this_cell) then - - cell_index = univ % cells(i) - c => cells(cell_index) - - path = trim(path) // "->c" // to_str(c%id) - - ! ==================================================================== - ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - if (c % type == FILL_UNIVERSE) then - - ! Enter this cell to update the current offset - offset = c % offset(map) + offset - - next_univ => universes(c % fill) - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - - ! ==================================================================== - ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type == FILL_LATTICE) then - - ! Set current lattice - lat => lattices(c % fill) % obj - - select type (lat) - - ! ================================================================== - ! RECTANGULAR LATTICES - type is (RectLattice) - - ! Write to the geometry stack - path = trim(path) // "->l" // to_str(lat%id) - - n_x = lat % n_cells(1) - n_y = lat % n_cells(2) - n_z = lat % n_cells(3) - old_m = 1 - old_l = 1 - old_k = 1 - - ! Loop over lattice coordinates - do k = 1, n_x - do l = 1, n_y - do m = 1, n_z - - if (target_offset >= lat % offset(map, k, l, m) + offset) then - if (k == n_x .and. l == n_y .and. m == n_z) then - ! This is last lattice cell, so target must be here - lat_offset = lat % offset(map, k, l, m) - offset = offset + lat_offset - next_univ => universes(lat % universes(k, l, m)) - if (lat % is_3d) then - path = trim(path) // "(" // trim(to_str(k-1)) // & - "," // trim(to_str(l-1)) // "," // & - trim(to_str(m-1)) // ")" - else - path = trim(path) // "(" // trim(to_str(k-1)) // & - "," // trim(to_str(l-1)) // ")" - end if - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - else - old_m = m - old_l = l - old_k = k - cycle - end if - else - ! Target is at this lattice position - lat_offset = lat % offset(map, old_k, old_l, old_m) - offset = offset + lat_offset - next_univ => universes(lat % universes(old_k, old_l, old_m)) - if (lat % is_3d) then - path = trim(path) // "(" // trim(to_str(old_k-1)) // & - "," // trim(to_str(old_l-1)) // "," // & - trim(to_str(old_m-1)) // ")" - else - path = trim(path) // "(" // trim(to_str(old_k-1)) // & - "," // trim(to_str(old_l-1)) // ")" - end if - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - end if - - end do - end do - end do - - ! ================================================================== - ! HEXAGONAL LATTICES - type is (HexLattice) - - ! Write to the geometry stack - path = trim(path) // "->l" // to_str(lat%id) - - n_z = lat % n_axial - n_y = 2 * lat % n_rings - 1 - n_x = 2 * lat % n_rings - 1 - old_m = 1 - old_l = 1 - old_k = 1 - - ! Loop over lattice coordinates - do m = 1, n_z - do l = 1, n_y - do k = 1, n_x - - ! This array position is never used - if (k + l < lat % n_rings + 1) then - cycle - ! This array position is never used - else if (k + l > 3*lat % n_rings - 1) then - cycle - end if - - if (target_offset >= lat % offset(map, k, l, m) + offset) then - if (k == lat % n_rings .and. l == n_y .and. m == n_z) then - ! This is last lattice cell, so target must be here - lat_offset = lat % offset(map, k, l, m) - offset = offset + lat_offset - next_univ => universes(lat % universes(k, l, m)) - if (lat % is_3d) then - path = trim(path) // "(" // & - trim(to_str(k - lat % n_rings)) // "," // & - trim(to_str(l - lat % n_rings)) // "," // & - trim(to_str(m - 1)) // ")" - else - path = trim(path) // "(" // & - trim(to_str(k - lat % n_rings)) // "," // & - trim(to_str(l - lat % n_rings)) // ")" - end if - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - else - old_m = m - old_l = l - old_k = k - cycle - end if - else - ! Target is at this lattice position - lat_offset = lat % offset(map, old_k, old_l, old_m) - offset = offset + lat_offset - next_univ => universes(lat % universes(old_k, old_l, old_m)) - if (lat % is_3d) then - path = trim(path) // "(" // & - trim(to_str(old_k - lat % n_rings)) // "," // & - trim(to_str(old_l - lat % n_rings)) // "," // & - trim(to_str(old_m - 1)) // ")" - else - path = trim(path) // "(" // & - trim(to_str(old_k - lat % n_rings)) // "," // & - trim(to_str(old_l - lat % n_rings)) // ")" - end if - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - end if - - end do - end do - end do - - end select - - end if - end if - end do - end subroutine find_offset - -!=============================================================================== -! ADD_FILTERS creates or extends the filters array -!=============================================================================== - - subroutine add_filters(n) - - integer, intent(in) :: n ! number of filters to add - - integer :: i ! loop counter - type(TallyFilterContainer), allocatable :: temp(:) ! temporary filters - - if (n_filters == 0) then - ! Allocate filters array - allocate(filters(n)) - else - ! Move filters to temporary array - allocate(temp(n_filters + n)) - do i = 1, n_filters - call move_alloc(filters(i) % obj, temp(i) % obj) - end do - - ! Move filters back from temporary array to filters array - call move_alloc(temp, filters) - end if - - ! Set n_filters - n_filters = size(filters) - - end subroutine add_filters - -end module tally_filter diff --git a/src/tally_filter_header.F90 b/src/tally_filter_header.F90 deleted file mode 100644 index 061642545..000000000 --- a/src/tally_filter_header.F90 +++ /dev/null @@ -1,108 +0,0 @@ -module tally_filter_header - - use constants, only: MAX_LINE_LEN - use particle_header, only: Particle - use stl_vector, only: VectorInt, VectorReal - - use hdf5 - - implicit none - -!=============================================================================== -! TALLYFILTERMATCH stores every valid bin and weight for a filter -!=============================================================================== - - type TallyFilterMatch - ! Index of the bin and weight being used in the current filter combination - integer :: i_bin - type(VectorInt) :: bins - type(VectorReal) :: weights - - ! Indicates whether all valid bins for this filter have been found - logical :: bins_present = .false. - end type TallyFilterMatch - -!=============================================================================== -! TALLYFILTER describes a filter that limits what events score to a tally. For -! example, a cell filter indicates that only particles in a specified cell -! should score to the tally. -!=============================================================================== - - type, abstract :: TallyFilter - integer :: id - integer :: n_bins = 0 - contains - procedure(get_next_bin_), deferred :: get_next_bin - procedure(to_statepoint_), deferred :: to_statepoint - procedure(text_label_), deferred :: text_label - procedure :: initialize => filter_initialize - end type TallyFilter - - abstract interface - -!=============================================================================== -! GET_NEXT_BIN gives the index for the next valid filter bin and a weight that -! will be applied to the flux. -! -! In principle, a filter can have multiple valid bins. If current_bin = -! NO_BIN_FOUND, then this method should give the first valid bin. Providing the -! first valid bin should then give the second valid bin, and so on. When there -! are no valid bins left, the next_bin should be NO_VALID_BIN. - - subroutine get_next_bin_(this, p, estimator, current_bin, next_bin, weight) - import TallyFilter - import Particle - class(TallyFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - integer, value, intent(in) :: current_bin - integer, intent(out) :: next_bin - real(8), intent(out) :: weight - end subroutine get_next_bin_ - -!=============================================================================== -! TO_STATEPOINT writes all the information needed to reconstruct the filter to -! the given filter_group. - - subroutine to_statepoint_(this, filter_group) - import TallyFilter - import HID_T - class(TallyFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - end subroutine to_statepoint_ - -!=============================================================================== -! TEXT_LABEL returns a string describing the given filter bin. For example, an -! energy filter might return the string "Incoming Energy [0.625E-6, 20.0)". -! This is used to write the tallies.out file. - - function text_label_(this, bin) result(label) - import TallyFilter - import MAX_LINE_LEN - class(TallyFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - end function text_label_ - - end interface - -!=============================================================================== -! TALLYFILTERCONTAINER contains an allocatable TallyFilter object for arrays of -! TallyFilters -!=============================================================================== - - type TallyFilterContainer - class(TallyFilter), allocatable :: obj - end type TallyFilterContainer - -contains - -!=============================================================================== -! INITIALIZE sets up any internal data, as necessary. If this procedure is not -! overriden by the derived class, then it will do nothing by default. - - subroutine filter_initialize(this) - class(TallyFilter), intent(inout) :: this - end subroutine filter_initialize - -end module tally_filter_header diff --git a/src/tally_header.F90 b/src/tally_header.F90 deleted file mode 100644 index 76e3d143f..000000000 --- a/src/tally_header.F90 +++ /dev/null @@ -1,165 +0,0 @@ -module tally_header - - use, intrinsic :: ISO_C_BINDING - - use hdf5 - - use constants, only: NONE, N_FILTER_TYPES - use tally_filter_header, only: TallyFilterContainer - use trigger_header, only: TriggerObject - - implicit none - -!=============================================================================== -! TALLYDERIVATIVE describes a first-order derivative that can be applied to -! tallies. -!=============================================================================== - - type TallyDerivative - integer :: id - integer :: variable - integer :: diff_material - integer :: diff_nuclide - real(8) :: flux_deriv - end type TallyDerivative - -!=============================================================================== -! TALLYOBJECT describes a user-specified tally. The region of phase space to -! tally in is given by the TallyFilters and the results are stored in a -! TallyResult array. -!=============================================================================== - - type TallyObject - ! Basic data - - integer :: id ! user-defined identifier - character(len=104) :: name = "" ! user-defined name - integer :: type ! volume, surface current - integer :: estimator ! collision, track-length - real(8) :: volume ! volume of region - integer, allocatable :: filter(:) ! index in filters array - - ! The stride attribute is used for determining the index in the results - ! array for a matching_bin combination. Since multiple dimensions are - ! mapped onto one dimension in the results array, the stride attribute gives - ! the stride for a given filter type within the results array - - integer, allocatable :: stride(:) - - ! This array provides a way to lookup what index in the filters array a - ! certain filter is. For example, if find_filter(FILTER_CELL) > 0, then the - ! value is the index in filters(:). - - integer :: find_filter(N_FILTER_TYPES) = 0 - - ! Individual nuclides to tally - integer :: n_nuclide_bins = 0 - integer, allocatable :: nuclide_bins(:) - logical :: all_nuclides = .false. - - ! Values to score, e.g. flux, absorption, etc. - ! scat_order is the scattering order for each score. - ! It is to be 0 if the scattering order is 0, or if the score is not a - ! scattering response. - integer :: n_score_bins = 0 - integer, allocatable :: score_bins(:) - integer, allocatable :: moment_order(:) - integer :: n_user_score_bins = 0 - - ! Results for each bin -- the first dimension of the array is for scores - ! (e.g. flux, total reaction rate, fission reaction rate, etc.) and the - ! second dimension of the array is for the combination of filters - ! (e.g. specific cell, specific energy group, etc.) - - integer :: total_filter_bins - integer :: total_score_bins - real(C_DOUBLE), allocatable :: results(:,:,:) - - ! reset property - allows a tally to be reset after every batch - logical :: reset = .false. - - ! Number of realizations of tally random variables - integer :: n_realizations = 0 - - ! Tally precision triggers - integer :: n_triggers = 0 ! # of triggers - type(TriggerObject), allocatable :: triggers(:) ! Array of triggers - - ! Index for the TallyDerivative for differential tallies. - integer :: deriv = NONE - - contains - procedure :: write_results_hdf5 - procedure :: read_results_hdf5 - end type TallyObject - -contains - - subroutine write_results_hdf5(this, group_id) - class(TallyObject), intent(in) :: this - integer(HID_T), intent(in) :: group_id - - integer :: hdf5_err - integer(HID_T) :: dset, dspace - integer(HID_T) :: memspace - integer(HSIZE_T) :: dims(3) - integer(HSIZE_T) :: dims_slab(3) - integer(HSIZE_T) :: offset(3) = [1,0,0] - - ! Create file dataspace - dims_slab(:) = shape(this % results) - dims_slab(1) = 2 - call h5screate_simple_f(3, dims_slab, dspace, hdf5_err) - - ! Create memory dataspace that contains only SUM and SUM_SQ values - dims(:) = shape(this % results) - call h5screate_simple_f(3, dims, memspace, hdf5_err) - call h5sselect_hyperslab_f(memspace, H5S_SELECT_SET_F, offset, dims_slab, & - hdf5_err) - - ! Create and write to dataset - call h5dcreate_f(group_id, "results", H5T_NATIVE_DOUBLE, dspace, dset, & - hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, this % results, dims_slab, & - hdf5_err, mem_space_id=memspace) - - ! Close identifiers - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(memspace, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_results_hdf5 - - subroutine read_results_hdf5(this, group_id) - class(TallyObject), intent(inout) :: this - integer(HID_T), intent(in) :: group_id - - integer :: hdf5_err - integer(HID_T) :: dset, dspace - integer(HID_T) :: memspace - integer(HSIZE_T) :: dims(3) - integer(HSIZE_T) :: dims_slab(3) - integer(HSIZE_T) :: offset(3) = [1,0,0] - - ! Create file dataspace - dims_slab(:) = shape(this % results) - dims_slab(1) = 2 - call h5screate_simple_f(3, dims_slab, dspace, hdf5_err) - - ! Create memory dataspace that contains only SUM and SUM_SQ values - dims(:) = shape(this % results) - call h5screate_simple_f(3, dims, memspace, hdf5_err) - call h5sselect_hyperslab_f(memspace, H5S_SELECT_SET_F, offset, dims_slab, & - hdf5_err) - - ! Create and write to dataset - call h5dopen_f(group_id, "results", dset, hdf5_err) - call h5dread_f(dset, H5T_NATIVE_DOUBLE, this % results, dims_slab, & - hdf5_err, mem_space_id=memspace) - - ! Close identifiers - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(memspace, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine read_results_hdf5 - -end module tally_header diff --git a/src/tally_initialize.F90 b/src/tally_initialize.F90 deleted file mode 100644 index 46c028364..000000000 --- a/src/tally_initialize.F90 +++ /dev/null @@ -1,127 +0,0 @@ -module tally_initialize - - use constants - use global - use tally_header, only: TallyObject - - implicit none - private - public :: configure_tallies - public :: add_tallies - -contains - -!=============================================================================== -! CONFIGURE_TALLIES initializes several data structures related to tallies. This -! is called after the basic tally data has already been read from the -! tallies.xml file. -!=============================================================================== - - subroutine configure_tallies() - - ! Allocate global tallies - allocate(global_tallies(3, N_GLOBAL_TALLIES)) - global_tallies(:,:) = ZERO - - call setup_tally_arrays() - - end subroutine configure_tallies - -!=============================================================================== -! SETUP_TALLY_ARRAYS allocates and populates several member arrays of the -! TallyObject derived type, including stride, filter_matches, and results. -!=============================================================================== - - subroutine setup_tally_arrays() - - integer :: i ! loop index for tallies - integer :: j ! loop index for filters - integer :: n ! temporary stride - integer :: i_filt ! filter index - type(TallyObject), pointer :: t - - TALLY_LOOP: do i = 1, n_tallies - ! Get pointer to tally - t => tallies(i) - - ! Allocate stride - allocate(t % stride(size(t % filter))) - - ! The filters are traversed in opposite order so that the last filter has - ! the shortest stride in memory and the first filter has the largest - ! stride - n = 1 - STRIDE: do j = size(t % filter), 1, -1 - i_filt = t % filter(j) - t % stride(j) = n - n = n * filters(i_filt) % obj % n_bins - end do STRIDE - - ! Set total number of filter and scoring bins - t % total_filter_bins = n - t % total_score_bins = t % n_score_bins * t % n_nuclide_bins - - ! Allocate results array - allocate(t % results(3, t % total_score_bins, t % total_filter_bins)) - t % results(:,:,:) = ZERO - - end do TALLY_LOOP - - ! Allocate array for matching filter bins -!$omp parallel - allocate(filter_matches(n_filters)) -!$omp end parallel - - end subroutine setup_tally_arrays - -!=============================================================================== -! ADD_TALLIES extends the tallies array with a new group of tallies and assigns -! pointers to each group. This is called once for user tallies, once for CMFD -! tallies, etc. -!=============================================================================== - - subroutine add_tallies(tally_group, n) - - character(*), intent(in) :: tally_group ! name of tally group - integer, intent(in) :: n ! number of tallies to add - - type(TallyObject), allocatable :: temp(:) ! temporary tallies array - - if (n_tallies == 0) then - ! Allocate tallies array - allocate(tallies(n)) - else - ! Allocate tallies array with increased size - allocate(temp(n_tallies + n)) - - ! Copy original tallies to temporary array - temp(1:n_tallies) = tallies - - ! Move allocation from temporary array - call move_alloc(FROM=temp, TO=tallies) - - end if - - ! Set index for ths tally group - select case(tally_group) - case ("user") - i_user_tallies = n_tallies - case ("cmfd") - i_cmfd_tallies = n_tallies - end select - - ! Set n_tallies - n_tallies = size(tallies) - - ! Reassign pointers for each group -- after the call to move_alloc, any - ! pointers that were associated with tallies before become unassociated - if (i_user_tallies >= 0) then - user_tallies => tallies(i_user_tallies+1 : i_user_tallies+n_user_tallies) - end if - if (i_cmfd_tallies >= 0) then - cmfd_tallies => tallies(i_cmfd_tallies+1 : i_cmfd_tallies+n_cmfd_tallies) - end if - - end subroutine add_tallies - -end module tally_initialize diff --git a/src/timer_header.F90 b/src/timer_header.F90 index 6f9785d56..a147a551d 100644 --- a/src/timer_header.F90 +++ b/src/timer_header.F90 @@ -22,6 +22,22 @@ module timer_header procedure :: reset => timer_reset end type Timer + ! ============================================================================ + ! TIMING VARIABLES + + type(Timer) :: time_total ! timer for total run + type(Timer) :: time_initialize ! timer for initialization + type(Timer) :: time_read_xs ! timer for reading cross sections + type(Timer) :: time_unionize ! timer for material xs-energy grid union + type(Timer) :: time_bank ! timer for fission bank synchronization + type(Timer) :: time_bank_sample ! timer for fission bank sampling + type(Timer) :: time_bank_sendrecv ! timer for fission bank SEND/RECV + type(Timer) :: time_tallies ! timer for accumulate tallies + type(Timer) :: time_inactive ! timer for inactive batches + type(Timer) :: time_active ! timer for active batches + type(Timer) :: time_transport ! timer for transport only + type(Timer) :: time_finalize ! timer for finalization + contains !=============================================================================== diff --git a/src/track_output.F90 b/src/track_output.F90 index bdef9579c..244bf182e 100644 --- a/src/track_output.F90 +++ b/src/track_output.F90 @@ -5,13 +5,15 @@ module track_output - use global + use hdf5 + + use constants use hdf5_interface use particle_header, only: Particle + use settings, only: path_output + use simulation_header use string, only: to_str - use hdf5 - implicit none private diff --git a/src/tracking.F90 b/src/tracking.F90 index c4baa2c01..1a00e5bb3 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -1,21 +1,26 @@ module tracking - use constants, only: MODE_EIGENVALUE + use constants use cross_section, only: calculate_xs use error, only: fatal_error, warning + use geometry_header, only: cells use geometry, only: find_cell, distance_to_boundary, cross_surface, & cross_lattice, check_cell_overlap - use global use output, only: write_message use message_passing + use mgxs_header + use nuclide_header use particle_header, only: LocalCoord, Particle use physics, only: collision use physics_mg, only: collision_mg use random_lcg, only: prn, prn_set_stream + use settings + use simulation_header use string, only: to_str + use tally_header use tally, only: score_analog_tally, score_tracklength_tally, & score_collision_tally, score_surface_current, & - score_track_derivative, & + score_track_derivative, score_surface_tally, & score_collision_derivative, zero_flux_derivs use track_output, only: initialize_particle_track, write_particle_track, & add_particle_track, finalize_particle_track @@ -36,7 +41,6 @@ contains integer :: next_level ! next coordinate level to check integer :: surface_crossed ! surface which particle is on integer :: lattice_translation(3) ! in-lattice translation vector - integer :: last_cell ! most recent cell particle was in integer :: n_event ! number of collisions/crossings real(8) :: d_boundary ! distance to nearest boundary real(8) :: d_collision ! sampled distance to collision @@ -69,7 +73,6 @@ contains if (active_tallies % size() > 0) call zero_flux_derivs() EVENT_LOOP: do - ! Set the random number stream if (p % type == NEUTRON) then call prn_set_stream(STREAM_TRACKING) @@ -77,6 +80,12 @@ contains call prn_set_stream(STREAM_PHOTON) end if + ! Store pre-collision particle properties + p % last_wgt = p % wgt + p % last_E = p % E + p % last_uvw = p % coord(1) % uvw + p % last_xyz = p % coord(1) % xyz + ! If the cell hasn't been determined based on the particle's location, ! initiate a search for the current cell. This generally happens at the ! beginning of the history and again for any secondary particles @@ -115,6 +124,10 @@ contains material_xs % absorption = ZERO material_xs % nu_fission = ZERO end if + + ! Finally, update the particle group while we have already checked for + ! if multi-group + p % last_g = p % g end if ! Find the distance to the nearest boundary @@ -157,7 +170,13 @@ contains ! PARTICLE CROSSES SURFACE if (next_level > 0) p % n_coord = next_level - last_cell = p % coord(p % n_coord) % cell + + ! Saving previous cell data + do j = 1, p % n_coord + p % last_cell(j) = p % coord(j) % cell + end do + p % last_n_coord = p % n_coord + p % coord(p % n_coord) % cell = NONE if (any(lattice_translation /= 0)) then ! Particle crosses lattice boundary @@ -167,9 +186,12 @@ contains else ! Particle crosses surface p % surface = surface_crossed - call cross_surface(p, last_cell) + + call cross_surface(p) p % event = EVENT_SURFACE end if + ! Score cell to cell partial currents + if(active_surface_tallies % size() > 0) call score_surface_tally(p) else ! ==================================================================== ! PARTICLE HAS COLLISION @@ -233,9 +255,6 @@ contains if (active_tallies % size() > 0) call score_collision_derivative(p) end if - ! Save coordinates for tallying purposes - p % last_xyz = p % coord(1) % xyz - ! If particle has too many events, display warning and kill it n_event = n_event + 1 if (n_event == MAX_EVENTS) then diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index 3659293b3..48f93f0e6 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -9,14 +9,18 @@ module volume_calc use constants use geometry, only: find_cell - use global + use geometry_header, only: universes, cells use hdf5_interface, only: file_create, file_close, write_attribute, & create_group, close_group, write_dataset use output, only: write_message, header, time_stamp + use material_header, only: materials use message_passing + use nuclide_header, only: nuclides use particle_header, only: Particle use random_lcg, only: prn, prn_set_stream, set_particle_seed + use settings, only: path_output use stl_vector, only: VectorInt, VectorReal + use string, only: to_str use timer_header, only: Timer use volume_header @@ -132,6 +136,7 @@ contains integer :: min_samples ! minimum number of samples per process integer :: remainder ! leftover samples from uneven divide #ifdef MPI + integer :: mpi_err ! MPI error code integer :: m ! index over materials integer :: n ! number of materials integer, allocatable :: data(:) ! array used to send number of hits diff --git a/src/volume_header.F90 b/src/volume_header.F90 index a01b664df..59a579a5f 100644 --- a/src/volume_header.F90 +++ b/src/volume_header.F90 @@ -16,6 +16,8 @@ module volume_header procedure :: from_xml => volume_from_xml end type VolumeCalculation + type(VolumeCalculation), allocatable :: volume_calcs(:) + contains subroutine volume_from_xml(this, node_vol) @@ -56,4 +58,12 @@ contains call get_node_value(node_vol, "samples", this % samples) end subroutine volume_from_xml +!=============================================================================== +! FREE_MEMORY_VOLUME deallocates global arrays defined in this module +!=============================================================================== + + subroutine free_memory_volume() + if (allocated(volume_calcs)) deallocate(volume_calcs) + end subroutine free_memory_volume + end module volume_header diff --git a/tests/run_tests.py b/tests/run_tests.py index cd6c02607..6e6b886a5 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -10,43 +10,43 @@ import glob import socket from subprocess import call, check_output from collections import OrderedDict -from optparse import OptionParser +from argparse import ArgumentParser # Command line parsing -parser = OptionParser() -parser.add_option('-j', '--parallel', dest='n_procs', default='1', - help="Number of parallel jobs.") -parser.add_option('-R', '--tests-regex', dest='regex_tests', - help="Run tests matching regular expression. \ - Test names are the directories present in tests folder.\ - This uses standard regex syntax to select tests.") -parser.add_option('-C', '--build-config', dest='build_config', - help="Build configurations matching regular expression. \ - Specific build configurations can be printed out with \ - optional argument -p, --print. This uses standard \ - regex syntax to select build configurations.") -parser.add_option('-l', '--list', action="store_true", - dest="list_build_configs", default=False, - help="List out build configurations.") -parser.add_option("-p", "--project", dest="project", default="", - help="project name for build") -parser.add_option("-D", "--dashboard", dest="dash", - help="Dash name -- Experimental, Nightly, Continuous") -parser.add_option("-u", "--update", action="store_true", dest="update", - help="Allow CTest to update repo. (WARNING: may overwrite\ - changes that were not pushed.") -parser.add_option("-s", "--script", action="store_true", dest="script", - help="Activate CTest scripting mode for coverage, valgrind\ - and dashboard capability.") -(options, args) = parser.parse_args() +parser = ArgumentParser() +parser.add_argument('-j', '--parallel', dest='n_procs', default='1', + help="Number of parallel jobs.") +parser.add_argument('-R', '--tests-regex', dest='regex_tests', + help="Run tests matching regular expression. \ + Test names are the directories present in tests folder.\ + This uses standard regex syntax to select tests.") +parser.add_argument('-C', '--build-config', dest='build_config', + help="Build configurations matching regular expression. \ + Specific build configurations can be printed out with \ + optional argument -p, --print. This uses standard \ + regex syntax to select build configurations.") +parser.add_argument('-l', '--list', action="store_true", + dest="list_build_configs", default=False, + help="List out build configurations.") +parser.add_argument("-p", "--project", dest="project", default="", + help="project name for build") +parser.add_argument("-D", "--dashboard", dest="dash", + help="Dash name -- Experimental, Nightly, Continuous") +parser.add_argument("-u", "--update", action="store_true", dest="update", + help="Allow CTest to update repo. (WARNING: may overwrite\ + changes that were not pushed.") +parser.add_argument("-s", "--script", action="store_true", dest="script", + help="Activate CTest scripting mode for coverage, valgrind\ + and dashboard capability.") +args = parser.parse_args() # Default compiler paths -FC='gfortran' -CC='gcc' -CXX='g++' -MPI_DIR='/opt/mpich/3.2-gnu' -HDF5_DIR='/opt/hdf5/1.8.16-gnu' -PHDF5_DIR='/opt/phdf5/1.8.16-gnu' +FC = 'gfortran' +CC = 'gcc' +CXX = 'g++' +MPI_DIR = '/opt/mpich/3.2-gnu' +HDF5_DIR = '/opt/hdf5/1.8.16-gnu' +PHDF5_DIR = '/opt/phdf5/1.8.16-gnu' # Script mode for extra capability script_mode = False @@ -115,6 +115,7 @@ endif() # Define test data structure tests = OrderedDict() + def cleanup(path): """Remove generated output files.""" for dirpath, dirnames, filenames in os.walk(path): @@ -173,7 +174,7 @@ class Test(object): # Sets the build name that will show up on the CDash def get_build_name(self): - self.build_name = options.project + '_' + self.name + self.build_name = args.project + '_' + self.name return self.build_name # Sets up build options for various tests. It is used both @@ -211,7 +212,7 @@ class Test(object): os.environ['HDF5_ROOT'] = PHDF5_DIR else: os.environ['HDF5_ROOT'] = HDF5_DIR - rc = call(['ctest', '-S', 'ctestscript.run','-V']) + rc = call(['ctest', '-S', 'ctestscript.run', '-V']) if rc != 0: self.success = False self.msg = 'Failed on ctest script.' @@ -243,12 +244,12 @@ class Test(object): return # Default make string - make_list = ['make','-s'] + make_list = ['make', '-s'] # Check for parallel - if options.n_procs is not None: + if args.n_procs is not None: make_list.append('-j') - make_list.append(options.n_procs) + make_list.append(args.n_procs) # Run make rc = call(make_list) @@ -265,14 +266,14 @@ class Test(object): ctest_list = ['ctest'] # Check for parallel - if options.n_procs is not None: + if args.n_procs is not None: ctest_list.append('-j') - ctest_list.append(options.n_procs) + ctest_list.append(args.n_procs) # Check for subset of tests - if options.regex_tests is not None: + if args.regex_tests is not None: ctest_list.append('-R') - ctest_list.append(options.regex_tests) + ctest_list.append(args.regex_tests) # Run ctests rc = call(ctest_list) @@ -282,7 +283,7 @@ class Test(object): # Simple function to add a test to the global tests dictionary -def add_test(name, debug=False, optimize=False, mpi=False, openmp=False,\ +def add_test(name, debug=False, optimize=False, mpi=False, openmp=False, phdf5=False, valgrind=False, coverage=False): tests.update({name: Test(name, debug, optimize, mpi, openmp, phdf5, valgrind, coverage)}) @@ -308,7 +309,7 @@ add_test('hdf5-debug_valgrind', debug=True, valgrind=True) add_test('hdf5-debug_coverage', debug=True, coverage=True) # Check to see if we should just print build configuration information to user -if options.list_build_configs: +if args.list_build_configs: for key in tests: print('Configuration Name: {0}'.format(key)) print(' Debug Flags:..........{0}'.format(tests[key].debug)) @@ -320,10 +321,10 @@ if options.list_build_configs: exit() # Delete items of dictionary that don't match regular expression -if options.build_config is not None: +if args.build_config is not None: to_delete = [] for key in tests: - if not re.search(options.build_config, key): + if not re.search(args.build_config, key): to_delete.append(key) for key in to_delete: del tests[key] @@ -333,27 +334,21 @@ if options.build_config is not None: # Experimental, Nightly, Continuous. On the CDash end, these can be # reorganized into groups when a hostname, dashboard and build name # are matched. -if options.dash is None: +if args.dash is None: dash = 'Experimental' submit = '' else: - dash = options.dash + dash = args.dash submit = 'ctest_submit()' # Check for update command, which will run git fetch/merge and will delete # any changes to repo that were not pushed to remote origin -if options.update: - update = 'ctest_update()' -else: - update = '' +update = 'ctest_update()' if args.update else '' # Check for CTest scipts mode # Sets up whether we should use just the basic ctest command or use # CTest scripting to perform tests. -if not options.dash is None or options.script: - script_mode = True -else: - script_mode = False +script_mode = (args.dash is not None or args.script) # Setup CTest script vars. Not used in non-script mode pwd = os.getcwd() @@ -364,17 +359,17 @@ ctest_vars = { 'dashboard': dash, 'submit': submit, 'update': update, - 'n_procs': options.n_procs + 'n_procs': args.n_procs } # Check project name -subprop = """set_property(GLOBAL PROPERTY SubProject {0})""" -if options.project == "" : - ctest_vars.update({'subproject':''}) -elif options.project == 'develop': - ctest_vars.update({'subproject':''}) +subprop = "set_property(GLOBAL PROPERTY SubProject {0})" +if args.project == "": + ctest_vars.update({'subproject': ''}) +elif args.project == 'develop': + ctest_vars.update({'subproject': ''}) else: - ctest_vars.update({'subproject':subprop.format(options.project)}) + ctest_vars.update({'subproject': subprop.format(args.project)}) # Set up default valgrind tests (subset of all tests) # Currently takes too long to run all the tests with valgrind @@ -396,8 +391,8 @@ if not script_mode: for key in to_delete: del tests[key] -# Check if tests empty -if len(list(tests.keys())) == 0: +# Check if tests is empty +if not tests: print('No tests to run.') exit() @@ -416,16 +411,16 @@ for key in iter(tests): # Verify fortran compiler exists if which(test.fc) is None: - self.msg = 'Compiler not found: {0}'.format(test.fc) - self.success = False + test.msg = 'Compiler not found: {0}'.format(test.fc) + test.success = False continue # Verify valgrind command exists if test.valgrind: valgrind_cmd = which('valgrind') if valgrind_cmd is None: - self.msg = 'No valgrind executable found.' - self.success = False + test.msg = 'No valgrind executable found.' + test.success = False continue else: valgrind_cmd = '' @@ -433,8 +428,8 @@ for key in iter(tests): # Verify gcov/lcov exist if test.coverage: if which('gcov') is None: - self.msg = 'No {} executable found.'.format(exe) - self.success = False + test.msg = 'No {} executable found.'.format(exe) + test.success = False continue # Set test specific CTest script vars. Not used in non-script mode @@ -447,16 +442,16 @@ for key in iter(tests): # Check for user custom tests # INCLUDE is a CTest command that allows for a subset # of tests to be executed. Only used in script mode. - if options.regex_tests is None: - ctest_vars.update({'tests' : ''}) + if args.regex_tests is None: + ctest_vars.update({'tests': ''}) # No user tests, use default valgrind tests if test.valgrind: - ctest_vars.update({'tests' : 'INCLUDE {0}'. + ctest_vars.update({'tests': 'INCLUDE {0}'. format(valgrind_default_tests)}) else: - ctest_vars.update({'tests' : 'INCLUDE {0}'. - format(options.regex_tests)}) + ctest_vars.update({'tests': 'INCLUDE {0}'. + format(args.regex_tests)}) # Main part of code that does the ctest execution. # It is broken up by two modes, script and non-script diff --git a/tests/test_cmfd_feed/settings.xml b/tests/test_cmfd_feed/settings.xml index 3e8b77b39..742aa2233 100644 --- a/tests/test_cmfd_feed/settings.xml +++ b/tests/test_cmfd_feed/settings.xml @@ -16,13 +16,14 @@ - + 10 1 1 -10.0 -1.0 -1.0 10.0 1.0 1.0 - + + 10 - - true + + true diff --git a/tests/test_cmfd_nofeed/settings.xml b/tests/test_cmfd_nofeed/settings.xml index 3e8b77b39..b56acb4b7 100644 --- a/tests/test_cmfd_nofeed/settings.xml +++ b/tests/test_cmfd_nofeed/settings.xml @@ -16,11 +16,12 @@ - + 10 1 1 -10.0 -1.0 -1.0 10.0 1.0 1.0 - + + 10 true diff --git a/tests/test_enrichment/test_enrichment.py b/tests/test_enrichment/test_enrichment.py index 48416885b..fa65d6dce 100644 --- a/tests/test_enrichment/test_enrichment.py +++ b/tests/test_enrichment/test_enrichment.py @@ -21,7 +21,7 @@ if __name__ == '__main__': sum_densities = 0. for nuc in densities.keys(): - assert nuc in ('U234', 'U235', 'U238') + assert nuc in ('U234', 'U235', 'U236', 'U238') sum_densities += densities[nuc][1] # Compute the weight percent U235 @@ -30,4 +30,8 @@ if __name__ == '__main__': # Compute the ratio of U234/U235 u234_to_u235 = densities['U234'][1] / densities['U235'][1] - assert np.isclose(u234_to_u235, 0.008, rtol=1.e-8) + assert np.isclose(u234_to_u235, 0.0089, rtol=1.e-8) + + # Compute the ratio of U236/U235 + u236_to_u235 = densities['U236'][1] / densities['U235'][1] + assert np.isclose(u236_to_u235, 0.0046, rtol=1.e-8) diff --git a/tests/test_entropy/settings.xml b/tests/test_entropy/settings.xml index df6a851ef..493da7223 100644 --- a/tests/test_entropy/settings.xml +++ b/tests/test_entropy/settings.xml @@ -12,10 +12,12 @@ - + 10 10 10 -10. -10. -10. 10. 10. 10. - + + + 1 diff --git a/tests/test_filter_energyfun/results_true.dat b/tests/test_filter_energyfun/results_true.dat index 4c9f5ec0a..110038edf 100644 --- a/tests/test_filter_energyfun/results_true.dat +++ b/tests/test_filter_energyfun/results_true.dat @@ -1,2 +1,2 @@ - energyfunction nuclide score mean std. dev. -0 02180f5f310ee4 Am241 (n,gamma) 1.00e-01 9.97e-03 + energyfunction nuclide score mean std. dev. +0 02180f5f310ee4 Am241 ((n,gamma) / (n,gamma)) 1.00e-01 9.97e-03 diff --git a/tests/test_plot/settings.xml b/tests/test_plot/settings.xml index 197b9c709..adf256d2d 100644 --- a/tests/test_plot/settings.xml +++ b/tests/test_plot/settings.xml @@ -3,10 +3,11 @@ plot - + 5 4 3 -10 -10 -10 10 10 10 - + + 1 diff --git a/tests/test_surface_tally/inputs_true.dat b/tests/test_surface_tally/inputs_true.dat new file mode 100644 index 000000000..e66d44273 --- /dev/null +++ b/tests/test_surface_tally/inputs_true.dat @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 10 + 0 + + + -0.62992 -0.62992 -1 0.62992 0.62992 1 + + + + + + + 13 + + + 14 + + + 0.0 4000000.0 20000000.0 + + + 0.0 0.785398163397 3.14159265359 + + + 0.0 0.785398163397 3.14159265359 + + + 1 + + + 14 + + + 13 + + + 2 + + + 3 + + + 5 6 1 2 3 + current + + + 5 4 1 2 3 + current + + + 7 8 1 2 3 + current + + + 7 4 1 2 3 + current + + + 4 1 2 3 + current + + + 10 1 2 3 + current + + + 11 1 + current + + + 11 1 + current + + diff --git a/tests/test_surface_tally/results_true.dat b/tests/test_surface_tally/results_true.dat new file mode 100644 index 000000000..51e2d7dc0 --- /dev/null +++ b/tests/test_surface_tally/results_true.dat @@ -0,0 +1,53 @@ +mean,std. dev. +2.1400000e-02,1.1850926e-03 +7.4700000e-02,2.5649345e-03 +1.6090000e-01,4.4433471e-03 +6.4810000e-01,8.6041979e-03 +2.0000000e-03,3.9440532e-04 +4.2000000e-03,8.2731158e-04 +1.1200000e-02,1.0934146e-03 +4.6400000e-02,2.2715633e-03 +2.1400000e-02,1.1850926e-03 +7.4700000e-02,2.5649345e-03 +1.6090000e-01,4.4433471e-03 +6.4810000e-01,8.6041979e-03 +2.0000000e-03,3.9440532e-04 +4.2000000e-03,8.2731158e-04 +1.1200000e-02,1.0934146e-03 +4.6400000e-02,2.2715633e-03 +5.5000000e-03,5.6273143e-04 +4.4300000e-02,1.8502252e-03 +4.5600000e-02,1.2220202e-03 +4.1780000e-01,6.4460151e-03 +0.0000000e+00,0.0000000e+00 +1.4000000e-03,3.3993463e-04 +0.0000000e+00,0.0000000e+00 +1.6600000e-02,1.0561986e-03 +-5.5000000e-03,5.6273143e-04 +-4.4300000e-02,1.8502252e-03 +-4.5600000e-02,1.2220202e-03 +-4.1780000e-01,6.4460151e-03 +0.0000000e+00,0.0000000e+00 +-1.4000000e-03,3.3993463e-04 +0.0000000e+00,0.0000000e+00 +-1.6600000e-02,1.0561986e-03 +1.5900000e-02,1.1200198e-03 +3.0400000e-02,3.2734623e-03 +1.1530000e-01,3.8327536e-03 +2.3030000e-01,7.2250336e-03 +2.0000000e-03,3.9440532e-04 +2.8000000e-03,7.8598841e-04 +1.1200000e-02,1.0934146e-03 +2.9800000e-02,2.5811281e-03 +0.0000000e+00,0.0000000e+00 +-3.0000000e-02,1.3743685e-03 +0.0000000e+00,0.0000000e+00 +-3.4810000e-01,5.9711343e-03 +0.0000000e+00,0.0000000e+00 +-3.1000000e-03,6.9041051e-04 +0.0000000e+00,0.0000000e+00 +-2.1900000e-02,2.4241837e-03 +-9.0250000e-01,2.0941320e-02 +-3.5200000e-02,1.2806248e-03 +-9.0250000e-01,2.0941320e-02 +-3.5200000e-02,1.2806248e-03 diff --git a/tests/test_surface_tally/test_surface_tally.py b/tests/test_surface_tally/test_surface_tally.py new file mode 100644 index 000000000..ae525a683 --- /dev/null +++ b/tests/test_surface_tally/test_surface_tally.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.pardir) +from testing_harness import PyAPITestHarness +import numpy as np +import openmc +import pandas as pd + + +class SurfaceTallyTestHarness(PyAPITestHarness): + def _build_inputs(self): + # Instantiate some Materials and register the appropriate Nuclides + uo2 = openmc.Material(name='UO2 fuel at 2.4% wt enrichment') + uo2.set_density('g/cc', 10.0) + uo2.add_nuclide('U238', 1.0) + uo2.add_nuclide('U235', 0.02) + uo2.add_nuclide('O16', 2.0) + + borated_water = openmc.Material(name='Borated water') + borated_water.set_density('g/cm3', 1) + borated_water.add_nuclide('B10', 10e-5) + borated_water.add_nuclide('H1', 2.0) + borated_water.add_nuclide('O16', 1.0) + + # Instantiate a Materials collection and export to XML + materials_file = openmc.Materials([uo2, borated_water]) + materials_file.export_to_xml() + + # Instantiate ZCylinder surfaces + fuel_or = openmc.ZCylinder(surface_id=1, x0=0, y0=0, R=1, \ + name='Fuel OR') + left = openmc.XPlane(surface_id=2, x0=-2, name='left') + right = openmc.XPlane(surface_id=3, x0=2, name='right') + bottom = openmc.YPlane(y0=-2, name='bottom') + top = openmc.YPlane(y0=2, name='top') + + left.boundary_type = 'vacuum' + right.boundary_type = 'reflective' + top.boundary_type = 'reflective' + bottom.boundary_type = 'reflective' + + # Instantiate Cells + fuel = openmc.Cell(name='fuel') + water = openmc.Cell(name='water') + + # Use surface half-spaces to define regions + fuel.region = -fuel_or + water.region = +fuel_or & -right & +bottom & -top + + # Register Materials with Cells + fuel.fill = uo2 + water.fill = borated_water + + # Instantiate pin cell Universe + pin_cell = openmc.Universe(name='pin cell') + pin_cell.add_cells([fuel, water]) + + # Instantiate root Cell and Universe + root_cell = openmc.Cell(name='root cell') + root_cell.region = +left & -right & +bottom & -top + root_cell.fill = pin_cell + root_univ = openmc.Universe(universe_id=0, name='root universe') + root_univ.add_cell(root_cell) + + # Instantiate a Geometry, register the root Universe + geometry = openmc.Geometry(root_univ) + geometry.export_to_xml() + + # Instantiate a Settings object, set all runtime parameters + settings_file = openmc.Settings() + settings_file.batches = 10 + settings_file.inactive = 0 + settings_file.particles = 1000 + #settings_file.output = {'tallies': True} + + # Create an initial uniform spatial source distribution + bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] + uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:],\ + only_fissionable=True) + settings_file.source = openmc.source.Source(space=uniform_dist) + settings_file.export_to_xml() + + # Tallies file + tallies_file = openmc.Tallies() + + # Create partial current tallies from fuel to water + # Filters + two_groups = [0., 4e6, 20e6] + energy_filter = openmc.EnergyFilter(two_groups) + polar_filter = openmc.PolarFilter([0, np.pi / 4, np.pi]) + azimuthal_filter = openmc.AzimuthalFilter([0, np.pi / 4, np.pi]) + surface_filter = openmc.SurfaceFilter([1]) + cell_from_filter = openmc.CellFromFilter(fuel) + cell_filter = openmc.CellFilter(water) + + # Use Cell to cell filters for partial current + cell_to_cell_tally = openmc.Tally(name=str('fuel_to_water_1')) + cell_to_cell_tally.filters = [cell_from_filter, cell_filter, \ + energy_filter, polar_filter, azimuthal_filter] + cell_to_cell_tally.scores = ['current'] + tallies_file.append(cell_to_cell_tally) + + # Use a Cell from + surface filters for partial current + cell_to_cell_tally = openmc.Tally(name=str('fuel_to_water_2')) + cell_to_cell_tally.filters = [cell_from_filter, surface_filter, \ + energy_filter, polar_filter, azimuthal_filter] + cell_to_cell_tally.scores = ['current'] + tallies_file.append(cell_to_cell_tally) + + # Create partial current tallies from water to fuel + # Filters + cell_from_filter = openmc.CellFromFilter(water) + cell_filter = openmc.CellFilter(fuel) + + # Cell to cell filters for partial current + cell_to_cell_tally = openmc.Tally(name=str('water_to_fuel_1')) + cell_to_cell_tally.filters = [cell_from_filter, cell_filter, \ + energy_filter, polar_filter, azimuthal_filter] + cell_to_cell_tally.scores = ['current'] + tallies_file.append(cell_to_cell_tally) + + # Cell from + surface filters for partial current + cell_to_cell_tally = openmc.Tally(name=str('water_to_fuel_2')) + cell_to_cell_tally.filters = [cell_from_filter, surface_filter, \ + energy_filter, polar_filter, azimuthal_filter] + cell_to_cell_tally.scores = ['current'] + tallies_file.append(cell_to_cell_tally) + + # Create a net current tally on inner surface using a surface filter + surface_filter = openmc.SurfaceFilter([1]) + surf_tally1 = openmc.Tally(name='net_cylinder') + surf_tally1.filters = [surface_filter, energy_filter, polar_filter, \ + azimuthal_filter] + surf_tally1.scores = ['current'] + tallies_file.append(surf_tally1) + + # Create a net current tally on left surface using a surface filter + # This surface has a vacuum boundary condition, so leakage is tallied + surface_filter = openmc.SurfaceFilter([2]) + surf_tally2 = openmc.Tally(name='leakage_left') + surf_tally2.filters = [surface_filter, energy_filter, polar_filter, \ + azimuthal_filter] + surf_tally2.scores = ['current'] + tallies_file.append(surf_tally2) + + # Create a net current tally on right surface using a surface filter + # This surface has a reflective boundary condition, but the zero + # net current is not picked up because particles are only tallied once + surface_filter = openmc.SurfaceFilter([3]) + surf_tally3 = openmc.Tally(name='net_right') + surf_tally3.filters = [surface_filter, energy_filter] + surf_tally3.scores = ['current'] + tallies_file.append(surf_tally3) + + surface_filter = openmc.SurfaceFilter([3]) + surf_tally3 = openmc.Tally(name='net_right') + surf_tally3.filters = [surface_filter, energy_filter] + surf_tally3.scores = ['current'] + tallies_file.append(surf_tally3) + + tallies_file.export_to_xml() + + def _get_results(self): + """Digest info in the statepoint and return as a string.""" + # Read the statepoint file. + sp = openmc.StatePoint(self._sp_name) + + # Extract the tally data as a Pandas DataFrame. + df = pd.DataFrame() + for t in sp.tallies.values(): + df = df.append(t.get_pandas_dataframe(), ignore_index=True) + + # Extract the relevant data as a CSV string. + cols = ('mean', 'std. dev.') + return df.to_csv(None, columns=cols, index=False, float_format='%.7e') + return outstr + + +if __name__ == '__main__': + harness = SurfaceTallyTestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_element_wo/test_element_wo.py b/tests/unit_tests/test_element_wo.py similarity index 92% rename from tests/test_element_wo/test_element_wo.py rename to tests/unit_tests/test_element_wo.py index e2a5726f1..7b9487f73 100644 --- a/tests/test_element_wo/test_element_wo.py +++ b/tests/unit_tests/test_element_wo.py @@ -5,13 +5,11 @@ import sys import numpy as np -sys.path.insert(0, os.pardir) -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from openmc import Material from openmc.data import NATURAL_ABUNDANCE, atomic_mass -if __name__ == '__main__': +def test_element_wo(): # This test doesn't require an OpenMC run. We just need to make sure the # element.expand() method expands elements with the proper nuclide # compositions.
01000231.000000e-021.080060e-01H1
11000231.080060e-011.166529e+00H1
21000231.166529e+001.259921e+01H1
31000231.259921e+011.360790e+02H1
41000231.360790e+021.469734e+03H1
51000231.469734e+031.587401e+04H1
61000231.587401e+041.714488e+05H1
71000231.714488e+051.851749e+06H1
81000231.851749e+062.000000e+07H1