diff --git a/.gitignore b/.gitignore index 7eb8f286ac..3832322966 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ scripts/G4EMLOW*/ # Images *.ppm *.voxel +*.vti # PyCharm project configuration files .idea diff --git a/CMakeLists.txt b/CMakeLists.txt index 9facdf7ba5..fded5876c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,6 @@ option(profile "Compile with profiling flags" OFF) option(debug "Compile with debug flags" OFF) option(optimize "Turn on all compiler optimization flags" OFF) option(coverage "Compile with coverage analysis flags" OFF) -option(mpif08 "Use Fortran 2008 MPI interface" OFF) option(dagmc "Enable support for DAGMC (CAD) geometry" OFF) # Maximum number of nested coordinates levels @@ -132,7 +131,7 @@ elseif(CMAKE_Fortran_COMPILER_ID STREQUAL Intel) # Intel compiler options list(APPEND f90flags -fpp -std08 -assume byterecl -traceback) if(debug) - list(APPEND f90flags -g -warn -ftrapuv -fp-stack-check + list(APPEND f90flags -g -warn -fp-stack-check "-check all" -fpe0 -O0) list(APPEND ldflags -g) endif() @@ -345,10 +344,6 @@ add_library(libopenmc SHARED src/photon_physics.F90 src/physics_common.F90 src/physics.F90 - src/physics_mg.F90 - src/plot.F90 - src/plot_header.F90 - src/progress_header.F90 src/pugixml/pugixml_f.F90 src/random_lcg.F90 src/reaction_header.F90 @@ -375,26 +370,14 @@ add_library(libopenmc SHARED 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_legendre.F90 - src/tallies/tally_filter_material.F90 src/tallies/tally_filter_mesh.F90 src/tallies/tally_filter_meshsurface.F90 - src/tallies/tally_filter_mu.F90 src/tallies/tally_filter_particle.F90 - src/tallies/tally_filter_polar.F90 src/tallies/tally_filter_sph_harm.F90 - src/tallies/tally_filter_sptl_legendre.F90 - src/tallies/tally_filter_surface.F90 - src/tallies/tally_filter_universe.F90 - src/tallies/tally_filter_zernike.F90 src/tallies/tally_header.F90 src/tallies/trigger.F90 src/tallies/trigger_header.F90 @@ -408,6 +391,7 @@ add_library(libopenmc SHARED src/distribution_spatial.cpp src/eigenvalue.cpp src/endf.cpp + src/error.cpp src/initialize.cpp src/finalize.cpp src/geometry.cpp @@ -423,8 +407,11 @@ add_library(libopenmc SHARED src/nuclide.cpp src/output.cpp src/particle.cpp + src/physics_common.cpp + src/physics_mg.cpp src/plot.cpp src/position.cpp + src/progress_bar.cpp src/pugixml/pugixml_c.cpp src/random_lcg.cpp src/reaction.cpp @@ -441,9 +428,33 @@ add_library(libopenmc SHARED src/string_functions.cpp src/summary.cpp src/surface.cpp + src/tallies/filter.cpp + src/tallies/filter_azimuthal.cpp + src/tallies/filter_cellborn.cpp + src/tallies/filter_cellfrom.cpp + src/tallies/filter_cell.cpp + src/tallies/filter_delayedgroup.cpp + src/tallies/filter_distribcell.cpp + src/tallies/filter_energyfunc.cpp + src/tallies/filter_energy.cpp + src/tallies/filter_legendre.cpp + src/tallies/filter_material.cpp + src/tallies/filter_mesh.cpp + src/tallies/filter_meshsurface.cpp + src/tallies/filter_mu.cpp + src/tallies/filter_particle.cpp + src/tallies/filter_polar.cpp + src/tallies/filter_sph_harm.cpp + src/tallies/filter_sptl_legendre.cpp + src/tallies/filter_surface.cpp + src/tallies/filter_universe.cpp + src/tallies/filter_zernike.cpp + src/tallies/tally.cpp + src/timer.cpp src/thermal.cpp src/xml_interface.cpp - src/xsdata.cpp) + src/xsdata.cpp + src/tallies/tally.cpp) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc @@ -470,9 +481,6 @@ if (HDF5_IS_PARALLEL) endif() if (MPI_ENABLED) target_compile_definitions(libopenmc PUBLIC -DOPENMC_MPI) - if (mpif08) - target_compile_definitions(libopenmc PRIVATE -DOPENMC_MPIF08) - endif() endif() # Set git SHA1 hash as a compile definition diff --git a/docs/source/io_formats/data_wmp.rst b/docs/source/io_formats/data_wmp.rst index 25916c5229..8d7cf6ba04 100644 --- a/docs/source/io_formats/data_wmp.rst +++ b/docs/source/io_formats/data_wmp.rst @@ -4,10 +4,15 @@ Windowed Multipole Library Format ================================= -**/version** (*char[]*) - The format version of the file. The current version is "v1.0" +**/** + +:Attributes: - **filetype** (*char[]*) -- String indicating the type of file + - **version** (*int[2]*) -- Major and minor version of the data + +**//** + +:Datasets: -**/nuclide/** - **broaden_poly** (*int[]*) If 1, Doppler broaden curve fit for window with corresponding index. If 0, do not. diff --git a/docs/source/pythonapi/mgxs.rst b/docs/source/pythonapi/mgxs.rst index 10f46b0216..f890bb469c 100644 --- a/docs/source/pythonapi/mgxs.rst +++ b/docs/source/pythonapi/mgxs.rst @@ -5,6 +5,15 @@ Energy Groups ------------- +Module Variables +++++++++++++++++ + +.. autodata:: openmc.mgxs.GROUP_STRUCTURES + :annotation: + +Classes ++++++++ + .. autosummary:: :toctree: generated :nosignatures: diff --git a/examples/jupyter/mgxs-part-ii.ipynb b/examples/jupyter/mgxs-part-ii.ipynb index c10f55956b..1058b71349 100644 --- a/examples/jupyter/mgxs-part-ii.ipynb +++ b/examples/jupyter/mgxs-part-ii.ipynb @@ -2048,8 +2048,8 @@ "o16 = df[df['nuclide'] == 'O16']['mean']\n", "\n", "# Cast DataFrames as NumPy arrays\n", - "h1 = h1.as_matrix()\n", - "o16 = o16.as_matrix()\n", + "h1 = h1.values\n", + "o16 = o16.values\n", "\n", "# Reshape arrays to 2D matrix for plotting\n", "h1.shape = (fine_groups.num_groups, fine_groups.num_groups)\n", diff --git a/examples/jupyter/nuclear-data.ipynb b/examples/jupyter/nuclear-data.ipynb index 91923faa1d..f1c4c2b2af 100644 --- a/examples/jupyter/nuclear-data.ipynb +++ b/examples/jupyter/nuclear-data.ipynb @@ -1466,7 +1466,7 @@ }, "outputs": [], "source": [ - "url = 'https://github.com/mit-crpg/WMP_Library/releases/download/v1.0/092238.h5'\n", + "url = 'https://github.com/mit-crpg/WMP_Library/releases/download/v1.1/092238.h5'\n", "filename, headers = urllib.request.urlretrieve(url, '092238.h5')" ] }, diff --git a/examples/jupyter/pandas-dataframes.ipynb b/examples/jupyter/pandas-dataframes.ipynb index 72d5d7313b..140a0d6847 100644 --- a/examples/jupyter/pandas-dataframes.ipynb +++ b/examples/jupyter/pandas-dataframes.ipynb @@ -215,9 +215,7 @@ { "cell_type": "code", "execution_count": 9, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# OpenMC simulation parameters\n", @@ -281,18 +279,7 @@ "cell_type": "code", "execution_count": 11, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Run openmc in plotting mode\n", "openmc.plot_geometry(output=False)" @@ -305,7 +292,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdC\nAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAxQTFRF\n////chIS6YCRTb/E6kGE+wAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAPZSURB\nVGje7Zs7buMwEIZ9iey50gyNjQpXKTYudIScgkdQYTfut1idwkdQkQNsYQO2Qj0sPiVK+mlQDmwg\nwIcgg8Cc4fCTSK5W4OeFkM8rHv+2I/rgxPZEPZgR7XtQxKdXYuUXJSUnBQ/9WCgo4vOSJ+WFUvF7\nE08mlia+rn7VcKXP8sRszFX8b2MdX2y6v1Tw6MZUw4H4ojfIjD8mvn/qRL5p4+vvlMqvp2EhR8WB\nzfiz20hXORmP9fi/bM9EeUFvV5H/0yRkeSbiGRfFJErxD9ENdz7Mbhig/h89fvtFdMiI/ePUIXV4\nlXju8K3DKv9NThOZ3q2KmUy6grxFES8rjeyic+FFQav+ncg3fXjH+Ts+/iibztFqOiZuZP/Z3Oaf\nPX40NGgST2r+uvQkXXp6cKvmr+r0e1Eef5um3+JHP3IFF1D/seNZJgaDmvY0Gav1s+2f1fqpIcub\nlfKGt6apotG/NVx3SInWtLX+7Vg/Pv1YqOsnun6JSVdOXT/X7vk75f938QP+8OmSBs0fXtymMhJb\nf8qlPynYmpKCh7OB1fzNalOj1sl0ZAruHLiA+RM73pDe/VjMVP89+aTXwjyc/x5n+u991895/utr\nJTy8/06TXh0r/5JOa2JmYmqi4r/vUm/H4wLmT+z4anhr05X+q6KUXhtzr/9qSff5L5uMT//V/NdU\n4YuBTPa/8P67l/6r44ds+hYuoP5jx9ciy6XTWlibBrmx8V/TdMfjkP+6pOsu/lvM9N90sf7r+f6m\n/65n+S8p/itN15v0UkW3/+48+PRfJX6S9Joo4g+G/1qYG9KroqP/WypcuvyXPf13wH89/hHef7MB\n6R3Cqn55U4rv4kfH3zaSgQuYP7HjVf89tXrbO+hfLdr+Ozv/SP1dgtQ/Ov8C+i/3+q/Zf2D/HWi6\nbjT6rym9I/v/03/b+LHS4cTg/utTsV7/net/Afzz4f0XGX84/2j9xZ4/sePR/of2X7D/o+vPo/sv\n6h9B/Bfxr9j1Hz2eN/hO8/wfff4A848+f/1A/530/I0+/8PvH9D3H9HnT+R49P0b+v4PfP/4E/wX\nfP8Mvf9G37/D/ovuP8SeP7Hj0f0vdP8tqP9O339cyv7p3P1fdP8Z3v9G999j13/seMax8x/o+ZN7\n+O+E8zdP/8XOf8Hnz9Dzb7HnT+x49PxlCp7/BM+fOv13wvnXBfivt2lMvD8TyH/Hnb+Gz3+j589j\nz5/Y8ej9h4D+W7qQmf57efqv239n3T+C7z+h969i13/seMax+3/o/cMcu/8Y2H9n3p+J6r98pv8m\n4fwXuH+M3n+OO3++AX9clR+4PhbRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA2LTA3VDEzOjE4\nOjQ5LTA0OjAwxfC/BgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNi0wN1QxMzoxODo0OS0wNDow\nMLStB7oAAAAASUVORK5CYII=\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8TqQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+IKCxApGTbiG9IAAAPZSURBVGje7Zs7buMwEIZ9iey50gyNjQpXKTYudIScgkdQYTfut1idwkdQkQNsYQO2Qj0sPiVK+mlQDmwgwIcgg8Cc4fCTSK5W4OeFkM8rHv+2I/rgxPZEPZgR7XtQxKdXYuUXJSUnBQ/9WCgo4vOSJ+WFUvF7E08mlia+rn7VcKXP8sRszFX8b2MdX2y6v1Tw6MZUw4H4ojfIjD8mvn/qRL5p4+vvlMqvp2EhR8WBzfiz20hXORmP9fi/bM9EeUFvV5H/0yRkeSbiGRfFJErxD9ENdz7Mbhig/h89fvtFdMiI/ePUIXV4lXju8K3DKv9NThOZ3q2KmUy6grxFES8rjeyic+FFQav+ncg3fXjH+Ts+/iibztFqOiZuZP/Z3OafPX40NGgST2r+uvQkXXp6cKvmr+r0e1Eef5um3+JHP3IFF1D/seNZJgaDmvY0Gav1s+2f1fqpIcublfKGt6apotG/NVx3SInWtLX+7Vg/Pv1YqOsnun6JSVdOXT/X7vk75f938QP+8OmSBs0fXtymMhJbf8qlPynYmpKCh7OB1fzNalOj1sl0ZAruHLiA+RM73pDe/VjMVP89+aTXwjyc/x5n+u991895/utrJTy8/06TXh0r/5JOa2JmYmqi4r/vUm/H4wLmT+z4anhr05X+q6KUXhtzr/9qSff5L5uMT//V/NdU4YuBTPa/8P67l/6r44ds+hYuoP5jx9ciy6XTWlibBrmx8V/TdMfjkP+6pOsu/lvM9N90sf7r+f6m/65n+S8p/itN15v0UkW3/+48+PRfJX6S9Joo4g+G/1qYG9KroqP/WypcuvyXPf13wH89/hHef7MB6R3Cqn55U4rv4kfH3zaSgQuYP7HjVf89tXrbO+hfLdr+Ozv/SP1dgtQ/Ov8C+i/3+q/Zf2D/HWi6bjT6rym9I/v/03/b+LHS4cTg/utTsV7/net/Afzz4f0XGX84/2j9xZ4/sePR/of2X7D/o+vPo/sv6h9B/Bfxr9j1Hz2eN/hO8/wfff4A848+f/1A/530/I0+/8PvH9D3H9HnT+R49P0b+v4PfP/4E/wXfP8Mvf9G37/D/ovuP8SeP7Hj0f0vdP8tqP9O339cyv7p3P1fdP8Z3v9G999j13/seMax8x/o+ZN7+O+E8zdP/8XOf8Hnz9Dzb7HnT+x49PxlCp7/BM+fOv13wvnXBfivt2lMvD8TyH/Hnb+Gz3+j589jz5/Y8ej9h4D+W7qQmf57efqv239n3T+C7z+h969i13/seMax+3/o/cMcu/8Y2H9n3p+J6r98pv8m4fwXuH+M3n+OO3++AX9clR+4PhbRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTEwLTExVDE2OjQxOjI1KzAyOjAwV3beHQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0xMC0xMVQxNjo0MToyNSswMjowMCYrZqEAAAAASUVORK5CYII=\n", "text/plain": [ "" ] @@ -333,9 +320,7 @@ { "cell_type": "code", "execution_count": 13, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate an empty Tallies object\n", @@ -396,7 +381,7 @@ "# Instantiate the tally\n", "tally = openmc.Tally(name='cell tally')\n", "tally.filters = [cell_filter]\n", - "tally.scores = ['scatter-y2']\n", + "tally.scores = ['scatter']\n", "tally.nuclides = ['U235', 'U238']\n", "\n", "# Add mesh and tally to Tallies\n", @@ -436,9 +421,7 @@ { "cell_type": "code", "execution_count": 17, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Export to \"tallies.xml\"\n", @@ -487,27 +470,27 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 MIT and OpenMC contributors\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.9.0\n", - " Git SHA1 | 897db1389dec7871026442bae96d9410ade85192\n", - " Date/Time | 2017-06-07 13:18:50\n", - " MPI Processes | 1\n", - " OpenMP Threads | 12\n", + " Version | 0.10.0\n", + " Git SHA1 | 199126b2fcc5cb094f2cc820ae13e1a972cacddd\n", + " Date/Time | 2018-10-11 16:41:25\n", + " OpenMP Threads | 8\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/johnny/Github/openmc/scripts/nndc_hdf5/U235.h5\n", - " Reading U238 from /home/johnny/Github/openmc/scripts/nndc_hdf5/U238.h5\n", - " Reading O16 from /home/johnny/Github/openmc/scripts/nndc_hdf5/O16.h5\n", - " Reading H1 from /home/johnny/Github/openmc/scripts/nndc_hdf5/H1.h5\n", - " Reading B10 from /home/johnny/Github/openmc/scripts/nndc_hdf5/B10.h5\n", - " Reading Zr90 from /home/johnny/Github/openmc/scripts/nndc_hdf5/Zr90.h5\n", + " Reading materials XML file...\n", + " Reading geometry XML file...\n", + " Building neighboring cells lists for each surface...\n", + " Reading U235 from /home/jan/openmc/nndc_hdf5/U235.h5\n", + " Reading U238 from /home/jan/openmc/nndc_hdf5/U238.h5\n", + " Reading O16 from /home/jan/openmc/nndc_hdf5/O16.h5\n", + " Reading H1 from /home/jan/openmc/nndc_hdf5/H1.h5\n", + " Reading B10 from /home/jan/openmc/nndc_hdf5/B10.h5\n", + " Reading Zr90 from /home/jan/openmc/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", + " Writing summary.h5 file...\n", " Initializing source particles...\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -534,7 +517,7 @@ " 18/1 0.67696 0.68367 +/- 0.00625\n", " 19/1 0.65444 0.68158 +/- 0.00615\n", " 20/1 0.69766 0.68266 +/- 0.00583\n", - " Triggers unsatisfied, max unc./thresh. is 1.17617 for absorption in tally 10002\n", + " Triggers unsatisfied, max unc./thresh. is 1.17617 for absorption in tally 3\n", " The estimated number of batches is 26\n", " Creating state point statepoint.020.h5...\n", " 21/1 0.64126 0.68007 +/- 0.00603\n", @@ -548,20 +531,20 @@ "\n", " =======================> TIMING STATISTICS <=======================\n", "\n", - " Total time for initialization = 3.6433E-01 seconds\n", - " Reading cross sections = 2.7963E-01 seconds\n", - " Total time in simulation = 1.4715E+00 seconds\n", - " Time in transport only = 1.3171E+00 seconds\n", - " Time in inactive batches = 2.2329E-01 seconds\n", - " Time in active batches = 1.2482E+00 seconds\n", - " Time synchronizing fission bank = 2.3145E-03 seconds\n", - " Sampling source sites = 1.4054E-03 seconds\n", - " SEND/RECV source sites = 7.6241E-04 seconds\n", - " Time accumulating tallies = 5.8822E-04 seconds\n", - " Total time for finalization = 5.0159E-05 seconds\n", - " Total time elapsed = 1.8511E+00 seconds\n", - " Calculation Rate (inactive) = 55980.8 neutrons/second\n", - " Calculation Rate (active) = 30044.0 neutrons/second\n", + " Total time for initialization = 6.5303E-01 seconds\n", + " Reading cross sections = 5.8105E-01 seconds\n", + " Total time in simulation = 4.6015E+00 seconds\n", + " Time in transport only = 3.5767E+00 seconds\n", + " Time in inactive batches = 4.5008E-01 seconds\n", + " Time in active batches = 4.1514E+00 seconds\n", + " Time synchronizing fission bank = 2.3493E-03 seconds\n", + " Sampling source sites = 1.7160E-03 seconds\n", + " SEND/RECV source sites = 4.7010E-04 seconds\n", + " Time accumulating tallies = 2.3040E-04 seconds\n", + " Total time for finalization = 2.6451E-02 seconds\n", + " Total time elapsed = 5.3123E+00 seconds\n", + " Calculation Rate (inactive) = 27772.9 particles/second\n", + " Calculation Rate (active) = 12646.3 particles/second\n", "\n", " ============================> RESULTS <============================\n", "\n", @@ -572,16 +555,6 @@ " Leakage Fraction = 0.34011 +/- 0.00283\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -630,7 +603,7 @@ "output_type": "stream", "text": [ "Tally\n", - "\tID =\t10000\n", + "\tID =\t1\n", "\tName =\tmesh tally\n", "\tFilters =\tMeshFilter, EnergyFilter\n", "\tNuclides =\ttotal \n", @@ -664,13 +637,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "[[[ 0.17581417]]\n", + "[[[0.17581417]]\n", "\n", - " [[ 0.30578219]]\n", + " [[0.06842901]]\n", "\n", - " [[ 0.06842901]]\n", + " [[0.30578219]]\n", "\n", - " [[ 0.12436752]]]\n" + " [[0.12436752]]]\n" ] } ], @@ -693,18 +666,18 @@ "data": { "text/html": [ "
\n", - "\n", "\n", " \n", @@ -776,9 +749,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -787,9 +760,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -798,9 +771,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -809,9 +782,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -820,9 +793,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -831,9 +804,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -842,9 +815,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -853,9 +826,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -864,9 +837,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -875,9 +848,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -886,9 +859,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -897,9 +870,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -908,9 +881,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -919,9 +892,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -930,9 +903,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -941,9 +914,9 @@ " \n", " \n", " \n", - " \n", " \n", " \n", + " \n", " \n", " \n", " \n", @@ -961,22 +934,22 @@ "1 1 1 1 0.00e+00 6.25e-01 nu-fission 5.46e-04 \n", "2 1 1 1 6.25e-01 2.00e+07 fission 8.42e-05 \n", "3 1 1 1 6.25e-01 2.00e+07 nu-fission 2.22e-04 \n", - "4 1 2 1 0.00e+00 6.25e-01 fission 1.85e-04 \n", - "5 1 2 1 0.00e+00 6.25e-01 nu-fission 4.52e-04 \n", - "6 1 2 1 6.25e-01 2.00e+07 fission 6.82e-05 \n", - "7 1 2 1 6.25e-01 2.00e+07 nu-fission 1.81e-04 \n", - "8 1 3 1 0.00e+00 6.25e-01 fission 2.05e-04 \n", - "9 1 3 1 0.00e+00 6.25e-01 nu-fission 5.00e-04 \n", - "10 1 3 1 6.25e-01 2.00e+07 fission 7.53e-05 \n", - "11 1 3 1 6.25e-01 2.00e+07 nu-fission 1.99e-04 \n", - "12 1 4 1 0.00e+00 6.25e-01 fission 2.06e-04 \n", - "13 1 4 1 0.00e+00 6.25e-01 nu-fission 5.03e-04 \n", - "14 1 4 1 6.25e-01 2.00e+07 fission 6.65e-05 \n", - "15 1 4 1 6.25e-01 2.00e+07 nu-fission 1.75e-04 \n", - "16 1 5 1 0.00e+00 6.25e-01 fission 2.03e-04 \n", - "17 1 5 1 0.00e+00 6.25e-01 nu-fission 4.94e-04 \n", - "18 1 5 1 6.25e-01 2.00e+07 fission 6.26e-05 \n", - "19 1 5 1 6.25e-01 2.00e+07 nu-fission 1.64e-04 \n", + "4 2 1 1 0.00e+00 6.25e-01 fission 1.85e-04 \n", + "5 2 1 1 0.00e+00 6.25e-01 nu-fission 4.52e-04 \n", + "6 2 1 1 6.25e-01 2.00e+07 fission 6.82e-05 \n", + "7 2 1 1 6.25e-01 2.00e+07 nu-fission 1.81e-04 \n", + "8 3 1 1 0.00e+00 6.25e-01 fission 2.05e-04 \n", + "9 3 1 1 0.00e+00 6.25e-01 nu-fission 5.00e-04 \n", + "10 3 1 1 6.25e-01 2.00e+07 fission 7.53e-05 \n", + "11 3 1 1 6.25e-01 2.00e+07 nu-fission 1.99e-04 \n", + "12 4 1 1 0.00e+00 6.25e-01 fission 2.06e-04 \n", + "13 4 1 1 0.00e+00 6.25e-01 nu-fission 5.03e-04 \n", + "14 4 1 1 6.25e-01 2.00e+07 fission 6.65e-05 \n", + "15 4 1 1 6.25e-01 2.00e+07 nu-fission 1.75e-04 \n", + "16 5 1 1 0.00e+00 6.25e-01 fission 2.03e-04 \n", + "17 5 1 1 0.00e+00 6.25e-01 nu-fission 4.94e-04 \n", + "18 5 1 1 6.25e-01 2.00e+07 fission 6.26e-05 \n", + "19 5 1 1 6.25e-01 2.00e+07 nu-fission 1.64e-04 \n", "\n", " std. dev. \n", " \n", @@ -1025,12 +998,14 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAY8AAAEcCAYAAAA/aDgKAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAHTpJREFUeJzt3X+cVnWd9/HXuwEdA1dUkhAIKKkYFzJ20vaxa/fgr4RK\ndi1Xsc1kvWMxpTY1gtg7190o02573BLILRsqW2rutq7cgUklk1lZYKj4e1nDQEkzFR2g4sfn/uN8\nxz1cDTPXF2bmYmbez8fjPLiuc77fc77nzOG8r/M95zqXIgIzM7Mcr6t1A8zMrOdxeJiZWTaHh5mZ\nZXN4mJlZNoeHmZllc3iYmVk2h4cd8CTdKOnztW5HrbW3HSSdL+ne7m6T9V0OD6uapA2StktqkfSS\npOWSRtS6XWWSQtIxtW6HWW/n8LBcH4iIgcBQ4Dlgfo3b02VU8P+RTiaprtZtsP3n/xi2TyLit8C/\nAQ2t4yQdJmmppF9LelrS37cefCVdJ+lbpbJfkvT9dIBukrRJ0mclvZDOcD68t2VL+pik9ZJelLRM\n0tFp/D2pyIPp7OjsNurWSfrfaTm/kHRxOlvpl6Y3S5on6UfANuDNko5Oy3kxLfdjpfnt0ZXUui6l\n9xskzZH0aDpbu0FSfWn6+yU9IOllST+WNL407Z2Sfi7pVUnfBF6rt/dNo69K2iLpcUknp5FnSbq/\nouAlku7Yy0zOl/RUWu4vyn+LtO0fS9MelTQhjR+btt3Lkh6RdEbFNrpO0gpJW4GJkg6W9GVJv5T0\nnKRFkg7pYP3sQBIRHjxUNQAbgFPS69cDNwFLS9OXAncAhwKjgCeBC0rlnwTOB04EXgCGp2lNwE7g\nGuBg4H8AW4G3pek3Ap9Pr09KdSeksvOBe0ptCOCYdtZhBvAoMBw4HPheqtMvTW8GfgkcC/QD+gP3\nAAspDt7HAb8GTqpsW2ldNlVss4eBEcARwI9K6/JO4HngBKAO+GgqfzBwEPA08KnUhg8BO8rLqliv\n89M2bC1/NrAlLfNg4EVgbKn8WuCDbcxnAPBKadsPBY5Nr88CngHeBQg4BhiZlrce+Gxq90nAqxV/\nvy3An1F8YK0HvgIsS+07FPh/wBdrvY97yDge1LoBHnrOkA5sLcDL6UD2LDAuTasDfg80lMr/LdBc\nen9COog9DUwtjW9KB74BpXG3Af8rvX7tAA18DbiqVG5gasuo9L6j8Lgb+NvS+1P4w/D4x9L0EcAu\n4NDSuC8CN1a2rbQuleExo/R+MvBf6fV1wD9VtO8JivB8T9q+Kk37Me2HR2X5nwEfKS1rXnp9LPAS\ncHAb8xmQ/r4fBA6pmHYX8Mk26pwI/Ap4XWncLcA/lLZR+UOGKD4cvKU07k+BX9R6H/dQ/eBuK8v1\nFxExiOLT48XADyS9ERhM8Qn06VLZp4FhrW8i4qfAUxQHj9sq5vtSRGytqHt0G8s/uryMiGgBflNe\nTgeOBjaW3m9so0x53NHAixHxakXbql1e5fzK6zUSuDR19bws6WWKsDo6Dc9EOrKW6ranrfKty7oJ\nOFeSgI8At0XE7ypnkP4GZ1OcoW1ON0W8PU0eAfxXG8s9GtgYEbsrll3eRuVt8AaKM9H7S+v9nTTe\negiHh+2TiNgVEf9O8an8zym6knZQHBBbvYmimwMASRdRdKE8C8yqmOXhkgZU1H22jUU/W15GqnNk\neTkd2EzRZdWqrbvFygfgZ4EjJB1a0bbW5W2lOBC2emMb8ysvo7xeGynOBgaVhtdHxC2pncPSwb5c\ntz1tlX8WICLuozgzPBE4F/iXvc0kIu6KiFMpuqweBxaX2vuWNqo8C4youLlgj789e27TF4DtFN1h\nret9WBQ3YlgP4fCwfZIudE+huG7wWETsojibmCfpUEkjgUuAr6fybwU+D/w1xSffWZKOq5jtFZIO\nknQi8H7gX9tY9C3ANEnHSToY+ALw04jYkKY/B7y5nabfBnxS0jBJg4DPtLeeEbGRorvoi5Lq0wXt\nC1rXC3gAmCzpiHQG9ndtzOYiScMlHQHMBb6Zxi8GZkg6IW3PAZLel4LqJxRdeZ+Q1F/SmcDx7bUV\nOKpU/ixgLLCiNH0p8FVgR0S0+Z0QSUMkTUmh/DuKbsrWM4p/Bi6T9Cepvcekv/NPKW4umJWW3QR8\nALi1rWWkM5TFwFckHZWWO0zSeztYPzuQ1LrfzEPPGSj677dTHFBepbgQ/OHS9MMpDqq/pviU+jmK\nDyj9KPrfZ5fKXgisozgTaQI2URxYX6C4YP2RUtkb2fO6wgyK7pMXgW+TLryXpm2m6Lf/qzbWoR/F\nxdrfAL+guMC8g3StgOKax/+sqDM8LefFtNzyNYx6ijB4BXgoza/ymscciov0L1N0H72+NP10YHWa\ntpkiMA9N0xopLmy/mpbxTdq/5vEjinDYQnFzwmkVZd5EEQRXtPM3Hgr8IM3j5bQ9Giq27xNpH3gY\neGcaf2yp3qPAX+7t71fabl+g6MZ8BXgM+ESt93EP1Q81b4AHD1RcZO7mZU8Cnm5n+gbg0ykYtlJc\nsB8C3JkO6t8DDk9l301xlvIy8GBarw0UF+WnpQPkq+mAWb5o30QRnpdS3H21GZjWBet6SFr+mFr/\nzT30/MHdVtanSDpE0mRJ/SQNAy4Hbu+g2geBU4G3UnTH3ElxW+obKM6sPpHmtZyia+4I4DLgW/x3\n1/DzFF1xf0QRJF9p/Y5E8kbgMIqLzBcACyQdvp+rW+lCYHVE/Gcnz9f6oH61boBZNxNwBUUX0HaK\nA/7nOqgzPyKeA5D0Q+D5iFib3t8OnExxLWdFRLReY/iupDUU30chIpaX5vcDSSspLl7/PI3bQXGL\n8E5ghaQW4G3Affuzsq0kbaBY97/ojPmZOTys5iKimT3vgOrKZW2j+JJbjudKr7e38X4gxR1gZ0n6\nQGlaf4rvcXxP0iSKs5y3UpyNvJ7imk+r36TgaLUtzbdTRMSozpqXGTg8zDrLRuBfIuJjlRPSXWHf\nAs4D7oiIHZL+g+JMwKxH8jUPs87xdeADkt6r4vlZ9SqeczWc4pEdB1PchbYznYWcVsvGmu0vh4dZ\nJ4ji+yBTKC6kt96q/GmKR3a8CnyC4jsmL1F8SW9ZjZpq1ila7203MzOrms88zMwsm8PDzMyyOTzM\nzCybw8PMzLI5PMzMLFuP+pLg4MGDY9SoUbVuRq+zdetWBgwY0HFBswOE99muc//9978QER3+MFeP\nCo9Ro0axZs2aWjej12lubqapqanWzTCrmvfZriOpo1+sBNxtZWZm+8DhYWZm2RweZmaWzeFhZmbZ\nHB5mZpbN4WFmPcbMmTOpr69n4sSJ1NfXM3PmzFo3qc/qUbfqmlnfNXPmTBYsWMDrXld85t25cycL\nFiwAYP78+bVsWp/kMw8z6xEWLlyIJK666iruvPNOrrrqKiSxcOHCWjetT3J4mFmPsHv3bubNm8cl\nl1xCfX09l1xyCfPmzWP37t21blqf5PAwM7NsvuZhZj1CXV0dc+fO5aCDDqKhoYFrrrmGuXPnUldX\nV+um9UkODzPrES688EIWLFjArFmz2LVrF3V1dUQEH//4x2vdtD7J3VZm1iPMnz+fU0899bVrHLt3\n7+bUU0/1nVY14vAwsx7hlltuYe3atYwcORJJjBw5krVr13LLLbfUuml9UlXhIel0SU9IWi9pdhvT\nJenaNP0hSRMy6l4qKSQN3r9VMbPebNasWdTV1bFkyRJWrlzJkiVLqKurY9asWbVuWp/UYXhIqgMW\nAJOABmCqpIaKYpOAMWmYDlxXTV1JI4DTgF/u95qYWa+2adMmli5dysSJE+nXrx8TJ05k6dKlbNq0\nqdZN65OqOfM4HlgfEU9FxO+BW4EpFWWmAEujcB8wSNLQKup+BZgFxP6uiJmZdZ9q7rYaBmwsvd8E\nnFBFmWHt1ZU0BXgmIh6UtNeFS5pOcTbDkCFDaG5urqLJlqOlpcXb1Q54gwcP5swzz2TAgAE8//zz\nHHXUUWzdupXBgwd7/62BmtyqK+n1wGcpuqzaFRHXA9cDNDY2hn96svP5Jz2tJzjnnHNYuHAhAwcO\nRBKS2LZtG+eff7733xqoptvqGWBE6f3wNK6aMnsb/xZgNPCgpA1p/M8lvTGn8WbWd6xatYo5c+Zw\n5JFHAnDkkUcyZ84cVq1aVeOW9U2KaP9yg6R+wJPAyRQH/tXAuRHxSKnM+4CLgckU3VLXRsTx1dRN\n9TcAjRHxQnttaWxsjDVr1mStoHXMZx7WE9TV1fHb3/6W/v37v7bP7tixg/r6enbt2lXr5vUaku6P\niMaOynV45hEROymC4S7gMeC2iHhE0gxJM1KxFcBTwHpgMfDx9uruw/qYWR83duxY7r333j3G3Xvv\nvYwdO7ZGLerbqrrmERErKAKiPG5R6XUAF1Vbt40yo6pph5n1XXPnzuWCCy7ga1/7Grt27WLVqlVc\ncMEFzJs3r9ZN65P8bCszO2C1dSfmSSedtMf7c889l3PPPXePcR11x9v+8+NJzOyAFRFtDiM/8+29\nTnNwdA+Hh5mZZXN4mJlZNoeHmZllc3iYmVk2h4eZmWVzeJiZWTaHh5mZZXN4mJlZNoeHmZllc3iY\nmVk2h4eZmWVzeJiZWTaHh5mZZXN4mJlZNoeHmZllc3iYmVk2h4eZmWVzeJiZWTaHh5mZZXN4mJlZ\nNoeHmZllc3iYmVk2h4eZmWVzeJiZWTaHh5mZZXN4mJlZNoeHmZllc3iYmVk2h4eZmWVzeJiZWTaH\nh5mZZXN4mJlZNoeHmZllc3iYmVk2h4eZmWWrKjwknS7pCUnrJc1uY7okXZumPyRpQkd1Jf1TKvuA\npJWSju6cVTIzs67WYXhIqgMWAJOABmCqpIaKYpOAMWmYDlxXRd2rI2J8RBwHfBv43P6vjpmZdYdq\nzjyOB9ZHxFMR8XvgVmBKRZkpwNIo3AcMkjS0vboR8Uqp/gAg9nNdzMysm/SroswwYGPp/SbghCrK\nDOuorqR5wHnAFmBi1a02M7OaqiY8ukxEzAXmSpoDXAxcXllG0nSKrjCGDBlCc3Nzt7axL2hpafF2\ntR7H+2xtVRMezwAjSu+Hp3HVlOlfRV2AbwAraCM8IuJ64HqAxsbGaGpqqqLJlqO5uRlvV+tRvrPc\n+2yNVXPNYzUwRtJoSQcB5wDLKsosA85Ld129G9gSEZvbqytpTKn+FODx/VwXMzPrJh2eeUTETkkX\nA3cBdcCSiHhE0ow0fRHFWcNkYD2wDZjWXt006yslvQ3YDTwNzOjUNTMzsy5T1TWPiFhBERDlcYtK\nrwO4qNq6afwHs1pqZmYHDH/D3MzMsjk8zMwsm8PDzMyyOTzMzCybw8PMzLI5PMzMLJvDw8zMsjk8\nzMwsm8PDzMyyOTzMzCybw8PMzLI5PMzMLJvDw8zMsjk8zMwsm8PDzMyyOTzMzCybw8PMzLI5PMzM\nLJvDw8zMsjk8zMwsm8PDzMyyOTzMzCybw8PMzLI5PMzMLJvDw8zMsjk8zMwsm8PDzMyyOTzMzCyb\nw8PMzLI5PMzMLJvDw8zMsjk8zMwsm8PDzMyyOTzMzCybw8PMzLI5PMzMLJvDw8zMsjk8zMwsW1Xh\nIel0SU9IWi9pdhvTJenaNP0hSRM6qivpakmPp/K3SxrUOatkZmZdrcPwkFQHLAAmAQ3AVEkNFcUm\nAWPSMB24roq63wX+OCLGA08Cc/Z7bczMrFtUc+ZxPLA+Ip6KiN8DtwJTKspMAZZG4T5gkKSh7dWN\niJURsTPVvw8Y3gnrY2Zm3aCa8BgGbCy935TGVVOmmroAfwPcWUVbzMzsANCv1g2QNBfYCXxjL9On\nU3SFMWTIEJqbm7uvcX1ES0uLt6v1ON5na6ua8HgGGFF6PzyNq6ZM//bqSjofeD9wckREWwuPiOuB\n6wEaGxujqampiiZbjubmZrxdrUf5znLvszVWTbfVamCMpNGSDgLOAZZVlFkGnJfuuno3sCUiNrdX\nV9LpwCzgjIjY1knrY2Zm3aDDM4+I2CnpYuAuoA5YEhGPSJqRpi8CVgCTgfXANmBae3XTrL8KHAx8\nVxLAfRExozNXzszMukZV1zwiYgVFQJTHLSq9DuCiauum8cdktdTMzA4Y/oa5mZllc3iYmVk2h4eZ\n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\n", + "image/png": "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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -1048,7 +1023,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 24, @@ -1057,12 +1032,14 @@ }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAU8AAAEWCAYAAADmTBXNAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3X28XVV95/HP997cm0dCgCCGBEiwgTFYihgDWmtFqiSU\nkj5MO/Aay4POK6/MC6x2tBaKUx0rLUqnnaKUTEYzSqVQRsSmmhZQ2+pMDRJoiIQHuUQwieEhQcJD\nwn06v/lj7ztzcrz33L1O9rnnnnO/b177lXP2Xmuvtc85/O7ee629liICMzNL09XqCpiZtSMHTzOz\nBjh4mpk1wMHTzKwBDp5mZg1w8DQza4CDZweSdKqkrZJekvQ7ktZJ+s+Hsb8/kPS5Muto1u7kfp6d\nR9LngRcj4ndbXZeySXoS+A8R8Y1W18WmNp95dqaTgO2trkQqSdNaXQezohw8O4ykbwHnAJ+V9LKk\nUyR9QdIn8+3zJX1N0guSnpf0HUld+bbfl7Q7v9x/TNK5+fqPS/pSVRkXStqe7+OfJL2+atuTkj4s\naZuk/ZL+RtKMMep6maT/I+nPJe0DPi7pdZK+JWmfpL2SbpE0L0//V8CJwN/lx/aRfP3Zkv4lr8+D\nkt7RjM/WrJqDZ4eJiHcC3wGujIg5EfGDmiQfAnYBxwLHAX8AhKRTgSuBN0fEEcB5wJO1+5d0CnAr\n8MF8H5vIgllvVbLfAlYCS4DTgcvqVPksYEdel2sBAX8CHA+8HjgB+Hh+bL8N/Aj4lfzYPi1pIfB1\n4JPA0cCHgTskHVvvczI7XA6eU88gsAA4KSIGI+I7kd34HgamA8sk9UTEkxHxxCj5/x3w9Yi4JyIG\ngT8FZgJvrUpzQ0T8OCKeB/4OOKNOfX4cEZ+JiKGIOBgRffm++yPiOeDPgF+sk/89wKaI2BQRlYi4\nB9gCnF/s4zBrjIPn1HM90AfcLWmHpKsAIqKP7Gzy48Czkm6TdPwo+Y8Hnhp5ExEVYCewsCrN01Wv\nDwBz6tRnZ/UbScflZe+W9CLwJWB+nfwnAb+ZX7K/IOkF4G1kfyDMmsbBc4qJiJci4kMRcTJwIfCf\nRu5tRsRfR8TbyAJSAJ8aZRc/zrcDIElkl9a7G61Szfs/ztf9bETMJTuzVJ30O4G/ioh5VcvsiLiu\nwfqYFeLgOcVIukDSz+RBbz/Z5Xol7xv6TknTgVeBg0BllF3cDvyypHMl9ZDdQ+0H/qWkKh4BvAzs\nz+9n/l7N9meAk6vefwn4FUnnSeqWNEPSOyQtKqk+ZqNy8Jx6lgLfIAtQ3wX+MiL+kex+53XAXrLL\n7tcAV9dmjojHyM4GP5On/RWyBpyBkur3X4AzyQL714Gv1Gz/E+Cj+SX6hyNiJ7CarOHrObIz0d/D\nv21rMneSNzNrgP86m5k1wMHTzKwBDp5mZg1w8DQza0BbDMTQ2z0rZvYcmZYpRutlMw5p/DStUGmg\nUa+7gb+LlQY+s9QGx4baJxvI1Mhn1sj3n3j8DTXQTkCj7qu8wkD0H9b/AOedMzv2PT9cKO392/rv\nioiVh1Neq7VF8JzZcyRvWXxpUh71N9Bzprs7Pc9EaOBYYs6s5Dx6tT85DwODScljqNj/XIeopOeJ\nxHoBqIE/ODE4lJZ+KC19I2UAyZ/ZvfHN9DJq7Ht+mO/ddWKhtN0LHq/31FhbaIvgaWaTXwCVUZ+r\n6EwOnmZWiiAYjAauLNpUSxqMJK3Mx4vsGxmYwszaX6Xgf51gws88JXUDNwLvIhtX8j5JGyPi4Ymu\ni5mVJwiGp9ATi60481wB9EXEjvx56NvInk02szZXIQotnaAV9zwXcugYjrvIRhM/hKQ1wBqAGdPm\nTkzNzKxh2YjanREYi5i0neQjYn1ELI+I5b3d6d1uzGzilXnmOV7biDI35Nu3STozIe+HJIWk+VXr\nrs7TPybpvPHq14ozz91kg+eOWETjA+ma2SQRwGBJ9zwLto2sIhticSnZ1etNwFnj5ZV0AvBusvmw\nRspbBlwEnEY2W8I3JJ0SMXb3gVaced4HLJW0JJ807CJgYwvqYWYlCoLhgksBRdpGVgM3R2YzME/S\nggJ5/xz4CIc+urYauC2fO+uHZFPVrKhXwQkPnhExRDZL413AI8DtEdF2c4ybWY2A4YILMF/Slqpl\nTc3eRmsbWVgwzZh5Ja0GdkfEgw2Ud4iWdJKPiE1kU9aaWYfInjAqbG9ELG9aZUYhaRbZjAPvLmN/\nbfGEUaW3m1dPnJeUp2so/d5LZVrauAhq4PbOcG/6yX73QHqn4u6DDTwPHbPTyzmQ9ty9DjbwnH5v\nT3IeNfA8eCN5eOVAUvKuBgYfqSSWARADiZ/z4Y0JkhPDlDa4TpG2kbHS9Iyx/nXAEuDBbAovFgEP\nSFpRsLxDTNrWdjNrL1mDkQotBRRpG9kIXJK3up8N7I+IPWPljYjvR8RrImJxRCwmuzQ/MyKezvd1\nkaTpkpaQNUJ9r14F2+LM08wmv6yfZzlnnhExJGmkbaQb2BAR2yWtzbevI7v1dz5Z484B4PJ6eccp\nb7uk24GHgSHginot7eDgaWYlqhQ7qyxktLaRPGiOvA7giqJ5R0mzuOb9tcC1Revn4GlmpSjzzLMd\nOHiaWSkCMTyFmlEcPM2sNGVetk92Dp5mVopADMQkncqmCRw8zawUWSd5X7abmSVzg5GZWaIIMRw+\n8zQzS1bxmaeZWZqswWjqhJS2ONLh6WL/yb1Jebr708vpGk4b6aNrML2Mgbnpf5ln/KSB2QbnpX+1\njVxxqTIjKf2M59M/tEpPesV69x1MzkNlenKWrp7Ez3n/S8llJA/yAShxAJIyzhfdYGRm1qBh9/M0\nM0sz1Z4wmvAjlXSCpH+U9LCk7ZI+MNF1MLPmqERXoaUTtOLMcwj4UEQ8IOkI4H5J99RM7GRmbSYb\nGKQzAmMREx4888FK9+SvX5L0CNlcIQ6eZm0sEIN+PHNiSFoMvBG4d5Rta4A1AD1zjprQeplZugim\nVCf5lh2ppDnAHcAHI+LF2u0RsT4ilkfE8mkz0ufWMbOJJioFl07QkuApqYcscN4SEV9pRR3MrFxB\nduZZZClC0kpJj0nqk3TVKNsl6YZ8+zZJZ46XV9If5Wm3Srpb0vH5+sWSDubrt0paV1terVa0tgv4\nPPBIRPzZRJdvZs0zTFehZTySuoEbgVXAMuBiSctqkq0im6htKdktvpsK5L0+Ik6PiDOArwF/WLW/\nJyLijHxZO14dW3Hm+fPAbwPvrIry57egHmZWokBUothSwAqgLyJ2RMQAcBuwuibNauDmyGwG5kla\nUC9vzS3C2WQnzA1pRWv7/6acp8HMbBLJph4uLaQsBHZWvd8FnFUgzcLx8kq6FrgE2A+cU5VuiaSt\n+fqPRsR36lWwLZ4wimnw6jHNj7czn01LX5mTXkZ3+mPK9B+RfoHQVXfS1NENNtAup9TH7tWTXkgj\nEp+5h8aeoZ/547QPQAPp9eruPiY5T+WF/WkZEp+FH2MnKeN5zpe0per9+ohYX0IlxhUR1wDXSLoa\nuBL4GFn3yRMjYp+kNwFflXTaaI3ZI9oieJrZ5BeQ8vTQ3ohYXmf7buCEqveL8nVF0vQUyAtwC9n0\nxB+LiH6gHyAi7pf0BHAKsGWUfEALuyqZWecZzs8+x1sKuA9YKmmJpF7gImBjTZqNwCV5q/vZwP78\nIZwx80paWpV/NfBovv7YvKEJSSeTNULtqFdBn3maWSkiVNpz6xExJOlK4C6gG9gQEdslrc23ryM7\nazwf6AMOAJfXy5vv+jpJpwIV4ClgpFX97cAnJA3m29ZGxPP16ujgaWalyBqMyns8MyI2kQXI6nXr\nql4HcEXRvPn63xgj/R1kfc8Lc/A0s5J4DiMzs2RZg9HU6YXo4GlmpfGQdGZmiUaeMJoqHDzNrDSe\nAM7MLFEEDFYcPM3MkmSX7Q6eZmbJEp5tb3ttETyjC4Zmp40cNe2V9C/x4HFpeZIHxQCGZjU8AlaS\nnpfSj793f3rdUvtED/em16vnQPoHHdPSz4C6B9LLqUxP/F9o9szkMroq6fVSd+IXU0LMc1clM7OG\n+LLdzKwhnTI/UREOnmZWiqy13VMPN10+/NMWYHdEXNCqephZOdxJfuJ8AHgEmNvCOphZiabSZXur\nph5eBPwy8LlWlG9m5RtpbS9pArhJr1Vnnv8N+AhwxFgJJK0hm06UaUceNUHVMrPDMZVa21sxb/sF\nwLMRcX+9dBGxPiKWR8Ty7tkNzExmZhMqQgxFV6GlE7TizPPngQvzudpnAHMlfSki3tOCuphZiTrl\nkryICf8TEBFXR8SiiFhMNjHTtxw4zdpf2fc8Ja2U9JikPklXjbJdkm7It2+TdOZ4eSX9UZ52q6S7\nJR1fte3qPP1jks4br36dcf5sZpNCWcEz78p4I7AKWAZcLGlZTbJVZLNcLiVrH7mpQN7rI+L0iDgD\n+Brwh3meZWQnc6cBK4G/HJlNcywtDZ4R8U/u42nWGUb6eZZ05rkC6IuIHRExANxGNlVwtdXAzZHZ\nDMyTtKBe3oh4sSr/bLIT5pF93RYR/RHxQ7IZOVfUq2BbPGEU3TA4N21whMFGeo/OHUpK3vVcb3IR\nM59Nvyf06rENDNihRgb5SK9bJfEXpOHkInj16PSfaddg+vEfsTPt+wcYnJM20EfPS+m/men9A8l5\nmD49Lf0r5ZxHJfTznC9pS9X79RGxvur9QmBn1ftdwFk1+xgtzcLx8kq6FrgE2A+cU7WvzaPsa0xt\nETzNbPKLgKHigyHvjYjlzazPWCLiGuAaSVcDVwIfa2Q/vudpZqUp8bJ9N3BC1ftF+boiaYrkBbgF\nGJnHvWie/8fB08xKUfI9z/uApZKWSOola8zZWJNmI3BJ3up+NrA/IvbUyytpaVX+1cCjVfu6SNJ0\nSUvIGqG+V6+Cvmw3s9JESf08I2JI0pXAXUA3sCEitktam29fB2wCzidr3DkAXF4vb77r6ySdClSA\np4CR/W2XdDvwMDAEXBERde/QO3iaWWnKHBgkIjaRBcjqdeuqXgdwRdG8+frfGCX5yLZrgWuL1s/B\n08xKETG1njBy8DSzkohhTz1sZpaurHue7cDB08xK4dkzzcwaEdl9z6nCwdPMSjOVpuFw8DSzUoQb\njCYhkXV1TRDd6dcP6krL08DYG/QflZ6p0pueZ2hO2kAqAMOz0n/4PS+l5dl/SvqxzO1LzkJXA2Np\nVHrSz5pm7E0rKLoaODPr7UnPE6nffznX275sNzNrgFvbzcwSRUyt4NmqqYfnSfqypEclPSLpLa2o\nh5mVy1MPN99fAP8QEf82H/VkVovqYWYl8j3PJpJ0JPB24DKAfJj8Bm7vm9lkEojKFGptb8WRLgGe\nA/6npH+V9DlJPzUxu6Q1krZI2jL88ssTX0szSxYFl07QiuA5DTgTuCki3gi8AvzUtKIRsT4ilkfE\n8u45cya6jmaWKm8wKrJ0glYEz13Aroi4N3//ZbJgambtbgqdek548IyIp4Gd+WjOAOeSjd5sZm3O\nZ57N937gFknbgDOAP25RPcysJAFUKiq0FCFppaTHJPVJ+qlbe/ncRTfk27dJOnO8vJKuz7tIbpN0\np6R5+frFkg5K2pov62rLq9WSrkoRsRVoybSjZtYkAZR0VimpG7gReBfZrb77JG2MiOqr1FVkE7Ut\nJZuX/SbgrHHy3gNcnc9z9CngauD38/09ERFnFK3j1OlXYGZNF1FsKWAF0BcRO/LujLeRzXZZbTVw\nc2Q2A/MkLaiXNyLujoihPP9msimGG9Iej2d2BcwZTMrSM31o/EQ1jpj9alL6/jn9yWUMDiaOcAJU\n9s1IzkNP+l354Wl1JwscPc+ctDwzd6YPcjE4NzkL055NP/4Dx6Z/N9E1PSn99J+k/Y4BKrN6k/N0\ndaceS0n3IYt/7PMlbal6vz4i1le9XwjsrHq/i+zsknHSLCyYF+C9wN9UvV8iaSuwH/hoRHyn3gG0\nR/A0szaQ1Bi0NyJadutO0jVkUwzfkq/aA5wYEfskvQn4qqTTIuLFsfbh4Glm5SmvG9Ju4ISq94vy\ndUXS9NTLK+ky4ALg3Hz6YiKiH+jPX98v6QngFKD67PgQvudpZuUIiIoKLQXcByyVtCQf/+IiYGNN\nmo3AJXmr+9nA/ojYUy+vpJXAR4ALI+LAyI4kHZs3NCHpZLJGqB31KugzTzMrUTn3TvPW8CuBu8iG\nQt8QEdslrc23rwM2AecDfcAB4PJ6efNdfxaYDtwjCWBzRKwlG2/jE5IGgQqwNiKer1dHB08zK0+J\nTw9FxCayAFm9bl3V6wCuKJo3X/8zY6S/A7gjpX4OnmZWng559LIIB08zK0eJneTbgYOnmZXGgyGb\nmTWi4HPrncDB08xK08h03O3KwdPMytFBY3UW4eBpZiWRG4wmm5nTBzh9ce2TWfUdOyN93qOTZu5L\nSr91f/qALPsHZibnebr3iOQ8/f3pX21lOH1gjK7utIFBhp5v4Cen9P8hD85Pz9OVPi4KlWlpD+kN\nzUwf5GNuX/pgIi3jM08zswZUWl2BiePgaWblmGL9PFsyMIik35W0XdJDkm6V1MCAlWY22SiKLZ1g\nwoOnpIXA7wDLI+INZA/uXzTR9TCzJvDsmf+fpPdLOqrkcqcBMyVNA2YBPy55/2ZmTVXkzPM4sgmU\nbs9npDusmxoRsRv4U+BHZKM374+Iu2vTSVojaYukLQMvHDycIs1sgviyvUpEfJRsYNDPA5cBj0v6\nY0mva6TA/Cx2NbAEOB6YLek9o5S7PiKWR8Ty3nnp3XvMbIIF2eOZRZYOUOieZz5u3tP5MgQcBXxZ\n0qcbKPOXgB9GxHMRMQh8BXhrA/sxs8lmCt3zHLerkqQPAJcAe4HPAb8XEYOSuoDHyYa0T/Ej4GxJ\ns4CDwLnUmSfEzNpHp1ySF1Gkn+fRwK9HxFPVKyOiIumC1AIj4l5JXwYeIDuL/Vdgff1cZtYWplDw\nLHLP82O1gbNq2yONFJrv899ExBsi4rfzmevMrN2VeNmeN1A/JqlP0lWjbJekG/Lt2ySdOV5eSddL\nejRPf6ekeVXbrs7TPybpvPHq1xZPGM3qHuCN83aOn7DKYy8fl1xOV+Kfzb0H5ySXMX9m+jP3e2Ju\ncp4zFqWNBQCwZcdJyXm6uhJv/i95JbmMoSdnJ+fpeTm9UUINPNvenfhnf+beoeQyIvH5eQD1Jj5D\nn/o9jlZmiS3p+UyWNwLvAnaR9fjZGBEPVyVbRdaYvRQ4C7gJOGucvPcAV+eTxH0KuBr4fUnLyPqb\nn0bWkP0NSadExJi/Ck89bGblKa+1fQXQFxE7ImIAuI2sl0611cDNkdkMzJO0oF7eiLg7Ikb+gm0m\nm9N9ZF+3RUR/RPyQbEbOFfUq6OBpZqVJ6Oc5f6Qfd76sqdnVQqD6cnNXvq5ImiJ5Ad4L/H1CeYdo\ni8t2M2sTxS/b90bE8ibWpC5J15A1WN/S6D4cPM2sHOU+PbQbOKHq/aJ8XZE0PfXySroMuAA4N+/D\nXrS8Q/iy3czKU15r+33AUklLJPWSNeZsrEmzEbgkb3U/m+xR7z318kpaSdY3/cKIOFCzr4skTZe0\nhKwR6nv1KugzTzMrjUoaDDlvDb8SuIts5LUNEbFd0tp8+zpgE3A+WePOAeDyennzXX8WmA7ckw/T\nsTki1ub7vh14mOxy/op6Le3g4Glmk1REbCILkNXr1lW9DuCKonnz9T9Tp7xrgWuL1s/B08zKM4We\nMHLwNLNydNBwc0U4eJpZeRw8zcwa4OBpZpZGlNfa3g7aIngORxcvDqVNsPmWeTuSy9kzcGRS+jcd\n86PkMp4bSB9M5C0Ln0zO0/fi/OQ8vTMGk/P0P582yr+G0gegiCPTR+zQYHdynkZ0JX5kA3PT69V9\nMP34Y1bihLRdJXT59j1PM7MGOXiamTXAwdPMLN1Uumxv2rPtkjZIelbSQ1XrjpZ0j6TH83/Lng/e\nzFppCk0A18yBQb4ArKxZdxXwzYhYCnwzf29mnSCy1vYiSydoWvCMiG8Dz9esXg18MX/9ReBXm1W+\nmbXAFDrznOh7nsflQ0ZBNgf8mBMN5SNLrwE44rWzJqBqZna4fM9zAuQjooz5UUfE+ohYHhHLZx41\nfQJrZmYNm0JnnhMdPJ/JJ2gi//fZCS7fzJqlaOB08GzIRuDS/PWlwN9OcPlm1iQiaQK4ttfMrkq3\nAt8FTpW0S9L7gOuAd0l6HPil/L2ZdQgHzxJExMURsSAieiJiUUR8PiL2RcS5EbE0In4pImpb482s\nnZV42S5ppaTHJPVJ+qlujfncRTfk27dJOnO8vJJ+U9J2SRVJy6vWL5Z0UNLWfFlXW16ttnjCqLdr\nmIXTX0jKs2zGruRynhmcm5T+qJ4D4yeq8YtzH03Os/nlMWcOGNOi1/wkOc+90xYn59k7e3ZS+hde\nShtIBCCeSisDoNKTnIVpB9IHLekaSjuNGpqRXoaG0ztGqn8gLUOlpNPBknYjqRu4EXgX2Rzq90na\nGBEPVyVbRTZR21LgLOAm4Kxx8j4E/Drw30cp9omIOKNoHdsieJpZGyj3knwF0BcROwAk3UbWT7w6\neK4Gbs577myWNC9viF48Vt6IeCRfd9gV9NTDZlae4pft8yVtqVrW1OxpIbCz6v2ufF2RNEXyjmZJ\nfsn+z5J+YbzEPvM0s9IkPHq5NyKWj59swuwBToyIfZLeBHxV0mkR8eJYGXzmaWalKbG1fTdwQtX7\nRfm6ImmK5D1ERPRHxL789f3AE8Ap9fI4eJpZOcrtJH8fsFTSEkm9wEVk/cSrbQQuyVvdzwb2549/\nF8l7CEnH5g1NSDqZrBGq7nQUvmw3s/KU1WgfMSTpSuAuoBvYEBHbJa3Nt68DNgHnA33AAeDyenkB\nJP0a8BngWODrkrZGxHnA24FPSBoEKsDa8bpSOniaWSlGnjAqS0RsIguQ1evWVb0O4IqiefP1dwJ3\njrL+DuCOlPo5eJpZaVRWf9E24OBpZuXooEE/inDwNLPSdMpz60U4eJpZeRw8zczS+cxzkpnd9Spn\nzepLynNs98Hkck6ZsWf8RFX2DqUNJAKw7eCJyXkW9O5PzvNqpH+1lUh/3vfomWmDoxzo700u48DM\n9IExYlr6/8W9+9M/s+HetM9s5r4GjqUr/XuJnsRjOfxHvfOCS9pPG2iL4GlmbSA6Z2bMIhw8zawU\nZffznOyaOZL8BknPSnqoat31kh7NBy69U9K8ZpVvZi0QUWzpAM18tv0LwMqadfcAb4iI04EfAFc3\nsXwzm2CehqMEEfFt4PmadXdHxFD+djPZaCdm1gmm2OyZrbzn+V7gb8bamA+OugbguON9a9asHUyl\nBqOWDEkn6RpgCLhlrDQRsT4ilkfE8nnHeOQ8s3agSrGlE0z4KZ2ky4ALgHPzUVHMrBMEHdMYVMSE\nBk9JK4GPAL8YEelTT5rZpNYpjUFFNLOr0q3Ad4FTJe2S9D7gs8ARwD1F50Y2szbiBqPDFxEXj7L6\n880qz8xaa6p1kncztpmVI8KDIU82r1Z6eHzgtUl5hnufTS7ntdPSBuDYM3hUchlvm/2D5Dz3HVyS\nnOef99ad+G9UJx+xLznPweGepPRP7Ts6uYzpC9Jvjw/+aHZynkYGx4juxPQNlNFWAanEquZtJH9B\nNg/R5yLiuprtyrefTzaH0WUR8UC9vJJ+E/g48HpgRURsqdrf1cD7gGHgdyLirnr1cx8gMytNWU8Y\n5TNZ3gisApYBF0taVpNsFdksl0vJ+oTfVCDvQ8CvA9+uKW8Z2Sybp5E9GfmXI7NpjsXB08zKEUAl\nii3jWwH0RcSOiBgAbgNW16RZDdwcmc3APEkL6uWNiEci4rFRylsN3JbP3/5Dshk5V9SroIOnmZWn\neGv7fElbqpY1NXtaCOyser8rX1ckTZG8tZLztMU9TzNrDwmt7XsjYnkTq9J0Dp5mVpoSG7d2AydU\nvV+UryuSpqdA3kbKO4Qv282sHOWOqnQfsFTSEkm9ZI05G2vSbAQuUeZsYH9E7CmYt9ZG4CJJ0yUt\nIWuE+l69DD7zNLNSZJ3kyznzjIghSVcCd5F1N9oQEdslrc23rwM2kXVT6iPrqnR5vbwAkn4N+Axw\nLPB1SVsj4rx837cDD5MNWnRFRAzXq6ODp5mVp8QRkyJiE1mArF63rup1AFcUzZuvvxO4c4w81wLX\nFq2fg6eZlaasM8924OBpZuXooEE/inDwNLOS+Nl2M7PG+LJ9cpnZNcjPTt+VlOdN03uTy7n5xSOT\n0p8z5+HkMnYOHpOc56Tevcl5ZnSnDyby8lD6Z/bMgblJ6Xt6hsZPVOOln8xKzsOs9JaLeD69595A\n2k+Gg6+ml3FE30ByHroSy1EDI5bUis6ZYqOItgieZtYmfOZpZtaAqRM7mzoNxwZJz0p6aJRtH5IU\nkuY3q3wzm3iqVAotnaCZj2d+gWxcvENIOgF4N/CjJpZtZhMtyDrJF1k6QNOCZ0R8G3h+lE1/TjaD\n5hQ6wTfrfCJQFFs6wURPPbwa2B0RD2qc1r18fL81AK9dmDjXgZm1RocExiImLHhKmgX8Adkl+7gi\nYj2wHuD1p0+fOt+IWTubQsFzIoekex2wBHhQ0pNk4+U9ICltZjczm5ym2D3PCTvzjIjvA68ZeZ8H\n0OURkd4D3MwmpU5pSS+imV2VbgW+C5wqaZek9zWrLDObDCK7bC+ydICmnXlGxMXjbF/crLLNrAWC\njgmMRbTFE0azBGf0plX16wdmJJdzYs9oPavGdnx3f3IZjw/0JOeZ1/1Kcp5pXemXTztfPio5zysD\nac/DVyoNXOwMpOeZ9lJ6D40Gvk66Eh/Vn7kv/XsZnpU+5kD3cGIQK+PZduiY+5lFeA4jMytNmf08\nJa2U9JikPklXjbJdkm7It2+TdOZ4eSUdLekeSY/n/x6Vr18s6aCkrfmyrra8Wg6eZlaeku55SuoG\nbgRWAcuAiyUtq0m2imyitqVkfcJvKpD3KuCbEbEU+Gb+fsQTEXFGvqwdr44OnmZWjggYrhRbxrcC\n6IuIHRExANwGrK5Jsxq4OTKbgXmSFoyTdzXwxfz1F4FfbfRwHTzNrDzFzzznS9pStayp2dNCYGfV\n+135uiK4nFQVAAAHl0lEQVRp6uU9Lp+eGOBp4LiqdEvyS/Z/lvQL4x1qWzQYmVmbKN7avjciljez\nKuOJiJA0UuE9wIkRsU/Sm4CvSjotIl4cK7/PPM2sHAFUotgyvt3ACVXvF+XriqSpl/eZ/NKe/N9n\nASKiPyL25a/vB54ATqlXQQdPMytJQFSKLeO7D1gqaYmkXuAiYGNNmo3AJXmr+9nA/vySvF7ejcCl\n+etLgb8FkHRs3tCEpJPJGqF21KugL9vNrBxB0cag8XcVMSTpSuAuoBvYEBHbJa3Nt68DNgHnA33A\nAeDyennzXV8H3J4/8fgU8Fv5+rcDn5A0SNZbdW1E1O347eBpZuUp8QmjiNhEFiCr162reh3AFUXz\n5uv3AeeOsv4O4I6U+jl4mll5/HimmVmqzhn0owgHTzMrRwBTaEi6tgieQnQrrWPAu2emD6bxk8qr\nSen/6WBtn93xdTcwcsK/HlicnOfUOc8k53lxIH0wldk9A0npH963ILkM9U9Mp5BXThxOznPkD9IG\nIBnuTR+AozIjfZCTafsTf2dlnTH6zNPMLFWU1treDhw8zawcAVGsD2dHaOZI8hskPSvpoZr175f0\nqKTtkj7drPLNrAXKe8Jo0mvmmecXgM8CN4+skHQO2agmPxcR/ZJeM0ZeM2tHvud5+CLi25IW16z+\nj8B1EdGfp3m2WeWb2QSLmFKt7RP9bPspwC9Iujcf9unNYyWUtGZkuKrn9qW3gppZC3gCuKaWdzRw\nNvBmsmdMT84fszpERKwH1gMs/7kZnfFpm3W0IIanzonORAfPXcBX8mD5PUkVYD7w3ATXw8zKNjIk\n3RQx0ZftXwXOAZB0CtAL7J3gOphZs5Q3JN2k17QzT0m3Au8gG25/F/AxYAOwIe++NABcOtolu5m1\nnwBiCp15NrO1/eIxNr2nWWWaWQtFdMxZZRF+wsjMSjOVGozUDlfNkp4jG/W51nxae8/U5bv8Tin/\npIg49nB2IOkfyOpUxN6IWHk45bVaWwTPsUja0soZ+Fy+y5/K5U91ngDOzKwBDp5mZg1o9+C53uW7\nfJdvrdDW9zzNzFql3c88zcxawsHTzKwBbRE8Ja2U9JikPklXjbJdkm7It2+TdGaJZZ8g6R8lPZyP\nfv+BUdK8Q9J+SVvz5Q/LKj/f/5OSvp/ve8so25t5/KdWHddWSS9K+mBNmlKPf7RZCCQdLekeSY/n\n/x41Rt66v5XDKP/6fAaEbZLulDRvjLx1v6vDKP/jknZXfcbnj5H3sI/fCoqISb0A3cATwMlkA4k8\nCCyrSXM+8PeAyIa7u7fE8hcAZ+avjwB+MEr57wC+1sTP4Elgfp3tTTv+Ub6Lp8k6VDft+IG3A2cC\nD1Wt+zRwVf76KuBTjfxWDqP8dwPT8tefGq38It/VYZT/ceDDBb6fwz5+L8WWdjjzXAH0RcSOiBgA\nbiObyqPaauDmyGwG5klKn+N2FBGxJyIeyF+/BDwCpM853FxNO/4a5wJPRMRoT3uVJiK+DTxfs3o1\n8MX89ReBXx0la5HfSkPlR8TdETGUv90MLErd7+GUX1Apx2/FtEPwXAjsrHq/i58OXkXSHLZ8WpE3\nAveOsvmt+SXd30s6reSiA/iGpPslrRll+4QcP3ARcOsY25p5/ADHRcSe/PXTwHGjpJmoz+G9ZGf6\noxnvuzoc788/4w1j3LaYqOM32iN4TgqS5gB3AB+MiBdrNj8AnBgRpwOfIRu3tExvi4gzgFXAFZLe\nXvL+xyWpF7gQ+F+jbG728R8ismvUlvSxk3QNMATcMkaSZn1XN5Fdjp8B7AH+a0n7tQa1Q/DcDZxQ\n9X5Rvi41TcMk9ZAFzlsi4iu12yPixYh4OX+9CeiRVHSAhHFFxO7832eBO8kuz6o19fhzq4AHIuKZ\nUerX1OPPPTNyKyL/d7TJA5v9O7gMuAD493kA/ykFvquGRMQzETEc2cTo/2OM/U7E78By7RA87wOW\nSlqSn/1cBGysSbMRuCRvdT4b2F91iXdYJAn4PPBIRPzZGGlem6dD0gqyz3VfSeXPlnTEyGuyhouH\napI17firXMwYl+zNPP4qG4FL89eXAn87Spoiv5WGSFoJfAS4MCIOjJGmyHfVaPnV97B/bYz9Nu34\nbRStbrEqspC1Jv+ArCXxmnzdWmBt/lrAjfn27wPLSyz7bWSXiNuArflyfk35VwLbyVo3NwNvLbH8\nk/P9PpiXMaHHn+9/NlkwPLJqXdOOnyxI7wEGye7bvQ84Bvgm8DjwDeDoPO3xwKZ6v5WSyu8ju584\n8htYV1v+WN9VSeX/Vf7dbiMLiAuadfxeii1+PNPMrAHtcNluZjbpOHiamTXAwdPMrAEOnmZmDXDw\nNDNrgIOnmVkDHDzNzBrg4GmlkfTmfOCKGfnTNtslvaHV9TJrBneSt1JJ+iQwA5gJ7IqIP2lxlcya\nwsHTSpU/U30f8CrZY5rDLa6SWVP4st3Kdgwwh2zU/RktrotZ0/jM00olaSPZCOZLyAavuLLFVTJr\nimmtroB1DkmXAIMR8deSuoF/kfTOiPhWq+tmVjafeZqZNcD3PM3MGuDgaWbWAAdPM7MGOHiamTXA\nwdPMrAEOnmZmDXDwNDNrwP8FKaipC7UCf1sAAAAASUVORK5CYII=\n", + "image/png": "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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -1098,12 +1075,12 @@ "output_type": "stream", "text": [ "Tally\n", - "\tID =\t10001\n", + "\tID =\t2\n", "\tName =\tcell tally\n", "\tFilters =\tCellFilter\n", "\tNuclides =\tU235 U238 \n", - "\tScores =\t['scatter-Y0,0', 'scatter-Y1,-1', 'scatter-Y1,0', 'scatter-Y1,1', 'scatter-Y2,-2', 'scatter-Y2,-1', 'scatter-Y2,0', 'scatter-Y2,1', 'scatter-Y2,2']\n", - "\tEstimator =\tanalog\n", + "\tScores =\t['scatter']\n", + "\tEstimator =\ttracklength\n", "\n" ] } @@ -1125,18 +1102,18 @@ "data": { "text/html": [ "
\n", - "\n", "
412110.00e+006.25e-01fission
512110.00e+006.25e-01nu-fission
612116.25e-012.00e+07fission
712116.25e-012.00e+07nu-fission
813110.00e+006.25e-01fission
913110.00e+006.25e-01nu-fission
1013116.25e-012.00e+07fission
1113116.25e-012.00e+07nu-fission
1214110.00e+006.25e-01fission
1314110.00e+006.25e-01nu-fission
1414116.25e-012.00e+07fission
1514116.25e-012.00e+07nu-fission
1615110.00e+006.25e-01fission
1715110.00e+006.25e-01nu-fission
1815116.25e-012.00e+07fission
1915116.25e-012.00e+07nu-fission
\n", " \n", @@ -1152,172 +1129,28 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", "
0100001U235scatter-Y0,03.86e-026.85e-04scatter3.81e-021.65e-04
110000U235scatter-Y1,-16.95e-043.15e-04
210000U235scatter-Y1,0-1.06e-043.79e-04
310000U235scatter-Y1,1-3.63e-043.18e-04
410000U235scatter-Y2,-21.20e-041.59e-04
510000U235scatter-Y2,-13.93e-051.86e-04
610000U235scatter-Y2,01.81e-041.85e-04
710000U235scatter-Y2,11.24e-041.81e-04
810000U235scatter-Y2,22.06e-042.26e-04
9100001U238scatter-Y0,0scatter2.33e+001.10e-02
1010000U238scatter-Y1,-12.90e-022.33e-03
1110000U238scatter-Y1,03.45e-032.38e-03
1210000U238scatter-Y1,1-2.72e-022.76e-03
1310000U238scatter-Y2,-2-2.02e-031.44e-03
1410000U238scatter-Y2,-18.07e-061.49e-03
1510000U238scatter-Y2,0-3.74e-071.79e-03
1610000U238scatter-Y2,16.54e-041.49e-03
1710000U238scatter-Y2,2-1.93e-031.36e-039.59e-03
\n", "
" ], "text/plain": [ - " cell nuclide score mean std. dev.\n", - "0 10000 U235 scatter-Y0,0 3.86e-02 6.85e-04\n", - "1 10000 U235 scatter-Y1,-1 6.95e-04 3.15e-04\n", - "2 10000 U235 scatter-Y1,0 -1.06e-04 3.79e-04\n", - "3 10000 U235 scatter-Y1,1 -3.63e-04 3.18e-04\n", - "4 10000 U235 scatter-Y2,-2 1.20e-04 1.59e-04\n", - "5 10000 U235 scatter-Y2,-1 3.93e-05 1.86e-04\n", - "6 10000 U235 scatter-Y2,0 1.81e-04 1.85e-04\n", - "7 10000 U235 scatter-Y2,1 1.24e-04 1.81e-04\n", - "8 10000 U235 scatter-Y2,2 2.06e-04 2.26e-04\n", - "9 10000 U238 scatter-Y0,0 2.33e+00 1.10e-02\n", - "10 10000 U238 scatter-Y1,-1 2.90e-02 2.33e-03\n", - "11 10000 U238 scatter-Y1,0 3.45e-03 2.38e-03\n", - "12 10000 U238 scatter-Y1,1 -2.72e-02 2.76e-03\n", - "13 10000 U238 scatter-Y2,-2 -2.02e-03 1.44e-03\n", - "14 10000 U238 scatter-Y2,-1 8.07e-06 1.49e-03\n", - "15 10000 U238 scatter-Y2,0 -3.74e-07 1.79e-03\n", - "16 10000 U238 scatter-Y2,1 6.54e-04 1.49e-03\n", - "17 10000 U238 scatter-Y2,2 -1.93e-03 1.36e-03" + " cell nuclide score mean std. dev.\n", + "0 1 U235 scatter 3.81e-02 1.65e-04\n", + "1 1 U238 scatter 2.33e+00 9.59e-03" ] }, "execution_count": 26, @@ -1349,15 +1182,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "[[[ 0.00136183 0.01104314]\n", - " [ 0.00022601 0.00068479]]]\n" + "[[[0.00958717]\n", + " [0.00016469]]]\n" ] } ], "source": [ - "# Get the standard deviations for two of the spherical harmonic\n", - "# scattering reaction rates \n", - "data = tally.get_values(scores=['scatter-Y2,2', 'scatter-Y0,0'], \n", + "# Get the standard deviations the total scattering rate\n", + "data = tally.get_values(scores=['scatter'], \n", " nuclides=['U238', 'U235'], value='std_dev')\n", "print(data)" ] @@ -1379,7 +1211,7 @@ "output_type": "stream", "text": [ "Tally\n", - "\tID =\t10002\n", + "\tID =\t3\n", "\tName =\tdistribcell tally\n", "\tFilters =\tDistribcellFilter\n", "\tNuclides =\ttotal \n", @@ -1413,25 +1245,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "[[[ 0.03500496]]\n", + "[[[0.03500496]]\n", "\n", - " [[ 0.03004793]]\n", + " [[0.02745568]]\n", "\n", - " [[ 0.02536586]]\n", + " [[0.02988488]]\n", "\n", - " [[ 0.03403647]]\n", + " [[0.04474905]]\n", "\n", - " [[ 0.02498 ]]\n", + " [[0.03697764]]\n", "\n", - " [[ 0.01892844]]\n", + " [[0.0409214 ]]\n", "\n", - " [[ 0.02662923]]\n", + " [[0.03366461]]\n", "\n", - " [[ 0.02875671]]\n", + " [[0.03210393]]\n", "\n", - " [[ 0.01945598]]\n", + " [[0.03216398]]\n", "\n", - " [[ 0.02612378]]]\n" + " [[0.04003553]]]\n" ] } ], @@ -1459,18 +1291,18 @@ "data": { "text/html": [ "
\n", - "\n", "\n", " \n", @@ -1514,265 +1346,265 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -1780,13 +1612,13 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -1797,51 +1629,51 @@ "" ], "text/plain": [ - " level 1 level 2 level 3 distribcell score \\\n", - " univ cell lat univ cell \n", - " id id id x y id id \n", - "558 10002 10003 10001 16 7 10000 10002 279 absorption \n", - "559 10002 10003 10001 16 7 10000 10002 279 scatter \n", - "560 10002 10003 10001 16 8 10000 10002 280 absorption \n", - "561 10002 10003 10001 16 8 10000 10002 280 scatter \n", - "562 10002 10003 10001 16 9 10000 10002 281 absorption \n", - "563 10002 10003 10001 16 9 10000 10002 281 scatter \n", - "564 10002 10003 10001 16 10 10000 10002 282 absorption \n", - "565 10002 10003 10001 16 10 10000 10002 282 scatter \n", - "566 10002 10003 10001 16 11 10000 10002 283 absorption \n", - "567 10002 10003 10001 16 11 10000 10002 283 scatter \n", - "568 10002 10003 10001 16 12 10000 10002 284 absorption \n", - "569 10002 10003 10001 16 12 10000 10002 284 scatter \n", - "570 10002 10003 10001 16 13 10000 10002 285 absorption \n", - "571 10002 10003 10001 16 13 10000 10002 285 scatter \n", - "572 10002 10003 10001 16 14 10000 10002 286 absorption \n", - "573 10002 10003 10001 16 14 10000 10002 286 scatter \n", - "574 10002 10003 10001 16 15 10000 10002 287 absorption \n", - "575 10002 10003 10001 16 15 10000 10002 287 scatter \n", - "576 10002 10003 10001 16 16 10000 10002 288 absorption \n", - "577 10002 10003 10001 16 16 10000 10002 288 scatter \n", + " level 1 level 2 level 3 distribcell score \\\n", + " univ cell lat univ cell \n", + " id id id x y id id \n", + "558 3 4 2 7 16 1 3 279 absorption \n", + "559 3 4 2 7 16 1 3 279 scatter \n", + "560 3 4 2 8 16 1 3 280 absorption \n", + "561 3 4 2 8 16 1 3 280 scatter \n", + "562 3 4 2 9 16 1 3 281 absorption \n", + "563 3 4 2 9 16 1 3 281 scatter \n", + "564 3 4 2 10 16 1 3 282 absorption \n", + "565 3 4 2 10 16 1 3 282 scatter \n", + "566 3 4 2 11 16 1 3 283 absorption \n", + "567 3 4 2 11 16 1 3 283 scatter \n", + "568 3 4 2 12 16 1 3 284 absorption \n", + "569 3 4 2 12 16 1 3 284 scatter \n", + "570 3 4 2 13 16 1 3 285 absorption \n", + "571 3 4 2 13 16 1 3 285 scatter \n", + "572 3 4 2 14 16 1 3 286 absorption \n", + "573 3 4 2 14 16 1 3 286 scatter \n", + "574 3 4 2 15 16 1 3 287 absorption \n", + "575 3 4 2 15 16 1 3 287 scatter \n", + "576 3 4 2 16 16 1 3 288 absorption \n", + "577 3 4 2 16 16 1 3 288 scatter \n", "\n", " mean std. dev. \n", " \n", " \n", - "558 7.77e-05 7.87e-06 \n", - "559 1.28e-02 5.09e-04 \n", - "560 8.92e-05 7.32e-06 \n", - "561 1.37e-02 4.99e-04 \n", - "562 9.50e-05 7.80e-06 \n", - "563 1.49e-02 4.74e-04 \n", - "564 1.15e-04 1.00e-05 \n", - "565 1.58e-02 6.18e-04 \n", - "566 1.13e-04 1.01e-05 \n", - "567 1.75e-02 5.66e-04 \n", - "568 1.08e-04 9.66e-06 \n", - "569 1.73e-02 5.40e-04 \n", - "570 1.16e-04 1.44e-05 \n", - "571 1.70e-02 6.90e-04 \n", - "572 1.16e-04 1.02e-05 \n", - "573 1.77e-02 6.80e-04 \n", - "574 1.20e-04 1.36e-05 \n", - "575 1.80e-02 7.80e-04 \n", + "558 6.81e-04 2.84e-05 \n", + "559 8.82e-02 1.86e-03 \n", + "560 6.65e-04 3.46e-05 \n", + "561 8.37e-02 2.02e-03 \n", + "562 5.61e-04 2.91e-05 \n", + "563 7.52e-02 1.79e-03 \n", + "564 4.77e-04 2.33e-05 \n", + "565 6.68e-02 1.14e-03 \n", + "566 4.64e-04 2.05e-05 \n", + "567 6.20e-02 1.61e-03 \n", + "568 4.44e-04 2.93e-05 \n", + "569 5.47e-02 1.53e-03 \n", + "570 3.67e-04 2.63e-05 \n", + "571 4.68e-02 1.52e-03 \n", + "572 2.76e-04 1.75e-05 \n", + "573 3.81e-02 1.28e-03 \n", + "574 2.08e-04 1.69e-05 \n", + "575 2.85e-02 1.13e-03 \n", "576 1.32e-04 1.30e-05 \n", "577 1.86e-02 7.12e-04 " ] @@ -1868,18 +1700,18 @@ "data": { "text/html": [ "
\n", - "\n", "
55810002100031000116342710000100021613279absorption7.77e-057.87e-066.81e-042.84e-05
55910002100031000116342710000100021613279scatter1.28e-025.09e-048.82e-021.86e-03
56010002100031000116342810000100021613280absorption8.92e-057.32e-066.65e-043.46e-05
56110002100031000116342810000100021613280scatter1.37e-024.99e-048.37e-022.02e-03
56210002100031000116342910000100021613281absorption9.50e-057.80e-065.61e-042.91e-05
56310002100031000116342910000100021613281scatter1.49e-024.74e-047.52e-021.79e-03
564100021000310001163421010000100021613282absorption1.15e-041.00e-054.77e-042.33e-05
565100021000310001163421010000100021613282scatter1.58e-026.18e-046.68e-021.14e-03
566100021000310001163421110000100021613283absorption1.13e-041.01e-054.64e-042.05e-05
567100021000310001163421110000100021613283scatter1.75e-025.66e-046.20e-021.61e-03
568100021000310001163421210000100021613284absorption1.08e-049.66e-064.44e-042.93e-05
569100021000310001163421210000100021613284scatter1.73e-025.40e-045.47e-021.53e-03
570100021000310001163421310000100021613285absorption1.16e-041.44e-053.67e-042.63e-05
571100021000310001163421310000100021613285scatter1.70e-026.90e-044.68e-021.52e-03
572100021000310001163421410000100021613286absorption1.16e-041.02e-052.76e-041.75e-05
573100021000310001163421410000100021613286scatter1.77e-026.80e-043.81e-021.28e-03
574100021000310001163421510000100021613287absorption1.20e-041.36e-052.08e-041.69e-05
575100021000310001163421510000100021613287scatter1.80e-027.80e-042.85e-021.13e-03
5761000210003100013421616100001000213288absorption1.32e-04
5771000210003100013421616100001000213288scatter1.86e-02
\n", " \n", @@ -2058,7 +1890,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/johnny/miniconda3/lib/python3.6/site-packages/ipykernel_launcher.py:4: SettingWithCopyWarning: \n", + "/home/jan/.local/lib/python3.6/site-packages/ipykernel_launcher.py:4: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", @@ -2069,7 +1901,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -2078,12 +1910,14 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -2105,7 +1939,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2114,12 +1948,14 @@ }, { "data": { - "image/png": "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\n1llnccIJJ/D444/nuLpo6IhARHLvHyPgswU1u8y9usLgUTucZMuWLZxyyilMnz6dAw88cIfT9urV\ni8WLF297fd5559GgQQMAjj/+eG677bbq11xLKAhEJDYKCwvp168fDz30EHfeeecOp93+Xi353DSk\nIBCR3NvJJ/dsSSQSjB8/noEDB3Lrrbdy/fXXVzrt22+/nbf/+Len7whEJFZ22203Jk2axLhx43jo\noYcqnObpp5/mpZde4pxzzslxddHQEYGIxE6zZs144YUXOProo7edFXTHHXcwduxYNm7cyCGHHMK0\nadO2jYP//o6gRYsWTJkyJZLas0FBICKxsWHDhm3P27Vrx7JlywAYNmwYI0eOrHS+6dOnZ7myaKlp\nSEQk5nREILWGLkATiYaOCEREYk5BICIScwoCEZGYUxCIiMScviwWkUhUdnJAVWVyUkGjRo3+6xTS\nRx55hNmzZ3P33Xdz7733sttuu3HhhRdWOO/06dMpKiqiX79+NVZzbZG1IDCzdsBfgVaAA/e7+51m\n1gx4CugILAe+6+5fZ6sOEZFMXHrppTscP336dBo1alQjQVBWVkZBQe35HJ7NpqEy4Bp37wL0BS43\nsy7ACGCqu3cCpoavRUQiNXLkSG6//XYA7rrrLrp06UK3bt04++yzWb58Offeey933HEHPXr04LXX\nXmP58uUce+yxdOvWjYEDB/LRRx8B8OGHH9K3b1+6du3KjTfeuO1mONOnT+eoo45i2LBhdOnSBYBT\nTjmFQw89lIMPPpj7779/Wy2NGjXiuuuu4+CDD+a4445j1qxZ9O/fn3333ZcJEybU+LZnLZLc/VPg\n0/D5ejNbBLQBTgb6h5M9CkwHfpGtOkREttq0aRM9evTY9vqrr75i2LBh35pu1KhRLFu2jHr16rFm\nzRqaNm3KpZdeSqNGjbbdvGbo0KEMHz6c4cOHM2bMGK688kqeffZZrrrqKq666irOOecc7r333v9a\n7ty5c3n33XfZZ599ABgzZgzNmjVj06ZN9OnTh9NPP53mzZuzceNGjj32WG677TZOPfVUbrzxRl5+\n+WUWLlzI8OHDK6y5OnLyZbGZdQR6Am8CrcKQAPiMoOlIRCTrGjRowDvvvLPtcfPNN1c4Xbdu3Tjv\nvPMYO3ZspU04M2fO5NxzzwXgggsuYMaMGduGn3nmmQDbxm912GGHbQsBCI48unfvTt++fVm5ciVL\n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\n", + "image/png": "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B4sla9oOn1zu8BUjTs1xJSJfN2XKFK666ipeeOEF2rRpA8A111zDddddB8BTTz3F8ccfz4IFC2jWLOihYezYsfTo0SOymrNJewQiEiuvvvoql1xyCZMmTeLgg79xW2wAzjnnHE466SSeeOKJHFcXDe0RiEju/XM4fLKgepe5fycYOGKXk2zbto3TTz+dadOmceihh+5y2u7du7NkyZIdr88//3zq1asHwIknnsidd95Z9ZprCAWBiMRGYWEhvXv35uGHH+aee+7Z5bQ736sln5uGFAQiknu7+eaeLYlEgvHjx9O/f3/uuOMObrzxxgqnffvtt/P2g39nOkYgIrGy1157MXnyZMaOHcvDDz9c7jRPP/00L774IkOHDs1xddHQHoGIxE7jxo15/vnnOe6443acFXT33XczZswYNm/ezBFHHMHUqVN3jIOvHyNo2rQpL7/8ciS1Z4OCQERiY9OmTTuet2rViuXLlwMwePBgbrnllgrnmzZtWpYri5aahkREYk57BFJj6AI0kWhoj0BEJOYUBCIiMacgEBGJOQWBiEjM6WCxiESiopMDKiuTkwoaNGjwtVNIH330UWbNmsV9993HqFGj2GuvvbjooovKnXfatGkUFRXRu3fvaqu5pshaEJhZK+CvQHPAgQfd/R4zaww8BbQFVgDfdfcvs1WHiEgmLrvssl2OnzZtGg0aNKiWICgtLaWgoOZ8D89m01ApcK27dwR6AVeYWUdgODDF3dsDU8LXIiKRuuWWW7jrrrsAuPfee+nYsSOdO3fm3HPPZcWKFYwaNYq7776brl278tprr7FixQqOP/54OnfuTP/+/fnggw8AeP/99+nVqxedOnXi5ptv3nEznGnTptGnTx8GDx5Mx44dATj99NM58sgjOfzww3nwwQd31NKgQQOuv/56Dj/8cE444QRmzpxJ3759adeuHRMnTqz2dc9aJLn7x8DH4fONZrYYOBA4DegbTvYYMA34RbbqEBHZbsuWLXTt2nXH6y+++ILBgwd/Y7oRI0awfPly6tSpw7p169hnn3247LLLaNCgwY6b1wwaNIhhw4YxbNgwRo8ezVVXXcUzzzzD1VdfzdVXX83QoUMZNWrU15Y7Z84cFi5cyEEHHQTA6NGjady4MVu2bKFnz56cddZZNGnShM2bN3P88cdz5513csYZZ3DzzTfz0ksvsWjRIoYN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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -2150,7 +1986,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.0" + "version": "3.6.6" } }, "nbformat": 4, diff --git a/include/openmc/capi.h b/include/openmc/capi.h index c48b212bd7..3ead7d8339 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -3,6 +3,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -48,9 +49,9 @@ extern "C" { int64_t openmc_get_seed(); int openmc_get_tally_index(int32_t id, int32_t* index); void openmc_get_tally_next_id(int32_t* id); + int openmc_global_tallies(double** ptr); int openmc_hard_reset(); int openmc_init(int argc, char* argv[], const void* intracomm); - int openmc_init_f(const int* intracomm); int openmc_legendre_filter_get_order(int32_t index, int* order); int openmc_legendre_filter_set_order(int32_t index, int order); int openmc_load_nuclide(const char name[]); @@ -94,7 +95,7 @@ extern "C" { int openmc_sphharm_filter_get_cosine(int32_t index, char cosine[]); int openmc_sphharm_filter_set_order(int32_t index, int order); int openmc_sphharm_filter_set_cosine(int32_t index, const char cosine[]); - int openmc_statepoint_write(const char filename[]); + int openmc_statepoint_write(const char filename[], bool* write_source); int openmc_tally_allocate(int32_t index, const char* type); int openmc_tally_get_active(int32_t index, bool* active); int openmc_tally_get_estimator(int32_t index, int32_t* estimator); @@ -105,7 +106,7 @@ extern "C" { int openmc_tally_get_scores(int32_t index, int** scores, int* n); int openmc_tally_get_type(int32_t index, int32_t* type); int openmc_tally_reset(int32_t index); - int openmc_tally_results(int32_t index, double** ptr, int shape_[3]); + int openmc_tally_results(int32_t index, double** ptr, size_t shape_[3]); int openmc_tally_set_active(int32_t index, bool active); int openmc_tally_set_estimator(int32_t index, const char* estimator); int openmc_tally_set_filters(int32_t index, int n, const int32_t* indices); @@ -134,10 +135,7 @@ extern "C" { // Global variables extern char openmc_err_msg[256]; - extern double openmc_keff; - extern double openmc_keff_std; extern int32_t n_cells; - extern int32_t n_filters; extern int32_t n_lattices; extern int32_t n_materials; extern int n_nuclides; @@ -148,15 +146,12 @@ extern "C" { extern int32_t n_surfaces; extern int32_t n_tallies; extern int32_t n_universes; - extern bool openmc_simulation_initialized; // Variables that are shared by necessity (can be removed from public header // later) extern bool openmc_master; extern int openmc_n_procs; - extern int openmc_n_threads; extern int openmc_rank; - extern int64_t openmc_work; #ifdef __cplusplus } diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 9c96258fb5..75da02c0a6 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -62,6 +62,7 @@ constexpr int MAX_SAMPLE {100000}; // Maximum number of words in a single line, length of line, and length of // single word +constexpr int MAX_LINE_LEN {250}; constexpr int MAX_WORD_LEN {150}; // Maximum number of external source spatial resamples to encounter before an @@ -314,9 +315,9 @@ constexpr int MG_GET_XS_CHI_DELAYED {14}; // TALLY-RELATED CONSTANTS // Tally result entries -constexpr int RESULT_VALUE {1}; -constexpr int RESULT_SUM {2}; -constexpr int RESULT_SUM_SQ {3}; +constexpr int RESULT_VALUE {0}; +constexpr int RESULT_SUM {1}; +constexpr int RESULT_SUM_SQ {2}; // Tally type // TODO: Convert to enum @@ -407,10 +408,11 @@ constexpr int RELATIVE_ERROR {2}; constexpr int STANDARD_DEVIATION {3}; // Global tally parameters -constexpr int K_COLLISION {1}; -constexpr int K_ABSORPTION {2}; -constexpr int K_TRACKLENGTH {3}; -constexpr int LEAKAGE {4}; +constexpr int N_GLOBAL_TALLIES {4}; +constexpr int K_COLLISION {0}; +constexpr int K_ABSORPTION {1}; +constexpr int K_TRACKLENGTH {2}; +constexpr int LEAKAGE {3}; // Differential tally independent variables constexpr int DIFF_DENSITY {1}; @@ -418,6 +420,7 @@ constexpr int DIFF_NUCLIDE_DENSITY {2}; constexpr int DIFF_TEMPERATURE {3}; constexpr int C_NONE {-1}; +constexpr int F90_NONE {0}; //TODO: replace usage of this with C_NONE // Interpolation rules enum class Interpolation { diff --git a/include/openmc/container_util.h b/include/openmc/container_util.h new file mode 100644 index 0000000000..1e65a18df6 --- /dev/null +++ b/include/openmc/container_util.h @@ -0,0 +1,17 @@ +#ifndef OPENMC_CONTAINER_UTIL_H +#define OPENMC_CONTAINER_UTIL_H + +#include // for find +#include // for begin, end + +namespace openmc { + +template +inline bool contains(const C& v, const T& x) +{ + return std::end(v) != std::find(std::begin(v), std::end(v), x); +} + +} + +#endif // OPENMC_CONTAINER_UTIL_H diff --git a/include/openmc/eigenvalue.h b/include/openmc/eigenvalue.h index cc879461ed..b7a75becbe 100644 --- a/include/openmc/eigenvalue.h +++ b/include/openmc/eigenvalue.h @@ -1,6 +1,10 @@ +//! \file eigenvalue.h +//! \brief Data/functions related to k-eigenvalue calculations + #ifndef OPENMC_EIGENVALUE_H #define OPENMC_EIGENVALUE_H +#include #include // for int64_t #include @@ -14,6 +18,8 @@ namespace openmc { // Global variables //============================================================================== +extern double keff_generation; //!< Single-generation k on each processor +extern std::array k_sum; //!< Used to reduce sum and sum_sq extern std::vector entropy; //!< Shannon entropy at each generation extern xt::xtensor source_frac; //!< Source fraction for UFS @@ -24,6 +30,34 @@ extern "C" int64_t n_bank; // Non-member functions //============================================================================== +//! Collect/normalize the tracklength keff from each process +extern "C" void calculate_generation_keff(); + +//! Calculate mean/standard deviation of keff during active generations +//! +//! This function sets the global variables keff and keff_std which represent +//! the mean and standard deviation of the mean of k-effective over active +//! generations. It also broadcasts the value from the master process. +extern "C" void calculate_average_keff(); + + +//! Calculates a minimum variance estimate of k-effective +//! +//! The minimum variance estimate is 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 follows that +//! described in T. Urbatsch et al., "Estimation and interpretation of keff +//! confidence intervals in MCNP," Nucl. Technol., 111, 169-182 (1995). +//! +//! \param[out] k_combined Estimate of k-effective and its standard deviation +//! \return Error status +extern "C" int openmc_get_keff(double* k_combined); + +//! Sample/redistribute source sites from accumulated fission sites +extern "C" void synchronize_bank(); + //! Calculates the Shannon entropy of the fission source distribution to assess //! source convergence extern "C" void shannon_entropy(); diff --git a/include/openmc/error.h b/include/openmc/error.h index 17b2f392ed..b02c0a9f85 100644 --- a/include/openmc/error.h +++ b/include/openmc/error.h @@ -9,7 +9,6 @@ namespace openmc { - extern "C" void fatal_error_from_c(const char* message, int message_len); extern "C" void warning_from_c(const char* message, int message_len); extern "C" void write_message_from_c(const char* message, int message_len, @@ -81,5 +80,9 @@ void write_message(const std::stringstream& message, int level) write_message(message.str(), level); } +#ifdef OPENMC_MPI +extern "C" void abort_mpi(int code); +#endif + } // namespace openmc #endif // OPENMC_ERROR_H diff --git a/include/openmc/finalize.h b/include/openmc/finalize.h index 58cf75606d..f99201abe7 100644 --- a/include/openmc/finalize.h +++ b/include/openmc/finalize.h @@ -1,6 +1,8 @@ #ifndef OPENMC_FINALIZE_H #define OPENMC_FINALIZE_H -extern "C" void openmc_free_bank(); +namespace openmc { + +} // namespace openmc #endif // OPENMC_FINALIZE_H diff --git a/include/openmc/geometry_aux.h b/include/openmc/geometry_aux.h index ac68f5b00e..da55c3d9b8 100644 --- a/include/openmc/geometry_aux.h +++ b/include/openmc/geometry_aux.h @@ -5,6 +5,7 @@ #define OPENMC_GEOMETRY_AUX_H #include +#include namespace openmc { @@ -26,7 +27,7 @@ extern "C" void assign_temperatures(); //! //! This function looks for a universe that is not listed in a Cell::fill or in //! a Lattice. -//! @return The index of the root universe. +//! \return The index of the root universe. //============================================================================== extern "C" int32_t find_root_universe(); @@ -41,14 +42,14 @@ extern "C" void neighbor_lists(); //! Populate all data structures needed for distribcells. //============================================================================== -extern "C" void prepare_distribcell(int32_t* filter_cell_list, int n); +extern "C" void prepare_distribcell(); //============================================================================== //! Recursively search through the geometry and count cell instances. //! //! This function will update the Cell::n_instances value for each cell in the //! geometry. -//! @param univ_indx The index of the universe to begin searching from (probably +//! \param univ_indx The index of the universe to begin searching from (probably //! the root universe). //============================================================================== @@ -56,51 +57,33 @@ extern "C" void count_cell_instances(int32_t univ_indx); //============================================================================== //! Recursively search through universes and count universe instances. -//! @param search_univ The index of the universe to begin searching from. -//! @param target_univ_id The ID of the universe to be counted. -//! @return The number of instances of target_univ_id in the geometry tree under +//! \param search_univ The index of the universe to begin searching from. +//! \param target_univ_id The ID of the universe to be counted. +//! \return The number of instances of target_univ_id in the geometry tree under //! search_univ. //============================================================================== extern "C" int count_universe_instances(int32_t search_univ, int32_t target_univ_id); -//============================================================================== -//! Find the length necessary for a string to contain a distribcell path. -//! @param target_cell The index of the Cell in the global Cell array. -//! @param map The index of the distribcell mapping corresponding to the target -//! cell. -//! @param target_offset An instance number for a distributed cell. -//! @param root_univ The index of the root Universe in the global Universe -//! array. -//! @return The size of a character array needed to fit the distribcell path. -//============================================================================== - -extern "C" int -distribcell_path_len(int32_t target_cell, int32_t map, int32_t target_offset, - int32_t root_univ); - //============================================================================== //! Build a character array representing the path to a distribcell instance. -//! @param target_cell The index of the Cell in the global Cell array. -//! @param map The index of the distribcell mapping corresponding to the target +//! \param target_cell The index of the Cell in the global Cell array. +//! \param map The index of the distribcell mapping corresponding to the target //! cell. -//! @param target_offset An instance number for a distributed cell. -//! @param root_univ The index of the root Universe in the global Universe -//! array. -//! @param[out] path The unique traversal through the geometry tree that leads -//! to the desired instance of the target cell. +//! \param target_offset An instance number for a distributed cell. +//! \return The unique traversal through the geometry tree that leads to the +//! desired instance of the target cell. //============================================================================== -extern "C" void -distribcell_path(int32_t target_cell, int32_t map, int32_t target_offset, - int32_t root_univ, char *path); +std::string +distribcell_path(int32_t target_cell, int32_t map, int32_t target_offset); //============================================================================== //! Determine the maximum number of nested coordinate levels in the geometry. -//! @param univ The index of the universe to begin seraching from (probably the +//! \param univ The index of the universe to begin seraching from (probably the //! root universe). -//! @return The number of coordinate levels. +//! \return The number of coordinate levels. //============================================================================== extern "C" int maximum_levels(int32_t univ); diff --git a/include/openmc/hdf5_interface.h b/include/openmc/hdf5_interface.h index fa64605513..313b841e20 100644 --- a/include/openmc/hdf5_interface.h +++ b/include/openmc/hdf5_interface.h @@ -8,6 +8,7 @@ #include // for strlen #include #include +#include #include #include "hdf5.h" @@ -51,7 +52,7 @@ void write_string(hid_t group_id, const char* name, const std::string& buffer, std::vector attribute_shape(hid_t obj_id, const char* name); std::vector dataset_names(hid_t group_id); -void ensure_exists(hid_t group_id, const char* name); +void ensure_exists(hid_t obj_id, const char* name, bool attribute=false); std::vector group_names(hid_t group_id); std::vector object_shape(hid_t obj_id); std::string object_name(hid_t obj_id); @@ -201,7 +202,13 @@ read_attribute(hid_t obj_id, const char* name, std::vector& vec) read_attr_string(obj_id, name, n, buffer[0]); for (int i = 0; i < m; ++i) { - vec.emplace_back(&buffer[i][0], std::min(strlen(buffer[i]), n)); + // Determine proper length of string -- strlen doesn't work because + // buffer[i] might not have any null characters + std::size_t k = 0; + for (; k < n; ++k) if (buffer[i][k] == '\0') break; + + // Create string based on (char*, size_t) constructor + vec.emplace_back(&buffer[i][0], k); } } @@ -333,7 +340,9 @@ write_attribute(hid_t obj_id, const char* name, const std::vector& buffer) // Templates/overloads for write_dataset //============================================================================== -template inline void +// Template for scalars (ensured by SFINAE) +template inline +std::enable_if_t>::value> write_dataset(hid_t obj_id, const char* name, T buffer) { write_dataset(obj_id, 0, nullptr, name, H5TypeMap::type_id, &buffer, false); @@ -359,9 +368,11 @@ write_dataset(hid_t obj_id, const char* name, const std::vector& buffer) write_dataset(obj_id, 1, dims, name, H5TypeMap::type_id, buffer.data(), false); } -template inline void -write_dataset(hid_t obj_id, const char* name, const xt::xarray& arr) +// Template for xarray, xtensor, etc. +template inline void +write_dataset(hid_t obj_id, const char* name, const xt::xcontainer& arr) { + using T = typename D::value_type; auto s = arr.shape(); std::vector dims {s.cbegin(), s.cend()}; write_dataset(obj_id, dims.size(), dims.data(), name, H5TypeMap::type_id, @@ -375,5 +386,11 @@ write_dataset(hid_t obj_id, const char* name, Position r) write_dataset(obj_id, name, buffer); } +inline void +write_dataset(hid_t obj_id, const char* name, std::string buffer) +{ + write_string(obj_id, name, buffer.c_str(), false); +} + } // namespace openmc #endif // OPENMC_HDF5_INTERFACE_H diff --git a/include/openmc/initialize.h b/include/openmc/initialize.h index 72ccc12f68..d2f4ff0c67 100644 --- a/include/openmc/initialize.h +++ b/include/openmc/initialize.h @@ -5,9 +5,6 @@ #include "mpi.h" #endif -extern "C" void print_usage(); -extern "C" void print_version(); - namespace openmc { int parse_command_line(int argc, char* argv[]); diff --git a/include/openmc/material.h b/include/openmc/material.h index 1af6f85976..b6d7e19cdb 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -24,8 +24,9 @@ extern std::unordered_map material_map; class Material { public: - int32_t id; //!< Unique ID + int32_t id_; //!< Unique ID double volume_ {-1.0}; //!< Volume in [cm^3] + bool fissionable {false}; //!< Does this material contain fissionable nuclides //! \brief Default temperature for cells containing this material. //! diff --git a/include/openmc/math_functions.h b/include/openmc/math_functions.h index a3c9c07cf0..ca0646961b 100644 --- a/include/openmc/math_functions.h +++ b/include/openmc/math_functions.h @@ -22,7 +22,7 @@ namespace openmc { //! @return The requested percentile //============================================================================== -extern "C" double normal_percentile_c(double p); +extern "C" double normal_percentile(double p); //============================================================================== //! Calculate the percentile of the Student's t distribution with a specified @@ -58,7 +58,7 @@ extern "C" void calc_pn_c(int n, double x, double pnx[]); //! evaluated at x //============================================================================== -extern "C" double evaluate_legendre_c(int n, const double data[], double x); +extern "C" double evaluate_legendre(int n, const double data[], double x); //============================================================================== //! Calculate the n-th order real spherical harmonics for a given angle (in @@ -90,7 +90,7 @@ extern "C" void calc_rn_c(int n, const double uvw[3], double rn[]); //! evaluated at rho and phi. //============================================================================== -extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); +extern "C" void calc_zn(int n, double rho, double phi, double zn[]); //============================================================================== //! Calculate only the even radial components of n-th order modified Zernike @@ -113,7 +113,7 @@ extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); //! evaluated at rho and phi when m = 0. //============================================================================== -extern "C" void calc_zn_rad_c(int n, double rho, double zn_rad[]); +extern "C" void calc_zn_rad(int n, double rho, double zn_rad[]); //============================================================================== //! Rotate the direction cosines through a polar angle whose cosine is mu and @@ -144,7 +144,7 @@ Direction rotate_angle(Direction u, double mu, double* phi); //! @result The sampled outgoing energy //============================================================================== -extern "C" double maxwell_spectrum_c(double T); +extern "C" double maxwell_spectrum(double T); //============================================================================== //! Samples an energy from a Watt energy-dependent fission distribution. @@ -159,7 +159,7 @@ extern "C" double maxwell_spectrum_c(double T); //! @result The sampled outgoing energy //============================================================================== -extern "C" double watt_spectrum_c(double a, double b); +extern "C" double watt_spectrum(double a, double b); //============================================================================== //! Doppler broadens the windowed multipole curvefit. diff --git a/include/openmc/message_passing.h b/include/openmc/message_passing.h index c910210db5..c730674ab4 100644 --- a/include/openmc/message_passing.h +++ b/include/openmc/message_passing.h @@ -2,7 +2,7 @@ #define OPENMC_MESSAGE_PASSING_H #ifdef OPENMC_MPI -#include "mpi.h" +#include #endif namespace openmc { diff --git a/include/openmc/mgxs_interface.h b/include/openmc/mgxs_interface.h index 02a43fece4..c813755111 100644 --- a/include/openmc/mgxs_interface.h +++ b/include/openmc/mgxs_interface.h @@ -7,6 +7,7 @@ #include "hdf5_interface.h" #include "mgxs.h" +#include namespace openmc { @@ -17,6 +18,9 @@ namespace openmc { extern std::vector nuclides_MG; extern std::vector macro_xs; extern "C" int num_energy_groups; +extern std::vector energy_bins; +extern std::vector energy_bin_avg; +extern std::vector rev_energy_bins; //============================================================================== // Mgxs data loading interface methods @@ -36,6 +40,8 @@ create_macro_xs_c(const char* mat_name, int n_nuclides, const int i_nuclides[], int n_temps, const double temps[], const double atom_densities[], double tolerance, int& method); +extern "C" void read_mg_cross_sections_header_c(hid_t file_id); + //============================================================================== // Mgxs tracking/transport/tallying interface methods //============================================================================== @@ -44,13 +50,6 @@ extern "C" void calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs); -extern "C" void -sample_scatter_c(int i_mat, int gin, int& gout, double& mu, double& wgt, - double uvw[3]); - -extern "C" void -sample_fission_energy_c(int i_mat, int gin, int& dg, int& gout); - extern "C" double get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg); diff --git a/include/openmc/output.h b/include/openmc/output.h index 1ba5d65991..6d6d6ba0f4 100644 --- a/include/openmc/output.h +++ b/include/openmc/output.h @@ -4,6 +4,7 @@ #ifndef OPENMC_OUTPUT_H #define OPENMC_OUTPUT_H +#include namespace openmc { @@ -16,11 +17,26 @@ namespace openmc { void header(const char* msg, int level); + +//============================================================================== +//! Retrieve a time stamp +//! +//! \return current time stamp (format: "yyyy-mm-dd hh:mm:ss") +//============================================================================== + +std::string time_stamp(); + +//============================================================================== +//! Display plot information +//============================================================================== + +extern "C" void print_plot(); + //============================================================================== //! Display information regarding cell overlap checking. //============================================================================== -extern "C" void print_overlap_check(); +void print_overlap_check(); extern "C" void title(); diff --git a/include/openmc/particle.h b/include/openmc/particle.h index 298b2878a0..6f2d58832b 100644 --- a/include/openmc/particle.h +++ b/include/openmc/particle.h @@ -146,9 +146,7 @@ extern "C" { //! site may have been produced from an external source, from fission, or //! simply as a secondary particle. //! \param src Source site data - //! \param run_CE Whether continuous-energy data is being used - //! \param energy_bin_avg An array of energy group bin averages - void from_source(const Bank* src, bool run_CE, const double* energy_bin_avg); + void from_source(const Bank* src); //! mark a particle as lost and create a particle restart file //! \param message A warning message to display @@ -174,8 +172,7 @@ extern "C" { void particle_create_secondary(Particle* p, const double* uvw, double E, int type, bool run_CE); void particle_initialize(Particle* p); - void particle_from_source(Particle* p, const Bank* src, bool run_CE, - const double* energy_bin_avg); + void particle_from_source(Particle* p, const Bank* src); void particle_mark_as_lost(Particle* p, const char* message); void particle_write_restart(Particle* p); diff --git a/include/openmc/physics_common.h b/include/openmc/physics_common.h new file mode 100644 index 0000000000..33fb8ed614 --- /dev/null +++ b/include/openmc/physics_common.h @@ -0,0 +1,16 @@ +//! \file physics_common.h +//! A collection of physics methods common to MG, CE, photon, etc. + +#ifndef OPENMC_PHYSICS_COMMON_H +#define OPENMC_PHYSICS_COMMON_H + +#include "openmc/particle.h" + +namespace openmc { + +//! \brief Performs the russian roulette operation for a particle +extern "C" void +russian_roulette(Particle* p); + +} // namespace openmc +#endif // OPENMC_PHYSICS_COMMON_H diff --git a/include/openmc/physics_mg.h b/include/openmc/physics_mg.h new file mode 100644 index 0000000000..14b7e6cbd5 --- /dev/null +++ b/include/openmc/physics_mg.h @@ -0,0 +1,60 @@ +//! \file physics_mg.h +//! Methods needed to perform the collision physics for multi-group mode + +#ifndef OPENMC_PHYSICS_MG_H +#define OPENMC_PHYSICS_MG_H + +#include "openmc/capi.h" +#include "openmc/particle.h" +#include "openmc/nuclide.h" + +namespace openmc { + +//TODO: Remove energy_bin_avg and material_xs parameters when they reside on +// the C-side this should happen after materials, physics, input, and tallies +// are brought over + +//! \brief samples particle behavior after a collision event. +//! \param p Particle to operate on +//! \param energy_bin_avg Average energy within each energy bin +//! \param material_xs The cross section cache for the current material +extern "C" void +collision_mg(Particle* p, const double* energy_bin_avg, + const MaterialMacroXS* material_xs); + +//! \brief samples a reaction type. +//! +//! Note that there is special logic when suvival biasing is turned on since +//! fission and disappearance are treated implicitly. +//! \param p Particle to operate on +//! \param energy_bin_avg Average energy within each energy bin +//! \param material_xs The cross section cache for the current material +void +sample_reaction(Particle* p, const double* energy_bin_avg, + const MaterialMacroXS* material_xs); + +//! \brief Samples the scattering event +//! \param p Particle to operate on +//! \param energy_bin_avg Average energy within each energy bin +void +scatter(Particle* p, const double* energy_bin_avg); + +//! \brief Determines the average total, prompt and delayed neutrons produced +//! from fission and creates the appropriate bank sites. +//! \param p Particle to operate on +//! \param bank_array The particle bank to populate +//! \param size_bank Number of particles currently in the bank +//! \param bank_array_size Allocated size of the bank +//! \param material_xs The cross section cache for the current material +void +create_fission_sites(Particle* p, Bank* bank_array, int64_t* size_bank, + int64_t bank_array_size, const MaterialMacroXS* material_xs); + +//! \brief Handles an absorption event +//! \param p Particle to operate on +//! \param material_xs The cross section cache for the current material +void +absorption(Particle* p, const MaterialMacroXS* material_xs); + +} // namespace openmc +#endif // OPENMC_PHYSICS_MG_H diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 2048189f79..64c092ce8a 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -1,15 +1,181 @@ #ifndef OPENMC_PLOT_H #define OPENMC_PLOT_H +#include +#include + +#include "xtensor/xarray.hpp" + #include "hdf5.h" +#include "openmc/position.h" +#include "openmc/constants.h" +#include "openmc/particle.h" +#include "openmc/xml_interface.h" namespace openmc { -extern "C" void voxel_init(hid_t file_id, const hsize_t* dims, hid_t* dspace, +//=============================================================================== +// Global variables +//=============================================================================== + +extern int PLOT_LEVEL_LOWEST; //!< lower bound on plot universe level + +extern std::unordered_map plot_map; //!< map of plot ids to index + +extern "C" int32_t n_plots; //!< number of plots in openmc run + +class Plot; +extern std::vector plots; //!< Plot instance container + +//=============================================================================== +// RGBColor holds color information for plotted objects +//=============================================================================== + +struct RGBColor { + //Constructors + RGBColor() : red(0), green(0), blue(0) { }; + RGBColor(const int v[3]) : red(v[0]), green(v[1]), blue(v[2]) { }; + RGBColor(int r, int g, int b) : red(r), green(g), blue(b) { }; + + RGBColor(const std::vector &v) { + if (v.size() != 3) { + throw std::out_of_range("Incorrect vector size for RGBColor."); + } + red = v[0]; + green = v[1]; + blue = v[2]; + } + + // Members + uint8_t red, green, blue; +}; + +typedef xt::xtensor ImageData; + +enum class PlotType { + slice = 1, + voxel = 2 +}; + +enum class PlotBasis { + xy = 1, + xz = 2, + yz = 3 +}; + +enum class PlotColorBy { + cells = 1, + mats = 2 +}; + +//=============================================================================== +// Plot class +//=============================================================================== + +class Plot +{ + +public: + // Constructor + Plot(pugi::xml_node plot); + + // Methods +private: + void set_id(pugi::xml_node plot_node); + void set_type(pugi::xml_node plot_node); + void set_output_path(pugi::xml_node plot_node); + void set_bg_color(pugi::xml_node plot_node); + void set_basis(pugi::xml_node plot_node); + void set_origin(pugi::xml_node plot_node); + void set_width(pugi::xml_node plot_node); + void set_universe(pugi::xml_node plot_node); + void set_default_colors(pugi::xml_node plot_node); + void set_user_colors(pugi::xml_node plot_node); + void set_meshlines(pugi::xml_node plot_node); + void set_mask(pugi::xml_node plot_node); + + // Members +public: + int id_; //!< Plot ID + PlotType type_; //!< Plot type (Slice/Voxel) + PlotColorBy color_by_; //!< Plot coloring (cell/material) + Position origin_; //!< Plot origin in geometry + Position width_; //!< Plot width in geometry + PlotBasis basis_; //!< Plot basis (XY/XZ/YZ) + std::array pixels_; //!< Plot size in pixels + int meshlines_width_; //!< Width of lines added to the plot + int level_; //!< Plot universe level + int index_meshlines_mesh_; //!< Index of the mesh to draw on the plot + RGBColor meshlines_color_; //!< Color of meshlines on the plot + RGBColor not_found_; //!< Plot background color + std::vector colors_; //!< Plot colors + std::string path_plot_; //!< Plot output filename +}; + +//=============================================================================== +// Non-member functions +//=============================================================================== + +//! Add mesh lines to image data of a plot object +//! \param[in] plot object +//! \param[out] image data associated with the plot object +void draw_mesh_lines(Plot pl, ImageData& data); + +//! Write a ppm image to file using a plot object's image data +//! \param[in] plot object +//! \param[out] image data associated with the plot object +void output_ppm(Plot pl, + const ImageData& data); + +//! Get the rgb color for a given particle position in a plot +//! \param[in] particle with position for current pixel +//! \param[in] plot object +//! \param[out] rgb color +//! \param[out] cell or material id for particle position +void position_rgb(Particle p, Plot pl, RGBColor& rgb, int& id); + +//! Initialize a voxel file +//! \param[in] id of an open hdf5 file +//! \param[in] dimensions of the voxel file (dx, dy, dz) +//! \param[out] dataspace pointer to voxel data +//! \param[out] dataset pointer to voxesl data +//! \param[out] pointer to memory space of voxel data +void voxel_init(hid_t file_id, const hsize_t* dims, hid_t* dspace, hid_t* dset, hid_t* memspace); -extern "C" void voxel_write_slice(int x, hid_t dspace, hid_t dset, + +//! Write a section of the voxel data to hdf5 +//! \param[in] voxel slice +//! \param[out] dataspace pointer to voxel data +//! \param[out] dataset pointer to voxesl data +//! \param[out] pointer to data to write +void voxel_write_slice(int x, hid_t dspace, hid_t dset, hid_t memspace, void* buf); -extern "C" void voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace); +//! Close voxel file entities +//! \param[in] data space to close +//! \param[in] dataset to close +//! \param[in] memory space to close +void voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace); + +//=============================================================================== +// External functions +//=============================================================================== + +//! Read plots from plots.xml node +//! \param[in] plot node of plots.xml +extern "C" void read_plots(pugi::xml_node* plot_node); + +//! Create a ppm image for a plot object +//! \param[in] plot object +void create_ppm(Plot pl); + +//! Create an hdf5 voxel file for a plot object +//! \param[in] plot object +void create_voxel(Plot pl); + +//! Create a randomly generated RGB color +//! \return RGBColor with random value +RGBColor random_color(); + } // namespace openmc #endif // OPENMC_PLOT_H diff --git a/include/openmc/progress_bar.h b/include/openmc/progress_bar.h new file mode 100644 index 0000000000..18a3eea47f --- /dev/null +++ b/include/openmc/progress_bar.h @@ -0,0 +1,22 @@ +#ifndef OPENMC_PROGRESSBAR_H +#define OPENMC_PROGRESSBAR_H + +#include + +class ProgressBar { + +public: + // Constructor + ProgressBar(); + + void set_value(double val); + +private: + std::string bar; + char bar_old[72] = "???% | |"; + +}; + + +#endif // OPENMC_PROGRESSBAR_H + diff --git a/include/openmc/random_lcg.h b/include/openmc/random_lcg.h index b02deec2c5..8fce803924 100644 --- a/include/openmc/random_lcg.h +++ b/include/openmc/random_lcg.h @@ -17,6 +17,7 @@ extern "C" const int STREAM_SOURCE; extern "C" const int STREAM_URR_PTABLE; extern "C" const int STREAM_VOLUME; extern "C" const int STREAM_PHOTON; +constexpr int64_t DEFAULT_SEED = 1; //============================================================================== //! Generate a pseudo-random number using a linear congruential generator. diff --git a/include/openmc/search.h b/include/openmc/search.h index c91ad18fe1..446e2916aa 100644 --- a/include/openmc/search.h +++ b/include/openmc/search.h @@ -4,7 +4,7 @@ #ifndef OPENMC_SEARCH_H #define OPENMC_SEARCH_H -#include // for lower_bound +#include // for lower_bound, upper_bound namespace openmc { @@ -19,6 +19,14 @@ lower_bound_index(It first, It last, const T& value) return (index == last) ? -1 : index - first; } +template +typename std::iterator_traits::difference_type +upper_bound_index(It first, It last, const T& value) +{ + It index = std::upper_bound(first, last, value) - 1; + return (index == last) ? -1 : index - first; +} + } // namespace openmc #endif // OPENMC_SEARCH_H diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 6565d3e460..47ca0f0787 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "pugixml.hpp" @@ -59,30 +61,33 @@ extern std::string path_statepoint; //!< path to a statepoint file extern "C" int32_t index_entropy_mesh; //!< Index of entropy mesh in global mesh array extern "C" int32_t index_ufs_mesh; //!< Index of UFS mesh in global mesh array - +extern "C" int32_t index_cmfd_mesh; //!< Index of CMFD mesh in global mesh array + extern "C" int32_t n_batches; //!< number of (inactive+active) batches extern "C" int32_t n_inactive; //!< number of inactive batches extern "C" int32_t gen_per_batch; //!< number of generations per batch extern "C" int64_t n_particles; //!< number of particles per generation extern "C" int electron_treatment; //!< how to treat secondary electrons -extern "C" double energy_cutoff[4]; //!< Energy cutoff in [eV] for each particle type +extern "C" std::array energy_cutoff; //!< Energy cutoff in [eV] for each particle type extern "C" int legendre_to_tabular_points; //!< number of points to convert Legendres extern "C" int max_order; //!< Maximum Legendre order for multigroup data extern "C" int n_log_bins; //!< number of bins for logarithmic energy grid extern "C" int n_max_batches; //!< Maximum number of batches - extern "C" int res_scat_method; //!< resonance upscattering method extern "C" double res_scat_energy_min; //!< Min energy in [eV] for res. upscattering extern "C" double res_scat_energy_max; //!< Max energy in [eV] for res. upscattering extern "C" int run_mode; //!< Run mode (eigenvalue, fixed src, etc.) +extern std::unordered_set sourcepoint_batch; //!< Batches when source should be written +extern std::unordered_set statepoint_batch; //!< Batches when state should be written extern "C" int temperature_method; //!< method for choosing temperatures extern "C" double temperature_tolerance; //!< Tolerance in [K] on choosing temperatures extern "C" double temperature_default; //!< Default T in [K] -extern "C" double temperature_range[2]; //!< Min/max T in [K] over which to load xs +extern "C" std::array temperature_range; //!< Min/max T in [K] over which to load xs extern "C" int trace_batch; //!< Batch to trace particle on extern "C" int trace_gen; //!< Generation to trace particle on extern "C" int64_t trace_particle; //!< Particle ID to enable trace on +extern std::vector> track_identifiers; //!< Particle numbers for writing tracks extern "C" int trigger_batch_interval; //!< Batch interval for triggers extern "C" int verbosity; //!< How verbose to make output extern "C" double weight_cutoff; //!< Weight cutoff for Russian roulette diff --git a/include/openmc/simulation.h b/include/openmc/simulation.h index 9c07790727..2002e95fed 100644 --- a/include/openmc/simulation.h +++ b/include/openmc/simulation.h @@ -4,36 +4,86 @@ #ifndef OPENMC_SIMULATION_H #define OPENMC_SIMULATION_H +#include "openmc/particle.h" + #include #include namespace openmc { +constexpr int STATUS_EXIT_NORMAL {0}; +constexpr int STATUS_EXIT_MAX_BATCH {1}; +constexpr int STATUS_EXIT_ON_TRIGGER {2}; + //============================================================================== -// Global variables +// Global variable declarations //============================================================================== -extern "C" int openmc_current_batch; -extern "C" int openmc_current_gen; -extern "C" int64_t openmc_current_work; -extern "C" int openmc_n_lost_particles; -extern "C" int openmc_total_gen; -extern "C" bool openmc_trace; +namespace simulation { +extern "C" int current_batch; //!< current batch +extern "C" int current_gen; //!< current fission generation +extern "C" int64_t current_work; //!< index in source back of current particle +extern "C" bool initialized; //!< has simulation been initialized? +extern "C" double keff; //!< average k over batches +extern "C" double keff_std; //!< standard deviation of average k +extern "C" double k_col_abs; //!< sum over batches of k_collision * k_absorption +extern "C" double k_col_tra; //!< sum over batches of k_collision * k_tracklength +extern "C" double k_abs_tra; //!< sum over batches of k_absorption * k_tracklength +extern "C" double log_spacing; //!< lethargy spacing for energy grid searches +extern "C" int n_lost_particles; //!< cumulative number of lost particles +extern "C" bool need_depletion_rx; //!< need to calculate depletion rx? +extern "C" int restart_batch; //!< batch at which a restart job resumed +extern "C" bool satisfy_triggers; //!< have tally triggers been satisfied? +extern "C" int total_gen; //!< total number of generations simulated +extern "C" int64_t work; //!< number of particles per process + +extern std::vector k_generation; extern std::vector work_index; -#pragma omp threadprivate(openmc_current_work, openmc_trace) +// Threadprivate variables +extern "C" bool trace; //!< flag to show debug information +#ifdef _OPENMP +extern "C" int n_threads; //!< number of OpenMP threads +extern "C" int thread_id; //!< ID of a given thread +#endif + +#pragma omp threadprivate(current_work, thread_id, trace) + +} // namespace simulation //============================================================================== // Functions //============================================================================== -//! Initialize simulation -extern "C" void openmc_simulation_init_c(); - //! Determine number of particles to transport per process void calculate_work(); +//! Initialize a batch +void initialize_batch(); + +//! Initialize a fission generation +void initialize_generation(); + +void initialize_history(Particle* p, int64_t index_source); + +//! Finalize a batch +//! +//! Handles synchronization and accumulation of tallies, calculation of Shannon +//! entropy, getting single-batch estimate of keff, and turning on tallies when +//! appropriate +void finalize_batch(); + +//! Finalize a fission generation +void finalize_generation(); + +//! Determine overall generation number +extern "C" int overall_generation(); + +#ifdef OPENMC_MPI +void broadcast_results(); +#endif + } // namespace openmc #endif // OPENMC_SIMULATION_H diff --git a/include/openmc/source.h b/include/openmc/source.h index 2e8c9491a2..efc6f12a61 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -58,6 +58,9 @@ extern "C" void initialize_source(); //! \return Sampled source site extern "C" Bank sample_external_source(); +//! Fill source bank at end of generation for fixed source simulations +void fill_source_bank_fixedsource(); + } // namespace openmc #endif // OPENMC_SOURCE_H diff --git a/include/openmc/state_point.h b/include/openmc/state_point.h index 64713ae961..6b019e7b8f 100644 --- a/include/openmc/state_point.h +++ b/include/openmc/state_point.h @@ -9,10 +9,11 @@ namespace openmc { -extern "C" void write_source_bank(hid_t group_id, int64_t* work_index, - Bank* source_bank); -extern "C" void read_source_bank(hid_t group_id, int64_t* work_index, - Bank* source_bank); +void write_source_point(const char* filename); +extern "C" void write_source_bank(hid_t group_id, Bank* source_bank); +extern "C" void read_source_bank(hid_t group_id, Bank* source_bank); +extern "C" void write_tally_results_nr(hid_t file_id); +extern "C" void restart_set_keff(); } // namespace openmc #endif // OPENMC_STATE_POINT_H diff --git a/include/openmc/string_functions.h b/include/openmc/string_functions.h index a3da179a06..a653d1e772 100644 --- a/include/openmc/string_functions.h +++ b/include/openmc/string_functions.h @@ -14,5 +14,7 @@ char* strtrim(char* c_str); void to_lower(std::string& str); +int word_count(std::string const& str); + } // namespace openmc #endif // STRING_FUNCTIONS_H diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h new file mode 100644 index 0000000000..24ce5cb89e --- /dev/null +++ b/include/openmc/tallies/filter.h @@ -0,0 +1,99 @@ +#ifndef OPENMC_TALLIES_FILTER_H +#define OPENMC_TALLIES_FILTER_H + +#include +#include +#include +#include + +#include "openmc/hdf5_interface.h" +#include "openmc/particle.h" +#include "pugixml.hpp" + + +namespace openmc { + +//============================================================================== +//! Stores bins and weights for filtered tally events. +//============================================================================== + +class FilterMatch +{ +public: + std::vector bins_; + std::vector weights_; +}; + +} // namespace openmc + +// Without an explicit instantiation of vector, the Intel compiler +// will complain about the threadprivate directive on filter_matches. Note that +// this has to happen *outside* of the openmc namespace +template class std::vector; + +namespace openmc { + +//============================================================================== +//! Modifies tally score events. +//============================================================================== + +class Filter +{ +public: + virtual ~Filter() = default; + + virtual std::string type() const = 0; + + //! Uses an XML input to fill the filter's data fields. + virtual void from_xml(pugi::xml_node node) = 0; + + //! Matches a tally event to a set of filter bins and weights. + //! + //! \param[out] match will contain the matching bins and corresponding + //! weights; note that there may be zero matching bins + virtual void + get_all_bins(const Particle* p, int estimator, FilterMatch& match) const = 0; + + //! Writes data describing this filter to an HDF5 statepoint group. + virtual void + to_statepoint(hid_t filter_group) const + { + write_dataset(filter_group, "type", type()); + write_dataset(filter_group, "n_bins", n_bins_); + } + + //! Return a string describing a filter bin for the tallies.out file. + // + //! For example, an `EnergyFilter` might return the string + //! "Incoming Energy [0.625E-6, 20.0)". + virtual std::string text_label(int bin) const = 0; + + virtual void initialize() {} + + int n_bins_; +}; + +//============================================================================== +// Global variables +//============================================================================== + +extern "C" int32_t n_filters; + +extern std::vector filter_matches; +#pragma omp threadprivate(filter_matches) + +extern std::vector> tally_filters; + +//============================================================================== + +extern "C" void free_memory_tally_c(); + +//============================================================================== + +// Filter-related Fortran functions that will be called from C++ +extern "C" int verify_filter(int32_t index); +extern "C" Filter* filter_from_f(int32_t index); +extern "C" void filter_update_n_bins(int32_t index); + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_H diff --git a/include/openmc/tallies/filter_azimuthal.h b/include/openmc/tallies/filter_azimuthal.h new file mode 100644 index 0000000000..b896400315 --- /dev/null +++ b/include/openmc/tallies/filter_azimuthal.h @@ -0,0 +1,35 @@ +#ifndef OPENMC_TALLIES_FILTER_AZIMUTHAL_H +#define OPENMC_TALLIES_FILTER_AZIMUTHAL_H + +#include +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Bins the incident neutron azimuthal angle (relative to the global xy-plane). +//============================================================================== + +class AzimuthalFilter : public Filter +{ +public: + ~AzimuthalFilter() = default; + + std::string type() const override {return "azimuthal";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + std::vector bins_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_AZIMUTHAL_H diff --git a/include/openmc/tallies/filter_cell.h b/include/openmc/tallies/filter_cell.h new file mode 100644 index 0000000000..9c654e82ed --- /dev/null +++ b/include/openmc/tallies/filter_cell.h @@ -0,0 +1,42 @@ +#ifndef OPENMC_TALLIES_FILTER_CELL_H +#define OPENMC_TALLIES_FILTER_CELL_H + +#include +#include +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Specifies which geometric cells tally events reside in. +//============================================================================== + +class CellFilter : public Filter +{ +public: + ~CellFilter() = default; + + std::string type() const override {return "cell";} + + void from_xml(pugi::xml_node node) override; + + void initialize() override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + //! The indices of the cells binned by this filter. + std::vector cells_; + + //! A map from cell indices to filter bin indices. + std::unordered_map map_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_CELL_H diff --git a/include/openmc/tallies/filter_cellborn.h b/include/openmc/tallies/filter_cellborn.h new file mode 100644 index 0000000000..400e82c282 --- /dev/null +++ b/include/openmc/tallies/filter_cellborn.h @@ -0,0 +1,26 @@ +#ifndef OPENMC_TALLIES_FILTER_CELLBORN_H +#define OPENMC_TALLIES_FILTER_CELLBORN_H + +#include + +#include "openmc/tallies/filter_cell.h" + +namespace openmc { + +//============================================================================== +//! Specifies which cell the particle was born in. +//============================================================================== + +class CellbornFilter : public CellFilter +{ +public: + std::string type() const override {return "cellborn";} + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + std::string text_label(int bin) const override; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_CELLBORN_H diff --git a/include/openmc/tallies/filter_cellfrom.h b/include/openmc/tallies/filter_cellfrom.h new file mode 100644 index 0000000000..bd3e08dcbb --- /dev/null +++ b/include/openmc/tallies/filter_cellfrom.h @@ -0,0 +1,26 @@ +#ifndef OPENMC_TALLIES_FILTER_CELLFROM_H +#define OPENMC_TALLIES_FILTER_CELLFROM_H + +#include + +#include "openmc/tallies/filter_cell.h" + +namespace openmc { + +//============================================================================== +//! Specifies which geometric cells particles exit when crossing a surface. +//============================================================================== + +class CellFromFilter : public CellFilter +{ +public: + std::string type() const override {return "cellfrom";} + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + std::string text_label(int bin) const override; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_CELLFROM_H diff --git a/include/openmc/tallies/filter_delayedgroup.h b/include/openmc/tallies/filter_delayedgroup.h new file mode 100644 index 0000000000..2f65a50593 --- /dev/null +++ b/include/openmc/tallies/filter_delayedgroup.h @@ -0,0 +1,37 @@ +#ifndef OPENMC_TALLIES_FILTER_DELAYEDGROUP_H +#define OPENMC_TALLIES_FILTER_DELAYEDGROUP_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! 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. +//============================================================================== + +class DelayedGroupFilter : public Filter +{ +public: + ~DelayedGroupFilter() = default; + + std::string type() const override {return "delayedgroup";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + std::vector groups_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_DELAYEDGROUP_H diff --git a/include/openmc/tallies/filter_distribcell.h b/include/openmc/tallies/filter_distribcell.h new file mode 100644 index 0000000000..73857e7cb0 --- /dev/null +++ b/include/openmc/tallies/filter_distribcell.h @@ -0,0 +1,36 @@ +#ifndef OPENMC_TALLIES_FILTER_DISTRIBCELL_H +#define OPENMC_TALLIES_FILTER_DISTRIBCELL_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Specifies which distributed geometric cells tally events reside in. +//============================================================================== + +class DistribcellFilter : public Filter +{ +public: + ~DistribcellFilter() = default; + + std::string type() const override {return "distribcell";} + + void from_xml(pugi::xml_node node) override; + + void initialize() override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + int32_t cell_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_DISTRIBCELL_H diff --git a/include/openmc/tallies/filter_energy.h b/include/openmc/tallies/filter_energy.h new file mode 100644 index 0000000000..025a77c624 --- /dev/null +++ b/include/openmc/tallies/filter_energy.h @@ -0,0 +1,55 @@ +#ifndef OPENMC_TALLIES_FILTER_ENERGY_H +#define OPENMC_TALLIES_FILTER_ENERGY_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Bins the incident neutron energy. +//============================================================================== + +class EnergyFilter : public Filter +{ +public: + ~EnergyFilter() = default; + + std::string type() const override {return "energy";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + std::vector bins_; + + //! True if transport group number can be used directly to get bin number + bool matches_transport_groups_ {false}; +}; + +//============================================================================== +//! Bins the outgoing neutron energy. +//! +//! Only scattering events use the get_all_bins functionality. Nu-fission +//! tallies manually iterate over the filter bins. +//============================================================================== + +class EnergyoutFilter : public EnergyFilter +{ +public: + std::string type() const override {return "energyout";} + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + std::string text_label(int bin) const override; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_ENERGY_H diff --git a/include/openmc/tallies/filter_energyfunc.h b/include/openmc/tallies/filter_energyfunc.h new file mode 100644 index 0000000000..6f51826267 --- /dev/null +++ b/include/openmc/tallies/filter_energyfunc.h @@ -0,0 +1,45 @@ +#ifndef OPENMC_TALLIES_FILTER_ENERGYFUNC_H +#define OPENMC_TALLIES_FILTER_ENERGYFUNC_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Multiplies tally scores by an arbitrary function of incident energy +//! described by a piecewise linear-linear interpolation. +//============================================================================== + +class EnergyFunctionFilter : public Filter +{ +public: + EnergyFunctionFilter() + : Filter {} + { + n_bins_ = 1; + } + + ~EnergyFunctionFilter() = default; + + std::string type() const override {return "energyfunction";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + //! Incident neutron energy interpolation grid. + std::vector energy_; + + //! Interpolant values. + std::vector y_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_ENERGYFUNC_H diff --git a/include/openmc/tallies/filter_legendre.h b/include/openmc/tallies/filter_legendre.h new file mode 100644 index 0000000000..054ba14e79 --- /dev/null +++ b/include/openmc/tallies/filter_legendre.h @@ -0,0 +1,34 @@ +#ifndef OPENMC_TALLIES_FILTER_LEGENDRE_H +#define OPENMC_TALLIES_FILTER_LEGENDRE_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Gives Legendre moments of the change in scattering angle +//============================================================================== + +class LegendreFilter : public Filter +{ +public: + ~LegendreFilter() = default; + + std::string type() const override {return "legendre";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + int order_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_LEGENDRE_H diff --git a/include/openmc/tallies/filter_material.h b/include/openmc/tallies/filter_material.h new file mode 100644 index 0000000000..d272451db5 --- /dev/null +++ b/include/openmc/tallies/filter_material.h @@ -0,0 +1,42 @@ +#ifndef OPENMC_TALLIES_FILTER_MATERIAL_H +#define OPENMC_TALLIES_FILTER_MATERIAL_H + +#include +#include +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Specifies which material tally events reside in. +//============================================================================== + +class MaterialFilter : public Filter +{ +public: + ~MaterialFilter() = default; + + std::string type() const override {return "material";} + + void from_xml(pugi::xml_node node) override; + + void initialize() override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + //! The indices of the materials binned by this filter. + std::vector materials_; + + //! A map from material indices to filter bin indices. + std::unordered_map map_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_MATERIAL_H diff --git a/include/openmc/tallies/filter_mesh.h b/include/openmc/tallies/filter_mesh.h new file mode 100644 index 0000000000..a6caa8f565 --- /dev/null +++ b/include/openmc/tallies/filter_mesh.h @@ -0,0 +1,41 @@ +#ifndef OPENMC_TALLIES_FILTER_MESH_H +#define OPENMC_TALLIES_FILTER_MESH_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! 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. +//============================================================================== + +class MeshFilter : public Filter +{ +public: + ~MeshFilter() = default; + + std::string type() const override {return "mesh";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + virtual int32_t mesh() const {return mesh_;} + + virtual void set_mesh(int32_t mesh); + +protected: + int32_t mesh_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_MESH_H diff --git a/include/openmc/tallies/filter_meshsurface.h b/include/openmc/tallies/filter_meshsurface.h new file mode 100644 index 0000000000..32393cfaca --- /dev/null +++ b/include/openmc/tallies/filter_meshsurface.h @@ -0,0 +1,22 @@ +#ifndef OPENMC_TALLIES_FILTER_MESHSURFACE_H +#define OPENMC_TALLIES_FILTER_MESHSURFACE_H + +#include "openmc/tallies/filter_mesh.h" + +namespace openmc { + +class MeshSurfaceFilter : public MeshFilter +{ +public: + std::string type() const override {return "meshsurface";} + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + std::string text_label(int bin) const override; + + void set_mesh(int32_t mesh) override; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_MESHSURFACE_H diff --git a/include/openmc/tallies/filter_mu.h b/include/openmc/tallies/filter_mu.h new file mode 100644 index 0000000000..68b2f7b023 --- /dev/null +++ b/include/openmc/tallies/filter_mu.h @@ -0,0 +1,35 @@ +#ifndef OPENMC_TALLIES_FILTER_MU_H +#define OPENMC_TALLIES_FILTER_MU_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Bins the incoming-outgoing direction cosine. This is only used for scatter +//! reactions. +//============================================================================== + +class MuFilter : public Filter +{ +public: + ~MuFilter() = default; + + std::string type() const override {return "mu";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + std::vector bins_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_MU_H diff --git a/include/openmc/tallies/filter_particle.h b/include/openmc/tallies/filter_particle.h new file mode 100644 index 0000000000..9f9328ace7 --- /dev/null +++ b/include/openmc/tallies/filter_particle.h @@ -0,0 +1,34 @@ +#ifndef OPENMC_TALLIES_FILTER_PARTICLE_H +#define OPENMC_TALLIES_FILTER_PARTICLE_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Bins by type of particle (e.g. neutron, photon). +//============================================================================== + +class ParticleFilter : public Filter +{ +public: + ~ParticleFilter() = default; + + std::string type() const override {return "particle";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + std::vector particles_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_PARTICLE_H diff --git a/include/openmc/tallies/filter_polar.h b/include/openmc/tallies/filter_polar.h new file mode 100644 index 0000000000..2bcdcd07f4 --- /dev/null +++ b/include/openmc/tallies/filter_polar.h @@ -0,0 +1,35 @@ +#ifndef OPENMC_TALLIES_FILTER_POLAR_H +#define OPENMC_TALLIES_FILTER_POLAR_H + +#include +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Bins the incident neutron polar angle (relative to the global z-axis). +//============================================================================== + +class PolarFilter : public Filter +{ +public: + ~PolarFilter() = default; + + std::string type() const override {return "polar";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + std::vector bins_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_POLAR_H diff --git a/include/openmc/tallies/filter_sph_harm.h b/include/openmc/tallies/filter_sph_harm.h new file mode 100644 index 0000000000..786ef1a587 --- /dev/null +++ b/include/openmc/tallies/filter_sph_harm.h @@ -0,0 +1,42 @@ +#ifndef OPENMC_TALLIES_FILTER_SPH_HARM_H +#define OPENMC_TALLIES_FILTER_SPH_HARM_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//TODO: those integer values are not needed when Fortran interop is removed +enum class SphericalHarmonicsCosine { + scatter = 1, particle = 2 +}; + +//============================================================================== +//! Gives spherical harmonics expansion moments of a tally score +//============================================================================== + +class SphericalHarmonicsFilter : public Filter +{ +public: + ~SphericalHarmonicsFilter() = default; + + std::string type() const override {return "sphericalharmonics";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + int order_; + + //! The type of angle that this filter measures when binning events. + SphericalHarmonicsCosine cosine_ {SphericalHarmonicsCosine::particle}; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_SPH_HARM_H diff --git a/include/openmc/tallies/filter_sptl_legendre.h b/include/openmc/tallies/filter_sptl_legendre.h new file mode 100644 index 0000000000..1b94ecf3db --- /dev/null +++ b/include/openmc/tallies/filter_sptl_legendre.h @@ -0,0 +1,48 @@ +#ifndef OPENMC_TALLIES_FILTER_SPTL_LEGENDRE_H +#define OPENMC_TALLIES_FILTER_SPTL_LEGENDRE_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//TODO: those integer values are not needed when Fortran interop is removed +enum class LegendreAxis { + x = 1, y = 2, z = 3 +}; + +//============================================================================== +//! Gives Legendre moments of the particle's normalized position along an axis +//============================================================================== + +class SpatialLegendreFilter : public Filter +{ +public: + ~SpatialLegendreFilter() = default; + + std::string type() const override {return "spatiallegendre";} + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + int order_; + + //! The Cartesian coordinate axis that the Legendre expansion is applied to. + LegendreAxis axis_; + + //! The minimum coordinate along the reference axis that the expansion covers. + double min_; + + //! The maximum coordinate along the reference axis that the expansion covers. + double max_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_SPTL_LEGENDRE_H diff --git a/include/openmc/tallies/filter_surface.h b/include/openmc/tallies/filter_surface.h new file mode 100644 index 0000000000..ea4dc5b990 --- /dev/null +++ b/include/openmc/tallies/filter_surface.h @@ -0,0 +1,42 @@ +#ifndef OPENMC_TALLIES_FILTER_SURFACE_H +#define OPENMC_TALLIES_FILTER_SURFACE_H + +#include +#include +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Specifies which surface particles are crossing +//============================================================================== + +class SurfaceFilter : public Filter +{ +public: + ~SurfaceFilter() = default; + + std::string type() const override {return "surface";} + + void from_xml(pugi::xml_node node) override; + + void initialize() override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + //! The indices of the surfaces binned by this filter. + std::vector surfaces_; + + //! A map from surface indices to filter bin indices. + std::unordered_map map_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_SURFACE_H diff --git a/include/openmc/tallies/filter_universe.h b/include/openmc/tallies/filter_universe.h new file mode 100644 index 0000000000..3ae0093cbd --- /dev/null +++ b/include/openmc/tallies/filter_universe.h @@ -0,0 +1,42 @@ +#ifndef OPENMC_TALLIES_FILTER_UNIVERSE_H +#define OPENMC_TALLIES_FILTER_UNIVERSE_H + +#include +#include +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Specifies which geometric universes tally events reside in. +//============================================================================== + +class UniverseFilter : public Filter +{ +public: + ~UniverseFilter() = default; + + std::string type() const override {return "universe";} + + void from_xml(pugi::xml_node node) override; + + void initialize() override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + //! The indices of the universes binned by this filter. + std::vector universes_; + + //! A map from universe indices to filter bin indices. + std::unordered_map map_; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_UNIVERSE_H diff --git a/include/openmc/tallies/filter_zernike.h b/include/openmc/tallies/filter_zernike.h new file mode 100644 index 0000000000..19fff13363 --- /dev/null +++ b/include/openmc/tallies/filter_zernike.h @@ -0,0 +1,65 @@ +#ifndef OPENMC_TALLIES_FILTER_ZERNIKE_H +#define OPENMC_TALLIES_FILTER_ZERNIKE_H + +#include + +#include "openmc/tallies/filter.h" + +namespace openmc { + +//============================================================================== +//! Gives Zernike polynomial moments of a particle's position +//============================================================================== + +class ZernikeFilter : public Filter +{ +public: + std::string type() const override {return "zernike";} + + ~ZernikeFilter() = default; + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + int order() const {return order_;} + + virtual void set_order(int order); + + //! Cartesian x coordinate for the origin of this expansion. + double x_; + + //! Cartesian y coordinate for the origin of this expansion. + double y_; + + //! Maximum radius from the origin covered by this expansion. + double r_; + +protected: + int order_; +}; + +//============================================================================== +//! Gives even order radial Zernike polynomial moments of a particle's position +//============================================================================== + +class ZernikeRadialFilter : public ZernikeFilter +{ +public: + std::string type() const override {return "zernikeradial";} + + void get_all_bins(const Particle* p, int estimator, FilterMatch& match) + const override; + + std::string text_label(int bin) const override; + + void set_order(int order) override; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_ZERNIKE_H diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h new file mode 100644 index 0000000000..05b5d141f9 --- /dev/null +++ b/include/openmc/tallies/tally.h @@ -0,0 +1,49 @@ +#ifndef OPENMC_TALLIES_TALLY_H +#define OPENMC_TALLIES_TALLY_H + +#include "openmc/constants.h" + +#include "xtensor/xtensor.hpp" + +namespace openmc { + +//============================================================================== +// Global variable declarations +//============================================================================== + +extern "C" double total_weight; + +// Threadprivate variables +extern "C" double global_tally_absorption; +extern "C" double global_tally_collision; +extern "C" double global_tally_tracklength; +extern "C" double global_tally_leakage; +#pragma omp threadprivate(global_tally_absorption, global_tally_collision, \ + global_tally_tracklength, global_tally_leakage) + +//============================================================================== +// Non-member functions +//============================================================================== + +// Alias for the type returned by xt::adapt(...). N is the dimension of the +// multidimensional array +template +using adaptor_type = xt::xtensor_adaptor, N>; + +//! Get the global tallies as a multidimensional array +//! \return Global tallies array +adaptor_type<2> global_tallies(); + +//! Get tally results as a multidimensional array +//! \param idx Index in tallies array +//! \return Tally results array +adaptor_type<3> tally_results(int idx); + +#ifdef OPENMC_MPI +//! Collect all tally results onto master process +extern "C" void reduce_tally_results(); +#endif + +} // namespace openmc + +#endif // OPENMC_TALLIES_TALLY_H diff --git a/include/openmc/timer.h b/include/openmc/timer.h new file mode 100644 index 0000000000..48b3a9e75d --- /dev/null +++ b/include/openmc/timer.h @@ -0,0 +1,60 @@ +#ifndef OPENMC_TIMER_H +#define OPENMC_TIMER_H + +#include + +namespace openmc { + +//============================================================================== +//! Class for measuring time elapsed +//============================================================================== + +class Timer { +public: + using clock = std::chrono::high_resolution_clock; + + Timer() {}; + + //! Start running the timer + void start (); + + //! Get total elapsed time in seconds + //! \return Elapsed time in [s] + double elapsed(); + + //! Stop running the timer + void stop(); + + //! Stop the timer and reset its elapsed time + void reset(); + +private: + bool running_ {false}; //!< is timer running? + std::chrono::time_point start_; //!< starting point for clock + double elapsed_ {0.0}; //!< elasped time in [s] +}; + +//============================================================================== +// Global variables +//============================================================================== + +extern Timer time_active; +extern Timer time_bank; +extern Timer time_bank_sample; +extern Timer time_bank_sendrecv; +extern Timer time_finalize; +extern Timer time_inactive; +extern Timer time_initialize; +extern Timer time_tallies; +extern Timer time_total; +extern Timer time_transport; + +//============================================================================== +// Non-member functions +//============================================================================== + +void reset_timers(); + +} // namespace openmc + +#endif // OPENMC_TIMER_H diff --git a/include/openmc/xml_interface.h b/include/openmc/xml_interface.h index 85db26faa3..6ab8c148cd 100644 --- a/include/openmc/xml_interface.h +++ b/include/openmc/xml_interface.h @@ -10,7 +10,6 @@ #include "xtensor/xarray.hpp" #include "xtensor/xadapt.hpp" - namespace openmc { inline bool diff --git a/openmc/arithmetic.py b/openmc/arithmetic.py index 4a5e7486a8..91e2f1ed44 100644 --- a/openmc/arithmetic.py +++ b/openmc/arithmetic.py @@ -1,6 +1,6 @@ import sys import copy -from collections import Iterable +from collections.abc import Iterable import numpy as np import pandas as pd diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 5d51fe93bb..6ac7b10153 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -71,7 +71,7 @@ def current_batch(): Current batch of the simulation """ - return c_int.in_dll(_dll, 'openmc_current_batch').value + return c_int.in_dll(_dll, 'current_batch').value def finalize(): @@ -220,8 +220,8 @@ def keff(): return tuple(k) else: # Otherwise, return the tracklength estimator - mean = c_double.in_dll(_dll, 'openmc_keff').value - std_dev = c_double.in_dll(_dll, 'openmc_keff_std').value \ + mean = c_double.in_dll(_dll, 'keff').value + std_dev = c_double.in_dll(_dll, 'keff_std').value \ if n > 1 else np.inf return (mean, std_dev) diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 8d9d794d16..5f6e74bbc8 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -238,13 +238,15 @@ class MaterialFilter(Filter): 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)] + #TODO: fix this off-by-one when materials become 0-indexed + return [Material(index=materials[i]+1) 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)) + #TODO: fix this off-by-one when materials become 0-indexed + bins = (c_int32*n)(*(m._index-1 for m in materials)) _dll.openmc_material_filter_set_bins(self._index, n, bins) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 390ee466ff..34beb585fd 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -12,26 +12,26 @@ _dll.t_percentile_c.argtypes = [c_double, c_int] _dll.calc_pn_c.restype = None _dll.calc_pn_c.argtypes = [c_int, c_double, ndpointer(c_double)] -_dll.evaluate_legendre_c.restype = c_double -_dll.evaluate_legendre_c.argtypes = [c_int, POINTER(c_double), c_double] +_dll.evaluate_legendre.restype = c_double +_dll.evaluate_legendre.argtypes = [c_int, POINTER(c_double), c_double] _dll.calc_rn_c.restype = None _dll.calc_rn_c.argtypes = [c_int, ndpointer(c_double), ndpointer(c_double)] -_dll.calc_zn_c.restype = None -_dll.calc_zn_c.argtypes = [c_int, c_double, c_double, ndpointer(c_double)] +_dll.calc_zn.restype = None +_dll.calc_zn.argtypes = [c_int, c_double, c_double, ndpointer(c_double)] -_dll.calc_zn_rad_c.restype = None -_dll.calc_zn_rad_c.argtypes = [c_int, c_double, ndpointer(c_double)] +_dll.calc_zn_rad.restype = None +_dll.calc_zn_rad.argtypes = [c_int, c_double, ndpointer(c_double)] _dll.rotate_angle_c.restype = None _dll.rotate_angle_c.argtypes = [ndpointer(c_double), c_double, POINTER(c_double)] -_dll.maxwell_spectrum_c.restype = c_double -_dll.maxwell_spectrum_c.argtypes = [c_double] +_dll.maxwell_spectrum.restype = c_double +_dll.maxwell_spectrum.argtypes = [c_double] -_dll.watt_spectrum_c.restype = c_double -_dll.watt_spectrum_c.argtypes = [c_double, c_double] +_dll.watt_spectrum.restype = c_double +_dll.watt_spectrum.argtypes = [c_double, c_double] _dll.broaden_wmp_polynomials_c.restype = None _dll.broaden_wmp_polynomials_c.argtypes = [c_double, c_double, c_int, @@ -100,9 +100,8 @@ def evaluate_legendre(data, x): """ data_arr = np.array(data, dtype=np.float64) - return _dll.evaluate_legendre_c(len(data), - data_arr.ctypes.data_as(POINTER(c_double)), - x) + return _dll.evaluate_legendre(len(data), + data_arr.ctypes.data_as(POINTER(c_double)), x) def calc_rn(n, uvw): @@ -154,7 +153,7 @@ def calc_zn(n, rho, phi): num_bins = ((n + 1) * (n + 2)) // 2 zn = np.zeros(num_bins, dtype=np.float64) - _dll.calc_zn_c(n, rho, phi, zn) + _dll.calc_zn(n, rho, phi, zn) return zn @@ -180,9 +179,9 @@ def calc_zn_rad(n, rho): num_bins = n // 2 + 1 zn_rad = np.zeros(num_bins, dtype=np.float64) - _dll.calc_zn_rad_c(n, rho, zn_rad) + _dll.calc_zn_rad(n, rho, zn_rad) return zn_rad - + def rotate_angle(uvw0, mu, phi=None): """ Rotates direction cosines through a polar angle whose cosine is @@ -231,7 +230,7 @@ def maxwell_spectrum(T): """ - return _dll.maxwell_spectrum_c(T) + return _dll.maxwell_spectrum(T) def watt_spectrum(a, b): @@ -251,7 +250,7 @@ def watt_spectrum(a, b): """ - return _dll.watt_spectrum_c(a, b) + return _dll.watt_spectrum(a, b) def broaden_wmp_polynomials(E, dopp, n): diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 44bf89415b..a414ead456 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -1,5 +1,5 @@ from collections.abc import Mapping -from ctypes import c_int, c_int32, c_double, c_char_p, c_bool, POINTER +from ctypes import c_int, c_int32, c_size_t, c_double, c_char_p, c_bool, POINTER from weakref import WeakValueDictionary import numpy as np @@ -60,7 +60,7 @@ _dll.openmc_tally_reset.argtypes = [c_int32] _dll.openmc_tally_reset.restype = c_int _dll.openmc_tally_reset.errcheck = _error_handler _dll.openmc_tally_results.argtypes = [ - c_int32, POINTER(POINTER(c_double)), POINTER(c_int*3)] + c_int32, POINTER(POINTER(c_double)), POINTER(c_size_t*3)] _dll.openmc_tally_results.restype = c_int _dll.openmc_tally_results.errcheck = _error_handler _dll.openmc_tally_set_active.argtypes = [c_int32, c_bool] @@ -296,9 +296,9 @@ class Tally(_FortranObjectWithID): @property def results(self): data = POINTER(c_double)() - shape = (c_int*3)() + shape = (c_size_t*3)() _dll.openmc_tally_results(self._index, data, shape) - return as_array(data, tuple(shape[::-1])) + return as_array(data, tuple(shape)) @property def scores(self): diff --git a/openmc/data/__init__.py b/openmc/data/__init__.py index dec11cd3a9..9aff59444d 100644 --- a/openmc/data/__init__.py +++ b/openmc/data/__init__.py @@ -4,7 +4,9 @@ HDF5_VERSION_MINOR = 0 HDF5_VERSION = (HDF5_VERSION_MAJOR, HDF5_VERSION_MINOR) # Version of WMP nuclear data format -WMP_VERSION = 'v1.0' +WMP_VERSION_MAJOR = 1 +WMP_VERSION_MINOR = 1 +WMP_VERSION = (WMP_VERSION_MAJOR, WMP_VERSION_MINOR) from .data import * diff --git a/openmc/data/multipole.py b/openmc/data/multipole.py index a0d90ac019..5d34c9ff6e 100644 --- a/openmc/data/multipole.py +++ b/openmc/data/multipole.py @@ -4,7 +4,7 @@ from math import exp, erf, pi, sqrt import h5py import numpy as np -from . import WMP_VERSION +from . import WMP_VERSION, WMP_VERSION_MAJOR from .data import K_BOLTZMANN import openmc.checkvalue as cv from openmc.mixin import EqualityMixin @@ -133,6 +133,8 @@ class WindowedMultipole(EqualityMixin): Parameters ---------- + name : str + Name of the nuclide using the GND naming convention Attributes ---------- @@ -171,7 +173,8 @@ class WindowedMultipole(EqualityMixin): a/E + b/sqrt(E) + c + d sqrt(E) + ... """ - def __init__(self): + def __init__(self, name): + self.name = name self.spacing = None self.sqrtAWR = None self.E_min = None @@ -181,6 +184,10 @@ class WindowedMultipole(EqualityMixin): self.broaden_poly = None self.curvefit = None + @property + def name(self): + return self._name + @property def fit_order(self): return self.curvefit.shape[1] - 1 @@ -221,6 +228,11 @@ class WindowedMultipole(EqualityMixin): def curvefit(self): return self._curvefit + @name.setter + def name(self, name): + cv.check_type('name', name, str) + self._name = name + @spacing.setter def spacing(self, spacing): if spacing is not None: @@ -322,17 +334,25 @@ class WindowedMultipole(EqualityMixin): group = group_or_filename else: h5file = h5py.File(group_or_filename, 'r') - try: - version = h5file['version'].value.decode() - except AttributeError: - version = h5file['version'].value[0].decode() - if version != WMP_VERSION: - raise ValueError('The given WMP data uses version ' - + version + ' whereas your installation of the OpenMC ' - 'Python API expects version ' + WMP_VERSION) - group = h5file['nuclide'] - out = cls() + # Make sure version matches + if 'version' in h5file.attrs: + major, minor = h5file.attrs['version'] + if major != WMP_VERSION_MAJOR: + raise IOError( + 'WMP data format uses version {}. {} whereas your ' + 'installation of the OpenMC Python API expects version ' + '{}.x.'.format(major, minor, WMP_VERSION_MAJOR)) + else: + raise IOError( + 'WMP data does not indicate a version. Your installation of ' + 'the OpenMC Python API expects version {}.x data.' + .format(WMP_VERSION_MAJOR)) + + group = list(h5file.values())[0] + + name = group.name[1:] + out = cls(name) # Read scalars. @@ -478,13 +498,16 @@ class WindowedMultipole(EqualityMixin): fun = np.vectorize(lambda x: self._evaluate(x, T)) return fun(E) - def export_to_hdf5(self, path, libver='earliest'): + def export_to_hdf5(self, path, mode='a', libver='earliest'): """Export windowed multipole data to an HDF5 file. Parameters ---------- path : str Path to write HDF5 file to + mode : {'r', r+', 'w', 'x', 'a'} + Mode that is used to open the HDF5 file. This is the second argument + to the :class:`h5py.File` constructor. libver : {'earliest', 'latest'} Compatibility mode for the HDF5 file. 'latest' will produce files that are less backwards compatible but have performance benefits. @@ -492,12 +515,11 @@ class WindowedMultipole(EqualityMixin): """ # Open file and write version. - with h5py.File(path, 'w', libver=libver) as f: - f.create_dataset('version', (1, ), dtype='S10') - f['version'][:] = WMP_VERSION.encode('ASCII') + with h5py.File(path, mode, libver=libver) as f: + f.attrs['filetype'] = np.string_('data_wmp') + f.attrs['version'] = np.array(WMP_VERSION) - # Make a nuclide group. - g = f.create_group('nuclide') + g = f.create_group(self.name) # Write scalars. g.create_dataset('spacing', data=np.array(self.spacing)) diff --git a/openmc/data/photon.py b/openmc/data/photon.py index 61c903158b..e0f71d5ce5 100644 --- a/openmc/data/photon.py +++ b/openmc/data/photon.py @@ -1,4 +1,5 @@ -from collections import OrderedDict, Mapping, Callable +from collections import OrderedDict +from collections.abc import Mapping, Callable from copy import deepcopy from io import StringIO from numbers import Integral, Real @@ -740,7 +741,7 @@ class IncidentPhoton(EqualityMixin): shell_values.insert(0, None) df = relax.transitions[shell].replace( shell_values, range(len(shell_values))) - sub_group.create_dataset('transitions', data=df.as_matrix()) + sub_group.create_dataset('transitions', data=df.values) # Determine threshold threshold = rx.xs.x[0] diff --git a/openmc/data/reaction.py b/openmc/data/reaction.py index 547208dae6..11807cadca 100644 --- a/openmc/data/reaction.py +++ b/openmc/data/reaction.py @@ -924,7 +924,7 @@ class Reaction(EqualityMixin): rx = cls(mt) rx.q_value = group.attrs['Q_value'] rx.center_of_mass = bool(group.attrs['center_of_mass']) - rx.redundant = bool(group.attrs['redundant']) + rx.redundant = bool(group.attrs.get('redundant', False)) # Read cross section at each temperature for T, Tgroup in group.items(): diff --git a/openmc/data/resonance_covariance.py b/openmc/data/resonance_covariance.py index 300e6dbf60..9e80e52f2c 100644 --- a/openmc/data/resonance_covariance.py +++ b/openmc/data/resonance_covariance.py @@ -1,4 +1,4 @@ -from collections import MutableSequence +from collections.abc import MutableSequence import warnings import io import copy diff --git a/openmc/filter.py b/openmc/filter.py index 6c904e61d1..ef68a4961a 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -1,5 +1,6 @@ from abc import ABCMeta -from collections import Iterable, OrderedDict +from collections import OrderedDict +from collections.abc import Iterable import copy import hashlib from itertools import product diff --git a/openmc/mesh.py b/openmc/mesh.py index f7cba22f8d..f454a6f88c 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from numbers import Real, Integral from xml.etree import ElementTree as ET import sys diff --git a/openmc/mgxs/__init__.py b/openmc/mgxs/__init__.py index 7fd6e0a692..86e9dce9ef 100644 --- a/openmc/mgxs/__init__.py +++ b/openmc/mgxs/__init__.py @@ -1,4 +1,44 @@ +import numpy as np + from openmc.mgxs.groups import EnergyGroups from openmc.mgxs.library import Library from openmc.mgxs.mgxs import * from openmc.mgxs.mdgxs import * + +GROUP_STRUCTURES = {} +"""Dictionary of commonly used energy group structures, including "CASMO-X" (where X +is 2, 4, 8, 16, 25, 40 or 70) from the CASMO_ lattice physics code. + +.. _CASMO: https://www.studsvik.com/SharepointFiles/CASMO-5%20Development%20and%20Applications.pdf + +""" + +GROUP_STRUCTURES['CASMO-2'] = np.array([ + 0., 6.25e-1, 2.e7]) +GROUP_STRUCTURES['CASMO-4'] = np.array([ + 0., 6.25e-1, 5.53e3, 8.21e5, 2.e7]) +GROUP_STRUCTURES['CASMO-8'] = np.array([ + 0., 5.8e-2, 1.4e-1, 2.8e-1, 6.25e-1, 4., 5.53e3, 8.21e5, 2.e7]) +GROUP_STRUCTURES['CASMO-16'] = np.array([ + 0., 3.e-2, 5.8e-2, 1.4e-1, 2.8e-1, 3.5e-1, 6.25e-1, 8.5e-1, + 9.72e-1, 1.02, 1.097, 1.15, 1.3, 4., 5.53e3, 8.21e5, 2.e7]) +GROUP_STRUCTURES['CASMO-25'] = np.array([ + 0., 3.e-2, 5.8e-2, 1.4e-1, 2.8e-1, 3.5e-1, 6.25e-1, 9.72e-1, 1.02, 1.097, + 1.15, 1.855, 4., 9.877, 1.5968e1, 1.4873e2, 5.53e3, 9.118e3, 1.11e5, 5.e5, + 8.21e5, 1.353e6, 2.231e6, 3.679e6, 6.0655e6, 2.e7]) +GROUP_STRUCTURES['CASMO-40'] = np.array([ + 0., 1.5e-2, 3.e-2, 4.2e-2, 5.8e-2, 8.e-2, 1.e-1, 1.4e-1, + 1.8e-1, 2.2e-1, 2.8e-1, 3.5e-1, 6.25e-1, 8.5e-1, 9.5e-1, + 9.72e-1, 1.02, 1.097, 1.15, 1.3, 1.5, 1.855, 2.1, 2.6, 3.3, 4., + 9.877, 1.5968e1, 2.77e1, 4.8052e1, 1.4873e2, 5.53e3, 9.118e3, + 1.11e5, 5.e5, 8.21e5, 1.353e6, 2.231e6, 3.679e6, 6.0655e6, 2.e7]) +GROUP_STRUCTURES['CASMO-70'] = np.array([ + 0., 5.e-3, 1.e-2, 1.5e-2, 2.e-2, 2.5e-2, 3.e-2, 3.5e-2, 4.2e-2, + 5.e-2, 5.8e-2, 6.7e-2, 8.e-2, 1.e-1, 1.4e-1, 1.8e-1, 2.2e-1, + 2.5e-1, 2.8e-1, 3.e-1, 3.2e-1, 3.5e-1, 4.e-1, 5.e-1, 6.25e-1, + 7.8e-1, 8.5e-1, 9.1e-1, 9.5e-1, 9.72e-1, 9.96e-1, 1.02, 1.045, + 1.071, 1.097, 1.123, 1.15, 1.3, 1.5, 1.855, 2.1, 2.6, 3.3, 4., + 9.877, 1.5968e1, 2.77e1, 4.8052e1, 7.5501e1, 1.4873e2, + 3.6726e2, 9.069e2, 1.4251e3, 2.2395e3, 3.5191e3, 5.53e3, + 9.118e3, 1.503e4, 2.478e4, 4.085e4, 6.734e4, 1.11e5, 1.83e5, + 3.025e5, 5.e5, 8.21e5, 1.353e6, 2.231e6, 3.679e6, 6.0655e6, 2.e7]) diff --git a/openmc/mgxs/groups.py b/openmc/mgxs/groups.py index 6dd56e8e7a..338bc4b00c 100644 --- a/openmc/mgxs/groups.py +++ b/openmc/mgxs/groups.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from numbers import Real import copy import sys diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 172d7ddb38..b077d04d1d 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -2774,7 +2774,7 @@ class TransportXS(MGXS): p1_tally = p1_tally.get_slice(filters=[openmc.LegendreFilter], filter_bins=[('P1',)], squeeze=True) - p1_tally.scores = ['scatter-1'] + p1_tally._scores = ['scatter-1'] # Compute total cross section total_xs = self.tallies['total'] / self.tallies['flux (tracklength)'] diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index e315f33140..51c91ef176 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -1840,10 +1840,10 @@ class XSdata(object): mu = np.linspace(-1, 1, xsdata.num_orders) # Evaluate the legendre on the mu grid for imu in range(len(mu)): - new_data[..., imu] = \ - np.sum((l + 0.5) * eval_legendre(l, mu[imu]) * - orig_data[..., l] - for l in range(self.num_orders)) + for l in range(self.num_orders): + new_data[..., imu] += ( + (l + 0.5) * eval_legendre(l, mu[imu]) * + orig_data[..., l]) elif target_format == 'histogram': # This code uses the vectorized integration capabilities @@ -1857,11 +1857,10 @@ class XSdata(object): mu_fine = np.linspace(mu[h_bin], mu[h_bin + 1], _NMU) table_fine = np.zeros(new_data.shape[:-1] + (_NMU,)) for imu in range(len(mu_fine)): - table_fine[..., imu] = \ - np.sum((l + 0.5) * - eval_legendre(l, mu_fine[imu]) * - orig_data[..., l] - for l in range(self.num_orders)) + for l in range(self.num_orders): + table_fine[..., imu] += ((l + 0.5) + * eval_legendre(l, mu_fine[imu]) * + orig_data[..., l]) new_data[..., h_bin] = simps(table_fine, mu_fine) elif self.scatter_format == 'tabular': diff --git a/openmc/model/model.py b/openmc/model/model.py index 7a81292c1a..8583da27a7 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -181,7 +181,8 @@ class Model(object): """Export model to XML files.""" self.settings.export_to_xml() - self.geometry.export_to_xml() + if not self.settings.dagmc: + self.geometry.export_to_xml() # If a materials collection was specified, export it. Otherwise, look # for all materials in the geometry and use that to automatically build diff --git a/openmc/polynomial.py b/openmc/polynomial.py index 8bcb74f68a..ac1ad675d6 100644 --- a/openmc/polynomial.py +++ b/openmc/polynomial.py @@ -1,6 +1,5 @@ import numpy as np import openmc -import openmc.capi as capi from collections.abc import Iterable @@ -74,6 +73,7 @@ class ZernikeRadial(Polynomial): return self._order def __call__(self, r): + import openmc.capi as capi if isinstance(r, Iterable): return [np.sum(self._norm_coef * capi.calc_zn_rad(self.order, r_i)) for r_i in r] diff --git a/openmc/settings.py b/openmc/settings.py index af64af01b4..85b434de57 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -226,8 +226,8 @@ class Settings(object): self._create_fission_neutrons = None self._log_grid_bins = None - self._dagmc = None - + self._dagmc = False + @property def run_mode(self): return self._run_mode @@ -375,7 +375,7 @@ class Settings(object): @property def dagmc(self): return self._dagmc - + @run_mode.setter def run_mode(self, run_mode): cv.check_value('run mode', run_mode, _RUN_MODES) @@ -523,7 +523,7 @@ class Settings(object): def dagmc(self, dagmc): cv.check_type('dagmc geometry', dagmc, bool) self._dagmc = dagmc - + @ptables.setter def ptables(self, ptables): cv.check_type('probability tables', ptables, bool) @@ -959,10 +959,10 @@ class Settings(object): elem.text = str(self._log_grid_bins) def _create_dagmc_subelement(self, root): - if self._dagmc is not None: + if self._dagmc: elem = ET.SubElement(root, "dagmc") - element.text = str(self._dagmc).lower() - + elem.text = str(self._dagmc).lower() + def export_to_xml(self, path='settings.xml'): """Export simulation settings to an XML file. @@ -1011,7 +1011,7 @@ class Settings(object): self._create_create_fission_neutrons_subelement(root_element) self._create_log_grid_bins_subelement(root_element) self._create_dagmc_subelement(root_element) - + # Clean the indentation in the file to be user-readable clean_indentation(root_element) diff --git a/openmc/tallies.py b/openmc/tallies.py index 1e4b142fe1..5bc6494bb5 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -376,7 +376,7 @@ class Tally(IDManagerMixin): def scores(self, scores): cv.check_type('tally scores', scores, MutableSequence) - for i, score in enumerate(scores[:-1]): + for i, score in enumerate(scores): # If the score is already in the Tally, raise an error if score in scores[i+1:]: msg = 'Unable to add a duplicate score "{0}" to Tally ID="{1}" ' \ @@ -390,7 +390,7 @@ class Tally(IDManagerMixin): for deprecated in ['scatter-', 'nu-scatter-', 'scatter-p', 'nu-scatter-p', 'scatter-y', 'nu-scatter-y', 'flux-y', 'total-y']: - if score.startswith(deprecated): + if score.strip().startswith(deprecated): msg = score.strip() + ' is no longer supported.' raise ValueError(msg) scores[i] = score.strip() diff --git a/openmc/universe.py b/openmc/universe.py index 294b114dd2..044da12e35 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -1,4 +1,5 @@ -from collections import OrderedDict, Iterable +from collections import OrderedDict +from collections.abc import Iterable from copy import copy, deepcopy from numbers import Integral, Real import random diff --git a/scripts/openmc-get-multipole-data b/scripts/openmc-get-multipole-data index c8a2d1e9b9..554a18428e 100755 --- a/scripts/openmc-get-multipole-data +++ b/scripts/openmc-get-multipole-data @@ -29,9 +29,9 @@ parser.add_argument('-b', '--batch', action='store_true', args = parser.parse_args() -baseUrl = 'https://github.com/mit-crpg/WMP_Library/releases/download/v1.0/' -files = ['WMP_Library_v1.0.tar.gz'] -checksums = ['22cb675734cfccb278dffd40dcfbf26a'] +baseUrl = 'https://github.com/mit-crpg/WMP_Library/releases/download/v1.1/' +files = ['WMP_Library_v1.1.tar.gz'] +checksums = ['8523895928dd6ba63fba803e3a45d4f3'] block_size = 16384 # ============================================================================== diff --git a/scripts/openmc-plot-mesh-tally b/scripts/openmc-plot-mesh-tally index 0dfb29b9b3..61ada852cb 100755 --- a/scripts/openmc-plot-mesh-tally +++ b/scripts/openmc-plot-mesh-tally @@ -11,8 +11,10 @@ import tkinter.font as font import tkinter.messagebox as messagebox import tkinter.ttk as ttk +import matplotlib +matplotlib.use("TkAgg") from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg -from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg +from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk from matplotlib.figure import Figure import matplotlib.pyplot as plt import numpy as np @@ -56,7 +58,7 @@ class MeshPlotter(tk.Frame): self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) # Create the navigation toolbar, tied to the canvas - self.mpl_toolbar = NavigationToolbar2TkAgg(self.canvas, figureFrame) + self.mpl_toolbar = NavigationToolbar2Tk(self.canvas, figureFrame) self.mpl_toolbar.update() self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) diff --git a/scripts/openmc-voxel-to-silovtk b/scripts/openmc-voxel-to-silovtk deleted file mode 100755 index e0cfc0a5ba..0000000000 --- a/scripts/openmc-voxel-to-silovtk +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 - -import struct -import sys -from argparse import ArgumentParser - -import numpy as np -import h5py - - -def main(): - # Process command line arguments - parser = ArgumentParser() - parser.add_argument('voxel_file', help='Path to voxel file') - parser.add_argument('-o', '--output', action='store', - default='plot', help='Path to output SILO or VTK file.') - parser.add_argument('-s', '--silo', action='store_true', - default=False, help='Flag to convert to SILO instead of VTK.') - args = parser.parse_args() - - # Read data from voxel file - fh = h5py.File(args.voxel_file, 'r') - dimension = fh.attrs['num_voxels'] - width = fh.attrs['voxel_width'] - lower_left = fh.attrs['lower_left'] - voxel_data = fh['data'].value - - nx, ny, nz = dimension - upper_right = lower_left + width*dimension - - if not args.silo: - import vtk - - grid = vtk.vtkImageData() - grid.SetDimensions(nx+1, ny+1, nz+1) - grid.SetOrigin(*lower_left) - grid.SetSpacing(*width) - - data = vtk.vtkDoubleArray() - data.SetName("id") - data.SetNumberOfTuples(nx*ny*nz) - for x in range(nx): - sys.stdout.write(" {}%\r".format(int(x/nx*100))) - sys.stdout.flush() - for y in range(ny): - for z in range(nz): - i = z*nx*ny + y*nx + x - data.SetValue(i, voxel_data[x, y, z]) - grid.GetCellData().AddArray(data) - - writer = vtk.vtkXMLImageDataWriter() - if vtk.vtkVersion.GetVTKMajorVersion() > 5: - writer.SetInputData(grid) - else: - writer.SetInput(grid) - if not args.output.endswith(".vti"): - args.output += ".vti" - writer.SetFileName(args.output) - writer.Write() - - else: - import silomesh - - if not args.output.endswith(".silo"): - args.output += ".silo" - silomesh.init_silo(args.output) - meshparams = list(map(int, dimension)) + list(map(float, lower_left)) + \ - list(map(float, upper_right)) - silomesh.init_mesh('plot', *meshparams) - silomesh.init_var("id") - for x in range(nx): - sys.stdout.write(" {}%\r".format(int(x/nx*100))) - sys.stdout.flush() - for y in range(ny): - for z in range(nz): - silomesh.set_value(float(voxel_data[x, y, z]), - x + 1, y + 1, z + 1) - print() - silomesh.finalize_var() - silomesh.finalize_mesh() - silomesh.finalize_silo() - - -if __name__ == '__main__': - main() diff --git a/scripts/openmc-voxel-to-vtk b/scripts/openmc-voxel-to-vtk new file mode 100755 index 0000000000..c0d8e9a147 --- /dev/null +++ b/scripts/openmc-voxel-to-vtk @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +import struct +import sys +from argparse import ArgumentParser + +import numpy as np +import h5py +import vtk + + +def main(): + # Process command line arguments + parser = ArgumentParser() + parser.add_argument('voxel_file', help='Path to voxel file') + parser.add_argument('-o', '--output', action='store', + default='plot', help='Path to output VTK file.') + args = parser.parse_args() + + # Read data from voxel file + fh = h5py.File(args.voxel_file, 'r') + dimension = fh.attrs['num_voxels'] + width = fh.attrs['voxel_width'] + lower_left = fh.attrs['lower_left'] + voxel_data = fh['data'].value + + nx, ny, nz = dimension + upper_right = lower_left + width*dimension + + grid = vtk.vtkImageData() + grid.SetDimensions(nx+1, ny+1, nz+1) + grid.SetOrigin(*lower_left) + grid.SetSpacing(*width) + + data = vtk.vtkDoubleArray() + data.SetName("id") + data.SetNumberOfTuples(nx*ny*nz) + for x in range(nx): + sys.stdout.write(" {}%\r".format(int(x/nx*100))) + sys.stdout.flush() + for y in range(ny): + for z in range(nz): + i = z*nx*ny + y*nx + x + data.SetValue(i, voxel_data[x, y, z]) + grid.GetCellData().AddArray(data) + + writer = vtk.vtkXMLImageDataWriter() + if vtk.vtkVersion.GetVTKMajorVersion() > 5: + writer.SetInputData(grid) + else: + writer.SetInput(grid) + if not args.output.endswith(".vti"): + args.output += ".vti" + writer.SetFileName(args.output) + writer.Write() + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py index cbf564480f..c0168d8805 100755 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ kwargs = { # Optional dependencies 'extras_require': { 'test': ['pytest', 'pytest-cov'], - 'vtk': ['vtk', 'silomesh'], + 'vtk': ['vtk'], }, } diff --git a/src/api.F90 b/src/api.F90 index 38329ad809..bb99180ccb 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -2,191 +2,20 @@ module openmc_api use, intrinsic :: ISO_C_BINDING - use bank_header, only: openmc_source_bank - use constants, only: K_BOLTZMANN - use eigenvalue, only: k_sum, openmc_get_keff + use constants use error use geometry, only: find_cell - use geometry_header - use hdf5_interface - use material_header - use math - use message_passing - use nuclide_header - use initialize, only: openmc_init_f use particle_header - use plot, only: openmc_plot_geometry - use random_lcg, only: openmc_get_seed, openmc_set_seed - use settings - use simulation_header - use state_point, only: openmc_statepoint_write - use tally_header - use tally_filter_header - use tally_filter - use tally, only: openmc_tally_allocate - use simulation - use string, only: to_f_string - use timer_header - use volume_calc, only: openmc_calculate_volumes - + use string, only: to_str #ifdef DAGMC use dagmc_header, only: free_memory_dagmc #endif implicit none - private - public :: openmc_calculate_volumes - public :: openmc_cell_filter_get_bins - public :: openmc_cell_get_id - public :: openmc_cell_set_id - public :: openmc_energy_filter_get_bins - public :: openmc_energy_filter_set_bins - public :: openmc_extend_filters - public :: openmc_extend_cells - 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_cell - 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_seed - public :: openmc_get_tally_index - public :: openmc_get_tally_next_id - public :: openmc_global_tallies - public :: openmc_hard_reset - public :: openmc_init_f - 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_meshsurface_filter_set_mesh - public :: openmc_next_batch - public :: openmc_nuclide_name - public :: openmc_plot_geometry - public :: openmc_reset - public :: openmc_set_seed - public :: openmc_simulation_finalize - public :: openmc_simulation_init - public :: openmc_source_bank - public :: openmc_tally_allocate - public :: openmc_tally_get_estimator - public :: openmc_tally_get_id - public :: openmc_tally_get_filters - public :: openmc_tally_get_n_realizations - public :: openmc_tally_get_nuclides - public :: openmc_tally_get_scores - public :: openmc_tally_get_type - public :: openmc_tally_results - public :: openmc_tally_set_estimator - 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_FINALIZE frees up memory by deallocating arrays and resetting global -! variables -!=============================================================================== - - function openmc_finalize() result(err) bind(C) - integer(C_INT) :: err - - interface - subroutine openmc_free_bank() bind(C) - end subroutine openmc_free_bank - end interface - - ! Clear results - err = openmc_reset() - - ! Reset global variables - assume_separate = .false. - check_overlaps = .false. - confidence_intervals = .false. - create_fission_neutrons = .true. - electron_treatment = ELECTRON_LED - energy_cutoff(:) = [ZERO, 1000.0_8, ZERO, ZERO] - energy_max(:) = [INFINITY, INFINITY] - energy_min(:) = [ZERO, ZERO] - entropy_on = .false. - gen_per_batch = 1 - index_entropy_mesh = -1 - index_ufs_mesh = -1 - keff = ONE - legendre_to_tabular = .true. - legendre_to_tabular_points = C_NONE - n_batch_interval = 1 - n_lost_particles = 0 - n_particles = -1 - n_source_points = 0 - n_state_points = 0 - n_tallies = 0 - output_summary = .true. - output_tallies = .true. - particle_restart_run = .false. - photon_transport = .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 = -1 - dagmc = .false. - satisfy_triggers = .false. - call openmc_set_seed(DEFAULT_SEED) - 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() - - err = 0 -#ifdef OPENMC_MPI - ! Free all MPI types - call MPI_TYPE_FREE(MPI_BANK, err) - call openmc_free_bank() -#endif - - end function openmc_finalize - !=============================================================================== ! OPENMC_FIND_CELL determines what cell contains a given point in space !=============================================================================== @@ -222,87 +51,25 @@ contains end function openmc_find_cell -!=============================================================================== -! OPENMC_HARD_RESET reset tallies and timers as well as the pseudorandom -! generator state -!=============================================================================== - - function openmc_hard_reset() result(err) bind(C) - integer(C_INT) :: err - - ! Reset all tallies and timers - err = openmc_reset() - - ! Reset total generations and keff guess - keff = ONE - total_gen = 0 - - ! Reset the random number generator state - call openmc_set_seed(DEFAULT_SEED) - end function openmc_hard_reset - -!=============================================================================== -! OPENMC_RESET resets tallies and timers -!=============================================================================== - - function openmc_reset() result(err) bind(C) - integer(C_INT) :: err - - integer :: i - - if (allocated(tallies)) then - do i = 1, size(tallies) - associate (t => tallies(i) % obj) - 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 - if (allocated(global_tallies)) then - global_tallies(:, :) = ZERO - end if - k_col_abs = ZERO - k_col_tra = ZERO - k_abs_tra = ZERO - k_sum(:) = ZERO - - ! 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() - - err = 0 - end function openmc_reset - !=============================================================================== ! FREE_MEMORY deallocates and clears all global allocatable arrays in the ! program !=============================================================================== - subroutine free_memory() + subroutine free_memory() bind(C) + use bank_header use cmfd_header + use geometry_header + use material_header use photon_header - use plot_header use sab_header use settings + use simulation_header use surface_header use tally_derivative_header + use tally_filter_header + use tally_header use trigger_header use volume_header @@ -317,7 +84,6 @@ contains 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() diff --git a/src/cell.cpp b/src/cell.cpp index f851922e1b..65582d7801 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -476,7 +476,7 @@ CSGCell::to_hdf5(hid_t cell_group) const std::vector mat_ids; for (auto i_mat : material_) { if (i_mat != MATERIAL_VOID) { - mat_ids.push_back(materials[i_mat]->id); + mat_ids.push_back(materials[i_mat]->id_); } else { mat_ids.push_back(MATERIAL_VOID); } @@ -649,6 +649,19 @@ read_cells(pugi::xml_node* node) cells.push_back(new CSGCell(cell_node)); } + // Fill the cell map. + for (int i = 0; i < cells.size(); i++) { + int32_t id = cells[i]->id_; + auto search = cell_map.find(id); + if (search == cell_map.end()) { + cell_map[id] = i; + } else { + std::stringstream err_msg; + err_msg << "Two or more cells use the same unique ID: " << id; + fatal_error(err_msg); + } + } + // Populate the Universe vector and map. for (int i = 0; i < cells.size(); i++) { int32_t uid = cells[i]->universe_; @@ -766,7 +779,19 @@ extern "C" { int32_t cell_id(Cell* c) {return c->id_;} - void cell_set_id(Cell* c, int32_t id) {c->id_ = id;} + void + cell_set_id(Cell* c, int32_t id) + { + c->id_ = id; + + // Find the index of this cell and update the cell map. + for (int i = 0; i < cells.size(); i++) { + if (cells[i] == c) { + cell_map[id] = i; + break; + } + } + } int cell_type(Cell* c) {return c->type_;} @@ -789,10 +814,6 @@ extern "C" { int32_t cell_fill(Cell* c) {return c->fill_;} - int32_t cell_n_instances(Cell* c) {return c->n_instances_;} - - int cell_distribcell_index(Cell* c) {return c->distribcell_index_;} - int cell_material_size(Cell* c) {return c->material_.size();} //TODO: off-by-one @@ -807,8 +828,6 @@ extern "C" { double cell_sqrtkT(Cell* c, int i) {return c->sqrtkT_[i];} - int32_t cell_offset(Cell* c, int map) {return c->offset_[map];} - void extend_cells_c(int32_t n) { cells.reserve(cells.size() + n); diff --git a/src/cmfd_data.F90 b/src/cmfd_data.F90 index ef85949b9c..f2fb4dc26b 100644 --- a/src/cmfd_data.F90 +++ b/src/cmfd_data.F90 @@ -99,7 +99,7 @@ contains select type(filt => filters(i_filter_mesh) % obj) type is (MeshFilter) - m = meshes(filt % mesh) + m = meshes(filt % mesh()) end select ! Set mesh widths @@ -154,27 +154,27 @@ contains ! Reset all bins to 1 do l = 1, size(t % filter) - call filter_matches(t % filter(l)) % bins % clear() - call filter_matches(t % filter(l)) % bins % push_back(1) + call filter_matches(t % filter(l)) % bins_clear() + call filter_matches(t % filter(l)) % bins_push_back(1) end do ! Set ijk as mesh indices ijk = (/ i, j, k /) ! Get bin number for mesh indices - filter_matches(i_filter_mesh) % bins % data(1) = & - m % get_bin_from_indices(ijk) + call filter_matches(i_filter_mesh) & + % bins_set_data(1, m % get_bin_from_indices(ijk)) ! Apply energy in filter if (energy_filters) then - filter_matches(i_filter_ein) % bins % data(1) = ng - h + 1 + call filter_matches(i_filter_ein) % bins_set_data(1, ng - h + 1) end if ! Calculate score index from bins score_index = 1 do l = 1, size(t % filter) score_index = score_index + (filter_matches(t % filter(l)) & - % bins % data(1) - 1) * t % stride(l) + % bins_data(1) - 1) * t % stride(l) end do ! Get flux @@ -198,30 +198,30 @@ contains ! Reset all bins to 1 do l = 1, size(t % filter) - call filter_matches(t % filter(l)) % bins % clear() - call filter_matches(t % filter(l)) % bins % push_back(1) + call filter_matches(t % filter(l)) % bins_clear() + call filter_matches(t % filter(l)) % bins_push_back(1) end do ! Set ijk as mesh indices ijk = (/ i, j, k /) ! Get bin number for mesh indices - filter_matches(i_filter_mesh) % bins % data(1) = & - m % get_bin_from_indices(ijk) + call filter_matches(i_filter_mesh) & + % bins_set_data(1, m % get_bin_from_indices(ijk)) if (energy_filters) then ! Apply energy in filter - filter_matches(i_filter_ein) % bins % data(1) = ng - h + 1 + call filter_matches(i_filter_ein) % bins_set_data(1, ng - h + 1) ! Set energy out bin - filter_matches(i_filter_eout) % bins % data(1) = ng - g + 1 + call filter_matches(i_filter_eout) % bins_set_data(1, ng - g + 1) end if ! Calculate score index from bins score_index = 1 do l = 1, size(t % filter) score_index = score_index + (filter_matches(t % filter(l)) & - % bins % data(1) - 1) * t % stride(l) + % bins_data(1) - 1) * t % stride(l) end do ! Get scattering @@ -244,23 +244,23 @@ contains ! Initialize and filter for energy do l = 1, size(t % filter) - call filter_matches(t % filter(l)) % bins % clear() - call filter_matches(t % filter(l)) % bins % push_back(1) + call filter_matches(t % filter(l)) % bins_clear() + call filter_matches(t % filter(l)) % bins_push_back(1) end do ! Set the bin for this mesh cell i_mesh = m % get_bin_from_indices([ i, j, k ]) - filter_matches(i_filter_mesh) % bins % data(1) = 12*(i_mesh - 1) + 1 + call filter_matches(i_filter_mesh) % bins_set_data(1, 12*(i_mesh - 1) + 1) ! Set the energy bin if needed if (energy_filters) then - filter_matches(i_filter_ein) % bins % data(1) = ng - h + 1 + call filter_matches(i_filter_ein) % bins_set_data(1, ng - h + 1) end if score_index = 0 do l = 1, size(t % filter) score_index = score_index + (filter_matches(t % filter(l)) & - % bins % data(1) - 1) * t % stride(l) + % bins_data(1) - 1) * t % stride(l) end do ! Left surface @@ -303,30 +303,30 @@ contains ! Reset all bins to 1 do l = 1, size(t % filter) - call filter_matches(t % filter(l)) % bins % clear() - call filter_matches(t % filter(l)) % bins % push_back(1) + call filter_matches(t % filter(l)) % bins_clear() + call filter_matches(t % filter(l)) % bins_push_back(1) end do ! Set ijk as mesh indices ijk = (/ i, j, k /) ! Get bin number for mesh indices - filter_matches(i_filter_mesh) % bins % data(1) = & - m % get_bin_from_indices(ijk) + call filter_matches(i_filter_mesh) & + % bins_set_data(1, m % get_bin_from_indices(ijk)) ! Apply energy in filter if (energy_filters) then - filter_matches(i_filter_ein) % bins % data(1) = ng - h + 1 + call filter_matches(i_filter_ein) % bins_set_data(1, ng - h + 1) end if ! Apply Legendre filter - filter_matches(i_filter_legendre) % bins % data(1) = 2 + call filter_matches(i_filter_legendre) % bins_set_data(1, 2) ! Calculate score index from bins score_index = 1 do l = 1, size(t % filter) score_index = score_index + (filter_matches(t % filter(l)) & - % bins % data(1) - 1) * t % stride(l) + % bins_data(1) - 1) * t % stride(l) end do ! Get p1 scatter rr and convert to p1 scatter xs diff --git a/src/cmfd_execute.F90 b/src/cmfd_execute.F90 index 18f5ebec19..681d08c5c1 100644 --- a/src/cmfd_execute.F90 +++ b/src/cmfd_execute.F90 @@ -13,13 +13,23 @@ module cmfd_execute private public :: execute_cmfd, cmfd_init_batch, cmfd_tally_init +#ifdef OPENMC_MPI + interface + subroutine cmfd_broadcast(n, buffer) bind(C) + import C_DOUBLE, C_INT + integer(C_INT), value :: n + real(C_DOUBLE), intent(out) :: buffer + end subroutine + end interface +#endif + contains !============================================================================== ! EXECUTE_CMFD runs the CMFD calculation !============================================================================== - subroutine execute_cmfd() + subroutine execute_cmfd() bind(C) use cmfd_data, only: set_up_cmfd use cmfd_solver, only: cmfd_solver_execute @@ -63,7 +73,7 @@ contains ! CMFD_INIT_BATCH handles cmfd options at the start of every batch !============================================================================== - subroutine cmfd_init_batch() + subroutine cmfd_init_batch() bind(C) ! Check to activate CMFD diffusion and possible feedback ! this guarantees that when cmfd begins at least one batch of tallies are @@ -105,9 +115,6 @@ 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 OPENMC_MPI - integer :: mpi_err ! MPI error code -#endif ! Get maximum of spatial and group indices nx = cmfd % indices(1) @@ -199,7 +206,7 @@ contains #ifdef OPENMC_MPI ! Broadcast full source to all procs - call MPI_BCAST(cmfd % cmfd_src, n, MPI_REAL8, 0, mpi_intracomm, mpi_err) + call cmfd_broadcast(n, cmfd % cmfd_src(1,1,1,1)) #endif end subroutine calc_fission_source @@ -232,9 +239,6 @@ contains real(8) :: norm ! normalization factor logical(C_BOOL) :: outside ! any source sites outside mesh logical :: in_mesh ! source site is inside mesh -#ifdef OPENMC_MPI - integer :: mpi_err -#endif interface subroutine cmfd_populate_sourcecounts(ng, energies, source_counts, outside) bind(C) @@ -300,8 +304,7 @@ contains ! Broadcast weight factors to all procs #ifdef OPENMC_MPI - call MPI_BCAST(cmfd % weightfactors, ng*nx*ny*nz, MPI_REAL8, 0, & - mpi_intracomm, mpi_err) + call cmfd_broadcast(ng*nx*ny*nz, cmfd % weightfactors(1,1,1,1)) #endif end if @@ -372,7 +375,7 @@ contains ! CMFD_TALLY_INIT !=============================================================================== - subroutine cmfd_tally_init() + subroutine cmfd_tally_init() bind(C) integer :: i if (cmfd_run) then do i = 1, size(cmfd_tallies) diff --git a/src/cmfd_execute.cpp b/src/cmfd_execute.cpp index 740c1f51f0..46371601b1 100644 --- a/src/cmfd_execute.cpp +++ b/src/cmfd_execute.cpp @@ -7,10 +7,12 @@ #include "openmc/capi.h" #include "openmc/mesh.h" +#include "openmc/message_passing.h" +#include "openmc/simulation.h" +#include "openmc/settings.h" namespace openmc { -extern "C" int index_cmfd_mesh; extern "C" void cmfd_populate_sourcecounts(int n_energy, const double* energies, @@ -22,11 +24,20 @@ cmfd_populate_sourcecounts(int n_energy, const double* energies, openmc_source_bank(&source_bank, &n); // Get source counts in each mesh bin / energy bin - auto& m = meshes.at(index_cmfd_mesh); - xt::xarray counts = m->count_sites(openmc_work, source_bank, n_energy, energies, outside); + auto& m = meshes.at(settings::index_cmfd_mesh); + xt::xarray counts = m->count_sites(simulation::work, source_bank, n_energy, energies, outside); // Copy data from the xarray into the source counts array std::copy(counts.begin(), counts.end(), source_counts); } +#ifdef OPENMC_MPI +extern "C" void +cmfd_broadcast(int n, double* buffer) +{ + MPI_Bcast(buffer, n, MPI_DOUBLE, 0, mpi::intracomm); +} +#endif + + } // namespace openmc diff --git a/src/cmfd_header.F90 b/src/cmfd_header.F90 index e5e5cc423e..f4b14b6f2b 100644 --- a/src/cmfd_header.F90 +++ b/src/cmfd_header.F90 @@ -51,8 +51,8 @@ module cmfd_header real(8), allocatable :: hxyz(:,:,:,:) ! Source distributions - real(8), allocatable :: cmfd_src(:,:,:,:) - real(8), allocatable :: openmc_src(:,:,:,:) + real(C_DOUBLE), allocatable :: cmfd_src(:,:,:,:) + real(C_DOUBLE), allocatable :: openmc_src(:,:,:,:) ! Source sites in each mesh box real(C_DOUBLE), allocatable :: sourcecounts(:,:) @@ -143,7 +143,7 @@ module cmfd_header logical, public :: cmfd_run_adjoint = .false. ! CMFD run logicals - logical, public :: cmfd_on = .false. + logical(C_BOOL), public, bind(C) :: cmfd_on = .false. ! CMFD display info character(len=25), public :: cmfd_display = 'balance' diff --git a/src/constants.F90 b/src/constants.F90 index a3118f84da..250b1ef376 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -17,6 +17,9 @@ module constants ! HDF5 data format integer, parameter :: HDF5_VERSION(2) = [1, 0] + ! WMP data format + integer, parameter :: WMP_VERSION(2) = [1, 1] + ! Version numbers for binary files integer, parameter :: VERSION_STATEPOINT(2) = [17, 0] integer, parameter :: VERSION_PARTICLE_RESTART(2) = [2, 0] @@ -25,7 +28,6 @@ module constants integer, parameter :: VERSION_VOLUME(2) = [1, 0] integer, parameter :: VERSION_VOXEL(2) = [1, 0] integer, parameter :: VERSION_MGXS_LIBRARY(2) = [1, 0] - character(10), parameter :: VERSION_MULTIPOLE = "v1.0" ! ============================================================================ ! ADJUSTABLE PARAMETERS diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 959dabd12e..d4e113977a 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -54,7 +54,7 @@ void load_dagmc_geometry() c->fill_ = C_NONE; // no fill, single universe cells.push_back(c); - cell_map[c->id_] = c->id_; + cell_map[c->id_] = i; // Populate the Universe vector and dict auto it = universe_map.find(dagmc_univ_id); diff --git a/src/distribution.cpp b/src/distribution.cpp index 01b09e65db..e546b11ddd 100644 --- a/src/distribution.cpp +++ b/src/distribution.cpp @@ -90,7 +90,7 @@ Maxwell::Maxwell(pugi::xml_node node) double Maxwell::sample() const { - return maxwell_spectrum_c(theta_); + return maxwell_spectrum(theta_); } //============================================================================== @@ -110,7 +110,7 @@ Watt::Watt(pugi::xml_node node) double Watt::sample() const { - return watt_spectrum_c(a_, b_); + return watt_spectrum(a_, b_); } //============================================================================== diff --git a/src/distribution_energy.cpp b/src/distribution_energy.cpp index 863ffd8b08..e311e322d2 100644 --- a/src/distribution_energy.cpp +++ b/src/distribution_energy.cpp @@ -279,7 +279,7 @@ double MaxwellEnergy::sample(double E) const while (true) { // Sample maxwell fission spectrum - double E_out = maxwell_spectrum_c(theta); + double E_out = maxwell_spectrum(theta); // Accept energy based on restriction energy if (E_out <= E - u_) return E_out; @@ -343,7 +343,7 @@ double WattEnergy::sample(double E) const while (true) { // Sample energy-dependent Watt fission spectrum - double E_out = watt_spectrum_c(a, b); + double E_out = watt_spectrum(a, b); // Accept energy based on restriction energy if (E_out <= E - u_) return E_out; diff --git a/src/eigenvalue.F90 b/src/eigenvalue.F90 index bd2a20b413..bdbbd1c2bc 100644 --- a/src/eigenvalue.F90 +++ b/src/eigenvalue.F90 @@ -2,539 +2,21 @@ module eigenvalue use, intrinsic :: ISO_C_BINDING - use algorithm, only: binary_search - use constants, only: ZERO - use error, only: fatal_error, warning - use math, only: t_percentile - use message_passing - use random_lcg, only: prn, set_particle_seed, advance_prn_seed - use settings + use bank_header use simulation_header - use string, only: to_str - use tally_header - use timer_header implicit none - real(8) :: keff_generation ! Single-generation k on each processor - real(8) :: k_sum(2) = ZERO ! Used to reduce sum and sum_sq + interface + function openmc_get_keff(k_combined) result(err) bind(C) + import C_INT, C_DOUBLE + real(C_DOUBLE), intent(out) :: k_combined(2) + integer(C_INT) :: err + end function + end interface contains -!=============================================================================== -! SYNCHRONIZE_BANK samples source sites from the fission sites that were -! accumulated during the generation. This routine is what allows this Monte -! Carlo to scale to large numbers of processors where other codes cannot. -!=============================================================================== - - subroutine synchronize_bank() - - integer :: i ! loop indices - integer :: j ! loop indices - integer(8) :: start ! starting index in global bank - integer(8) :: finish ! ending index in global bank - integer(8) :: total ! total sites in global fission bank - integer(8) :: index_temp ! index in temporary source bank - integer(8) :: sites_needed ! # of sites to be sampled - real(8) :: p_sample ! probability of sampling a site - type(Bank), save, allocatable :: & - & temp_sites(:) ! local array of extra sites on each node - -#ifdef OPENMC_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 OPENMC_MPIF08 - type(MPI_Request) :: request(20) -#else - integer :: request(20) ! communication request for send/recving sites -#endif - integer :: n_request ! number of communication requests - integer(8) :: index_local ! index in local source bank - integer(8), save, allocatable :: & - & bank_position(:) ! starting positions in global source bank -#endif - - ! In order to properly understand the fission bank algorithm, you need to - ! think of the fission and source bank as being one global array divided - ! over multiple processors. At the start, each processor has a random amount - ! of fission bank sites -- each processor needs to know the total number of - ! sites in order to figure out the probability for selecting - ! sites. Furthermore, each proc also needs to know where in the 'global' - ! fission bank its own sites starts in order to ensure reproducibility by - ! skipping ahead to the proper seed. - -#ifdef OPENMC_MPI - start = 0_8 - call MPI_EXSCAN(n_bank, start, 1, MPI_INTEGER8, MPI_SUM, & - mpi_intracomm, mpi_err) - - ! While we would expect the value of start on rank 0 to be 0, the MPI - ! standard says that the receive buffer on rank 0 is undefined and not - ! significant - if (rank == 0) start = 0_8 - - finish = start + n_bank - total = finish - call MPI_BCAST(total, 1, MPI_INTEGER8, n_procs - 1, & - mpi_intracomm, mpi_err) - -#else - start = 0_8 - finish = n_bank - total = n_bank -#endif - - ! If there are not that many particles per generation, it's possible that no - ! fission sites were created at all on a single processor. Rather than add - ! extra logic to treat this circumstance, we really want to ensure the user - ! runs enough particles to avoid this in the first place. - - if (n_bank == 0) then - call fatal_error("No fission sites banked on processor " // to_str(rank)) - end if - - ! Make sure all processors start at the same point for random sampling. Then - ! skip ahead in the sequence using the starting index in the 'global' - ! fission bank for each processor. - - call set_particle_seed(int(total_gen + overall_generation(), 8)) - call advance_prn_seed(start) - - ! Determine how many fission sites we need to sample from the source bank - ! and the probability for selecting a site. - - if (total < n_particles) then - sites_needed = mod(n_particles,total) - else - sites_needed = n_particles - end if - p_sample = real(sites_needed,8)/real(total,8) - - call time_bank_sample % start() - - ! ========================================================================== - ! SAMPLE N_PARTICLES FROM FISSION BANK AND PLACE IN TEMP_SITES - - ! Allocate temporary source bank - index_temp = 0_8 - if (.not. allocated(temp_sites)) allocate(temp_sites(3*work)) - - do i = 1, int(n_bank,4) - - ! If there are less than n_particles particles banked, automatically add - ! int(n_particles/total) sites to temp_sites. For example, if you need - ! 1000 and 300 were banked, this would add 3 source sites per banked site - ! and the remaining 100 would be randomly sampled. - if (total < n_particles) then - do j = 1, int(n_particles/total) - index_temp = index_temp + 1 - temp_sites(index_temp) = fission_bank(i) - end do - end if - - ! Randomly sample sites needed - if (prn() < p_sample) then - index_temp = index_temp + 1 - temp_sites(index_temp) = fission_bank(i) - end if - end do - - ! At this point, the sampling of source sites is done and now we need to - ! figure out where to send source sites. Since it is possible that one - ! processor's share of the source bank spans more than just the immediate - ! neighboring processors, we have to perform an ALLGATHER to determine the - ! indices for all processors - -#ifdef OPENMC_MPI - ! First do an exclusive scan to get the starting indices for - start = 0_8 - call MPI_EXSCAN(index_temp, start, 1, MPI_INTEGER8, MPI_SUM, & - mpi_intracomm, mpi_err) - finish = start + index_temp - - ! Allocate space for bank_position if this hasn't been done yet - if (.not. allocated(bank_position)) allocate(bank_position(n_procs)) - call MPI_ALLGATHER(start, 1, MPI_INTEGER8, bank_position, 1, & - MPI_INTEGER8, mpi_intracomm, mpi_err) -#else - start = 0_8 - finish = index_temp -#endif - - ! Now that the sampling is complete, we need to ensure that we have exactly - ! n_particles source sites. The way this is done in a reproducible manner is - ! to adjust only the source sites on the last processor. - - if (rank == n_procs - 1) then - if (finish > n_particles) then - ! If we have extra sites sampled, we will simply discard the extra - ! ones on the last processor - index_temp = n_particles - start - - elseif (finish < n_particles) then - ! If we have too few sites, repeat sites from the very end of the - ! fission bank - sites_needed = n_particles - finish - do i = 1, int(sites_needed,4) - index_temp = index_temp + 1 - temp_sites(index_temp) = fission_bank(n_bank - sites_needed + i) - end do - end if - - ! the last processor should not be sending sites to right - finish = work_index(rank + 1) - end if - - call time_bank_sample % stop() - call time_bank_sendrecv % start() - -#ifdef OPENMC_MPI - ! ========================================================================== - ! SEND BANK SITES TO NEIGHBORS - - index_local = 1 - n_request = 0 - - if (start < n_particles) then - ! Determine the index of the processor which has the first part of the - ! source_bank for the local processor - neighbor = binary_search(work_index, n_procs + 1, start) - 1 - - SEND_SITES: do while (start < finish) - ! Determine the number of sites to send - n = min(work_index(neighbor + 1), finish) - start - - ! Initiate an asynchronous send of source sites to the neighboring - ! process - if (neighbor /= rank) then - n_request = n_request + 1 - call MPI_ISEND(temp_sites(index_local), int(n), MPI_BANK, neighbor, & - rank, mpi_intracomm, request(n_request), mpi_err) - end if - - ! Increment all indices - start = start + n - index_local = index_local + n - neighbor = neighbor + 1 - - ! Check for sites out of bounds -- this only happens in the rare - ! circumstance that a processor close to the end has so many sites that - ! it would exceed the bank on the last processor - if (neighbor > n_procs - 1) exit - end do SEND_SITES - end if - - ! ========================================================================== - ! RECEIVE BANK SITES FROM NEIGHBORS OR TEMPORARY BANK - - start = work_index(rank) - index_local = 1 - - ! Determine what process has the source sites that will need to be stored at - ! the beginning of this processor's source bank. - - if (start >= bank_position(n_procs)) then - neighbor = n_procs - 1 - else - neighbor = binary_search(bank_position, n_procs, start) - 1 - end if - - RECV_SITES: do while (start < work_index(rank + 1)) - ! Determine how many sites need to be received - if (neighbor == n_procs - 1) then - n = work_index(rank + 1) - start - else - n = min(bank_position(neighbor + 2), work_index(rank + 1)) - start - end if - - if (neighbor /= rank) then - ! If the source sites are not on this processor, initiate an - ! asynchronous receive for the source sites - - n_request = n_request + 1 - call MPI_IRECV(source_bank(index_local), int(n), MPI_BANK, & - neighbor, neighbor, mpi_intracomm, request(n_request), mpi_err) - - else - ! If the source sites are on this procesor, we can simply copy them - ! from the temp_sites bank - - index_temp = start - bank_position(rank+1) + 1 - source_bank(index_local:index_local+n-1) = & - temp_sites(index_temp:index_temp+n-1) - end if - - ! Increment all indices - start = start + n - index_local = index_local + n - neighbor = neighbor + 1 - end do RECV_SITES - - ! Since we initiated a series of asynchronous ISENDs and IRECVs, now we have - ! to ensure that the data has actually been communicated before moving on to - ! the next generation - - call MPI_WAITALL(n_request, request, MPI_STATUSES_IGNORE, mpi_err) - - ! Deallocate space for bank_position on the very last generation - if (current_batch == n_max_batches .and. current_gen == gen_per_batch) & - deallocate(bank_position) -#else - source_bank = temp_sites(1:n_particles) -#endif - - call time_bank_sendrecv % stop() - - ! Deallocate space for the temporary source bank on the last generation - if (current_batch == n_max_batches .and. current_gen == gen_per_batch) & - deallocate(temp_sites) - - end subroutine synchronize_bank - -!=============================================================================== -! CALCULATE_GENERATION_KEFF collects the single-processor tracklength k's onto -! the master processor and normalizes them. This should work whether or not the -! no-reduce method is being used. -!=============================================================================== - - subroutine calculate_generation_keff() - - real(8) :: keff_reduced -#ifdef OPENMC_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 - -#ifdef OPENMC_MPI - ! Combine values across all processors - call MPI_ALLREDUCE(keff_generation, keff_reduced, 1, MPI_REAL8, & - MPI_SUM, mpi_intracomm, mpi_err) -#else - keff_reduced = keff_generation -#endif - - ! Normalize single batch estimate of k - ! TODO: This should be normalized by total_weight, not by n_particles - keff_reduced = keff_reduced / n_particles - call k_generation % push_back(keff_reduced) - - end subroutine calculate_generation_keff - -!=============================================================================== -! CALCULATE_AVERAGE_KEFF calculates the mean and standard deviation of the mean -! of k-effective during active generations and broadcasts the mean to all -! processors -!=============================================================================== - - subroutine calculate_average_keff() - - integer :: i ! overall generation within simulation - integer :: n ! number of active generations - real(8) :: alpha ! significance level for CI - real(8) :: t_value ! t-value for confidence intervals - - ! Determine overall generation and number of active generations - i = overall_generation() - if (current_batch > n_inactive) then - n = gen_per_batch*n_realizations + current_gen - else - n = 0 - end if - - if (n <= 0) then - ! For inactive generations, use current generation k as estimate for next - ! generation - keff = k_generation % data(i) - - else - ! Sample mean of keff - k_sum(1) = k_sum(1) + k_generation % data(i) - k_sum(2) = k_sum(2) + k_generation % data(i)**2 - - ! Determine mean - keff = k_sum(1) / n - - if (n > 1) then - if (confidence_intervals) then - ! Calculate t-value for confidence intervals - alpha = ONE - CONFIDENCE_LEVEL - t_value = t_percentile(ONE - alpha/TWO, n - 1) - else - t_value = ONE - end if - - ! Standard deviation of the sample mean of k - keff_std = t_value * sqrt((k_sum(2)/n - keff**2) / (n - 1)) - end if - end if - - end subroutine calculate_average_keff - -!=============================================================================== -! 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 -! follows that described in T. Urbatsch et al., "Estimation and interpretation -! of keff confidence intervals in MCNP," Nucl. Technol., 111, 169-182 (1995). -!=============================================================================== - - 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 - real(8) :: n ! number of realizations - real(8) :: kv(3) ! vector of k-effective estimates - real(8) :: cov(3,3) ! sample covariance matrix - real(8) :: f ! weighting factor - 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) then - err = -1 - return - end if - - ! Initialize variables - n = real(n_realizations, 8) - - ! Copy estimates of k-effective and its variance (not variance of the mean) - kv(1) = global_tallies(RESULT_SUM, K_COLLISION) / n - kv(2) = global_tallies(RESULT_SUM, K_ABSORPTION) / n - kv(3) = global_tallies(RESULT_SUM, K_TRACKLENGTH) / n - cov(1, 1) = (global_tallies(RESULT_SUM_SQ, K_COLLISION) - & - n * kv(1) * kv(1)) / (n - ONE) - cov(2, 2) = (global_tallies(RESULT_SUM_SQ, K_ABSORPTION) - & - n * kv(2) * kv(2)) / (n - ONE) - cov(3, 3) = (global_tallies(RESULT_SUM_SQ, K_TRACKLENGTH) - & - n * kv(3) * kv(3)) / (n - ONE) - - ! Calculate covariances based on sums with Bessel's correction - cov(1, 2) = (k_col_abs - n * kv(1) * kv(2)) / (n - ONE) - cov(1, 3) = (k_col_tra - n * kv(1) * kv(3)) / (n - ONE) - cov(2, 3) = (k_abs_tra - n * kv(2) * kv(3)) / (n - ONE) - cov(2, 1) = cov(1, 2) - cov(3, 1) = cov(1, 3) - cov(3, 2) = cov(2, 3) - - ! Check to see if two estimators are the same; this is guaranteed to happen - ! in MG-mode with survival biasing when the collision and absorption - ! estimators are the same, but can theoretically happen at anytime. - ! If it does, the standard estimators will produce floating-point - ! exceptions and an expression specifically derived for the combination of - ! two estimators (vice three) should be used instead. - - ! First we will identify if there are any matching estimators - if ((abs(kv(1) - kv(2)) / kv(1) < FP_REL_PRECISION) .and. & - (abs(cov(1, 1) - cov(2, 2)) / cov(1, 1) < FP_REL_PRECISION)) then - ! 1 and 2 match, so only use 1 and 3 in our comparisons - i = 1 - j = 3 - - else if ((abs(kv(1) - kv(3)) / kv(1) < FP_REL_PRECISION) .and. & - (abs(cov(1, 1) - cov(3, 3)) / cov(1, 1) < FP_REL_PRECISION)) then - ! 1 and 3 match, so only use 1 and 2 in our comparisons - i = 1 - j = 2 - - else if ((abs(kv(2) - kv(3)) / kv(2) < FP_REL_PRECISION) .and. & - (abs(cov(2, 2) - cov(3, 3)) / cov(2, 2) < FP_REL_PRECISION)) then - ! 2 and 3 match, so only use 1 and 2 in our comparisons - i = 1 - j = 2 - - else - ! No two estimators match, so set i to 0 and this will be the indicator - ! to use all three estimators. - i = 0 - end if - - if (i == 0) then - ! Use three estimators as derived in the paper by Urbatsch - - ! Initialize variables - g = ZERO - S = ZERO - - do l = 1, 3 - ! Permutations of estimates - if (l == 1) then - ! i = collision, j = absorption, k = tracklength - i = 1 - j = 2 - k = 3 - elseif (l == 2) then - ! i = absortion, j = tracklength, k = collision - i = 2 - j = 3 - k = 1 - elseif (l == 3) then - ! i = tracklength, j = collision, k = absorption - i = 3 - j = 1 - k = 2 - end if - - ! Calculate weighting - f = cov(j, j) * (cov(k, k) - cov(i, k)) - cov(k, k) * cov(i, j) + & - cov(j, k) * (cov(i, j) + cov(i, k) - cov(j, k)) - - ! Add to S sums for variance of combined estimate - S(1) = S(1) + f * cov(1, l) - S(2) = S(2) + (cov(j, j) + cov(k, k) - TWO * cov(j, k)) * kv(l) * kv(l) - S(3) = S(3) + (cov(k, k) + cov(i, j) - cov(j, k) - & - cov(i, k)) * kv(l) * kv(j) - - ! Add to sum for combined k-effective - k_combined(1) = k_combined(1) + f * kv(l) - g = g + f - end do - - ! Complete calculations of S sums - S = (n - ONE) * S - S(1) = (n - ONE)**2 * S(1) - - ! Calculate combined estimate of k-effective - k_combined(1) = k_combined(1) / g - - ! Calculate standard deviation of combined estimate - g = (n - ONE)**2 * g - k_combined(2) = sqrt(S(1) / & - (g * n * (n - THREE)) * (ONE + n * ((S(2) - TWO * S(3)) / g))) - - else - ! Use only two estimators - ! These equations are derived analogously to that done in the paper by - ! Urbatsch, but are simpler than for the three estimators case since the - ! block matrices of the three estimator equations reduces to scalars here - - ! Store the commonly used term - f = kv(i) - kv(j) - g = cov(i, i) + cov(j, j) - TWO * cov(i, j) - - ! Calculate combined estimate of k-effective - k_combined(1) = kv(i) - (cov(i, i) - cov(i, j)) / g * f - - ! Calculate standard deviation of combined estimate - k_combined(2) = (cov(i, i) * cov(j, j) - cov(i, j) * cov(i, j)) * & - (g + n * f * f) / (n * (n - TWO) * g * g) - k_combined(2) = sqrt(k_combined(2)) - - end if - err = 0 - - end function openmc_get_keff - #ifdef _OPENMP !=============================================================================== ! JOIN_BANK_FROM_THREADS joins threadprivate fission banks into a single fission @@ -542,7 +24,7 @@ contains ! to preserve the order of the bank when using varying numbers of threads. !=============================================================================== - subroutine join_bank_from_threads() + subroutine join_bank_from_threads() bind(C) integer(8) :: total ! total number of fission bank sites integer :: i ! loop index for threads diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index 454fd57e6b..927b014b52 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -1,16 +1,28 @@ #include "openmc/eigenvalue.h" +#include "xtensor/xbuilder.hpp" #include "xtensor/xmath.hpp" #include "xtensor/xtensor.hpp" #include "xtensor/xview.hpp" #include "openmc/capi.h" +#include "openmc/constants.h" #include "openmc/error.h" #include "openmc/hdf5_interface.h" +#include "openmc/math_functions.h" #include "openmc/mesh.h" #include "openmc/message_passing.h" +#include "openmc/random_lcg.h" +#include "openmc/search.h" #include "openmc/settings.h" #include "openmc/simulation.h" +#include "openmc/timer.h" +#include "openmc/tallies/tally.h" + +#include // for min +#include +#include // for sqrt, abs, pow +#include namespace openmc { @@ -18,6 +30,8 @@ namespace openmc { // Global variables //============================================================================== +double keff_generation; +std::array k_sum; std::vector entropy; xt::xtensor source_frac; @@ -25,6 +39,457 @@ xt::xtensor source_frac; // Non-member functions //============================================================================== +void calculate_generation_keff() +{ + auto gt = global_tallies(); + + // Get keff for this generation by subtracting off the starting value + keff_generation = gt(K_TRACKLENGTH, RESULT_VALUE) - keff_generation; + + double keff_reduced; +#ifdef OPENMC_MPI + // Combine values across all processors + MPI_Allreduce(&keff_generation, &keff_reduced, 1, MPI_DOUBLE, + MPI_SUM, mpi::intracomm); +#else + keff_reduced = keff_generation; +#endif + + // Normalize single batch estimate of k + // TODO: This should be normalized by total_weight, not by n_particles + keff_reduced /= settings::n_particles; + simulation::k_generation.push_back(keff_reduced); +} + +void synchronize_bank() +{ + time_bank.start(); + + // Get pointers to source/fission bank + Bank* source_bank; + Bank* fission_bank; + int64_t n; + openmc_source_bank(&source_bank, &n); + openmc_fission_bank(&fission_bank, &n); + + // In order to properly understand the fission bank algorithm, you need to + // think of the fission and source bank as being one global array divided + // over multiple processors. At the start, each processor has a random amount + // of fission bank sites -- each processor needs to know the total number of + // sites in order to figure out the probability for selecting + // sites. Furthermore, each proc also needs to know where in the 'global' + // fission bank its own sites starts in order to ensure reproducibility by + // skipping ahead to the proper seed. + +#ifdef OPENMC_MPI + int64_t start = 0; + MPI_Exscan(&n_bank, &start, 1, MPI_INT64_T, MPI_SUM, mpi::intracomm); + + // While we would expect the value of start on rank 0 to be 0, the MPI + // standard says that the receive buffer on rank 0 is undefined and not + // significant + if (mpi::rank == 0) start = 0; + + int64_t finish = start + n_bank; + int64_t total = finish; + MPI_Bcast(&total, 1, MPI_INT64_T, mpi::n_procs - 1, mpi::intracomm); + +#else + int64_t start = 0; + int64_t finish = n_bank; + int64_t total = n_bank; +#endif + + // If there are not that many particles per generation, it's possible that no + // fission sites were created at all on a single processor. Rather than add + // extra logic to treat this circumstance, we really want to ensure the user + // runs enough particles to avoid this in the first place. + + if (n_bank == 0) { + fatal_error("No fission sites banked on MPI rank " + std::to_string(mpi::rank)); + } + + // Make sure all processors start at the same point for random sampling. Then + // skip ahead in the sequence using the starting index in the 'global' + // fission bank for each processor. + + set_particle_seed(simulation::total_gen + overall_generation()); + advance_prn_seed(start); + + // Determine how many fission sites we need to sample from the source bank + // and the probability for selecting a site. + + int64_t sites_needed; + if (total < settings::n_particles) { + sites_needed = settings::n_particles % total; + } else { + sites_needed = settings::n_particles; + } + double p_sample = static_cast(sites_needed) / total; + + time_bank_sample.start(); + + // ========================================================================== + // SAMPLE N_PARTICLES FROM FISSION BANK AND PLACE IN TEMP_SITES + + // Allocate temporary source bank + int64_t index_temp = 0; + std::vector temp_sites(3*simulation::work); + + for (int64_t i = 0; i < n_bank; ++i) { + // If there are less than n_particles particles banked, automatically add + // int(n_particles/total) sites to temp_sites. For example, if you need + // 1000 and 300 were banked, this would add 3 source sites per banked site + // and the remaining 100 would be randomly sampled. + if (total < settings::n_particles) { + for (int64_t j = 1; j <= settings::n_particles / total; ++j) { + temp_sites[index_temp] = fission_bank[i]; + ++index_temp; + } + } + + // Randomly sample sites needed + if (prn() < p_sample) { + temp_sites[index_temp] = fission_bank[i]; + ++index_temp; + } + } + + // At this point, the sampling of source sites is done and now we need to + // figure out where to send source sites. Since it is possible that one + // processor's share of the source bank spans more than just the immediate + // neighboring processors, we have to perform an ALLGATHER to determine the + // indices for all processors + +#ifdef OPENMC_MPI + // First do an exclusive scan to get the starting indices for + start = 0; + MPI_Exscan(&index_temp, &start, 1, MPI_INT64_T, MPI_SUM, mpi::intracomm); + finish = start + index_temp; + + // Allocate space for bank_position if this hasn't been done yet + int64_t bank_position[mpi::n_procs]; + MPI_Allgather(&start, 1, MPI_INT64_T, bank_position, 1, + MPI_INT64_T, mpi::intracomm); +#else + start = 0; + finish = index_temp; +#endif + + // Now that the sampling is complete, we need to ensure that we have exactly + // n_particles source sites. The way this is done in a reproducible manner is + // to adjust only the source sites on the last processor. + + if (mpi::rank == mpi::n_procs - 1) { + if (finish > settings::n_particles) { + // If we have extra sites sampled, we will simply discard the extra + // ones on the last processor + index_temp = settings::n_particles - start; + + } else if (finish < settings::n_particles) { + // If we have too few sites, repeat sites from the very end of the + // fission bank + sites_needed = settings::n_particles - finish; + for (int i = 0; i < sites_needed; ++i) { + temp_sites[index_temp] = fission_bank[n_bank - sites_needed + i]; + ++index_temp; + } + } + + // the last processor should not be sending sites to right + finish = simulation::work_index[mpi::rank + 1]; + } + + time_bank_sample.stop(); + time_bank_sendrecv.start(); + +#ifdef OPENMC_MPI + // ========================================================================== + // SEND BANK SITES TO NEIGHBORS + + int64_t index_local = 0; + std::vector requests; + + if (start < settings::n_particles) { + // Determine the index of the processor which has the first part of the + // source_bank for the local processor + int neighbor = upper_bound_index(simulation::work_index.begin(), + simulation::work_index.end(), start); + + while (start < finish) { + // Determine the number of sites to send + int64_t n = std::min(simulation::work_index[neighbor + 1], finish) - start; + + // Initiate an asynchronous send of source sites to the neighboring + // process + if (neighbor != mpi::rank) { + requests.emplace_back(); + MPI_Isend(&temp_sites[index_local], static_cast(n), mpi::bank, + neighbor, mpi::rank, mpi::intracomm, &requests.back()); + } + + // Increment all indices + start += n; + index_local += n; + ++neighbor; + + // Check for sites out of bounds -- this only happens in the rare + // circumstance that a processor close to the end has so many sites that + // it would exceed the bank on the last processor + if (neighbor > mpi::n_procs - 1) break; + } + } + + // ========================================================================== + // RECEIVE BANK SITES FROM NEIGHBORS OR TEMPORARY BANK + + start = simulation::work_index[mpi::rank]; + index_local = 0; + + // Determine what process has the source sites that will need to be stored at + // the beginning of this processor's source bank. + + int neighbor; + if (start >= bank_position[mpi::n_procs - 1]) { + neighbor = mpi::n_procs - 1; + } else { + neighbor = upper_bound_index(bank_position, bank_position + mpi::n_procs, start); + } + + while (start < simulation::work_index[mpi::rank + 1]) { + // Determine how many sites need to be received + int64_t n; + if (neighbor == mpi::n_procs - 1) { + n = simulation::work_index[mpi::rank + 1] - start; + } else { + n = std::min(bank_position[neighbor + 1], simulation::work_index[mpi::rank + 1]) - start; + } + + if (neighbor != mpi::rank) { + // If the source sites are not on this processor, initiate an + // asynchronous receive for the source sites + + requests.emplace_back(); + MPI_Irecv(&source_bank[index_local], static_cast(n), mpi::bank, + neighbor, neighbor, mpi::intracomm, &requests.back()); + + } else { + // If the source sites are on this procesor, we can simply copy them + // from the temp_sites bank + + index_temp = start - bank_position[mpi::rank]; + std::copy(&temp_sites[index_temp], &temp_sites[index_temp + n], + &source_bank[index_local]); + } + + // Increment all indices + start += n; + index_local += n; + ++neighbor; + } + + // Since we initiated a series of asynchronous ISENDs and IRECVs, now we have + // to ensure that the data has actually been communicated before moving on to + // the next generation + + int n_request = requests.size(); + MPI_Waitall(n_request, requests.data(), MPI_STATUSES_IGNORE); + +#else + std::copy(temp_sites.data(), temp_sites.data() + settings::n_particles, source_bank); +#endif + + time_bank_sendrecv.stop(); + time_bank.stop(); +} + +void calculate_average_keff() +{ + // Determine overall generation and number of active generations + int i = overall_generation() - 1; + int n; + if (simulation::current_batch > settings::n_inactive) { + n = settings::gen_per_batch*n_realizations + simulation::current_gen; + } else { + n = 0; + } + + if (n <= 0) { + // For inactive generations, use current generation k as estimate for next + // generation + simulation::keff = simulation::k_generation[i]; + } else { + // Sample mean of keff + k_sum[0] += simulation::k_generation[i]; + k_sum[1] += std::pow(simulation::k_generation[i], 2); + + // Determine mean + simulation::keff = k_sum[0] / n; + + if (n > 1) { + double t_value; + if (settings::confidence_intervals) { + // Calculate t-value for confidence intervals + double alpha = 1.0 - CONFIDENCE_LEVEL; + t_value = t_percentile_c(1.0 - alpha/2.0, n - 1); + } else { + t_value = 1.0; + } + + // Standard deviation of the sample mean of k + simulation::keff_std = t_value * std::sqrt((k_sum[1]/n - + std::pow(simulation::keff, 2)) / (n - 1)); + } + } +} + +int openmc_get_keff(double* k_combined) +{ + k_combined[0] = 0.0; + k_combined[1] = 0.0; + + // 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 -1; + } + + // Initialize variables + int64_t n = n_realizations; + + // Copy estimates of k-effective and its variance (not variance of the mean) + auto gt = global_tallies(); + + std::array kv {}; + xt::xtensor cov = xt::zeros({3, 3}); + kv[0] = gt(K_COLLISION, RESULT_SUM) / n; + kv[1] = gt(K_ABSORPTION, RESULT_SUM) / n; + kv[2] = gt(K_TRACKLENGTH, RESULT_SUM) / n; + cov(0, 0) = (gt(K_COLLISION, RESULT_SUM_SQ) - n*kv[0]*kv[0]) / (n - 1); + cov(1, 1) = (gt(K_ABSORPTION, RESULT_SUM_SQ) - n*kv[1]*kv[1]) / (n - 1); + cov(2, 2) = (gt(K_TRACKLENGTH, RESULT_SUM_SQ) - n*kv[2]*kv[2]) / (n - 1); + + // Calculate covariances based on sums with Bessel's correction + cov(0, 1) = (simulation::k_col_abs - n * kv[0] * kv[1]) / (n - 1); + cov(0, 2) = (simulation::k_col_tra - n * kv[0] * kv[2]) / (n - 1); + cov(1, 2) = (simulation::k_abs_tra - n * kv[1] * kv[2]) / (n - 1); + cov(1, 0) = cov(0, 1); + cov(2, 0) = cov(0, 2); + cov(2, 1) = cov(1, 2); + + // Check to see if two estimators are the same; this is guaranteed to happen + // in MG-mode with survival biasing when the collision and absorption + // estimators are the same, but can theoretically happen at anytime. + // If it does, the standard estimators will produce floating-point + // exceptions and an expression specifically derived for the combination of + // two estimators (vice three) should be used instead. + + // First we will identify if there are any matching estimators + int i, j, k; + if ((std::abs(kv[0] - kv[1]) / kv[0] < FP_REL_PRECISION) && + (std::abs(cov(0, 0) - cov(1, 1)) / cov(0, 0) < FP_REL_PRECISION)) { + // 0 and 1 match, so only use 0 and 2 in our comparisons + i = 0; + j = 2; + + } else if ((std::abs(kv[0] - kv[2]) / kv[0] < FP_REL_PRECISION) && + (std::abs(cov(0, 0) - cov(2, 2)) / cov(0, 0) < FP_REL_PRECISION)) { + // 0 and 2 match, so only use 0 and 1 in our comparisons + i = 0; + j = 1; + + } else if ((std::abs(kv[1] - kv[2]) / kv[1] < FP_REL_PRECISION) && + (std::abs(cov(1, 1) - cov(2, 2)) / cov(1, 1) < FP_REL_PRECISION)) { + // 1 and 2 match, so only use 0 and 1 in our comparisons + i = 0; + j = 1; + + } else { + // No two estimators match, so set i to -1 and this will be the indicator + // to use all three estimators. + i = -1; + } + + if (i == -1) { + // Use three estimators as derived in the paper by Urbatsch + + // Initialize variables + double g = 0.0; + std::array S {}; + + for (int l = 0; l < 3; ++l) { + // Permutations of estimates + switch (l) { + case 0: + // i = collision, j = absorption, k = tracklength + i = 0; + j = 1; + k = 2; + break; + case 1: + // i = absortion, j = tracklength, k = collision + i = 1; + j = 2; + k = 0; + break; + case 2: + // i = tracklength, j = collision, k = absorption + i = 2; + j = 0; + k = 1; + break; + } + + // Calculate weighting + double f = cov(j, j) * (cov(k, k) - cov(i, k)) - cov(k, k) * cov(i, j) + + cov(j, k) * (cov(i, j) + cov(i, k) - cov(j, k)); + + // Add to S sums for variance of combined estimate + S[0] += f * cov(0, l); + S[1] += (cov(j, j) + cov(k, k) - 2.0 * cov(j, k)) * kv[l] * kv[l]; + S[2] += (cov(k, k) + cov(i, j) - cov(j, k) - cov(i, k)) * kv[l] * kv[j]; + + // Add to sum for combined k-effective + k_combined[0] += f * kv[l]; + g += f; + } + + // Complete calculations of S sums + for (auto& S_i : S) { + S_i *= (n - 1); + } + S[0] *= (n - 1)*(n - 1); + + // Calculate combined estimate of k-effective + k_combined[0] /= g; + + // Calculate standard deviation of combined estimate + g *= (n - 1)*(n - 1); + k_combined[1] = std::sqrt(S[0] / (g*n*(n - 3)) * + (1 + n*((S[1] - 2*S[2]) / g))); + + } else { + // Use only two estimators + // These equations are derived analogously to that done in the paper by + // Urbatsch, but are simpler than for the three estimators case since the + // block matrices of the three estimator equations reduces to scalars here + + // Store the commonly used term + double f = kv[i] - kv[j]; + double g = cov(i, i) + cov(j, j) - 2.0*cov(i, j); + + // Calculate combined estimate of k-effective + k_combined[0] = kv[i] - (cov(i, i) - cov(i, j)) / g * f; + + // Calculate standard deviation of combined estimate + k_combined[1] = (cov(i, i)*cov(j, j) - cov(i, j)*cov(i, j)) * + (g + n*f*f) / (n*(n - 2)*g*g); + k_combined[1] = std::sqrt(k_combined[1]); + + } + return 0; +} + void shannon_entropy() { // Get pointer to entropy mesh @@ -66,7 +531,7 @@ void ufs_count_sites() { auto &m = meshes[settings::index_ufs_mesh]; - if (openmc_current_batch == 1 && openmc_current_gen == 1) { + if (simulation::current_batch == 1 && simulation::current_gen == 1) { // On the first generation, just assume that the source is already evenly // distributed so that effectively the production of fission sites is not // biased @@ -82,7 +547,7 @@ void ufs_count_sites() // count number of source sites in each ufs mesh cell bool sites_outside; - source_frac = m->count_sites(openmc_work, source_bank, 0, nullptr, + source_frac = m->count_sites(simulation::work, source_bank, 0, nullptr, &sites_outside); // Check for sites outside of the mesh @@ -102,7 +567,7 @@ void ufs_count_sites() // Since the total starting weight is not equal to n_particles, we need to // renormalize the weight of the source sites - for (int i = 0; i < openmc_work; ++i) { + for (int i = 0; i < simulation::work; ++i) { source_bank[i].wgt *= settings::n_particles / total; } } @@ -127,20 +592,40 @@ double ufs_get_weight(const Particle* p) } } -extern "C" void entropy_to_hdf5(hid_t group) +extern "C" void write_eigenvalue_hdf5(hid_t group) { + write_dataset(group, "n_inactive", settings::n_inactive); + write_dataset(group, "generations_per_batch", settings::gen_per_batch); + write_dataset(group, "k_generation", simulation::k_generation); if (settings::entropy_on) { write_dataset(group, "entropy", entropy); } + write_dataset(group, "k_col_abs", simulation::k_col_abs); + write_dataset(group, "k_col_tra", simulation::k_col_tra); + write_dataset(group, "k_abs_tra", simulation::k_abs_tra); + std::array k_combined; + openmc_get_keff(k_combined.data()); + write_dataset(group, "k_combined", k_combined); } -extern "C" void entropy_from_hdf5(hid_t group) +extern "C" void read_eigenvalue_hdf5(hid_t group) { + read_dataset(group, "generations_per_batch", settings::gen_per_batch); + int n = simulation::restart_batch*settings::gen_per_batch; + simulation::k_generation.resize(n); + read_dataset(group, "k_generation", simulation::k_generation); if (settings::entropy_on) { read_dataset(group, "entropy", entropy); } + read_dataset(group, "k_col_abs", simulation::k_col_abs); + read_dataset(group, "k_col_tra", simulation::k_col_tra); + read_dataset(group, "k_abs_tra", simulation::k_abs_tra); } +//============================================================================== +// Fortran compatibility +//============================================================================== + extern "C" double entropy_c(int i) { return entropy.at(i - 1); @@ -151,5 +636,6 @@ extern "C" void entropy_clear() entropy.clear(); } +extern "C" void k_sum_reset() { k_sum.fill(0.0); } } // namespace openmc diff --git a/src/error.F90 b/src/error.F90 index c00006c8d0..ad07987ebe 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -130,14 +130,20 @@ contains character(*) :: message integer, optional :: error_code ! error code - integer :: code ! error code + integer(C_INT) :: code ! error code integer :: i_start ! starting position integer :: i_end ! ending position integer :: line_wrap ! length of line integer :: length ! length of message integer :: indent ! length of indentation + #ifdef OPENMC_MPI - integer :: mpi_err + interface + subroutine abort_mpi(code) bind(C) + import C_INT + integer(C_INT), value :: code + end subroutine + end interface #endif @@ -190,7 +196,7 @@ contains #ifdef OPENMC_MPI ! Abort MPI - call MPI_ABORT(mpi_intracomm, code, mpi_err) + call abort_mpi(code) #endif ! Abort program diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000000..d17219f31d --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,14 @@ +#include "openmc/error.h" + +#include "openmc/message_passing.h" + +namespace openmc { + +#ifdef OPENMC_MPI +void abort_mpi(int code) +{ + MPI_Abort(mpi::intracomm, code); +} +#endif + +} // namespace openmc diff --git a/src/finalize.cpp b/src/finalize.cpp index 3b4f26764c..94e3d1a7f2 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -1,10 +1,137 @@ #include "openmc/finalize.h" +#include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/eigenvalue.h" +#include "openmc/geometry.h" #include "openmc/message_passing.h" +#include "openmc/nuclide.h" +#include "openmc/random_lcg.h" +#include "openmc/settings.h" +#include "openmc/simulation.h" +#include "openmc/timer.h" +#include "openmc/tallies/tally.h" -void openmc_free_bank() +using namespace openmc; + +// Functions defined in Fortran +extern "C" void free_memory(); +extern "C" void reset_timers_f(); + +int openmc_finalize() { + // Clear results + openmc_reset(); + + // Reset global variables + settings::assume_separate = false; + settings::check_overlaps = false; + settings::confidence_intervals = false; + settings::create_fission_neutrons = true; + settings::electron_treatment = ELECTRON_LED; + settings::energy_cutoff = {0.0, 1000.0, 0.0, 0.0}; + settings::entropy_on = false; + settings::gen_per_batch = 1; + settings::index_entropy_mesh = -1; + settings::index_ufs_mesh = -1; + settings::legendre_to_tabular = true; + settings::legendre_to_tabular_points = -1; + settings::n_particles = -1; + settings::output_summary = true; + settings::output_tallies = true; + settings::particle_restart_run = false; + settings::photon_transport = false; + settings::reduce_tallies = true; + settings::res_scat_on = false; + settings::res_scat_method = RES_SCAT_ARES; + settings::res_scat_energy_min = 0.01; + settings::res_scat_energy_max = 1000.0; + settings::restart_run = false; + settings::run_CE = true; + settings::run_mode = -1; + settings::dagmc = false; + settings::source_latest = false; + settings::source_separate = false; + settings::source_write = true; + settings::survival_biasing = false; + settings::temperature_default = 293.6; + settings::temperature_method = TEMPERATURE_NEAREST; + settings::temperature_multipole = false; + settings::temperature_range = {0.0, 0.0}; + settings::temperature_tolerance = 10.0; + settings::trigger_on = false; + settings::trigger_predict = false; + settings::trigger_batch_interval = 1; + settings::ufs_on = false; + settings::urr_ptables_on = true; + settings::verbosity = 7; + settings::weight_cutoff = 0.25; + settings::weight_survive = 1.0; + settings::write_all_tracks = false; + settings::write_initial_source = false; + + simulation::keff = 1.0; + simulation::n_lost_particles = 0; + simulation::satisfy_triggers = false; + simulation::total_gen = 0; + + energy_max = {INFTY, INFTY}; + energy_min = {0.0, 0.0}; + n_tallies = 0; + openmc_root_universe = -1; + openmc_set_seed(DEFAULT_SEED); + + // Deallocate arrays + free_memory(); + + // Free all MPI types #ifdef OPENMC_MPI - MPI_Type_free(&openmc::mpi::bank); + MPI_Type_free(&mpi::bank); #endif + + return 0; +} + +int openmc_reset() +{ + for (int i = 1; i <= n_tallies; ++i) { + openmc_tally_reset(i); + } + + // Reset global tallies (can't really use global_tallies() right now because + // it doesn't have any information about whether the underlying buffer was + // allocated) + n_realizations = 0; + double* buffer = nullptr; + openmc_global_tallies(&buffer); + if (buffer) { + for (int i = 0; i < 3*N_GLOBAL_TALLIES; ++i) { + buffer[i] = 0.0; + } + } + + simulation::k_col_abs = 0.0; + simulation::k_col_tra = 0.0; + simulation::k_abs_tra = 0.0; + k_sum = {0.0, 0.0}; + + // Reset timers + reset_timers(); + reset_timers_f(); + + return 0; +} + +int openmc_hard_reset() +{ + // Reset all tallies and timers + openmc_reset(); + + // Reset total generations and keff guess + simulation::keff = 1.0; + simulation::total_gen = 0; + + // Reset the random number generator state + openmc_set_seed(DEFAULT_SEED); + return 0; } diff --git a/src/geometry.cpp b/src/geometry.cpp index 857dbd32e0..cff5657a56 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -16,8 +16,6 @@ namespace openmc { std::vector overlap_check_count; -constexpr int F90_NONE {0}; //TODO: replace usage of this with C_NONE - //============================================================================== extern "C" bool @@ -91,7 +89,7 @@ find_cell(Particle* p, int search_surf) { if (cells[i_cell]->contains(r, u, surf)) { p->coord[p->n_coord-1].cell = i_cell; - if (settings::verbosity >= 10 || openmc_trace) { + if (settings::verbosity >= 10 || simulation::trace) { std::stringstream msg; msg << " Entering cell " << cells[i_cell]->id_; write_message(msg, 1); @@ -255,7 +253,7 @@ cross_lattice(Particle* p, int lattice_translation[3]) { Lattice& lat {*lattices[p->coord[p->n_coord-1].lattice-1]}; - if (settings::verbosity >= 10 || openmc_trace) { + if (settings::verbosity >= 10 || simulation::trace) { std::stringstream msg; msg << " Crossing lattice " << lat.id_ << ". Current position (" << p->coord[p->n_coord-1].lattice_x << "," diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 20f64e1876..e3934480b6 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -12,6 +12,8 @@ #include "openmc/material.h" #include "openmc/settings.h" #include "openmc/surface.h" +#include "openmc/tallies/filter.h" +#include "openmc/tallies/filter_distribcell.h" namespace openmc { @@ -181,12 +183,15 @@ neighbor_lists() //============================================================================== void -prepare_distribcell(int32_t* filter_cell_list, int n) +prepare_distribcell() { - // Read the list of cells contained in distribcell filters from Fortran. + // Find all cells listed in a DistribcellFilter. std::unordered_set distribcells; - for (int i = 0; i < n; i++) { - distribcells.insert(filter_cell_list[i]); + for (auto& filt : tally_filters) { + auto* distrib_filt = dynamic_cast(filt.get()); + if (distrib_filt) { + distribcells.insert(distrib_filt->cell_); + } } // Find all cells with distributed materials or temperatures. Make sure that @@ -393,29 +398,11 @@ distribcell_path_inner(int32_t target_cell, int32_t map, int32_t target_offset, } } -//============================================================================== - -int -distribcell_path_len(int32_t target_cell, int32_t map, int32_t target_offset, - int32_t root_univ) +std::string +distribcell_path(int32_t target_cell, int32_t map, int32_t target_offset) { - Universe& root = *universes[root_univ]; - std::string path_ {distribcell_path_inner(target_cell, map, target_offset, - root, 0)}; - return path_.size() + 1; -} - -//============================================================================== - -void -distribcell_path(int32_t target_cell, int32_t map, int32_t target_offset, - int32_t root_univ, char* path) -{ - Universe& root = *universes[root_univ]; - std::string path_ {distribcell_path_inner(target_cell, map, target_offset, - root, 0)}; - path_.copy(path, path_.size()); - path[path_.size()] = '\0'; + auto& root_univ = *universes[openmc_root_universe]; + return distribcell_path_inner(target_cell, map, target_offset, root_univ, 0); } //============================================================================== diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index b58b5415ba..9af1fcbaa0 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -59,20 +59,6 @@ module geometry_header integer(C_INT32_T) :: fill end function cell_fill_c - function cell_n_instances_c(cell_ptr) bind(C, name='cell_n_instances') & - result(n_instances) - import C_PTR, C_INT32_T - type(C_PTR), intent(in), value :: cell_ptr - integer(C_INT32_T) :: n_instances - end function cell_n_instances_c - - function cell_distribcell_index_c(cell_ptr) & - bind(C, name='cell_distribcell_index') result(distribcell_index) - import C_PTR, C_INT - type(C_PTR), intent(in), value :: cell_ptr - integer(C_INT) :: distribcell_index - end function cell_distribcell_index_c - function cell_material_size_c(cell_ptr) bind(C, name='cell_material_size') & result(n) import C_PTR, C_INT @@ -103,14 +89,6 @@ module geometry_header real(C_DOUBLE) :: sqrtkT end function cell_sqrtkT_c - function cell_offset_c(cell_ptr, map) bind(C, name="cell_offset") & - result(offset) - import C_PTR, C_INT, C_INT32_T - type(C_PTR), intent(in), value :: cell_ptr - integer(C_INT), intent(in), value :: map - integer(C_INT32_T) :: offset - end function cell_offset_c - function lattice_pointer(lat_ind) bind(C) result(ptr) import C_PTR, C_INT32_T integer(C_INT32_T), intent(in), value :: lat_ind @@ -123,23 +101,6 @@ module geometry_header integer(C_INT32_T) :: id end function lattice_id_c - function lattice_are_valid_indices_c(lat_ptr, i_xyz) & - bind(C, name='lattice_are_valid_indices') result (is_valid) - import C_PTR, C_INT, C_BOOL - type(C_PTR), intent(in), value :: lat_ptr - integer(C_INT), intent(in) :: i_xyz(3) - logical(C_BOOL) :: is_valid - end function lattice_are_valid_indices_c - - function lattice_offset_c(lat_ptr, map, i_xyz) & - bind(C, name='lattice_offset') result(offset) - import C_PTR, C_INT, C_INT32_T - type(C_PTR), intent(in), value :: lat_ptr - integer(C_INT), intent(in), value :: map - integer(C_INT), intent(in) :: i_xyz(3) - integer(C_INT32_T) :: offset - end function lattice_offset_c - subroutine extend_cells_c(n) bind(C) import C_INT32_t integer(C_INT32_T), intent(in), value :: n @@ -154,8 +115,6 @@ module geometry_header type(C_PTR) :: ptr contains procedure :: id => lattice_id - procedure :: are_valid_indices => lattice_are_valid_indices - procedure :: offset => lattice_offset end type Lattice !=============================================================================== @@ -176,13 +135,10 @@ module geometry_header procedure :: type => cell_type procedure :: universe => cell_universe procedure :: fill => cell_fill - procedure :: n_instances => cell_n_instances - procedure :: distribcell_index => cell_distribcell_index procedure :: material_size => cell_material_size procedure :: material => cell_material procedure :: sqrtkT_size => cell_sqrtkT_size procedure :: sqrtkT => cell_sqrtkT - procedure :: offset => cell_offset end type Cell @@ -197,7 +153,6 @@ module geometry_header ! Dictionaries which map user IDs to indices in the global arrays type(DictIntInt) :: cell_dict - type(DictIntInt) :: universe_dict type(DictIntInt) :: lattice_dict contains @@ -208,21 +163,6 @@ contains id = lattice_id_c(this % ptr) end function lattice_id - function lattice_are_valid_indices(this, i_xyz) result (is_valid) - class(Lattice), intent(in) :: this - integer(C_INT), intent(in) :: i_xyz(3) - logical(C_BOOL) :: is_valid - is_valid = lattice_are_valid_indices_c(this % ptr, i_xyz) - end function lattice_are_valid_indices - - function lattice_offset(this, map, i_xyz) result(offset) - class(Lattice), intent(in) :: this - integer(C_INT), intent(in) :: map - integer(C_INT), intent(in) :: i_xyz(3) - integer(C_INT32_T) :: offset - offset = lattice_offset_c(this % ptr, map, i_xyz) - end function lattice_offset - !=============================================================================== function cell_id(this) result(id) @@ -255,18 +195,6 @@ contains fill = cell_fill_c(this % ptr) end function cell_fill - function cell_n_instances(this) result(n_instances) - class(Cell), intent(in) :: this - integer(C_INT32_T) :: n_instances - n_instances = cell_n_instances_c(this % ptr) - end function cell_n_instances - - function cell_distribcell_index(this) result(distribcell_index) - class(Cell), intent(in) :: this - integer(C_INT) :: distribcell_index - distribcell_index = cell_distribcell_index_c(this % ptr) - end function cell_distribcell_index - function cell_material_size(this) result(n) class(Cell), intent(in) :: this integer(C_INT) :: n @@ -293,13 +221,6 @@ contains sqrtkT = cell_sqrtkT_c(this % ptr, i) end function cell_sqrtkT - function cell_offset(this, map) result(offset) - class(Cell), intent(in) :: this - integer(C_INT), intent(in) :: map - integer(C_INT32_T) :: offset - offset = cell_offset_c(this % ptr, map) - end function cell_offset - !=============================================================================== ! GET_TEMPERATURES returns a list of temperatures that each nuclide/S(a,b) table ! appears at in the model. Later, this list is used to determine the actual @@ -383,7 +304,6 @@ contains if (allocated(lattices)) deallocate(lattices) call cell_dict % clear() - call universe_dict % clear() call lattice_dict % clear() end subroutine free_memory_geometry diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 8872a84f6a..7856abb507 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -13,9 +13,6 @@ module hdf5_interface use, intrinsic :: ISO_C_BINDING use error, only: fatal_error -#ifdef PHDF5 - use message_passing, only: mpi_intracomm, MPI_INFO_NULL -#endif use string, only: to_c_string, to_f_string implicit none diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index d72f7d424d..4b0aedd0ae 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -148,13 +148,22 @@ dataset_typesize(hid_t dset) void -ensure_exists(hid_t group_id, const char* name) +ensure_exists(hid_t obj_id, const char* name, bool attribute) { - if (!object_exists(group_id, name)) { - std::stringstream err_msg; - err_msg << "Object \"" << name << "\" does not exist in group " - << object_name(group_id); - fatal_error(err_msg); + if (attribute) { + if (!attribute_exists(obj_id, name)) { + std::stringstream err_msg; + err_msg << "Attribute \"" << name << "\" does not exist in object " + << object_name(obj_id); + fatal_error(err_msg); + } + } else { + if (!object_exists(obj_id, name)) { + std::stringstream err_msg; + err_msg << "Object \"" << name << "\" does not exist in object " + << object_name(obj_id); + fatal_error(err_msg); + } } } diff --git a/src/initialize.F90 b/src/initialize.F90 index 0dbaa83bff..4801a90648 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -2,18 +2,8 @@ module initialize use, intrinsic :: ISO_C_BINDING -#ifdef _OPENMP - use omp_lib -#endif - - use bank_header, only: Bank - use constants - use input_xml, only: read_input_xml - use message_passing - use random_lcg, only: openmc_set_seed use settings - use string, only: ends_with, to_f_string - use timer_header + use string, only: to_f_string implicit none @@ -42,134 +32,11 @@ module initialize contains -!=============================================================================== -! OPENMC_INIT takes care of all initialization tasks, i.e. reading -! from command line, reading xml input files, initializing random -! number seeds, reading cross sections, initializing starting source, -! setting up timers, etc. -!=============================================================================== - - function openmc_init_f(intracomm) result(err) bind(C) - integer, intent(in), optional :: intracomm ! MPI intracommunicator - integer(C_INT) :: err - -#ifdef _OPENMP - character(MAX_WORD_LEN) :: envvar -#endif - - ! 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. -#ifdef OPENMC_MPI -#ifdef OPENMC_MPIF08 - type(MPI_Comm), intent(in) :: comm ! MPI intracommunicator - if (present(intracomm)) then - comm % MPI_VAL = intracomm - else - comm = MPI_COMM_WORLD - end if -#else - integer :: comm - if (present(intracomm)) then - comm = intracomm - else - comm = MPI_COMM_WORLD - end if -#endif -#endif - - ! Start total and initialization timer - call time_total%start() - call time_initialize%start() - -#ifdef OPENMC_MPI - ! Setup MPI - call initialize_mpi(comm) -#endif - -#ifdef _OPENMP - ! Change schedule of main parallel-do loop if OMP_SCHEDULE is set - call get_environment_variable("OMP_SCHEDULE", envvar) - if (len_trim(envvar) == 0) then - call omp_set_schedule(omp_sched_static, 0) - end if -#endif - - ! Read command line arguments - call read_command_line() - - ! Initialize random number generator -- if the user specifies a seed, it - ! will be re-initialized later - call openmc_set_seed(DEFAULT_SEED) - - ! Read XML input files - call read_input_xml() - - ! Check for particle restart run - if (particle_restart_run) run_mode = MODE_PARTICLE - - ! Stop initialization timer - call time_initialize%stop() - - err = 0 - end function openmc_init_f - -#ifdef OPENMC_MPI -!=============================================================================== -! INITIALIZE_MPI starts up the Message Passing Interface (MPI) and determines -! the number of processors the problem is being run with as well as the rank of -! each processor. -!=============================================================================== - - subroutine initialize_mpi(intracomm) -#ifdef OPENMC_MPIF08 - type(MPI_Comm), intent(in) :: intracomm ! MPI intracommunicator -#else - integer, intent(in) :: intracomm ! MPI intracommunicator -#endif - - integer :: mpi_err ! MPI error code - integer :: bank_blocks(5) ! Count for each datatype -#ifdef OPENMC_MPIF08 - type(MPI_Datatype) :: bank_types(5) -#else - integer :: bank_types(5) ! Datatypes -#endif - integer(MPI_ADDRESS_KIND) :: bank_disp(5) ! Displacements - type(Bank) :: b - - ! Indicate that MPI is turned on - mpi_enabled = .true. - mpi_intracomm = intracomm - - ! ========================================================================== - ! CREATE MPI_BANK TYPE - - ! Determine displacements for MPI_BANK type - call MPI_GET_ADDRESS(b % wgt, bank_disp(1), mpi_err) - call MPI_GET_ADDRESS(b % xyz, bank_disp(2), mpi_err) - call MPI_GET_ADDRESS(b % uvw, bank_disp(3), mpi_err) - call MPI_GET_ADDRESS(b % E, bank_disp(4), mpi_err) - call MPI_GET_ADDRESS(b % delayed_group, bank_disp(5), mpi_err) - - ! Adjust displacements - bank_disp = bank_disp - bank_disp(1) - - ! Define MPI_BANK for fission sites - bank_blocks = (/ 1, 3, 3, 1, 1 /) - bank_types = (/ MPI_REAL8, MPI_REAL8, MPI_REAL8, MPI_REAL8, MPI_INTEGER /) - call MPI_TYPE_CREATE_STRUCT(5, bank_blocks, bank_disp, & - bank_types, MPI_BANK, mpi_err) - call MPI_TYPE_COMMIT(MPI_BANK, mpi_err) - - end subroutine initialize_mpi -#endif - !=============================================================================== ! READ_COMMAND_LINE reads all parameters from the command line !=============================================================================== - subroutine read_command_line() + subroutine read_command_line() bind(C) ! Arguments were already read on C++ side (initialize.cpp). Here we just ! convert the C-style strings to Fortran style diff --git a/src/initialize.cpp b/src/initialize.cpp index 0c26135dc9..e04ac2922d 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -1,12 +1,13 @@ #include "openmc/initialize.h" #include +#include // for getenv #include #include #include #ifdef _OPENMP -#include "omp.h" +#include #endif #include "openmc/capi.h" @@ -14,12 +15,17 @@ #include "openmc/error.h" #include "openmc/hdf5_interface.h" #include "openmc/message_passing.h" +#include "openmc/random_lcg.h" #include "openmc/settings.h" +#include "openmc/simulation.h" #include "openmc/string_utils.h" +#include "openmc/timer.h" // data/functions from Fortran side extern "C" void print_usage(); extern "C" void print_version(); +extern "C" void read_command_line(); +extern "C" void read_input_xml(); // Paths to various files extern "C" { @@ -28,6 +34,8 @@ extern "C" { int openmc_init(int argc, char* argv[], const void* intracomm) { + using namespace openmc; + #ifdef OPENMC_MPI // Check if intracomm was passed MPI_Comm comm; @@ -38,21 +46,41 @@ int openmc_init(int argc, char* argv[], const void* intracomm) } // Initialize MPI for C++ - openmc::initialize_mpi(comm); + initialize_mpi(comm); #endif // Parse command-line arguments - int err = openmc::parse_command_line(argc, argv); + int err = parse_command_line(argc, argv); if (err) return err; - // Continue with rest of initialization -#ifdef OPENMC_MPI - MPI_Fint fcomm = MPI_Comm_c2f(comm); - openmc_init_f(&fcomm); -#else - openmc_init_f(nullptr); + // Start total and initialization timer + time_total.start(); + time_initialize.start(); + +#ifdef _OPENMP + // If OMP_SCHEDULE is not set, default to a static schedule + char* envvar = std::getenv("OMP_SCHEDULE"); + if (!envvar) { + omp_set_schedule(omp_sched_static, 0); + } #endif + // Read command line arguments + read_command_line(); + + // Initialize random number generator -- if the user specifies a seed, it + // will be re-initialized later + openmc_set_seed(DEFAULT_SEED); + + // Read XML input files + read_input_xml(); + + // Check for particle restart run + if (settings::particle_restart_run) settings::run_mode = RUN_MODE_PARTICLE; + + // Stop initialization timer + time_initialize.stop(); + return 0; } @@ -79,16 +107,18 @@ void initialize_mpi(MPI_Comm intracomm) // Create bank datatype Bank b; - MPI_Aint disp[] { - offsetof(Bank, wgt), - offsetof(Bank, xyz), - offsetof(Bank, uvw), - offsetof(Bank, E), - offsetof(Bank, delayed_group) - }; - int blocks[] {1, 3, 3, 1, 1}; - MPI_Datatype types[] {MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_INT}; - MPI_Type_create_struct(5, blocks, disp, types, &mpi::bank); + MPI_Aint disp[6]; + MPI_Get_address(&b.wgt, &disp[0]); + MPI_Get_address(&b.xyz, &disp[1]); + MPI_Get_address(&b.uvw, &disp[2]); + MPI_Get_address(&b.E, &disp[3]); + MPI_Get_address(&b.delayed_group, &disp[4]); + MPI_Get_address(&b.particle, &disp[5]); + for (int i = 5; i >= 0; --i) disp[i] -= disp[0]; + + int blocks[] {1, 3, 3, 1, 1, 1}; + MPI_Datatype types[] {MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_INT, MPI_INT}; + MPI_Type_create_struct(6, blocks, disp, types, &mpi::bank); MPI_Type_commit(&mpi::bank); } #endif // OPENMC_MPI @@ -172,13 +202,13 @@ parse_command_line(int argc, char* argv[]) #ifdef _OPENMP // Read and set number of OpenMP threads - openmc_n_threads = std::stoi(argv[i]); - if (openmc_n_threads < 1) { + simulation::n_threads = std::stoi(argv[i]); + if (simulation::n_threads < 1) { std::string msg {"Number of threads must be positive."}; strcpy(openmc_err_msg, msg.c_str()); return OPENMC_E_INVALID_ARGUMENT; } - omp_set_num_threads(openmc_n_threads); + omp_set_num_threads(simulation::n_threads); #else if (openmc_master) warning("Ignoring number of threads specified on command line."); diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 420ae5bbb8..74ca51223b 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -22,12 +22,12 @@ module input_xml use mgxs_data, only: create_macro_xs, read_mgxs use mgxs_interface use nuclide_header - use output, only: title, header, print_plot + use multipole_header + use output, only: title, header use photon_header - use plot_header use random_lcg, only: prn use surface_header - use set_header, only: SetChar + use set_header, only: SetChar, SetInt use settings use stl_vector, only: VectorInt, VectorReal, VectorChar use string, only: to_lower, to_str, str_to_int, str_to_real, & @@ -59,12 +59,8 @@ module input_xml integer(C_INT32_T), intent(in), value :: univ_indx end subroutine count_cell_instances - subroutine prepare_distribcell_c(cell_list, n) & - bind(C, name="prepare_distribcell") - import C_INT32_T, C_INT - integer(C_INT), intent(in), value :: n - integer(C_INT32_T), intent(in) :: cell_list(n) - end subroutine prepare_distribcell_c + subroutine prepare_distribcell() bind(C) + end subroutine prepare_distribcell subroutine read_surfaces(node_ptr) bind(C) import C_PTR @@ -100,6 +96,14 @@ module input_xml integer(C_INT) :: n end function maximum_levels + subroutine read_plots(node_ptr) bind(C) + import C_PTR + type(C_PTR) :: node_ptr + end subroutine read_plots + + subroutine print_plot() bind(C) + end subroutine print_plot + subroutine set_particle_energy_bounds(particle, E_min, E_max) bind(C) import C_INT, C_DOUBLE integer(C_INT), value :: particle @@ -115,7 +119,7 @@ contains ! geometry, materials, and tallies. !=============================================================================== - subroutine read_input_xml() + subroutine read_input_xml() bind(C) type(VectorReal), allocatable :: nuc_temps(:) ! List of T to read for each nuclide type(VectorReal), allocatable :: sab_temps(:) ! List of T to read for each S(a,b) @@ -210,10 +214,7 @@ contains integer :: i integer :: n - integer, allocatable :: temp_int_array(:) - integer :: n_tracks type(XMLNode) :: root - type(XMLNode) :: node_sp type(XMLNode) :: node_res_scat type(XMLNode) :: node_vol type(XMLNode), allocatable :: node_vol_list(:) @@ -221,113 +222,6 @@ contains ! Get proper XMLNode type given pointer root % ptr = root_ptr - if (run_mode == MODE_EIGENVALUE) then - ! Preallocate space for keff and entropy by generation - call k_generation % reserve(n_max_batches*gen_per_batch) - end if - - ! Particle tracks - if (check_for_node(root, "track")) then - ! Make sure that there are three values per particle - n_tracks = node_word_count(root, "track") - if (mod(n_tracks, 3) /= 0) then - call fatal_error("Number of integers specified in 'track' is not & - &divisible by 3. Please provide 3 integers per particle to be & - &tracked.") - end if - - ! Allocate space and get list of tracks - allocate(temp_int_array(n_tracks)) - call get_node_array(root, "track", temp_int_array) - - ! Reshape into track_identifiers - allocate(track_identifiers(3, n_tracks/3)) - track_identifiers = reshape(temp_int_array, [3, n_tracks/3]) - end if - - ! Check if the user has specified to write state points - if (check_for_node(root, "state_point")) then - - ! Get pointer to state_point node - node_sp = root % child("state_point") - - ! Determine number of batches at which to store state points - if (check_for_node(node_sp, "batches")) then - n_state_points = node_word_count(node_sp, "batches") - else - n_state_points = 0 - end if - - if (n_state_points > 0) then - ! User gave specific batches to write state points - allocate(temp_int_array(n_state_points)) - call get_node_array(node_sp, "batches", temp_int_array) - do i = 1, n_state_points - call statepoint_batch % add(temp_int_array(i)) - end do - deallocate(temp_int_array) - else - ! If neither were specified, write state point at last batch - n_state_points = 1 - call statepoint_batch % add(n_batches) - end if - else - ! If no tag was present, by default write state point at - ! last batch only - n_state_points = 1 - call statepoint_batch % add(n_batches) - end if - - ! Check if the user has specified to write source points - if (check_for_node(root, "source_point")) then - - ! Get pointer to source_point node - node_sp = root % child("source_point") - - ! Determine number of batches at which to store source points - if (check_for_node(node_sp, "batches")) then - n_source_points = node_word_count(node_sp, "batches") - else - n_source_points = 0 - end if - - if (n_source_points > 0) then - ! User gave specific batches to write source points - allocate(temp_int_array(n_source_points)) - call get_node_array(node_sp, "batches", temp_int_array) - do i = 1, n_source_points - call sourcepoint_batch % add(temp_int_array(i)) - end do - deallocate(temp_int_array) - else - ! If neither were specified, write source points with state points - n_source_points = n_state_points - do i = 1, n_state_points - call sourcepoint_batch % add(statepoint_batch % get_item(i)) - end do - end if - else - ! If no tag was present, by default we keep source bank in - ! statepoint file and write it out at statepoints intervals - n_source_points = n_state_points - do i = 1, n_state_points - call sourcepoint_batch % add(statepoint_batch % get_item(i)) - end do - end if - - ! If source is not seperate and is to be written out in the statepoint file, - ! make sure that the sourcepoint batch numbers are contained in the - ! statepoint list - if (.not. source_separate) then - do i = 1, n_source_points - if (.not. statepoint_batch % contains(sourcepoint_batch % & - get_item(i))) then - call fatal_error('Sourcepoint batches are not a subset& - & of statepoint batches.') - end if - end do - end if - ! Resonance scattering parameters if (check_for_node(root, "resonance_scattering")) then node_res_scat = root % child("resonance_scattering") @@ -363,7 +257,7 @@ contains subroutine read_geometry_dagmc() - integer :: i, j + integer :: i integer :: univ_id integer :: n_cells_in_univ logical :: file_exists @@ -395,7 +289,6 @@ contains 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) @@ -473,9 +366,6 @@ contains surfaces(i) % ptr = surface_pointer(i - 1); if (surfaces(i) % bc() /= BC_TRANSMIT) boundary_exists = .true. - - ! Add surface to dictionary - call surface_dict % set(surfaces(i) % id(), i) end do ! Check to make sure a boundary condition was applied to at least one @@ -565,7 +455,6 @@ contains 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 - 1) call univ_ids % push_back(univ_id) else n_cells_in_univ = 1 + cells_in_univ_dict % get(univ_id) @@ -634,8 +523,6 @@ contains do i = 1, n_surfaces surfaces(i) % ptr = surface_pointer(i - 1); - ! Add surface to dictionary - call surface_dict % set(surfaces(i) % id(), i) end do end subroutine allocate_surfaces @@ -1332,7 +1219,11 @@ contains ! Read derivative attributes. do i = 1, size(node_deriv_list) +!$omp parallel +!$omp critical (ReadTallyDeriv) call tally_derivs(i) % from_xml(node_deriv_list(i)) +!$omp end critical (ReadTallyDeriv) +!$omp end parallel ! Update tally derivative dictionary call tally_deriv_dict % set(tally_derivs(i) % id, i) @@ -2146,26 +2037,10 @@ contains subroutine read_plots_xml() - integer :: i, j - integer :: n_cols, col_id, n_comp, n_masks, n_meshlines - integer :: meshid - integer(C_INT) :: err, idx - integer, allocatable :: iarray(:) logical :: file_exists ! does plots.xml file exist? character(MAX_LINE_LEN) :: filename ! absolute path to plots.xml - character(MAX_LINE_LEN) :: temp_str - character(MAX_WORD_LEN) :: meshtype - type(ObjectPlot), pointer :: pl => null() type(XMLDocument) :: doc type(XMLNode) :: root - type(XMLNode) :: node_plot - type(XMLNode) :: node_col - type(XMLNode) :: node_mask - type(XMLNode) :: node_meshlines - type(XMLNode), allocatable :: node_plot_list(:) - type(XMLNode), allocatable :: node_col_list(:) - type(XMLNode), allocatable :: node_mask_list(:) - type(XMLNode), allocatable :: node_meshline_list(:) ! Check if plots.xml exists filename = trim(path_input) // "plots.xml" @@ -2182,425 +2057,7 @@ contains call doc % load_file(filename) root = doc % document_element() - ! Get list pointer to XML - call get_node_list(root, "plot", node_plot_list) - - ! Allocate plots array - n_plots = size(node_plot_list) - allocate(plots(n_plots)) - - READ_PLOTS: do i = 1, n_plots - pl => plots(i) - - ! Get pointer to plot XML node - node_plot = node_plot_list(i) - - ! Copy data into plots - if (check_for_node(node_plot, "id")) then - call get_node_value(node_plot, "id", pl % id) - else - call fatal_error("Must specify plot id in plots XML file.") - end if - - ! Check to make sure 'id' hasn't been used - 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 - - ! Copy plot type - temp_str = 'slice' - if (check_for_node(node_plot, "type")) & - call get_node_value(node_plot, "type", temp_str) - temp_str = to_lower(temp_str) - select case (trim(temp_str)) - case ("slice") - pl % type = PLOT_TYPE_SLICE - case ("voxel") - pl % type = PLOT_TYPE_VOXEL - case default - call fatal_error("Unsupported plot type '" // trim(temp_str) & - // "' in plot " // trim(to_str(pl % id))) - end select - - ! Set output file path - filename = "plot_" // trim(to_str(pl % id)) - if (check_for_node(node_plot, "filename")) & - call get_node_value(node_plot, "filename", filename) - select case (pl % type) - case (PLOT_TYPE_SLICE) - pl % path_plot = trim(path_input) // trim(filename) // ".ppm" - case (PLOT_TYPE_VOXEL) - pl % path_plot = trim(path_input) // trim(filename) // ".h5" - end select - - ! Copy plot pixel size - if (pl % type == PLOT_TYPE_SLICE) then - if (node_word_count(node_plot, "pixels") == 2) then - call get_node_array(node_plot, "pixels", pl % pixels(1:2)) - else - call fatal_error(" must be length 2 in slice plot " & - // trim(to_str(pl % id))) - end if - else if (pl % type == PLOT_TYPE_VOXEL) then - if (node_word_count(node_plot, "pixels") == 3) then - call get_node_array(node_plot, "pixels", pl % pixels(1:3)) - else - call fatal_error(" must be length 3 in voxel plot " & - // trim(to_str(pl % id))) - end if - end if - - ! Copy plot background color - if (check_for_node(node_plot, "background")) then - if (pl % type == PLOT_TYPE_VOXEL) then - if (master) call warning("Background color ignored in voxel plot " & - // trim(to_str(pl % id))) - end if - if (node_word_count(node_plot, "background") == 3) then - call get_node_array(node_plot, "background", pl % not_found % rgb) - else - call fatal_error("Bad background RGB in plot " & - // trim(to_str(pl % id))) - end if - else - pl % not_found % rgb = (/ 255, 255, 255 /) - end if - - ! Copy plot basis - if (pl % type == PLOT_TYPE_SLICE) then - temp_str = 'xy' - if (check_for_node(node_plot, "basis")) & - call get_node_value(node_plot, "basis", temp_str) - temp_str = to_lower(temp_str) - select case (trim(temp_str)) - case ("xy") - pl % basis = PLOT_BASIS_XY - case ("xz") - pl % basis = PLOT_BASIS_XZ - case ("yz") - pl % basis = PLOT_BASIS_YZ - case default - call fatal_error("Unsupported plot basis '" // trim(temp_str) & - // "' in plot " // trim(to_str(pl % id))) - end select - end if - - ! Copy plotting origin - if (node_word_count(node_plot, "origin") == 3) then - call get_node_array(node_plot, "origin", pl % origin) - else - call fatal_error("Origin must be length 3 in plot " & - // trim(to_str(pl % id))) - end if - - ! Copy plotting width - if (pl % type == PLOT_TYPE_SLICE) then - if (node_word_count(node_plot, "width") == 2) then - call get_node_array(node_plot, "width", pl % width(1:2)) - else - call fatal_error(" must be length 2 in slice plot " & - // trim(to_str(pl % id))) - end if - else if (pl % type == PLOT_TYPE_VOXEL) then - if (node_word_count(node_plot, "width") == 3) then - call get_node_array(node_plot, "width", pl % width(1:3)) - else - call fatal_error(" must be length 3 in voxel plot " & - // trim(to_str(pl % id))) - end if - end if - - ! Copy plot cell universe level - if (check_for_node(node_plot, "level")) then - call get_node_value(node_plot, "level", pl % level) - - if (pl % level < 0) then - call fatal_error("Bad universe level in plot " & - // trim(to_str(pl % id))) - end if - else - pl % level = PLOT_LEVEL_LOWEST - end if - - ! Copy plot color type and initialize all colors randomly - temp_str = "cell" - if (check_for_node(node_plot, "color_by")) & - call get_node_value(node_plot, "color_by", temp_str) - temp_str = to_lower(temp_str) - select case (trim(temp_str)) - case ("cell") - - pl % color_by = PLOT_COLOR_CELLS - allocate(pl % colors(n_cells)) - do j = 1, n_cells - pl % colors(j) % rgb(1) = int(prn()*255) - pl % colors(j) % rgb(2) = int(prn()*255) - pl % colors(j) % rgb(3) = int(prn()*255) - end do - - case ("material") - - pl % color_by = PLOT_COLOR_MATS - allocate(pl % colors(n_materials)) - do j = 1, n_materials - pl % colors(j) % rgb(1) = int(prn()*255) - pl % colors(j) % rgb(2) = int(prn()*255) - pl % colors(j) % rgb(3) = int(prn()*255) - end do - - case default - call fatal_error("Unsupported plot color type '" // trim(temp_str) & - // "' in plot " // trim(to_str(pl % id))) - end select - - ! Get the number of nodes and get a list of them - call get_node_list(node_plot, "color", node_col_list) - n_cols = size(node_col_list) - - ! Copy user specified colors - if (n_cols /= 0) then - - if (pl % type == PLOT_TYPE_VOXEL) then - if (master) call warning("Color specifications ignored in voxel & - &plot " // trim(to_str(pl % id))) - end if - - do j = 1, n_cols - - ! Get pointer to color spec XML node - node_col = node_col_list(j) - - ! Check and make sure 3 values are specified for RGB - if (node_word_count(node_col, "rgb") /= 3) then - call fatal_error("Bad RGB in plot " & - // trim(to_str(pl % id))) - end if - - ! Ensure that there is an id for this color specification - if (check_for_node(node_col, "id")) then - call get_node_value(node_col, "id", col_id) - else - call fatal_error("Must specify id for color specification in & - &plot " // trim(to_str(pl % id))) - end if - - ! Add RGB - if (pl % color_by == PLOT_COLOR_CELLS) then - - 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)) & - // " specified in plot " // trim(to_str(pl % id))) - end if - - else if (pl % color_by == PLOT_COLOR_MATS) then - - 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 " & - // trim(to_str(col_id)) // " specified in plot " & - // trim(to_str(pl % id))) - end if - - end if - end do - end if - - ! Deal with meshlines - call get_node_list(node_plot, "meshlines", node_meshline_list) - n_meshlines = size(node_meshline_list) - if (n_meshlines /= 0) then - - if (pl % type == PLOT_TYPE_VOXEL) then - call warning("Meshlines ignored in voxel plot " & - // trim(to_str(pl % id))) - end if - - select case(n_meshlines) - case (0) - ! Skip if no meshlines are specified - case (1) - - ! Get pointer to meshlines - node_meshlines = node_meshline_list(1) - - ! Check mesh type - if (check_for_node(node_meshlines, "meshtype")) then - call get_node_value(node_meshlines, "meshtype", meshtype) - else - call fatal_error("Must specify a meshtype for meshlines & - &specification in plot " // trim(to_str(pl % id))) - end if - - ! Ensure that there is a linewidth for this meshlines specification - if (check_for_node(node_meshlines, "linewidth")) then - call get_node_value(node_meshlines, "linewidth", & - pl % meshlines_width) - else - call fatal_error("Must specify a linewidth for meshlines & - &specification in plot " // trim(to_str(pl % id))) - end if - - ! Check for color - if (check_for_node(node_meshlines, "color")) then - - ! Check and make sure 3 values are specified for RGB - if (node_word_count(node_meshlines, "color") /= 3) then - call fatal_error("Bad RGB for meshlines color in plot " & - // trim(to_str(pl % id))) - end if - - call get_node_array(node_meshlines, "color", & - pl % meshlines_color % rgb) - else - - pl % meshlines_color % rgb = (/ 0, 0, 0 /) - - end if - - ! Set mesh based on type - select case (trim(meshtype)) - case ('ufs') - - if (index_ufs_mesh < 0) then - call fatal_error("No UFS mesh for meshlines on plot " & - // trim(to_str(pl % id))) - end if - - pl % index_meshlines_mesh = index_ufs_mesh - - case ('cmfd') - - if (.not. cmfd_run) then - call fatal_error("Need CMFD run to plot CMFD mesh for & - &meshlines on plot " // trim(to_str(pl % id))) - end if - - pl % index_meshlines_mesh = index_cmfd_mesh - - case ('entropy') - - if (index_entropy_mesh < 0) then - call fatal_error("No entropy mesh for meshlines on plot " & - // trim(to_str(pl % id))) - end if - - pl % index_meshlines_mesh = index_entropy_mesh - - case ('tally') - - ! Ensure that there is a mesh id if the type is tally - if (check_for_node(node_meshlines, "id")) then - call get_node_value(node_meshlines, "id", meshid) - else - call fatal_error("Must specify a mesh id for meshlines tally & - &mesh specification in plot " // trim(to_str(pl % id))) - end if - - ! Check if the specified tally mesh exists - err = openmc_get_mesh_index(meshid, idx) - if (err /= 0) then - call fatal_error("Could not find mesh " & - // trim(to_str(meshid)) // " specified in meshlines for & - &plot " // trim(to_str(pl % id))) - end if - pl % index_meshlines_mesh = idx - - case default - call fatal_error("Invalid type for meshlines on plot " & - // trim(to_str(pl % id)) // ": " // trim(meshtype)) - end select - - case default - call fatal_error("Mutliple meshlines specified in plot " & - // trim(to_str(pl % id))) - end select - - end if - - ! Deal with masks - call get_node_list(node_plot, "mask", node_mask_list) - n_masks = size(node_mask_list) - if (n_masks /= 0) then - - if (pl % type == PLOT_TYPE_VOXEL) then - if (master) call warning("Mask ignored in voxel plot " & - // trim(to_str(pl % id))) - end if - - select case(n_masks) - case default - call fatal_error("Mutliple masks specified in plot " & - // trim(to_str(pl % id))) - case (1) - - ! Get pointer to mask - node_mask = node_mask_list(1) - - ! Determine how many components there are and allocate - n_comp = 0 - n_comp = node_word_count(node_mask, "components") - if (n_comp == 0) then - call fatal_error("Missing in mask of plot " & - // trim(to_str(pl % id))) - end if - allocate(iarray(n_comp)) - call get_node_array(node_mask, "components", iarray) - - ! First we need to change the user-specified identifiers to indices - ! in the cell and material arrays - do j=1, n_comp - col_id = iarray(j) - - if (pl % color_by == PLOT_COLOR_CELLS) then - - 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 & - &plot " // trim(to_str(pl % id))) - end if - - else if (pl % color_by == PLOT_COLOR_MATS) then - - 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 & - &plot " // trim(to_str(pl % id))) - end if - - end if - end do - - ! Alter colors based on mask information - do j = 1, size(pl % colors) - if (.not. any(j == iarray)) then - if (check_for_node(node_mask, "background")) then - call get_node_array(node_mask, "background", pl % colors(j) % rgb) - else - pl % colors(j) % rgb(:) = [255, 255, 255] - end if - end if - end do - - deallocate(iarray) - - end select - - end if - - ! Add plot to dictionary - call plot_dict % set(pl % id, i) - - end do READ_PLOTS + call read_plots(root % ptr) ! Close plots XML file call doc % clear() @@ -2724,6 +2181,13 @@ contains integer(HID_T) :: file_id character(len=MAX_WORD_LEN), allocatable :: names(:) + interface + subroutine read_mg_cross_sections_header_c(file_id) bind(C) + import HID_T + integer(HID_T), value :: file_id + end subroutine + end interface + ! Check if MGXS Library exists inquire(FILE=path_cross_sections, EXIST=file_exists) if (.not. file_exists) then @@ -2771,6 +2235,9 @@ contains energy_bin_avg(i) = HALF * (energy_bins(i) + energy_bins(i + 1)) end do + ! Set up energy bins on C++ side + call read_mg_cross_sections_header_c(file_id) + ! Get the minimum and maximum energies energy_min(NEUTRON) = energy_bins(num_energy_groups + 1) energy_max(NEUTRON) = energy_bins(1) @@ -2986,7 +2453,7 @@ contains ! Check if material is fissionable if (nuclides(materials(i) % nuclide(j)) % fissionable) then - materials(i) % fissionable = .true. + call materials(i) % set_fissionable(.true.) end if end do @@ -3110,7 +2577,9 @@ contains logical :: file_exists ! Does multipole library exist? character(7) :: readable ! Is multipole library readable? - character(MAX_FILE_LEN) :: filename ! Path to multipole xs library + character(MAX_FILE_LEN) :: filename ! Path to multipole xs library + integer(HID_T) :: file_id + integer(HID_T) :: group_id ! For the time being, and I know this is a bit hacky, we just assume ! that the file will be ZZZAAAmM.h5. @@ -3136,45 +2605,24 @@ contains end if ! Display message - call write_message("Loading Multipole XS table: " // filename, 6) + call write_message("Loading Windowed Multipole XS from " // filename, 6) + ! Open file and make sure version is sufficient + file_id = file_open(filename, 'r') + call check_wmp_version(file_id) + + ! Read nuclide data from HDF5 + group_id = open_group(file_id, nuc % name) allocate(nuc % multipole) - - ! Call the read routine - call nuc % multipole % from_hdf5(filename) + call nuc % multipole % from_hdf5(group_id) nuc % mp_present = .true. + ! Close the group and file. + call close_group(group_id) + call file_close(file_id) + end associate end subroutine read_multipole_data -!=============================================================================== -! PREPARE_DISTRIBCELL initializes any distribcell filters present and sets the -! offsets for distribcells -!=============================================================================== - - subroutine prepare_distribcell() - - integer :: i, j - type(SetInt) :: cell_list ! distribcells to track - integer(C_INT32_T), allocatable :: cell_list_c(:) - - ! Find all cells listed in a distribcell filter. - 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 - - allocate(cell_list_c(cell_list % size())) - do i = 1, cell_list % size() - cell_list_c(i) = cell_list % get_item(i) - 1 - end do - call prepare_distribcell_c(cell_list_c, cell_list % size()) - - end subroutine prepare_distribcell - end module input_xml diff --git a/src/lattice.cpp b/src/lattice.cpp index 21f4768506..14b679903d 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -893,12 +893,6 @@ extern "C" { Lattice* lattice_pointer(int lat_ind) {return lattices[lat_ind];} int32_t lattice_id(Lattice *lat) {return lat->id_;} - - bool lattice_are_valid_indices(Lattice *lat, const int i_xyz[3]) - {return lat->are_valid_indices(i_xyz);} - - int32_t lattice_offset(Lattice *lat, int map, const int i_xyz[3]) - {return lat->offset(map, i_xyz);} } } // namespace openmc diff --git a/src/material.cpp b/src/material.cpp index 1a0c2d2078..ecc6eed2bc 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -23,7 +23,7 @@ std::unordered_map material_map; Material::Material(pugi::xml_node node) { if (check_for_node(node, "id")) { - id = std::stoi(get_node_value(node, "id")); + id_ = std::stoi(get_node_value(node, "id")); } else { fatal_error("Must specify id of material in materials XML file."); } @@ -52,7 +52,7 @@ read_materials(pugi::xml_node* node) // Populate the material map. for (int i = 0; i < materials.size(); i++) { - int32_t mid = materials[i]->id; + int32_t mid = materials[i]->id_; auto search = material_map.find(mid); if (search == material_map.end()) { material_map[mid] = i; @@ -78,7 +78,7 @@ openmc_material_get_volume(int32_t index, double* volume) return 0; } else { std::stringstream msg; - msg << "Volume for material with ID=" << m->id << " not set."; + msg << "Volume for material with ID=" << m->id_ << " not set."; set_errmsg(msg); return OPENMC_E_UNASSIGNED; } @@ -113,15 +113,22 @@ openmc_material_set_volume(int32_t index, double volume) extern "C" { Material* material_pointer(int32_t indx) {return materials[indx];} - int32_t material_id(Material* mat) {return mat->id;} + int32_t material_id(Material* mat) {return mat->id_;} void material_set_id(Material* mat, int32_t id, int32_t index) { - mat->id = id; + mat->id_ = id; //TODO: off-by-one material_map[id] = index - 1; } + bool material_fissionable(Material* mat) {return mat->fissionable;} + + void material_set_fissionable(Material* mat, bool fissionable) + { + mat->fissionable = fissionable; + } + void extend_materials_c(int32_t n) { materials.reserve(materials.size() + n); diff --git a/src/material_header.F90 b/src/material_header.F90 index 71e6ec5f1a..b4d0617255 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -51,6 +51,20 @@ module material_header integer(C_INT32_T), intent(in), value :: index end subroutine material_set_id_c + function material_fissionable_c(mat_ptr) & + bind(C, name='material_fissionable') result(fissionable) + import C_PTR, C_BOOL + type(C_PTR), intent(in), value :: mat_ptr + logical(C_BOOL) :: fissionable + end function material_fissionable_c + + subroutine material_set_fissionable_c(mat_ptr, fissionable) & + bind(C, name='material_set_fissionable') + import C_PTR, C_BOOL + type(C_PTR), intent(in), value :: mat_ptr + logical(C_BOOL), intent(in), value :: fissionable + end subroutine material_set_fissionable_c + subroutine extend_materials_c(n) bind(C) import C_INT32_T integer(C_INT32_T), intent(in), value :: n @@ -95,7 +109,6 @@ module material_header character(20), allocatable :: sab_names(:) ! name of S(a,b) table ! Does this material contain fissionable nuclides? Is it depletable? - logical :: fissionable = .false. logical :: depletable = .false. ! enforce isotropic scattering in lab for specific nuclides @@ -105,6 +118,8 @@ module material_header contains procedure :: id => material_id procedure :: set_id => material_set_id + procedure :: fissionable => material_fissionable + procedure :: set_fissionable => material_set_fissionable procedure :: set_density => material_set_density procedure :: init_nuclide_index => material_init_nuclide_index procedure :: assign_sab_tables => material_assign_sab_tables @@ -139,6 +154,18 @@ contains call material_set_id_c(this % ptr, id, index) end subroutine material_set_id + function material_fissionable(this) result(fissionable) + class(Material), intent(in) :: this + logical(C_BOOL) :: fissionable + fissionable = material_fissionable_c(this % ptr) + end function material_fissionable + + subroutine material_set_fissionable(this, fissionable) + class(Material),intent(in) :: this + logical, intent(in) :: fissionable + call material_set_fissionable_c(this % ptr, logical(fissionable, C_BOOL)) + end subroutine material_set_fissionable + function material_set_density(this, density) result(err) class(Material), intent(inout) :: this real(8), intent(in) :: density @@ -685,7 +712,7 @@ contains integer(C_INT) :: err if (index >= 1 .and. index <= size(materials)) then - fissionable = materials(index) % fissionable + fissionable = materials(index) % fissionable() err = 0 else err = E_OUT_OF_BOUNDS diff --git a/src/math.F90 b/src/math.F90 index 9964922c17..fe4156b9dc 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -10,12 +10,7 @@ module math public :: t_percentile public :: calc_pn public :: calc_rn - public :: calc_zn - public :: calc_zn_rad - public :: evaluate_legendre public :: rotate_angle - public :: maxwell_spectrum - public :: watt_spectrum public :: faddeeva public :: w_derivative public :: broaden_wmp_polynomials @@ -42,16 +37,6 @@ module math real(C_DOUBLE), intent(out) :: pnx(n + 1) end subroutine calc_pn - pure function evaluate_legendre_c_intfc(n, data, x) & - bind(C, name='evaluate_legendre_c') result(val) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: n - real(C_DOUBLE), intent(in) :: data(n) - real(C_DOUBLE), value, intent(in) :: x - real(C_DOUBLE) :: val - end function evaluate_legendre_c_intfc - pure subroutine calc_rn(n, uvw, rn) bind(C, name='calc_rn_c') use ISO_C_BINDING implicit none @@ -60,23 +45,6 @@ module math real(C_DOUBLE), intent(out) :: rn(2 * n + 1) end subroutine calc_rn - pure subroutine calc_zn(n, rho, phi, zn) bind(C, name='calc_zn_c') - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: n - real(C_DOUBLE), value, intent(in) :: rho - real(C_DOUBLE), value, intent(in) :: phi - real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - end subroutine calc_zn - - pure subroutine calc_zn_rad(n, rho, zn_rad) bind(C, name='calc_zn_rad_c') - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: n - real(C_DOUBLE), value, intent(in) :: rho - real(C_DOUBLE), intent(out) :: zn_rad((n / 2) + 1) - end subroutine calc_zn_rad - subroutine rotate_angle_c_intfc(uvw, mu, phi) bind(C, name='rotate_angle_c') use ISO_C_BINDING implicit none @@ -85,23 +53,6 @@ module math real(C_DOUBLE), optional, intent(in) :: phi end subroutine rotate_angle_c_intfc - function maxwell_spectrum(T) bind(C, name='maxwell_spectrum_c') & - result(E_out) - use ISO_C_BINDING - implicit none - real(C_DOUBLE), value, intent(in) :: T - real(C_DOUBLE) :: E_out - end function maxwell_spectrum - - function watt_spectrum(a, b) bind(C, name='watt_spectrum_c') & - result(E_out) - use ISO_C_BINDING - implicit none - real(C_DOUBLE), value, intent(in) :: a - real(C_DOUBLE), value, intent(in) :: b - real(C_DOUBLE) :: E_out - end function watt_spectrum - subroutine broaden_wmp_polynomials(E, dopp, n, factors) & bind(C, name='broaden_wmp_polynomials_c') use ISO_C_BINDING @@ -157,20 +108,6 @@ module math contains -!=============================================================================== -! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -! and the value of x -!=============================================================================== - - pure function evaluate_legendre(data, x) result(val) bind(C) - real(C_DOUBLE), intent(in) :: data(:) - real(C_DOUBLE), intent(in) :: x - real(C_DOUBLE) :: val - - val = evaluate_legendre_c_intfc(size(data) - 1, data, x) - - end function evaluate_legendre - !=============================================================================== ! ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is ! mu and through an azimuthal angle sampled uniformly. Note that this is done diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 5c1a2f4dce..a8646f09d9 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -6,7 +6,7 @@ namespace openmc { // Mathematical methods //============================================================================== -double normal_percentile_c(double p) { +double normal_percentile(double p) { constexpr double p_low = 0.02425; constexpr double a[6] = {-3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.383577518672690e2, @@ -79,7 +79,7 @@ double t_percentile_c(double p, int df){ // 16 (4), pp. 1123-1132 (1987). double n = df; double k = 1. / (n - 2.); - double z = normal_percentile_c(p); + double z = normal_percentile(p); double z2 = z * z; t = std::sqrt(n * k) * (z + (z2 - 3.) * z * k / 4. + ((5. * z2 - 56.) * z2 + 75.) * z * k * k / 96. + (((z2 - 27.) * 3. * z2 + 417.) * z2 - 315.) * @@ -103,7 +103,7 @@ void calc_pn_c(int n, double x, double pnx[]) { } -double evaluate_legendre_c(int n, const double data[], double x) { +double evaluate_legendre(int n, const double data[], double x) { double pnx[n + 1]; double val = 0.0; calc_pn_c(n, x, pnx); @@ -510,7 +510,7 @@ void calc_rn_c(int n, const double uvw[3], double rn[]){ } -void calc_zn_c(int n, double rho, double phi, double zn[]) { +void calc_zn(int n, double rho, double phi, double zn[]) { // =========================================================================== // Determine vector of sin(n*phi) and cos(n*phi). This takes advantage of the // following recurrence relations so that only a single sin/cos have to be @@ -587,7 +587,7 @@ void calc_zn_c(int n, double rho, double phi, double zn[]) { } -void calc_zn_rad_c(int n, double rho, double zn_rad[]) { +void calc_zn_rad(int n, double rho, double zn_rad[]) { // Calculate R_p0(rho) as Zn_p0(rho) // Set up the array of the coefficients @@ -658,7 +658,7 @@ Direction rotate_angle(Direction u, double mu, double* phi) } -double maxwell_spectrum_c(double T) { +double maxwell_spectrum(double T) { // Set the random numbers double r1 = prn(); double r2 = prn(); @@ -674,8 +674,8 @@ double maxwell_spectrum_c(double T) { } -double watt_spectrum_c(double a, double b) { - double w = maxwell_spectrum_c(a); +double watt_spectrum(double a, double b) { + double w = maxwell_spectrum(a); double E_out = w + 0.25 * a * a * b + (2. * prn() - 1.) * std::sqrt(a * a * b * w); return E_out; diff --git a/src/mesh.cpp b/src/mesh.cpp index af088ffd04..2c3e508fe3 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -20,6 +20,7 @@ #include "openmc/hdf5_interface.h" #include "openmc/message_passing.h" #include "openmc/search.h" +#include "openmc/tallies/filter.h" #include "openmc/xml_interface.h" namespace openmc { @@ -465,42 +466,38 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, // ======================================================================== // Compute the length of the track segment in the each mesh cell and return - double distance; - int j; if (ijk0 == ijk1) { // The track ends in this cell. Use the particle end location rather // than the mesh surface. - distance = (r1 - r0).norm(); - } else { - // The track exits this cell. Determine the distance to the closest mesh - // surface. - xt::xtensor d = xt::zeros({n}); - for (int j = 0; j < n; ++j) { - if (std::fabs(u[j]) < FP_PRECISION) { - d(j) = INFTY; - } else if (u[j] > 0) { - double xyz_cross = lower_left_[j] + ijk0(j) * width_[j]; - d(j) = (xyz_cross - r0[j]) / u[j]; - } else { - double xyz_cross = lower_left_[j] + (ijk0(j) - 1) * width_[j]; - d(j) = (xyz_cross - r0[j]) / u[j]; - } + double distance = (r1 - r0).norm(); + bins.push_back(get_bin_from_indices(ijk0.data())); + lengths.push_back(distance / total_distance); + break; + } + + // The track exits this cell. Determine the distance to the closest mesh + // surface. + xt::xtensor d = xt::zeros({n}); + for (int k = 0; k < n; ++k) { + if (std::fabs(u[k]) < FP_PRECISION) { + d(k) = INFTY; + } else if (u[k] > 0) { + double xyz_cross = lower_left_[k] + ijk0(k) * width_[k]; + d(k) = (xyz_cross - r0[k]) / u[k]; + } else { + double xyz_cross = lower_left_[k] + (ijk0(k) - 1) * width_[k]; + d(k) = (xyz_cross - r0[k]) / u[k]; } - j = xt::argmin(d)(0); - distance = d(j); } // Assign the next tally bin and the score. - int bin = get_bin_from_indices(ijk0.data()); - bins.push_back(bin); + auto j = xt::argmin(d)(0); + double distance = d(j); + bins.push_back(get_bin_from_indices(ijk0.data())); lengths.push_back(distance / total_distance); - // If the particle track ends in that bin, then we are done. - if (ijk0 == ijk1) break; - - // 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. + // Translate the starting coordintes by the distance to the oncoming mesh + // surface. r0 += distance * u; // Increment the indices into the next mesh cell. @@ -899,10 +896,6 @@ void meshes_to_hdf5(hid_t group) //============================================================================== extern "C" { - // Declaration of Fortran procedures - void vector_int_push_back(void* ptr, int value); - void vector_real_push_back(void* ptr, double value); - int n_meshes() { return meshes.size(); } RegularMesh* mesh_ptr(int i) { return meshes.at(i).get(); } @@ -941,35 +934,6 @@ extern "C" { m->get_indices_from_bin(bin, ijk); } - void mesh_bins_crossed(RegularMesh* m, const Particle* p, void* match_bins, - void* match_weights) - { - // Get bins crossed - std::vector bins; - std::vector lengths; - m->bins_crossed(p, bins, lengths); - - // Call bindings for VectorInt and VectorReal on Fortran side - for (int i = 0; i < bins.size(); ++i) { - vector_int_push_back(match_bins, bins[i]); - vector_real_push_back(match_weights, lengths[i]); - } - } - - void mesh_surface_bins_crossed(RegularMesh* m, const Particle* p, - void* match_bins, void* match_weights) - { - // Get surface bins crossed - std::vector bins; - m->surface_bins_crossed(p, bins); - - // Call bindings for VectorInt and VectorReal - for (auto b : bins) { - vector_int_push_back(match_bins, b); - vector_real_push_back(match_weights, 1.0); - } - } - void free_memory_mesh() { meshes.clear(); diff --git a/src/mesh_header.F90 b/src/mesh_header.F90 index d7e26fc6b8..a0c14077a8 100644 --- a/src/mesh_header.F90 +++ b/src/mesh_header.F90 @@ -140,21 +140,21 @@ module mesh_header pure function mesh_get_bin(ptr, xyz) result(bin) bind(C) import C_PTR, C_DOUBLE, C_INT - type(C_PTR), value :: ptr + type(C_PTR), intent(in), value :: ptr real(C_DOUBLE), intent(in) :: xyz(*) integer(C_INT) :: bin end function pure function mesh_get_bin_from_indices(ptr, ijk) result(bin) bind(C) import C_PTR, C_INT - type(C_PTR), value :: ptr + type(C_PTR), intent(in), value :: ptr integer(C_INT), intent(in) :: ijk(*) integer(C_INT) :: bin end function pure subroutine mesh_get_indices(ptr, xyz, ijk, in_mesh) bind(C) import C_PTR, C_DOUBLE, C_INT, C_BOOL - type(C_PTR), value :: ptr + type(C_PTR), intent(in), value :: ptr real(C_DOUBLE), intent(in) :: xyz(*) integer(C_INT), intent(out) :: ijk(*) logical(C_BOOL), intent(out) :: in_mesh @@ -162,8 +162,8 @@ module mesh_header pure subroutine mesh_get_indices_from_bin(ptr, bin, ijk) bind(C) import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT), value :: bin + type(C_PTR), intent(in), value :: ptr + integer(C_INT), intent(in), value :: bin integer(C_INT), intent(out) :: ijk(*) end subroutine diff --git a/src/message_passing.F90 b/src/message_passing.F90 index 6ade0895a9..37d2034475 100644 --- a/src/message_passing.F90 +++ b/src/message_passing.F90 @@ -2,28 +2,12 @@ module message_passing use, intrinsic :: ISO_C_BINDING -#ifdef OPENMC_MPI -#ifdef OPENMC_MPIF08 - use mpi_f08 -#else - use mpi -#endif -#endif - - ! The defaults set here for the number of processors, rank, and master and - ! mpi_enabled flag are for when MPI is not being used at all, i.e. a serial - ! run. In this case, these variables are still used at times. + ! The defaults set here for the number of processors, rank, and master and are + ! for when MPI is not being used at all, i.e. a serial run. In this case, these + ! variables are still used at times. integer(C_INT), bind(C, name='openmc_n_procs') :: n_procs = 1 ! number of processes integer(C_INT), bind(C, name='openmc_rank') :: rank = 0 ! rank of process logical(C_BOOL), bind(C, name='openmc_master') :: master = .true. ! master process? - logical :: mpi_enabled = .false. ! is MPI in use and initialized? -#ifdef OPENMC_MPIF08 - type(MPI_Datatype) :: MPI_BANK ! MPI datatype for fission bank - type(MPI_Comm) :: mpi_intracomm ! MPI intra-communicator -#else - integer :: MPI_BANK ! MPI datatype for fission bank - integer :: mpi_intracomm ! MPI intra-communicator -#endif end module message_passing diff --git a/src/message_passing.cpp b/src/message_passing.cpp index dc287e3b3a..8d8779cca2 100644 --- a/src/message_passing.cpp +++ b/src/message_passing.cpp @@ -13,4 +13,23 @@ MPI_Datatype bank; #endif } // namespace mpi + +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +#ifdef OPENMC_MPI +extern "C" void +send_int(void* buffer, int count, int dest, int tag) +{ + MPI_Send(buffer, count, MPI_INTEGER, dest, tag, mpi::intracomm); +} + +extern "C" void +recv_int(void* buffer, int count, int source, int tag) +{ + MPI_Recv(buffer, count, MPI_INTEGER, source, tag, mpi::intracomm, MPI_STATUS_IGNORE); +} +#endif + } // namespace openmc diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 12c73fab4f..1b6e85ffb2 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -26,7 +26,6 @@ namespace openmc { std::vector nuclides_MG; std::vector macro_xs; - //============================================================================== // Mgxs base-class methods //============================================================================== diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index 03a0e9402f..fee40ebcf7 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -90,7 +90,8 @@ contains end if end do NUCLIDE_LOOP - mat % fissionable = query_fissionable_c(mat % n_nuclides, mat % nuclide) + call mat % set_fissionable( & + logical(query_fissionable_c(mat % n_nuclides, mat % nuclide))) end do MATERIAL_LOOP @@ -125,13 +126,9 @@ contains name = trim(mat % name) // C_NULL_CHAR - ! Do not read materials which we do not actually use in the problem to - ! reduce storage - if (allocated(kTs(i_mat) % data)) then - call create_macro_xs_c(name, mat % n_nuclides, mat % nuclide, & - kTs(i_mat) % size(), kTs(i_mat) % data, mat % atom_density, & - temperature_tolerance, temperature_method) - end if + call create_macro_xs_c(name, mat % n_nuclides, mat % nuclide, & + kTs(i_mat) % size(), kTs(i_mat) % data, mat % atom_density, & + temperature_tolerance, temperature_method) end do end subroutine create_macro_xs diff --git a/src/mgxs_interface.F90 b/src/mgxs_interface.F90 index 9dccc92301..77f629b80e 100644 --- a/src/mgxs_interface.F90 +++ b/src/mgxs_interface.F90 @@ -62,26 +62,6 @@ module mgxs_interface real(C_DOUBLE), intent(inout) :: nu_fiss_xs end subroutine calculate_xs_c - subroutine sample_scatter_c(i_mat, gin, gout, mu, wgt, uvw) bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: i_mat - integer(C_INT), value, intent(in) :: gin - integer(C_INT), intent(inout) :: gout - real(C_DOUBLE), intent(inout) :: mu - real(C_DOUBLE), intent(inout) :: wgt - real(C_DOUBLE), intent(inout) :: uvw(1:3) - end subroutine sample_scatter_c - - subroutine sample_fission_energy_c(i_mat, gin, dg, gout) bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: i_mat - integer(C_INT), value, intent(in) :: gin - integer(C_INT), intent(inout) :: dg - integer(C_INT), intent(inout) :: gout - end subroutine sample_fission_energy_c - subroutine get_name_c(index, name_len, name) bind(C) use ISO_C_BINDING implicit none @@ -156,7 +136,7 @@ module mgxs_interface real(8), allocatable :: energy_bins(:) ! Midpoint of the energy group structure - real(8), allocatable :: energy_bin_avg(:) + real(C_DOUBLE), allocatable :: energy_bin_avg(:) ! Energy group structure with increasing energy real(C_DOUBLE), allocatable, target :: rev_energy_bins(:) diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index 0bb464da07..0729e466c7 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -8,6 +8,14 @@ namespace openmc { +//============================================================================== +// Global variable definitions +//============================================================================== + +std::vector energy_bins; +std::vector energy_bin_avg; +std::vector rev_energy_bins; + //============================================================================== // Mgxs data loading interface methods //============================================================================== @@ -83,6 +91,26 @@ create_macro_xs_c(const char* mat_name, int n_nuclides, const int i_nuclides[], } } +//============================================================================== + +void read_mg_cross_sections_header_c(hid_t file_id) +{ + ensure_exists(file_id, "energy_groups", true); + read_attribute(file_id, "energy_groups", num_energy_groups); + + ensure_exists(file_id, "group structure", true); + read_attribute(file_id, "group structure", rev_energy_bins); + + // Reverse energy bins + std::copy(rev_energy_bins.crbegin(), rev_energy_bins.crend(), + std::back_inserter(energy_bins)); + + // Create average energies + for (int i = 0; i < energy_bins.size() - 1; ++i) { + energy_bin_avg.push_back(0.5*(energy_bins[i] + energy_bins[i+1])); + } +} + //============================================================================== // Mgxs tracking/transport/tallying interface methods //============================================================================== @@ -97,36 +125,6 @@ calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], //============================================================================== -void -sample_scatter_c(int i_mat, int gin, int& gout, double& mu, double& wgt, - double uvw[3]) -{ - int gout_c = gout - 1; - macro_xs[i_mat - 1].sample_scatter(gin - 1, gout_c, mu, wgt); - - // adjust return value for fortran indexing - gout = gout_c + 1; - - // Rotate the angle - rotate_angle_c(uvw, mu, nullptr); -} - -//============================================================================== - -void -sample_fission_energy_c(int i_mat, int gin, int& dg, int& gout) -{ - int dg_c = 0; - int gout_c = 0; - macro_xs[i_mat - 1].sample_fission_energy(gin - 1, dg_c, gout_c); - - // adjust return values for fortran indexing - dg = dg_c + 1; - gout = gout_c + 1; -} - -//============================================================================== - double get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) { diff --git a/src/multipole_header.F90 b/src/multipole_header.F90 index 64610e4716..359bb3ac14 100644 --- a/src/multipole_header.F90 +++ b/src/multipole_header.F90 @@ -1,8 +1,9 @@ module multipole_header use constants - use error, only: fatal_error use hdf5_interface + use error, only: fatal_error + use string, only: to_str implicit none @@ -30,17 +31,15 @@ module multipole_header type MultipoleArray - !========================================================================= + character(20) :: name ! name of nuclide, e.g. U235 + ! Isotope Properties + logical :: fissionable ! Is this isotope fissionable? + complex(8), allocatable :: data(:,:) ! Poles and residues + real(8) :: sqrtAWR ! Square root of the atomic + ! weight ratio - logical :: fissionable ! Is this isotope fissionable? - complex(8), allocatable :: data(:,:) ! Poles and residues - real(8) :: sqrtAWR ! Square root of the atomic - ! weight ratio - - !========================================================================= ! Windows - integer :: fit_order ! Order of the fit. 1 linear, ! 2 quadratic, etc. real(8) :: E_min ! Start energy for the windows @@ -53,11 +52,9 @@ module multipole_header real(8), allocatable :: curvefit(:,:,:) ! Contains the fitting function. ! (reaction type, coeff index, ! window index) - integer, allocatable :: broaden_poly(:) ! if 1, broaden, if 0, don't. contains - procedure :: from_hdf5 => multipole_from_hdf5 end type MultipoleArray @@ -65,29 +62,22 @@ module multipole_header contains !=============================================================================== -! FROM_HDF5 loads multipole data from an HDF5 file. +! FROM_HDF5 loads multipole data from a HDF5 file. !=============================================================================== - subroutine multipole_from_hdf5(this, filename) + subroutine multipole_from_hdf5(this, group_id) class(MultipoleArray), intent(inout) :: this - character(len=*), intent(in) :: filename + integer(HID_T), intent(in) :: group_id - character(len=10) :: version integer :: n_poles, n_residues, n_windows integer(HSIZE_T) :: dims_1d(1), dims_2d(2), dims_3d(3) - integer(HID_T) :: file_id - integer(HID_T) :: group_id integer(HID_T) :: dset - ! Open file for reading and move into the /isotope group - file_id = file_open(filename, 'r', parallel=.true.) - group_id = open_group(file_id, "/nuclide") + ! Get name of nuclide from group + this % name = get_name(group_id) - ! Check the file version number. - call read_dataset(version, file_id, "version") - if (version /= VERSION_MULTIPOLE) call fatal_error("The current multipole& - & format version is " // trim(VERSION_MULTIPOLE) // " but the file "& - // trim(filename) // " uses version " // trim(version) // ".") + ! Get rid of leading '/' + this % name = trim(this % name(2:)) ! Read scalar values. call read_dataset(this % spacing, group_id, "spacing") @@ -121,8 +111,8 @@ contains dset = open_dataset(group_id, "broaden_poly") call get_shape(dset, dims_1d) if (dims_1d(1) /= n_windows) call fatal_error("broaden_poly array shape is& - ¬ consistent with the windows array shape in multipole library"& - // trim(filename) // ".") + ¬ consistent with the windows array shape in WMP library for"& + // trim(this % name) // ".") allocate(this % broaden_poly(n_windows)) call read_dataset(this % broaden_poly, dset) call close_dataset(dset) @@ -131,15 +121,38 @@ contains dset = open_dataset(group_id, "curvefit") call get_shape(dset, dims_3d) if (dims_3d(3) /= n_windows) call fatal_error("curvefit array shape is not& - &consistent with the windows array shape in multipole library"& - // trim(filename) // ".") + &consistent with the windows array shape in WMP library for"& + // trim(this % name) // ".") allocate(this % curvefit(dims_3d(1), dims_3d(2), dims_3d(3))) call read_dataset(this % curvefit, dset) call close_dataset(dset) this % fit_order = int(dims_3d(2), 4) - 1 - ! Close the group and file. - call close_group(group_id) - call file_close(file_id) end subroutine multipole_from_hdf5 + +!=============================================================================== +! CHECK_WMP_VERSION checks for the right version of WMP data within HDF5 +! files +!=============================================================================== + + subroutine check_wmp_version(file_id) + integer(HID_T), intent(in) :: file_id + + integer, allocatable :: version(:) + + if (attribute_exists(file_id, 'version')) then + call read_attribute(version, file_id, 'version') + if (version(1) /= WMP_VERSION(1)) then + call fatal_error("WMP data format uses version " // trim(to_str(& + version(1))) // "." // trim(to_str(version(2))) // " whereas & + &your installation of OpenMC expects version " // trim(to_str(& + WMP_VERSION(1))) // ".x data.") + end if + else + call fatal_error("WMP data does not indicate a version. Your & + &installation of OpenMC expects version " // trim(to_str(& + WMP_VERSION(1))) // ".x data.") + end if + end subroutine check_wmp_version + end module multipole_header diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 341ff6511c..da66aea4a1 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -53,7 +53,7 @@ module nuclide_header type :: Nuclide ! Nuclide meta-data - character(20) :: name ! name of nuclide, e.g. U235.71c + character(20) :: name ! name of nuclide, e.g. U235 integer :: Z ! atomic number integer :: A ! mass number integer :: metastable ! metastable state diff --git a/src/output.F90 b/src/output.F90 index 4363731f09..485d957a6e 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -14,7 +14,6 @@ module output use mgxs_interface use nuclide_header use particle_header, only: LocalCoord, Particle - use plot_header use sab_header, only: SAlphaBeta use settings use simulation_header @@ -285,7 +284,7 @@ contains ! below them !=============================================================================== - subroutine print_columns() + subroutine print_columns() bind(C) write(UNIT=ou, FMT='(2X,A9,3X)', ADVANCE='NO') "Bat./Gen." write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') " k " @@ -323,7 +322,7 @@ contains ! PRINT_GENERATION displays information for a generation of neutrons. !=============================================================================== - subroutine print_generation() + subroutine print_generation() bind(C) integer :: i ! overall generation integer :: n ! number of active generations @@ -339,7 +338,7 @@ contains ! write out information about batch and generation write(UNIT=OUTPUT_UNIT, FMT='(2X,A9)', ADVANCE='NO') & trim(to_str(current_batch)) // "/" // trim(to_str(current_gen)) - write(UNIT=OUTPUT_UNIT, FMT='(3X,F8.5)', ADVANCE='NO') k_generation % data(i) + write(UNIT=OUTPUT_UNIT, FMT='(3X,F8.5)', ADVANCE='NO') k_generation(i) ! write out entropy info if (entropy_on) write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & @@ -360,7 +359,7 @@ contains ! multiplication factor as well as the average value if we're in active batches !=============================================================================== - subroutine print_batch_keff() + subroutine print_batch_keff() bind(C) integer :: i ! overall generation integer :: n ! number of active generations @@ -373,7 +372,7 @@ contains write(UNIT=OUTPUT_UNIT, FMT='(2X,A9)', ADVANCE='NO') & trim(to_str(current_batch)) // "/" // trim(to_str(gen_per_batch)) write(UNIT=OUTPUT_UNIT, FMT='(3X,F8.5)', ADVANCE='NO') & - k_generation % data(i) + k_generation(i) ! write out entropy info if (entropy_on) write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & @@ -412,85 +411,12 @@ contains end subroutine print_batch_keff -!=============================================================================== -! PRINT_PLOT displays selected options for plotting -!=============================================================================== - - subroutine print_plot() - - integer :: i ! loop index for plots - type(ObjectPlot), pointer :: pl - - ! Display header for plotting - call header("PLOTTING SUMMARY", 5) - - do i = 1, n_plots - pl => plots(i) - - ! Plot id - write(ou,100) "Plot ID:", trim(to_str(pl % id)) - - ! Plot filename - write(ou,100) "Plot file:", trim(pl % path_plot) - - ! Plot level - write(ou,100) "Universe depth:", trim(to_str(pl % level)) - - ! Plot type - if (pl % type == PLOT_TYPE_SLICE) then - write(ou,100) "Plot Type:", "Slice" - else if (pl % type == PLOT_TYPE_VOXEL) then - write(ou,100) "Plot Type:", "Voxel" - end if - - ! Plot parameters - write(ou,100) "Origin:", trim(to_str(pl % origin(1))) // & - " " // trim(to_str(pl % origin(2))) // " " // & - trim(to_str(pl % origin(3))) - if (pl % type == PLOT_TYPE_SLICE) then - write(ou,100) "Width:", trim(to_str(pl % width(1))) // & - " " // trim(to_str(pl % width(2))) - else if (pl % type == PLOT_TYPE_VOXEL) then - write(ou,100) "Width:", trim(to_str(pl % width(1))) // & - " " // trim(to_str(pl % width(2))) // & - " " // trim(to_str(pl % width(3))) - end if - if (pl % color_by == PLOT_COLOR_CELLS) then - write(ou,100) "Coloring:", "Cells" - else if (pl % color_by == PLOT_COLOR_MATS) then - write(ou,100) "Coloring:", "Materials" - end if - if (pl % type == PLOT_TYPE_SLICE) then - select case (pl % basis) - case (PLOT_BASIS_XY) - write(ou,100) "Basis:", "xy" - case (PLOT_BASIS_XZ) - write(ou,100) "Basis:", "xz" - case (PLOT_BASIS_YZ) - write(ou,100) "Basis:", "yz" - end select - write(ou,100) "Pixels:", trim(to_str(pl % pixels(1))) // " " // & - trim(to_str(pl % pixels(2))) - else if (pl % type == PLOT_TYPE_VOXEL) then - write(ou,100) "Voxels:", trim(to_str(pl % pixels(1))) // " " // & - trim(to_str(pl % pixels(2))) // " " // trim(to_str(pl % pixels(3))) - end if - - write(ou,*) - - end do - - ! Format descriptor for columns -100 format (1X,A,T25,A) - - end subroutine print_plot - !=============================================================================== ! PRINT_RUNTIME displays the total time elapsed for the entire run, for ! initialization, for computation, and for intergeneration synchronization. !=============================================================================== - subroutine print_runtime() + subroutine print_runtime() bind(C) integer :: n_active real(8) :: speed_inactive ! # of neutrons/second in inactive batches @@ -501,49 +427,49 @@ contains call header("Timing Statistics", 6) ! display time elapsed for various sections - write(ou,100) "Total time for initialization", time_initialize % elapsed + write(ou,100) "Total time for initialization", time_initialize_elapsed() write(ou,100) " Reading cross sections", time_read_xs % elapsed - write(ou,100) "Total time in simulation", time_inactive % elapsed + & - time_active % elapsed - write(ou,100) " Time in transport only", time_transport % elapsed + write(ou,100) "Total time in simulation", time_inactive_elapsed() + & + time_active_elapsed() + write(ou,100) " Time in transport only", time_transport_elapsed() if (run_mode == MODE_EIGENVALUE) then - write(ou,100) " Time in inactive batches", time_inactive % elapsed + write(ou,100) " Time in inactive batches", time_inactive_elapsed() end if - write(ou,100) " Time in active batches", time_active % elapsed + write(ou,100) " Time in active batches", time_active_elapsed() if (run_mode == MODE_EIGENVALUE) then - write(ou,100) " Time synchronizing fission bank", time_bank % elapsed - write(ou,100) " Sampling source sites", time_bank_sample % elapsed - write(ou,100) " SEND/RECV source sites", time_bank_sendrecv % elapsed + write(ou,100) " Time synchronizing fission bank", time_bank_elapsed() + write(ou,100) " Sampling source sites", time_bank_sample_elapsed() + write(ou,100) " SEND/RECV source sites", time_bank_sendrecv_elapsed() end if - write(ou,100) " Time accumulating tallies", time_tallies % elapsed + write(ou,100) " Time accumulating tallies", time_tallies_elapsed() if (cmfd_run) write(ou,100) " Time in CMFD", time_cmfd % elapsed if (cmfd_run) write(ou,100) " Building matrices", & time_cmfdbuild % elapsed if (cmfd_run) write(ou,100) " Solving matrices", & time_cmfdsolve % elapsed - write(ou,100) "Total time for finalization", time_finalize % elapsed - write(ou,100) "Total time elapsed", time_total % elapsed + write(ou,100) "Total time for finalization", time_finalize_elapsed() + write(ou,100) "Total time elapsed", time_total_elapsed() ! Calculate particle rate in active/inactive batches n_active = current_batch - n_inactive if (restart_run) then if (restart_batch < n_inactive) then speed_inactive = real(n_particles * (n_inactive - restart_batch) * & - gen_per_batch) / time_inactive % elapsed + gen_per_batch) / time_inactive_elapsed() speed_active = real(n_particles * n_active * gen_per_batch) / & - time_active % elapsed + time_active_elapsed() else speed_inactive = ZERO speed_active = real(n_particles * (n_batches - restart_batch) * & - gen_per_batch) / time_active % elapsed + gen_per_batch) / time_active_elapsed() end if else if (n_inactive > 0) then speed_inactive = real(n_particles * n_inactive * gen_per_batch) / & - time_inactive % elapsed + time_inactive_elapsed() end if speed_active = real(n_particles * n_active * gen_per_batch) / & - time_active % elapsed + time_active_elapsed() end if ! display calculation rate @@ -566,7 +492,7 @@ contains ! leakage rate. !=============================================================================== - subroutine print_results() + subroutine print_results() bind(C) integer :: n ! number of realizations real(8) :: alpha ! significance level for CI @@ -633,7 +559,7 @@ contains ! tallies and their standard deviations !=============================================================================== - subroutine write_tallies() + subroutine write_tallies() bind(C) integer :: i ! index in tallies array integer :: j ! level in tally hierarchy @@ -653,17 +579,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(TallyFilterMatch), allocatable :: matches(:) + integer, allocatable :: filter_bins(:) character(MAX_WORD_LEN) :: temp_name ! Skip if there are no tallies if (n_tallies == 0) return - allocate(matches(n_filters)) - do i = 1, n_filters - allocate(matches(i) % bins) - allocate(matches(i) % weights) - end do + allocate(filter_bins(n_filters)) ! Initialize names for scores score_names(abs(SCORE_FLUX)) = "Flux" @@ -747,8 +669,7 @@ contains ! Initialize bins, filter level, and indentation do h = 1, size(t % filter) - call matches(t % filter(h)) % bins % clear() - call matches(t % filter(h)) % bins % push_back(0) + filter_bins(t % filter(h)) = 0 end do j = 1 indent = 0 @@ -759,18 +680,17 @@ contains if (size(t % filter) == 0) exit find_bin ! Increment bin combination - matches(t % filter(j)) % bins % data(1) = & - matches(t % filter(j)) % bins % data(1) + 1 + filter_bins(t % filter(j)) = filter_bins(t % filter(j)) + 1 ! ================================================================= ! REACHED END OF BINS FOR THIS FILTER, MOVE TO NEXT FILTER - if (matches(t % filter(j)) % bins % data(1) > & + if (filter_bins(t % filter(j)) > & filters(t % filter(j)) % obj % n_bins) then ! If this is the first filter, then exit if (j == 1) exit print_bin - matches(t % filter(j)) % bins % data(1) = 0 + filter_bins(t % filter(j)) = 0 j = j - 1 indent = indent - 2 @@ -784,7 +704,7 @@ contains ! Print current filter information write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & trim(filters(t % filter(j)) % obj % & - text_label(matches(t % filter(j)) % bins % data(1))) + text_label(filter_bins(t % filter(j)))) indent = indent + 2 j = j + 1 end if @@ -795,7 +715,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(matches(t % filter(j)) % bins % data(1))) + text_label(filter_bins(t % filter(j)))) end if ! Determine scoring index for this bin combination -- note that unlike @@ -804,8 +724,8 @@ contains filter_index = 1 do h = 1, size(t % filter) - filter_index = filter_index + (max(matches(t % filter(h)) & - % bins % data(1),1) - 1) * t % stride(h) + filter_index = filter_index & + + (max(filter_bins(t % filter(h)) ,1) - 1) * t % stride(h) end do ! Write results for this filter bin combination @@ -859,11 +779,6 @@ contains close(UNIT=unit_tally) - do i = 1, n_filters - deallocate(matches(i) % bins) - deallocate(matches(i) % weights) - end do - end subroutine write_tallies !=============================================================================== diff --git a/src/output.cpp b/src/output.cpp index fba6e5af7b..79800e21ad 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -5,13 +5,14 @@ #include // for setw #include #include +#include #include "openmc/cell.h" #include "openmc/geometry.h" #include "openmc/message_passing.h" #include "openmc/capi.h" #include "openmc/settings.h" - +#include "openmc/plot.h" namespace openmc { @@ -35,13 +36,97 @@ header(const char* msg, int level) { // Print header based on verbosity level. if (settings::verbosity >= level) { - std::cout << out.str() << std::endl << std::endl; + std::cout << out.str() << "\n\n"; } } +std::string time_stamp() +{ + int base_year = 1990; + std::stringstream ts; + std::time_t t = std::time(0); // get time now + std::tm* now = std::localtime(&t); + ts << now->tm_year + base_year << "-" << now->tm_mon + << "-" << now->tm_mday << " " << now->tm_hour + << ":" << now->tm_min << ":" << now->tm_sec; + return ts.str(); +} + //============================================================================== -extern "C" void +//=============================================================================== +// PRINT_PLOT displays selected options for plotting +//=============================================================================== + +void print_plot() { + + header("PLOTTING SUMMARY", 5); + + for (auto pl : plots) { + // Plot id + std::cout << "Plot ID: " << pl.id_ << "\n"; + // Plot filename + std::cout << "Plot file: " << pl.path_plot_ << "\n"; + // Plot level + std::cout << "Universe depth: " << pl.level_ << "\n"; + + // Plot type + if (PlotType::slice == pl.type_) { + std::cout << "Plot Type: Slice" << "\n"; + } else if (PlotType::voxel == pl.type_) { + std::cout << "Plot Type: Voxel" << "\n"; + } + + // Plot parameters + std::cout << "Origin: " << pl.origin_[0] << " " + << pl.origin_[1] << " " + << pl.origin_[2] << "\n"; + + if (PlotType::slice == pl.type_) { + std::cout << std::setprecision(4) + << "Width: " + << pl.width_[0] << " " + << pl.width_[1] << "\n"; + } else if (PlotType::voxel == pl.type_) { + std::cout << std::setprecision(4) + << "Width: " + << pl.width_[0] << " " + << pl.width_[1] << " " + << pl.width_[2] << "\n"; + } + + if (PlotColorBy::cells == pl.color_by_) { + std::cout << "Coloring: Cells" << "\n"; + } else if (PlotColorBy::mats == pl.color_by_) { + std::cout << "Coloring: Materials" << "\n"; + } + + if (PlotType::slice == pl.type_) { + switch(pl.basis_) { + case PlotBasis::xy: + std::cout << "Basis: XY" << "\n"; + break; + case PlotBasis::xz: + std::cout << "Basis: XZ" << "\n"; + break; + case PlotBasis::yz: + std::cout << "Basis: YZ" << "\n"; + break; + } + std::cout << "Pixels: " << pl.pixels_[0] << " " + << pl.pixels_[1] << " " << "\n"; + } else if (PlotType::voxel == pl.type_) { + std::cout << "Voxels: " << pl.pixels_[0] << " " + << pl.pixels_[1] << " " + << pl.pixels_[2] << "\n"; + } + + std::cout << "\n"; + + } +} + +void print_overlap_check() { #ifdef OPENMC_MPI std::vector temp(overlap_check_count); diff --git a/src/particle.cpp b/src/particle.cpp index ca57c2faab..c8a5a1b567 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -7,6 +7,7 @@ #include "openmc/constants.h" #include "openmc/error.h" #include "openmc/hdf5_interface.h" +#include "openmc/mgxs_interface.h" #include "openmc/settings.h" #include "openmc/simulation.h" @@ -92,7 +93,7 @@ Particle::initialize() } void -Particle::from_source(const Bank* src, bool run_CE, const double* energy_bin_avg) +Particle::from_source(const Bank* src) { // set defaults initialize(); @@ -106,7 +107,7 @@ Particle::from_source(const Bank* src, bool run_CE, const double* energy_bin_avg std::copy(src->xyz, src->xyz + 3, last_xyz_current); std::copy(src->xyz, src->xyz + 3, last_xyz); std::copy(src->uvw, src->uvw + 3, last_uvw); - if (run_CE) { + if (settings::run_CE) { E = src->E; g = 0; } else { @@ -127,15 +128,15 @@ Particle::mark_as_lost(const char* message) // Increment number of lost particles alive = false; #pragma omp atomic - openmc_n_lost_particles += 1; + simulation::n_lost_particles += 1; // Count the total number of simulated particles (on this processor) - auto n = openmc_current_batch * settings::gen_per_batch * openmc_work; + auto n = simulation::current_batch * settings::gen_per_batch * simulation::work; // Abort the simulation if the maximum number of lost particles has been // reached - if (openmc_n_lost_particles >= MAX_LOST_PARTICLES && - openmc_n_lost_particles >= REL_MAX_LOST_PARTICLES*n) { + if (simulation::n_lost_particles >= MAX_LOST_PARTICLES && + simulation::n_lost_particles >= REL_MAX_LOST_PARTICLES*n) { fatal_error("Maximum number of lost particles has been reached."); } } @@ -148,7 +149,7 @@ Particle::write_restart() const // Set up file name std::stringstream filename; - filename << settings::path_output << "particle_" << openmc_current_batch + filename << settings::path_output << "particle_" << simulation::current_batch << '_' << id << ".h5"; #pragma omp critical (WriteParticleRestart) @@ -165,9 +166,9 @@ Particle::write_restart() const #endif // Write data to file - write_dataset(file_id, "current_batch", openmc_current_batch); + write_dataset(file_id, "current_batch", simulation::current_batch); write_dataset(file_id, "generations_per_batch", settings::gen_per_batch); - write_dataset(file_id, "current_generation", openmc_current_gen); + write_dataset(file_id, "current_generation", simulation::current_gen); write_dataset(file_id, "n_particles", settings::n_particles); switch (settings::run_mode) { case RUN_MODE_FIXEDSOURCE: @@ -188,7 +189,7 @@ Particle::write_restart() const int64_t n; openmc_source_bank(&src, &n); - int64_t i = openmc_current_work; + int64_t i = simulation::current_work; write_dataset(file_id, "weight", src[i-1].wgt); write_dataset(file_id, "energy", src[i-1].E); hsize_t dims[] {3}; @@ -212,10 +213,9 @@ void particle_create_secondary(Particle* p, const double* uvw, double E, p->create_secondary(uvw, E, type, run_CE); } void particle_initialize(Particle* p) { p->initialize(); } -void particle_from_source(Particle* p, const Bank* src, bool run_CE, - const double* energy_bin_avg) +void particle_from_source(Particle* p, const Bank* src) { - p->from_source(src, run_CE, energy_bin_avg); + p->from_source(src); } void particle_mark_as_lost(Particle* p, const char* message) { diff --git a/src/particle_header.F90 b/src/particle_header.F90 index df5deff8c8..db2317f4b2 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -129,12 +129,10 @@ module particle_header type(Particle), intent(inout) :: p end subroutine particle_initialize - subroutine particle_from_source(p, src, run_CE, energy_bin_avg) bind(C) - import Particle, Bank, C_BOOL, C_DOUBLE + subroutine particle_from_source(p, src) bind(C) + import Particle, Bank, C_DOUBLE type(Particle), intent(inout) :: p type(Bank), intent(in) :: src - logical(C_BOOL), value :: run_CE - real(C_DOUBLE), intent(in) :: energy_bin_avg(*) end subroutine particle_from_source subroutine particle_mark_as_lost_c(p, message) bind(C, name='particle_mark_as_lost') diff --git a/src/physics_common.F90 b/src/physics_common.F90 index 63bf62ce62..0d5b9fb324 100644 --- a/src/physics_common.F90 +++ b/src/physics_common.F90 @@ -1,33 +1,20 @@ module physics_common - use constants + use, intrinsic :: ISO_C_BINDING + use particle_header, only: Particle - use random_lcg, only: prn - use settings, only: weight_cutoff, weight_survive implicit none -contains - !=============================================================================== -! RUSSIAN_ROULETTE +! RUSSIAN_ROULETTE FROM C !=============================================================================== - subroutine russian_roulette(p) - - type(Particle), intent(inout) :: p - - if (p % wgt < weight_cutoff) then - if (prn() < p % wgt / weight_survive) then - p % wgt = weight_survive - p % last_wgt = p % wgt - else - p % wgt = ZERO - p % last_wgt = ZERO - p % alive = .false. - end if - end if - - end subroutine russian_roulette + interface + subroutine russian_roulette(p) bind(C) + import Particle + type(Particle), intent(inout) :: p + end subroutine russian_roulette + end interface end module physics_common diff --git a/src/physics_common.cpp b/src/physics_common.cpp new file mode 100644 index 0000000000..02c77ae7d9 --- /dev/null +++ b/src/physics_common.cpp @@ -0,0 +1,26 @@ +#include "openmc/physics_common.h" + +#include "openmc/settings.h" +#include "openmc/random_lcg.h" + +namespace openmc { + +//============================================================================== +// RUSSIAN_ROULETTE +//============================================================================== + +void russian_roulette(Particle* p) +{ + if (p->wgt < settings::weight_cutoff) { + if (prn() < p->wgt / settings::weight_survive) { + p->wgt = settings::weight_survive; + p->last_wgt = p->wgt; + } else { + p->wgt = 0.; + p->last_wgt = 0.; + p->alive = false; + } + } +} + +} //namespace openmc diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 deleted file mode 100644 index 1d60b371ee..0000000000 --- a/src/physics_mg.F90 +++ /dev/null @@ -1,275 +0,0 @@ -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, write_message - use material_header, only: Material, materials - use math, only: rotate_angle - use mgxs_interface - use message_passing - use nuclide_header, only: material_xs - use particle_header - use physics_common - use random_lcg, only: prn - use settings - use simulation_header - use string, only: to_str - use tally_header - - implicit none - -contains - -!=============================================================================== -! COLLISION_MG samples a nuclide and reaction and then calls the appropriate -! routine for that reaction -!=============================================================================== - - subroutine collision_mg(p) - - type(Particle), intent(inout) :: p - - ! Add to collision counter for particle - p % n_collision = p % n_collision + 1 - - ! Sample nuclide/reaction for the material the particle is in - call sample_reaction(p) - - ! Display information about collision - if (verbosity >= 10 .or. trace) then - call write_message(" " // "Energy Group = " // trim(to_str(p % g))) - end if - - end subroutine collision_mg - -!=============================================================================== -! SAMPLE_REACTION samples a nuclide based on the macroscopic cross sections for -! each nuclide within a material and then samples a reaction for that nuclide -! and calls the appropriate routine to process the physics. Note that there is -! special logic when suvival biasing is turned on since fission and -! disappearance are treated implicitly. -!=============================================================================== - - subroutine sample_reaction(p) - - type(Particle), intent(inout) :: p - - type(Material), pointer :: mat - - mat => materials(p % material) - - ! Create fission bank sites. Note that while a fission reaction is sampled, - ! it never actually "happens", i.e. the weight of the particle does not - ! change when sampling fission sites. The following block handles all - ! absorption (including fission) - - if (mat % fissionable) then - if (run_mode == MODE_EIGENVALUE) then - call create_fission_sites(p, fission_bank, n_bank) - elseif (run_mode == MODE_FIXEDSOURCE .and. create_fission_neutrons) then - call create_fission_sites(p, p % secondary_bank, p % n_secondary) - end if - end if - - ! If survival biasing is being used, the following subroutine adjusts the - ! weight of the particle. Otherwise, it checks to see if absorption occurs - - if (material_xs % absorption > ZERO) then - call absorption(p) - else - p % absorb_wgt = ZERO - end if - if (.not. p % alive) return - - ! Sample a scattering reaction and determine the secondary energy of the - ! exiting neutron - call scatter(p) - - ! Play russian roulette if survival biasing is turned on - if (survival_biasing) then - call russian_roulette(p) - if (.not. p % alive) return - end if - - end subroutine sample_reaction - -!=============================================================================== -! ABSORPTION -!=============================================================================== - - subroutine absorption(p) - - type(Particle), intent(inout) :: p - - if (survival_biasing) then - ! Determine weight absorbed in survival biasing - p % absorb_wgt = (p % wgt * & - material_xs % absorption / material_xs % total) - - ! Adjust weight of particle by probability of absorption - p % wgt = p % wgt - p % absorb_wgt - p % last_wgt = p % wgt - - ! Score implicit absorption estimate of keff -!$omp atomic - global_tallies(RESULT_VALUE, K_ABSORPTION) = & - global_tallies(RESULT_VALUE, K_ABSORPTION) + p % absorb_wgt * & - material_xs % nu_fission / material_xs % absorption - else - ! See if disappearance reaction happens - if (material_xs % absorption > prn() * material_xs % total) then - ! Score absorption estimate of keff -!$omp atomic - global_tallies(RESULT_VALUE, K_ABSORPTION) = & - global_tallies(RESULT_VALUE, K_ABSORPTION) + p % wgt * & - material_xs % nu_fission / material_xs % absorption - - p % alive = .false. - p % event = EVENT_ABSORB - end if - end if - - end subroutine absorption - -!=============================================================================== -! SCATTER -!=============================================================================== - - subroutine scatter(p) - - type(Particle), intent(inout) :: p - - call sample_scatter_c(p % material, p % last_g, p % g, p % mu, & - p % wgt, p % coord(1) % uvw) - - ! Update energy value for downstream compatability (in tallying) - p % E = energy_bin_avg(p % g) - - ! Set event component - p % event = EVENT_SCATTER - - end subroutine scatter - -!=============================================================================== -! CREATE_FISSION_SITES determines the average total, prompt, and delayed -! neutrons produced from fission and creates appropriate bank sites. -!=============================================================================== - - subroutine create_fission_sites(p, bank_array, size_bank) - type(Particle), intent(inout) :: p - type(Bank), intent(inout) :: bank_array(:) - integer(8), intent(inout) :: size_bank - - integer :: nu_d(MAX_DELAYED_GROUPS) ! number of delayed neutrons born - integer :: i ! loop index - integer :: dg ! delayed group - integer :: gout ! group out - integer :: nu ! actual number of neutrons produced - 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 - - interface - function ufs_get_weight(p) result(weight) bind(C) - import Particle, C_DOUBLE - type(Particle), intent(in) :: p - real(C_DOUBLE) :: WEIGHT - end function - end interface - - ! TODO: Heat generation from fission - - ! If uniform fission source weighting is turned on, we increase of decrease - ! the expected number of fission sites produced - - if (ufs) then - weight = ufs_get_weight(p) - else - weight = ONE - end if - - ! Determine expected number of neutrons produced - nu_t = p % wgt / keff * weight * & - material_xs % nu_fission / material_xs % total - - ! Sample number of neutrons produced - if (prn() > nu_t - int(nu_t)) then - nu = int(nu_t) - else - nu = int(nu_t) + 1 - end if - - ! Check for bank size getting hit. For fixed source calculations, this is a - ! fatal error. For eigenvalue calculations, it just means that k-effective - ! was too high for a single batch. - if (size_bank + nu > size(bank_array)) then - if (run_mode == MODE_FIXEDSOURCE) then - call fatal_error("Secondary particle bank size limit reached. If you & - &are running a subcritical multiplication problem, k-effective & - &may be too close to one.") - else - if (master) call warning("Maximum number of sites in fission bank & - &reached. This can result in irreproducible results using different & - &numbers of processes/threads.") - end if - end if - - ! Bank source neutrons - if (nu == 0 .or. size_bank == size(bank_array)) return - - ! Initialize counter of delayed neutrons encountered for each delayed group - ! to zero. - nu_d(:) = 0 - - p % fission = .true. ! Fission neutrons will be banked - do i = int(size_bank,4) + 1, int(min(size_bank + nu, int(size(bank_array),8)),4) - ! Bank source neutrons by copying particle data - bank_array(i) % xyz = p % coord(1) % xyz - - ! Set particle as neutron - bank_array(i) % particle = NEUTRON - - ! Set weight of fission bank site - bank_array(i) % wgt = ONE/weight - - ! Sample cosine of angle -- fission neutrons are treated as being emitted - ! isotropically. - mu = TWO * prn() - ONE - - ! Sample azimuthal angle uniformly in [0,2*pi) - phi = TWO * PI * prn() - bank_array(i) % uvw(1) = mu - bank_array(i) % uvw(2) = sqrt(ONE - mu*mu) * cos(phi) - bank_array(i) % uvw(3) = sqrt(ONE - mu*mu) * sin(phi) - - ! Sample secondary energy distribution for fission reaction and set energy - ! in fission bank - call sample_fission_energy_c(p % material, p % g, dg, gout) - - bank_array(i) % E = real(gout, 8) - bank_array(i) % delayed_group = dg - - ! Set delayed group on particle too - p % delayed_group = dg - - ! Increment the number of neutrons born delayed - if (p % delayed_group > 0) then - nu_d(p % delayed_group) = nu_d(p % delayed_group) + 1 - end if - - end do - - ! increment number of bank sites - size_bank = min(size_bank + nu, int(size(bank_array),8)) - - ! Store total weight banked for analog fission tallies - p % n_bank = nu - p % wgt_bank = nu/weight - p % n_delayed_bank(:) = nu_d(:) - - end subroutine create_fission_sites - -end module physics_mg diff --git a/src/physics_mg.cpp b/src/physics_mg.cpp new file mode 100644 index 0000000000..40e5ba1aea --- /dev/null +++ b/src/physics_mg.cpp @@ -0,0 +1,234 @@ +#include "openmc/physics_mg.h" + +#include +#include + +#include "xtensor/xarray.hpp" + +#include "openmc/constants.h" +#include "openmc/eigenvalue.h" +#include "openmc/error.h" +#include "openmc/material.h" +#include "openmc/math_functions.h" +#include "openmc/message_passing.h" +#include "openmc/mgxs_interface.h" +#include "openmc/physics_common.h" +#include "openmc/random_lcg.h" +#include "openmc/settings.h" +#include "openmc/simulation.h" +#include "openmc/tallies/tally.h" + +namespace openmc { + +void +collision_mg(Particle* p, const double* energy_bin_avg, + const MaterialMacroXS* material_xs) +{ + // Add to the collision counter for the particle + p->n_collision++; + + // Sample the reaction type + sample_reaction(p, energy_bin_avg, material_xs); + + // Display information about collision + if ((settings::verbosity >= 10) || (simulation::trace)) { + std::stringstream msg; + msg << " Energy Group = " << p->g; + write_message(msg, 1); + } +} + +void +sample_reaction(Particle* p, const double* energy_bin_avg, + const MaterialMacroXS* material_xs) +{ + // Create fission bank sites. Note that while a fission reaction is sampled, + // it never actually "happens", i.e. the weight of the particle does not + // change when sampling fission sites. The following block handles all + // absorption (including fission) + + if (materials[p->material - 1]->fissionable) { + if (settings::run_mode == RUN_MODE_EIGENVALUE) { + Bank* result_bank; + int64_t result_bank_size; + // Get pointer to fission bank from Fortran side + openmc_fission_bank(&result_bank, &result_bank_size); + create_fission_sites(p, result_bank, &n_bank, result_bank_size, + material_xs); + } else if ((settings::run_mode == RUN_MODE_FIXEDSOURCE) && + (settings::create_fission_neutrons)) { + create_fission_sites(p, p->secondary_bank, &(p->n_secondary), + MAX_SECONDARY, material_xs); + } + } + + // If survival biasing is being used, the following subroutine adjusts the + // weight of the particle. Otherwise, it checks to see if absorption occurs. + if (material_xs->absorption > 0.) { + absorption(p, material_xs); + } else { + p->absorb_wgt = 0.; + } + if (!p->alive) return; + + // Sample a scattering event to determine the energy of the exiting neutron + scatter(p, energy_bin_avg); + + // Play Russian roulette if survival biasing is turned on + if (settings::survival_biasing) { + russian_roulette(p); + if (!p->alive) return; + } +} + +void +scatter(Particle* p, const double* energy_bin_avg) +{ + // Adjust indices for Fortran to C++ indexing + // TODO: Remove when no longer needed + int gin = p->last_g - 1; + int gout = p->g - 1; + int i_mat = p->material - 1; + macro_xs[i_mat].sample_scatter(gin, gout, p->mu, p->wgt); + + // Adjust return value for fortran indexing + // TODO: Remove when no longer needed + p->g = gout + 1; + + // Rotate the angle + rotate_angle_c(p->coord[0].uvw, p->mu, nullptr); + + // Update energy value for downstream compatability (in tallying) + p->E = energy_bin_avg[gout]; + + // Set event component + p->event = EVENT_SCATTER; +} + +void +create_fission_sites(Particle* p, Bank* bank_array, int64_t* size_bank, + int64_t bank_array_size, const MaterialMacroXS* material_xs) +{ + // TODO: Heat generation from fission + + // If uniform fission source weighting is turned on, we increase or decrease + // the expected number of fission sites produced + double weight = settings::ufs_on ? ufs_get_weight(p) : 1.0; + + // Determine the expected number of neutrons produced + double nu_t = p->wgt / simulation::keff * weight * material_xs->nu_fission / + material_xs->total; + + // Sample the number of neutrons produced + int nu = static_cast(nu_t); + if (prn() <= (nu_t - int(nu_t))) { + nu++; + } + + // Check for the bank size getting hit. For fixed source calculations, this + // is a fatal error; for eigenvalue calculations, it just means that k-eff + // was too high for a single batch. + if (*size_bank + nu > bank_array_size) { + if (settings::run_mode == RUN_MODE_FIXEDSOURCE) { + throw std::runtime_error{"Secondary particle bank size limit reached." + " If you are running a subcritical multiplication problem," + " k-effective may be too close to one."}; + } else { + if (mpi::master) { + std::stringstream msg; + msg << "Maximum number of sites in fission bank reached. This can" + " result in irreproducible results using different numbers of" + " processes/threads."; + warning(msg); + } + } + } + + // Begin banking the source neutrons + // First, if our bank is full then don't continue + if ((nu == 0) || (*size_bank == bank_array_size)) return; + + // Initialize the counter of delayed neutrons encountered for each delayed + // group. + double nu_d[MAX_DELAYED_GROUPS] = {0.}; + + p->fission = true; + for (size_t i = static_cast(*size_bank); + i < static_cast(std::min(*size_bank + nu, bank_array_size)); i++) { + // Bank source neutrons by copying the particle data + bank_array[i].xyz[0] = p->coord[0].xyz[0]; + bank_array[i].xyz[1] = p->coord[0].xyz[1]; + bank_array[i].xyz[2] = p->coord[0].xyz[2]; + + // Set that the bank particle is a neutron + bank_array[i].particle = static_cast(ParticleType::neutron); + + // Set the weight of the fission bank site + bank_array[i].wgt = 1. / weight; + + // Sample the cosine of the angle, assuming fission neutrons are emitted + // isotropically + double mu = 2. * prn() - 1.; + + // Sample the azimuthal angle uniformly in [0, 2.pi) + double phi = 2. * PI * prn(); + bank_array[i].uvw[0] = mu; + bank_array[i].uvw[1] = std::sqrt(1. - mu * mu) * std::cos(phi); + bank_array[i].uvw[2] = std::sqrt(1. - mu * mu) * std::sin(phi); + + // Sample secondary energy distribution for the fission reaction and set + // the energy in the fission bank + int dg; + int gout; + macro_xs[p->material - 1].sample_fission_energy(p->g - 1, dg, gout); + bank_array[i].E = static_cast(gout + 1); + bank_array[i].delayed_group = dg + 1; + + // Set the delayed group on the particle as well + p->delayed_group = dg + 1; + + // Increment the number of neutrons born delayed + if (p->delayed_group > 0) { + nu_d[dg]++; + } + } + + // Increment number of bank sites + *size_bank = std::min(*size_bank + nu, bank_array_size); + + // Store the total weight banked for analog fission tallies + p->n_bank = nu; + p->wgt_bank = nu / weight; + for (size_t d = 0; d < MAX_DELAYED_GROUPS; d++) { + p->n_delayed_bank[d] = nu_d[d]; + } +} + +void +absorption(Particle* p, const MaterialMacroXS* material_xs) +{ + if (settings::survival_biasing) { + // Determine weight absorbed in survival biasing + p->absorb_wgt = p->wgt * material_xs->absorption / material_xs->total; + + // Adjust weight of particle by the probability of absorption + p->wgt -= p->absorb_wgt; + p->last_wgt = p->wgt; + + // Score implicit absorpion estimate of keff +#pragma omp atomic + global_tally_absorption += p->absorb_wgt * material_xs->nu_fission / + material_xs->absorption; + } else { + if (material_xs->absorption > prn() * material_xs->total) { +#pragma omp atomic + global_tally_absorption += p->wgt * material_xs->nu_fission / + material_xs->absorption; + p->alive = false; + p->event = EVENT_ABSORB; + } + + } +} + +} //namespace openmc diff --git a/src/plot.F90 b/src/plot.F90 deleted file mode 100644 index bfc8bc2251..0000000000 --- a/src/plot.F90 +++ /dev/null @@ -1,456 +0,0 @@ -module plot - - use, intrinsic :: ISO_C_BINDING - - use constants - use error, only: fatal_error, write_message - use geometry, only: find_cell, check_cell_overlap - use geometry_header, only: Cell, root_universe, cells - use hdf5_interface - use output, only: time_stamp - use material_header, only: materials - use mesh_header, only: meshes, RegularMesh - use particle_header - use plot_header - use progress_header, only: ProgressBar - use settings, only: check_overlaps - use string, only: to_str - - implicit none - private - - public :: openmc_plot_geometry - - integer, parameter :: RED = 1 - integer, parameter :: GREEN = 2 - integer, parameter :: BLUE = 3 - -contains - -!=============================================================================== -! RUN_PLOT controls the logic for making one or many plots -!=============================================================================== - - function openmc_plot_geometry() result(err) bind(C) - integer(C_INT) :: err - - integer :: i ! loop index for plots - - do i = 1, n_plots - associate (pl => plots(i)) - ! Display output message - call write_message("Processing plot " // trim(to_str(pl % id)) & - // ": " // trim(pl % path_plot) // " ...", 5) - - if (pl % type == PLOT_TYPE_SLICE) then - ! create 2d image - call create_ppm(pl) - else if (pl % type == PLOT_TYPE_VOXEL) then - ! create dump for 3D silomesh utility script - call create_voxel(pl) - end if - end associate - end do - - err = 0 - end function openmc_plot_geometry - -!=============================================================================== -! POSITION_RGB computes the red/green/blue values for a given plot with the -! current particle's position -!=============================================================================== - - subroutine position_rgb(p, pl, rgb, id) - type(Particle), intent(inout) :: p - type(ObjectPlot), intent(in) :: pl - integer, intent(out) :: rgb(3) - integer, intent(out) :: id - - integer :: j - logical :: found_cell - - p % n_coord = 1 - - call find_cell(p, found_cell) - j = p % n_coord - if (check_overlaps) call check_cell_overlap(p) - - ! Set coordinate level if specified - if (pl % level >= 0) j = pl % level + 1 - - if (.not. found_cell) then - ! If no cell, revert to default color - rgb = pl % not_found % rgb - id = -1 - else - if (pl % color_by == PLOT_COLOR_MATS) then - ! Assign color based on material - associate (c => cells(p % coord(j) % cell + 1)) - if (c % type() == FILL_UNIVERSE) then - ! If we stopped on a middle universe level, treat as if not found - rgb = pl % not_found % rgb - id = -1 - else if (p % material == MATERIAL_VOID) then - ! By default, color void cells white - rgb = 255 - id = -1 - else - rgb = pl % colors(p % material) % rgb - id = materials(p % material) % id() - end if - end associate - else if (pl % color_by == PLOT_COLOR_CELLS) then - ! Assign color based on cell - rgb = pl % colors(p % coord(j) % cell + 1) % rgb - id = cells(p % coord(j) % cell + 1) % id() - else - rgb = 0 - id = -1 - end if - end if - - end subroutine position_rgb - -!=============================================================================== -! CREATE_PPM creates an image based on user input from a plots.xml -! specification in the portable pixmap format (PPM) -!=============================================================================== - - subroutine create_ppm(pl) - type(ObjectPlot), intent(in) :: pl - - integer :: in_i - integer :: out_i - integer :: x, y ! pixel location - integer :: rgb(3) ! colors (red, green, blue) from 0-255 - integer :: id - integer :: height, width - real(8) :: in_pixel - real(8) :: out_pixel - real(8) :: xyz(3) - integer, allocatable :: data(:,:,:) - type(Particle) :: p - - width = pl % pixels(1) - height = pl % pixels(2) - - in_pixel = pl % width(1)/dble(width) - out_pixel = pl % width(2)/dble(height) - - ! Allocate and initialize results array - allocate(data(3, width, height)) - data(:,:,:) = 0 - - if (pl % basis == PLOT_BASIS_XY) then - in_i = 1 - out_i = 2 - xyz(1) = pl % origin(1) - pl % width(1) / TWO - xyz(2) = pl % origin(2) + pl % width(2) / TWO - xyz(3) = pl % origin(3) - else if (pl % basis == PLOT_BASIS_XZ) then - in_i = 1 - out_i = 3 - xyz(1) = pl % origin(1) - pl % width(1) / TWO - xyz(2) = pl % origin(2) - xyz(3) = pl % origin(3) + pl % width(2) / TWO - else if (pl % basis == PLOT_BASIS_YZ) then - in_i = 2 - out_i = 3 - xyz(1) = pl % origin(1) - xyz(2) = pl % origin(2) - pl % width(1) / TWO - xyz(3) = pl % origin(3) + pl % width(2) / TWO - end if - - ! allocate and initialize particle - call particle_initialize(p) - p % coord(1) % xyz = xyz - p % coord(1) % uvw = [ HALF, HALF, HALF ] - p % coord(1) % universe = root_universe - -!$omp parallel do firstprivate(p) private(x, rgb, id) reduction(+ : data) - do y = 1, height - ! Set y coordinate - p % coord(1) % xyz(out_i) = xyz(out_i) - out_pixel*(y - 1) - do x = 1, width - ! Set x coordinate - p % coord(1) % xyz(in_i) = xyz(in_i) + in_pixel*(x - 1) - - ! get pixel color - call position_rgb(p, pl, rgb, id) - - ! Create a pixel at (x,y) with color (r,g,b) - data(:, x, y) = rgb - end do - end do -!$omp end parallel do - - ! Draw tally mesh boundaries on the image if requested - if (pl % index_meshlines_mesh >= 0) call draw_mesh_lines(pl, data) - - ! Write out the ppm to a file - call output_ppm(pl, data) - - end subroutine create_ppm - -!=============================================================================== -! DRAW_MESH_LINES draws mesh line boundaries on an image -!=============================================================================== - - subroutine draw_mesh_lines(pl, data) - type(ObjectPlot), intent(in) :: pl - integer, intent(inout) :: data(:,:,:) - - logical :: in_mesh - integer :: out_, in_ ! pixel location - integer :: rgb(3) ! RGB color for meshlines pixels - integer :: outrange(2), inrange(2) ! range of pixel locations - integer :: i, j ! loop indices - integer :: plus - integer :: ijk_ll(3) ! mesh bin ijk indicies of plot lower left - integer :: ijk_ur(3) ! mesh bin ijk indicies of plot upper right - integer :: outer, inner - real(8) :: frac - real(8) :: width(3) ! real widths of the plot - real(8) :: xyz_ll_plot(3) ! lower left xyz of plot image - real(8) :: xyz_ur_plot(3) ! upper right xyz of plot image - real(8) :: xyz_ll(3) ! lower left xyz - real(8) :: xyz_ur(3) ! upper right xyz - type(RegularMesh) :: m - - rgb(:) = pl % meshlines_color % rgb - - select case (pl % basis) - case(PLOT_BASIS_XY) - outer = 1 - inner = 2 - case(PLOT_BASIS_XZ) - outer = 1 - inner = 3 - case(PLOT_BASIS_YZ) - outer = 2 - inner = 3 - end select - - xyz_ll_plot = pl % origin - xyz_ur_plot = pl % origin - - xyz_ll_plot(outer) = pl % origin(outer) - pl % width(1) / TWO - xyz_ll_plot(inner) = pl % origin(inner) - pl % width(2) / TWO - xyz_ur_plot(outer) = pl % origin(outer) + pl % width(1) / TWO - xyz_ur_plot(inner) = pl % origin(inner) + pl % width(2) / TWO - - width = xyz_ur_plot - xyz_ll_plot - - m = meshes(pl % index_meshlines_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) - do j = ijk_ll(inner), ijk_ur(inner) - ! check if we're in the mesh for this ijk - if (i > 0 .and. i <= m % dimension(outer) .and. & - j > 0 .and. j <= m % dimension(inner)) then - - ! get xyz's of lower left and upper right of this mesh cell - xyz_ll(outer) = m % lower_left(outer) + m % width(outer) * (i - 1) - xyz_ll(inner) = m % lower_left(inner) + m % width(inner) * (j - 1) - xyz_ur(outer) = m % lower_left(outer) + m % width(outer) * i - xyz_ur(inner) = m % lower_left(inner) + m % width(inner) * j - - ! map the xyz ranges to pixel ranges - - frac = (xyz_ll(outer) - xyz_ll_plot(outer)) / width(outer) - outrange(1) = int(frac * real(pl % pixels(1), 8)) - frac = (xyz_ur(outer) - xyz_ll_plot(outer)) / width(outer) - outrange(2) = int(frac * real(pl % pixels(1), 8)) - - frac = (xyz_ur(inner) - xyz_ll_plot(inner)) / width(inner) - inrange(1) = int((ONE - frac) * real(pl % pixels(2), 8)) - frac = (xyz_ll(inner) - xyz_ll_plot(inner)) / width(inner) - inrange(2) = int((ONE - frac) * real(pl % pixels(2), 8)) - - ! draw lines - do out_ = outrange(1), outrange(2) - do plus = 0, pl % meshlines_width - data(:, out_ + 1, inrange(1) + plus + 1) = rgb - data(:, out_ + 1, inrange(2) + plus + 1) = rgb - data(:, out_ + 1, inrange(1) - plus + 1) = rgb - data(:, out_ + 1, inrange(2) - plus + 1) = rgb - end do - end do - do in_ = inrange(1), inrange(2) - do plus = 0, pl % meshlines_width - data(:, outrange(1) + plus + 1, in_ + 1) = rgb - data(:, outrange(2) + plus + 1, in_ + 1) = rgb - data(:, outrange(1) - plus + 1, in_ + 1) = rgb - data(:, outrange(2) - plus + 1, in_ + 1) = rgb - end do - end do - - end if - end do - end do - - end subroutine draw_mesh_lines - -!=============================================================================== -! OUTPUT_PPM writes out a previously generated image to a PPM file -!=============================================================================== - - subroutine output_ppm(pl, data) - type(ObjectPlot), intent(in) :: pl - integer, intent(in) :: data(:,:,:) - - integer :: y ! loop index for height - integer :: x ! loop index for width - integer :: unit_plot - - ! Open PPM file for writing - open(NEWUNIT=unit_plot, FILE=pl % path_plot) - - ! Write header - write(unit_plot, '(A2)') 'P6' - write(unit_plot, '(I0,'' '',I0)') pl % pixels(1), pl % pixels(2) - write(unit_plot, '(A)') '255' - - ! Write color for each pixel - do y = 1, pl % pixels(2) - do x = 1, pl % pixels(1) - write(unit_plot, '(3A1)', advance='no') achar(data(RED, x, y)), & - achar(data(GREEN, x, y)), achar(data(BLUE, x, y)) - end do - end do - - ! Close plot file - close(UNIT=unit_plot) - end subroutine output_ppm - -!=============================================================================== -! CREATE_VOXEL outputs a binary file that can be input into silomesh for 3D -! geometry visualization. It works the same way as create_ppm by dragging a -! particle across the geometry for the specified number of voxels. The first -! 3 int(4)'s in the binary are the number of x, y, and z voxels. The next 3 -! real(8)'s are the widths of the voxels in the x, y, and z directions. The next -! 3 real(8)'s are the x, y, and z coordinates of the lower left point. Finally -! the binary is filled with entries of four int(4)'s each. Each 'row' in the -! binary contains four int(4)'s: 3 for x,y,z position and 1 for cell or material -! id. For 1 million voxels this produces a file of approximately 15MB. -!=============================================================================== - - subroutine create_voxel(pl) - type(ObjectPlot), intent(in) :: pl - - integer(C_INT) :: x, y, z ! voxel location indices - integer :: rgb(3) ! colors (red, green, blue) from 0-255 - integer :: id ! id of cell or material - integer(C_INT), target :: data(pl%pixels(3),pl%pixels(2)) - integer(HID_T) :: file_id - integer(HID_T) :: dspace - integer(HID_T) :: memspace - integer(HID_T) :: dset - integer(HSIZE_T) :: dims(3) - real(8) :: vox(3) ! x, y, and z voxel widths - real(8) :: ll(3) ! lower left starting point for each sweep direction - type(Particle) :: p - type(ProgressBar) :: progress - - interface - subroutine voxel_init(file_id, dims, dspace, dset, memspace) bind(C) - import HID_T, HSIZE_T - integer(HID_T), value :: file_id - integer(HSIZE_T), intent(in) :: dims(*) - integer(HID_T), intent(out) :: dspace - integer(HID_T), intent(out) :: dset - integer(HID_T), intent(out) :: memspace - end subroutine voxel_init - - subroutine voxel_write_slice(x, dspace, dset, memspace, buf) bind(C) - import C_INT, HID_T, C_PTR - integer(C_INT), value :: x - integer(HID_T), value :: dspace - integer(HID_T), value :: dset - integer(HID_T), value :: memspace - type(C_PTR), value :: buf - end subroutine voxel_write_slice - - subroutine voxel_finalize(dspace, dset, memspace) bind(C) - import HID_T - integer(HID_T), value :: dspace - integer(HID_T), value :: dset - integer(HID_T), value :: memspace - end subroutine voxel_finalize - end interface - - ! compute voxel widths in each direction - vox = pl % width/dble(pl % pixels) - - ! initial particle position - ll = pl % origin - pl % width / TWO - - ! allocate and initialize particle - call particle_initialize(p) - p % coord(1) % xyz = ll - p % coord(1) % uvw = [ HALF, HALF, HALF ] - p % coord(1) % universe = root_universe - - ! Open binary plot file for writing - file_id = file_open(pl%path_plot, 'w') - - ! write header info - call write_attribute(file_id, "filetype", 'voxel') - call write_attribute(file_id, "version", VERSION_VOXEL) - call write_attribute(file_id, "openmc_version", VERSION) -#ifdef GIT_SHA1 - call write_attribute(file_id, "git_sha1", GIT_SHA1) -#endif - - ! Write current date and time - call write_attribute(file_id, "date_and_time", time_stamp()) - - call write_attribute(file_id, "num_voxels", pl%pixels) - call write_attribute(file_id, "voxel_width", vox) - call write_attribute(file_id, "lower_left", ll) - - ! Create dataset for voxel data -- note that the dimensions are reversed - ! since we want the order in the file to be z, y, x - dims(:) = pl % pixels - call voxel_init(file_id, dims, dspace, dset, memspace) - - ! move to center of voxels - ll = ll + vox / TWO - - do x = 1, pl % pixels(1) - call progress % set_value(dble(x)/dble(pl % pixels(1))*100) - do y = 1, pl % pixels(2) - do z = 1, pl % pixels(3) - ! get voxel color - call position_rgb(p, pl, rgb, id) - - ! write to plot file - data(z,y) = id - - ! advance particle in z direction - p % coord(1) % xyz(3) = p % coord(1) % xyz(3) + vox(3) - end do - - ! advance particle in y direction - p % coord(1) % xyz(2) = p % coord(1) % xyz(2) + vox(2) - p % coord(1) % xyz(3) = ll(3) - end do - - ! advance particle in y direction - p % coord(1) % xyz(1) = p % coord(1) % xyz(1) + vox(1) - p % coord(1) % xyz(2) = ll(2) - p % coord(1) % xyz(3) = ll(3) - - ! Write to HDF5 dataset - call voxel_write_slice(x, dspace, dset, memspace, c_loc(data)) - end do - - call voxel_finalize(dspace, dset, memspace) - call file_close(file_id) - - end subroutine create_voxel - -end module plot diff --git a/src/plot.cpp b/src/plot.cpp index 79f04c47f3..7b0cddb631 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -1,10 +1,931 @@ +#include +#include + #include "openmc/plot.h" +#include "openmc/constants.h" +#include "openmc/settings.h" +#include "openmc/error.h" +#include "openmc/particle.h" +#include "openmc/geometry.h" +#include "openmc/cell.h" +#include "openmc/material.h" +#include "openmc/string_functions.h" +#include "openmc/mesh.h" +#include "openmc/output.h" +#include "openmc/hdf5_interface.h" +#include "openmc/random_lcg.h" +#include "openmc/output.h" +#include "openmc/progress_bar.h" namespace openmc { +//============================================================================== +// Global variables +//============================================================================== + +int PLOT_LEVEL_LOWEST = -1; + +std::unordered_map plot_map; + +int n_plots; + +std::vector plots; + +const RGBColor WHITE = {255, 255, 255}; + +//============================================================================== +// RUN_PLOT controls the logic for making one or many plots +//============================================================================== + +extern "C" +int openmc_plot_geometry() +{ + int err; + + for (auto pl : plots) { + std::stringstream ss; + ss << "Processing plot " << pl.id_ << ": " + << pl.path_plot_ << "..."; + write_message(ss.str(), 5); + + if (PlotType::slice == pl.type_) { + // create 2D image + create_ppm(pl); + } else if (PlotType::voxel == pl.type_) { + // create voxel file for 3D viewing + create_voxel(pl); + } + } + return 0; +} + + void -voxel_init(hid_t file_id, const hsize_t* dims, hid_t* dspace, hid_t* dset, - hid_t* memspace) +read_plots(pugi::xml_node* plots_node) +{ + n_plots = 0; + for (auto node : plots_node->children("plot")) { + Plot pl(node); + plots.push_back(pl); + plot_map[pl.id_] = n_plots++; + } +} + +//============================================================================== +// CREATE_PPM creates an image based on user input from a plots.xml +// specification in the portable pixmap format (PPM) +//============================================================================== + +void create_ppm(Plot pl) +{ + + size_t width = pl.pixels_[0]; + size_t height = pl.pixels_[1]; + + double in_pixel = (pl.width_[0])/static_cast(width); + double out_pixel = (pl.width_[1])/static_cast(height); + + ImageData data; + data.resize({width, height}); + + int in_i, out_i; + double xyz[3]; + switch(pl.basis_) { + case PlotBasis::xy : + in_i = 0; + out_i = 1; + xyz[0] = pl.origin_[0] - pl.width_[0] / 2.; + xyz[1] = pl.origin_[1] + pl.width_[1] / 2.; + xyz[2] = pl.origin_[2]; + break; + case PlotBasis::xz : + in_i = 0; + out_i = 2; + xyz[0] = pl.origin_[0] - pl.width_[0] / 2.; + xyz[1] = pl.origin_[1]; + xyz[2] = pl.origin_[2] + pl.width_[1] / 2.; + break; + case PlotBasis::yz : + in_i = 1; + out_i = 2; + xyz[0] = pl.origin_[0]; + xyz[1] = pl.origin_[1] - pl.width_[0] / 2.; + xyz[2] = pl.origin_[2] + pl.width_[1] / 2.; + break; + } + + double dir[3] = {0.5, 0.5, 0.5}; + +#pragma omp parallel +{ + Particle p; + p.initialize(); + std::copy(xyz, xyz+3, p.coord[0].xyz); + std::copy(dir, dir+3, p.coord[0].uvw); + p.coord[0].universe = openmc_root_universe; + +#pragma omp for + for (int y = 0; y < height; y++) { + p.coord[0].xyz[out_i] = xyz[out_i] - out_pixel * y; + for (int x = 0; x < width; x++) { + // local variables + RGBColor rgb; + int id; + p.coord[0].xyz[in_i] = xyz[in_i] + in_pixel * x; + position_rgb(p, pl, rgb, id); + data(x,y) = rgb; + } + } +} + // draw mesh lines if present + if (pl.index_meshlines_mesh_ >= 0) {draw_mesh_lines(pl, data);} + + // write ppm data to file + output_ppm(pl, data); +} + +void +Plot::set_id(pugi::xml_node plot_node) +{ + // Copy data into plots + if (check_for_node(plot_node, "id")) { + id_ = std::stoi(get_node_value(plot_node, "id")); + } else { + fatal_error("Must specify plot id in plots XML file."); + } + + // Check to make sure 'id' hasn't been used + if (plot_map.find(id_) != plot_map.end()) { + std::stringstream err_msg; + err_msg << "Two or more plots use the same unique ID: " << id_; + fatal_error(err_msg.str()); + } +} + +void +Plot::set_type(pugi::xml_node plot_node) +{ + // Copy plot type + // Default is slice + type_ = PlotType::slice; + // check type specified on plot node + if (check_for_node(plot_node, "type")) { + std::string type_str = get_node_value(plot_node, "type", true); + // set type using node value + if (type_str == "slice") { + type_ = PlotType::slice; + } + else if (type_str == "voxel") { + type_ = PlotType::voxel; + } else { + // if we're here, something is wrong + std::stringstream err_msg; + err_msg << "Unsupported plot type '" << type_str + << "' in plot " << id_; + fatal_error(err_msg.str()); + } + } +} + +void +Plot::set_output_path(pugi::xml_node plot_node) +{ + // Set output file path + std::stringstream filename; + + if (check_for_node(plot_node, "filename")) { + filename << get_node_value(plot_node, "filename"); + } else { + filename << "plot_" << id_; + } + // add appropriate file extension to name + switch(type_) { + case PlotType::slice: + filename << ".ppm"; + break; + case PlotType::voxel: + filename << ".h5"; + break; + } + + path_plot_ = filename.str(); + + // Copy plot pixel size + std::vector pxls = get_node_array(plot_node, "pixels"); + if (PlotType::slice == type_) { + if (pxls.size() == 2) { + pixels_[0] = pxls[0]; + pixels_[1] = pxls[1]; + } else { + std::stringstream err_msg; + err_msg << " must be length 2 in slice plot " + << id_; + fatal_error(err_msg.str()); + } + } else if (PlotType::voxel == type_) { + if (pxls.size() == 3) { + pixels_[0] = pxls[0]; + pixels_[1] = pxls[1]; + pixels_[2] = pxls[2]; + } else { + std::stringstream err_msg; + err_msg << " must be length 3 in voxel plot " + << id_; + fatal_error(err_msg.str()); + } + } +} + +void +Plot::set_bg_color(pugi::xml_node plot_node) +{ + // Copy plot background color + if (check_for_node(plot_node, "background")) { + std::vector bg_rgb = get_node_array(plot_node, "background"); + if (PlotType::voxel == type_) { + if (openmc_master) { + std::stringstream err_msg; + err_msg << "Background color ignored in voxel plot " + << id_; + warning(err_msg.str()); + } + } + if (bg_rgb.size() == 3) { + not_found_ = bg_rgb; + } else { + std::stringstream err_msg; + err_msg << "Bad background RGB in plot " + << id_; + fatal_error(err_msg); + } + } else { + // default to a white background + not_found_ = WHITE; + } +} + +void +Plot::set_basis(pugi::xml_node plot_node) +{ + // Copy plot basis + if (PlotType::slice == type_) { + std::string pl_basis = "xy"; + if (check_for_node(plot_node, "basis")) { + pl_basis = get_node_value(plot_node, "basis", true); + } + if ("xy" == pl_basis) { + basis_ = PlotBasis::xy; + } else if ("xz" == pl_basis) { + basis_ = PlotBasis::xz; + } else if ("yz" == pl_basis) { + basis_ = PlotBasis::yz; + } else { + std::stringstream err_msg; + err_msg << "Unsupported plot basis '" << pl_basis + << "' in plot " << id_; + fatal_error(err_msg); + } + } +} + +void +Plot::set_origin(pugi::xml_node plot_node) +{ + // Copy plotting origin + std::vector pl_origin = get_node_array(plot_node, "origin"); + if (pl_origin.size() == 3) { + origin_[0] = pl_origin[0]; + origin_[1] = pl_origin[1]; + origin_[2] = pl_origin[2]; + } else { + std::stringstream err_msg; + err_msg << "Origin must be length 3 in plot " + << id_; + fatal_error(err_msg); + } +} + +void +Plot::set_width(pugi::xml_node plot_node) +{ + // Copy plotting width + std::vector pl_width = get_node_array(plot_node, "width"); + if (PlotType::slice == type_) { + if (pl_width.size() == 2) { + width_[0] = pl_width[0]; + width_[1] = pl_width[1]; + } else { + std::stringstream err_msg; + err_msg << " must be length 2 in slice plot " + << id_; + fatal_error(err_msg); + } + } else if (PlotType::voxel == type_) { + if (pl_width.size() == 3) { + pl_width = get_node_array(plot_node, "width"); + width_[0] = pl_width[0]; + width_[1] = pl_width[1]; + width_[2] = pl_width[2]; + } else { + std::stringstream err_msg; + err_msg << " must be length 3 in voxel plot " + << id_; + fatal_error(err_msg); + } + } +} + +void +Plot::set_universe(pugi::xml_node plot_node) +{ + // Copy plot universe level + if (check_for_node(plot_node, "level")) { + level_ = std::stoi(get_node_value(plot_node, "level")); + if (level_ < 0) { + std::stringstream err_msg; + err_msg << "Bad universe level in plot " << id_; + fatal_error(err_msg); + } + } else { + level_ = PLOT_LEVEL_LOWEST; + } +} + +void +Plot::set_default_colors(pugi::xml_node plot_node) +{ + // Copy plot color type and initialize all colors randomly + std::string pl_color_by = "cell"; + if (check_for_node(plot_node, "color_by")) { + pl_color_by = get_node_value(plot_node, "color_by", true); + } + if ("cell" == pl_color_by) { + color_by_ = PlotColorBy::cells; + colors_.resize(n_cells); + for (int i = 0; i < n_cells; i++) { + colors_[i] = random_color(); + } + + } else if("material" == pl_color_by) { + color_by_ = PlotColorBy::mats; + colors_.resize(n_materials); + for (int i = 0; i < materials.size(); i++) { + colors_[i] = random_color(); + } + } else { + std::stringstream err_msg; + err_msg << "Unsupported plot color type '" << pl_color_by + << "' in plot " << id_; + fatal_error(err_msg); + } +} + +void +Plot::set_user_colors(pugi::xml_node plot_node) +{ + if (!plot_node.select_nodes("color").empty() && PlotType::voxel == type_) { + if (openmc_master) { + std::stringstream err_msg; + err_msg << "Color specifications ignored in voxel plot " + << id_; + warning(err_msg); + } + } + + for (auto cn : plot_node.children("color")) { + // Make sure 3 values are specified for RGB + std::vector user_rgb = get_node_array(cn, "rgb"); + if (user_rgb.size() != 3) { + std::stringstream err_msg; + err_msg << "Bad RGB in plot " << id_; + fatal_error(err_msg); + } + // Ensure that there is an id for this color specification + int col_id; + if (check_for_node(cn, "id")) { + col_id = std::stoi(get_node_value(cn, "id")); + } else { + std::stringstream err_msg; + err_msg << "Must specify id for color specification in plot " + << id_; + fatal_error(err_msg); + } + // Add RGB + if (PlotColorBy::cells == color_by_) { + if (cell_map.find(col_id) != cell_map.end()) { + col_id = cell_map[col_id]; + colors_[col_id] = user_rgb; + } else { + std::stringstream err_msg; + err_msg << "Could not find cell " << col_id + << " specified in plot " << id_; + fatal_error(err_msg); + } + } else if (PlotColorBy::mats == color_by_) { + if (material_map.find(col_id) != material_map.end()) { + col_id = material_map[col_id]; + colors_[col_id] = user_rgb; + } else { + std::stringstream err_msg; + err_msg << "Could not find material " << col_id + << " specified in plot " << id_; + fatal_error(err_msg); + } + } + } // color node loop +} + +void +Plot::set_meshlines(pugi::xml_node plot_node) +{ + // Deal with meshlines + pugi::xpath_node_set mesh_line_nodes = plot_node.select_nodes("meshlines"); + + if (!mesh_line_nodes.empty()) { + if (PlotType::voxel == type_) { + std::stringstream msg; + msg << "Meshlines ignored in voxel plot " << id_; + warning(msg); + } + + if (mesh_line_nodes.size() == 1) { + // Get first meshline node + pugi::xml_node meshlines_node = mesh_line_nodes[0].node(); + + // Check mesh type + std::string meshtype; + if (check_for_node(meshlines_node, "meshtype")) { + meshtype = get_node_value(meshlines_node, "meshtype"); + } else { + std::stringstream err_msg; + err_msg << "Must specify a meshtype for meshlines specification in plot " << id_; + fatal_error(err_msg); + } + + // Ensure that there is a linewidth for this meshlines specification + std::string meshline_width; + if (check_for_node(meshlines_node, "linewidth")) { + meshline_width = get_node_value(meshlines_node, "linewidth"); + meshlines_width_ = std::stoi(meshline_width); + } else { + std::stringstream err_msg; + err_msg << "Must specify a linewidth for meshlines specification in plot " << id_; + fatal_error(err_msg); + } + + // Check for color + if (check_for_node(meshlines_node, "color")) { + // Check and make sure 3 values are specified for RGB + std::vector ml_rgb = get_node_array(meshlines_node, "color"); + if (ml_rgb.size() != 3) { + std::stringstream err_msg; + err_msg << "Bad RGB for meshlines color in plot " << id_; + fatal_error(err_msg); + } + meshlines_color_ = ml_rgb; + } + + // Set mesh based on type + if ("ufs" == meshtype) { + if (settings::index_ufs_mesh < 0) { + std::stringstream err_msg; + err_msg << "No UFS mesh for meshlines on plot " << id_; + fatal_error(err_msg); + } else { + index_meshlines_mesh_ = settings::index_ufs_mesh; + } + } else if ("cmfd" == meshtype) { + if (!settings::cmfd_run) { + std::stringstream err_msg; + err_msg << "Need CMFD run to plot CMFD mesh for meshlines on plot " << id_; + fatal_error(err_msg); + } else { + index_meshlines_mesh_ = settings::index_cmfd_mesh; + } + } else if ("entropy" == meshtype) { + if (settings::index_entropy_mesh < 0) { + std::stringstream err_msg; + err_msg <<"No entropy mesh for meshlines on plot " << id_; + fatal_error(err_msg); + } else { + index_meshlines_mesh_ = settings::index_entropy_mesh; + } + } else if ("tally" == meshtype) { + // Ensure that there is a mesh id if the type is tally + int tally_mesh_id; + if (check_for_node(meshlines_node, "id")) { + tally_mesh_id = std::stoi(get_node_value(meshlines_node, "id")); + } else { + std::stringstream err_msg; + err_msg << "Must specify a mesh id for meshlines tally " + << "mesh specification in plot " << id_; + fatal_error(err_msg); + } + // find the tally index + int idx; + int err = openmc_get_mesh_index(tally_mesh_id, &idx); + if (err != 0) { + std::stringstream err_msg; + err_msg << "Could not find mesh " << tally_mesh_id + << " specified in meshlines for plot " << id_; + fatal_error(err_msg); + } + index_meshlines_mesh_ = idx; + } else { + std::stringstream err_msg; + err_msg << "Invalid type for meshlines on plot " << id_ ; + fatal_error(err_msg); + } + } else { + std::stringstream err_msg; + err_msg << "Mutliple meshlines specified in plot " << id_; + fatal_error(err_msg); + } + } +} + +void +Plot::set_mask(pugi::xml_node plot_node) +{ + // Deal with masks + pugi::xpath_node_set mask_nodes = plot_node.select_nodes("mask"); + + if (!mask_nodes.empty()) { + if (PlotType::voxel == type_) { + if (openmc_master) { + std::stringstream wrn_msg; + wrn_msg << "Mask ignored in voxel plot " << id_; + warning(wrn_msg); + } + } + + if (mask_nodes.size() == 1) { + // Get pointer to mask + pugi::xml_node mask_node = mask_nodes[0].node(); + + // Determine how many components there are and allocate + std::vector iarray = get_node_array(mask_node, "components"); + if (iarray.size() == 0) { + std::stringstream err_msg; + err_msg << "Missing in mask of plot " << id_; + fatal_error(err_msg); + } + + // First we need to change the user-specified identifiers to indices + // in the cell and material arrays + for (auto& col_id : iarray) { + if (PlotColorBy::cells == color_by_) { + if (cell_map.find(col_id) != cell_map.end()) { + col_id = cell_map[col_id]; + } + else { + std::stringstream err_msg; + err_msg << "Could not find cell " << col_id + << " specified in the mask in plot " << id_; + fatal_error(err_msg); + } + } else if (PlotColorBy::mats == color_by_) { + if (material_map.find(col_id) != material_map.end()) { + col_id = material_map[col_id]; + } + else { + std::stringstream err_msg; + err_msg << "Could not find material " << col_id + << " specified in the mask in plot " << id_; + fatal_error(err_msg); + } + } + } + + // Alter colors based on mask information + for (int j = 0; j < colors_.size(); j++) { + if (std::find(iarray.begin(), iarray.end(), j) == iarray.end()) { + if (check_for_node(mask_node, "background")) { + std::vector bg_rgb = get_node_array(mask_node, "background"); + colors_[j] = bg_rgb; + } else { + colors_[j] = WHITE; + } + } + } + + } else { + std::stringstream err_msg; + err_msg << "Mutliple masks specified in plot " << id_; + fatal_error(err_msg); + } + } +} + +Plot::Plot(pugi::xml_node plot_node): +index_meshlines_mesh_(-1) +{ + set_id(plot_node); + set_type(plot_node); + set_output_path(plot_node); + set_bg_color(plot_node); + set_basis(plot_node); + set_origin(plot_node); + set_width(plot_node); + set_universe(plot_node); + set_default_colors(plot_node); + set_user_colors(plot_node); + set_meshlines(plot_node); + set_mask(plot_node); +} // End Plot constructor + +//============================================================================== +// POSITION_RGB computes the red/green/blue values for a given plot with the +// current particle's position +//============================================================================== + + +void position_rgb(Particle p, Plot pl, RGBColor& rgb, int& id) +{ + p.n_coord = 1; + + bool found_cell = find_cell(&p, 0); + + int j = p.n_coord - 1; + + if (settings::check_overlaps) {check_cell_overlap(&p);} + + // Set coordinate level if specified + if (pl.level_ >= 0) {j = pl.level_ + 1;} + + if (!found_cell) { + // If no cell, revert to default color + rgb = pl.not_found_; + id = -1; + } else { + if (PlotColorBy::mats == pl.color_by_) { + // Assign color based on material + Cell* c = cells[p.coord[j].cell]; + if (c->type_ == FILL_UNIVERSE) { + // If we stopped on a middle universe level, treat as if not found + rgb = pl.not_found_; + id = -1; + } else if (p.material == MATERIAL_VOID) { + // By default, color void cells white + rgb = WHITE; + id = -1; + } else { + rgb = pl.colors_[p.material - 1]; + id = materials[p.material - 1]->id_; + } + } else if (PlotColorBy::cells == pl.color_by_) { + // Assign color based on cell + rgb = pl.colors_[p.coord[j].cell]; + id = cells[p.coord[j].cell]->id_; + } + } // endif found_cell +} + +//============================================================================== +// OUTPUT_PPM writes out a previously generated image to a PPM file +//============================================================================== + +void output_ppm(Plot pl, const ImageData& data) +{ + // Open PPM file for writing + std::string fname = pl.path_plot_; + fname = strtrim(fname); + std::ofstream of; + + of.open(fname); + + // Write header + of << "P6" << "\n"; + of << pl.pixels_[0] << " " << pl.pixels_[1] << "\n"; + of << "255" << "\n"; + of.close(); + + of.open(fname, std::ios::binary | std::ios::app); + // Write color for each pixel + for (int y = 0; y < pl.pixels_[1]; y++) { + for (int x = 0; x < pl.pixels_[0]; x++) { + RGBColor rgb = data(x,y); + of << rgb.red << rgb.green << rgb.blue; + } + } + + // Close file + // THIS IS HERE TO MATCH FORTRAN VERSION, NOT TECHNICALLY NECESSARY + of << "\n"; + of.close(); +} + +//============================================================================== +// DRAW_MESH_LINES draws mesh line boundaries on an image +//============================================================================== + +void draw_mesh_lines(Plot pl, ImageData& data) +{ + RGBColor rgb; + rgb = pl.meshlines_color_; + + int outer, inner; + switch(pl.basis_) { + case PlotBasis::xy : + outer = 0; + inner = 1; + break; + case PlotBasis::xz : + outer = 0; + inner = 2; + break; + case PlotBasis::yz : + outer = 1; + inner = 2; + break; + } + + double xyz_ll_plot[3], xyz_ur_plot[3]; + xyz_ll_plot[0] = pl.origin_[0]; + xyz_ll_plot[1] = pl.origin_[1]; + xyz_ll_plot[2] = pl.origin_[2]; + + xyz_ur_plot[0] = pl.origin_[0]; + xyz_ur_plot[1] = pl.origin_[1]; + xyz_ur_plot[2] = pl.origin_[2]; + + xyz_ll_plot[outer] = pl.origin_[outer] - pl.width_[0] / 2.; + xyz_ll_plot[inner] = pl.origin_[inner] - pl.width_[1] / 2.; + xyz_ur_plot[outer] = pl.origin_[outer] + pl.width_[0] / 2.; + xyz_ur_plot[inner] = pl.origin_[inner] + pl.width_[1] / 2.; + + int width[3]; + width[0] = xyz_ur_plot[0] - xyz_ll_plot[0]; + width[1] = xyz_ur_plot[1] - xyz_ll_plot[1]; + width[2] = xyz_ur_plot[2] - xyz_ll_plot[2]; + + auto& m = meshes[pl.index_meshlines_mesh_]; + + int ijk_ll[3], ijk_ur[3]; + bool in_mesh; + m->get_indices(Position(xyz_ll_plot), &(ijk_ll[0]), &in_mesh); + m->get_indices(Position(xyz_ur_plot), &(ijk_ur[0]), &in_mesh); + + // Fortran/C++ index correction + ijk_ur[0]++; ijk_ur[1]++; ijk_ur[2]++; + + double xyz_ll[3], xyz_ur[3]; + // sweep through all meshbins on this plane and draw borders + for (int i = ijk_ll[outer]; i <= ijk_ur[outer]; i++) { + for (int j = ijk_ll[inner]; j <= ijk_ur[inner]; j++) { + // check if we're in the mesh for this ijk + if (i > 0 && i <= m->shape_[outer] && j >0 && j <= m->shape_[inner] ) { + int outrange[3], inrange[3]; + // get xyz's of lower left and upper right of this mesh cell + xyz_ll[outer] = m->lower_left_[outer] + m->width_[outer] * (i - 1); + xyz_ll[inner] = m->lower_left_[inner] + m->width_[inner] * (j - 1); + xyz_ur[outer] = m->lower_left_[outer] + m->width_[outer] * i; + xyz_ur[inner] = m->lower_left_[inner] + m->width_[inner] * j; + + // map the xyz ranges to pixel ranges + double frac = (xyz_ll[outer] - xyz_ll_plot[outer]) / width[outer]; + outrange[0] = int(frac * double(pl.pixels_[0])); + frac = (xyz_ur[outer] - xyz_ll_plot[outer]) / width[outer]; + outrange[1] = int(frac * double(pl.pixels_[0])); + + frac = (xyz_ur[inner] - xyz_ll_plot[inner]) / width[inner]; + inrange[0] = int((1. - frac) * (double)pl.pixels_[1]); + frac = (xyz_ll[inner] - xyz_ll_plot[inner]) / width[inner]; + inrange[1] = int((1. - frac) * (double)pl.pixels_[1]); + + // draw lines + for (int out_ = outrange[0]; out_ <= outrange[1]; out_++) { + for (int plus = 0; plus <= pl.meshlines_width_; plus++) { + data(out_, inrange[0] + plus) = rgb; + data(out_, inrange[1] + plus) = rgb; + data(out_, inrange[0] - plus) = rgb; + data(out_, inrange[1] - plus) = rgb; + } + } + + for (int in_ = inrange[0]; in_ <= inrange[1]; in_++) { + for (int plus = 0; plus <= pl.meshlines_width_; plus++) { + data(outrange[0] + plus, in_) = rgb; + data(outrange[1] + plus, in_) = rgb; + data(outrange[0] - plus, in_) = rgb; + data(outrange[1] - plus, in_) = rgb; + } + } + + } // end if(in mesh) + } + } // end outer loops +} // end draw_mesh_lines + +//============================================================================== +// CREATE_VOXEL outputs a binary file that can be input into silomesh for 3D +// geometry visualization. It works the same way as create_ppm by dragging a +// particle across the geometry for the specified number of voxels. The first 3 +// int(4)'s in the binary are the number of x, y, and z voxels. The next 3 +// real(8)'s are the widths of the voxels in the x, y, and z directions. The +// next 3 real(8)'s are the x, y, and z coordinates of the lower left +// point. Finally the binary is filled with entries of four int(4)'s each. Each +// 'row' in the binary contains four int(4)'s: 3 for x,y,z position and 1 for +// cell or material id. For 1 million voxels this produces a file of +// approximately 15MB. +// ============================================================================= + +void create_voxel(Plot pl) +{ + + // compute voxel widths in each direction + std::array vox; + vox[0] = pl.width_[0]/(double)pl.pixels_[0]; + vox[1] = pl.width_[1]/(double)pl.pixels_[1]; + vox[2] = pl.width_[2]/(double)pl.pixels_[2]; + + // initial particle position + std::array ll; + ll[0] = pl.origin_[0] - pl.width_[0] / 2.; + ll[1] = pl.origin_[1] - pl.width_[1] / 2.; + ll[2] = pl.origin_[2] - pl.width_[2] / 2.; + + // allocate and initialize particle + double dir[3] = {0.5, 0.5, 0.5}; + Particle p; + p.initialize(); + std::copy(ll.begin(), ll.begin()+ll.size(), p.coord[0].xyz); + std::copy(dir, dir+3, p.coord[0].uvw); + p.coord[0].universe = openmc_root_universe; + + // Open binary plot file for writing + std::ofstream of; + std::string fname = std::string(pl.path_plot_); + fname = strtrim(fname); + hid_t file_id = file_open(fname, 'w'); + + // write header info + write_attribute(file_id, "filetype", "voxel"); + write_attribute(file_id, "version", VERSION_VOXEL); + write_attribute(file_id, "openmc_version", VERSION); + +#ifdef GIT_SHA1 + write_attribute(file_id, "git_sha1", GIT_SHA1); +#endif + + // Write current date and time + write_attribute(file_id, "date_and_time", time_stamp().c_str()); + hsize_t three = 3; + write_attribute(file_id, "num_voxels", pl.pixels_); + write_attribute(file_id, "voxel_width", vox); + write_attribute(file_id, "lower_left", ll); + + // Create dataset for voxel data -- note that the dimensions are reversed + // since we want the order in the file to be z, y, x + hsize_t dims[3]; + dims[0] = pl.pixels_[0]; + dims[1] = pl.pixels_[1]; + dims[2] = pl.pixels_[2]; + hid_t dspace, dset, memspace; + voxel_init(file_id, &(dims[0]), &dspace, &dset, &memspace); + + // move to center of voxels + ll[0] = ll[0] + vox[0] / 2.; + ll[1] = ll[1] + vox[1] / 2.; + ll[2] = ll[2] + vox[2] / 2.; + + int data[pl.pixels_[1]][pl.pixels_[2]]; + + ProgressBar pb; + + RGBColor rgb; + int id; + for (int x = 0; x < pl.pixels_[0]; x++) { + pb.set_value(100.*(double)x/(double)(pl.pixels_[0]-1)); + for (int y = 0; y < pl.pixels_[1]; y++) { + for (int z = 0; z < pl.pixels_[2]; z++) { + // get voxel color + position_rgb(p, pl, rgb, id); + // write to plot data + data[y][z] = id; + // advance particle in z direction + p.coord[0].xyz[2] = p.coord[0].xyz[2] + vox[2]; + } + // advance particle in y direction + p.coord[0].xyz[1] = p.coord[0].xyz[1] + vox[1]; + p.coord[0].xyz[2] = ll[2]; + } + // advance particle in x direction + p.coord[0].xyz[0] = p.coord[0].xyz[0] + vox[0]; + p.coord[0].xyz[1] = ll[1]; + p.coord[0].xyz[2] = ll[2]; + // Write to HDF5 dataset + voxel_write_slice(x, dspace, dset, memspace, &(data[0])); + } + + voxel_finalize(dspace, dset, memspace); + file_close(file_id); + +} + +void +voxel_init(hid_t file_id, const hsize_t* dims, + hid_t* dspace, hid_t* dset, hid_t* memspace) { // Create dataspace/dataset for voxel data *dspace = H5Screate_simple(3, dims, nullptr); @@ -25,7 +946,7 @@ voxel_init(hid_t file_id, const hsize_t* dims, hid_t* dspace, hid_t* dset, void voxel_write_slice(int x, hid_t dspace, hid_t dset, hid_t memspace, void* buf) { - hssize_t offset[3] {x - 1, 0, 0}; + hssize_t offset[3] {x, 0, 0}; H5Soffset_simple(dspace, offset); H5Dwrite(dset, H5T_NATIVE_INT, memspace, dspace, H5P_DEFAULT, buf); } @@ -39,4 +960,8 @@ voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace) H5Sclose(memspace); } +RGBColor random_color() { + return {int(prn()*255), int(prn()*255), int(prn()*255)}; +} + } // namespace openmc diff --git a/src/plot_header.F90 b/src/plot_header.F90 deleted file mode 100644 index 761481b332..0000000000 --- a/src/plot_header.F90 +++ /dev/null @@ -1,74 +0,0 @@ -module plot_header - - use, intrinsic :: ISO_C_BINDING - - use constants - use dict_header, only: DictIntInt - - implicit none - -!=============================================================================== -! ObjectColor holds color information for plotted objects -!=============================================================================== - - type ObjectColor - integer :: rgb(3) - end type ObjectColor - -!=============================================================================== -! PLOTSLICE holds plot information -!=============================================================================== - - type ObjectPlot - integer :: id ! Unique ID - character(MAX_LINE_LEN) :: path_plot ! path for plot file - integer :: type ! Type - integer :: color_by ! quantity to color regions by - real(8) :: origin(3) ! xyz center of plot location - real(8) :: width(3) ! xyz widths of plot - integer :: basis ! direction of plot slice - integer :: pixels(3) ! pixel width/height of plot slice - integer :: meshlines_width ! pixel width of meshlines - integer :: level ! universe depth to plot the cells of - integer :: index_meshlines_mesh = -1 ! index of mesh to plot - type(ObjectColor) :: meshlines_color ! Color for meshlines - type(ObjectColor) :: not_found ! color for positions where no cell found - type(ObjectColor), allocatable :: colors(:) ! colors of cells/mats - end type ObjectPlot - - ! Plot type - integer, parameter :: PLOT_TYPE_SLICE = 1 - integer, parameter :: PLOT_TYPE_VOXEL = 2 - - ! Plot level - integer, parameter :: PLOT_LEVEL_LOWEST = -1 - - ! Plot basis plane - integer, parameter :: PLOT_BASIS_XY = 1 - integer, parameter :: PLOT_BASIS_XZ = 2 - integer, parameter :: PLOT_BASIS_YZ = 3 - - ! Indicate whether color refers to unique cell or unique material - 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/progress_bar.cpp b/src/progress_bar.cpp new file mode 100644 index 0000000000..7917f9ffd7 --- /dev/null +++ b/src/progress_bar.cpp @@ -0,0 +1,67 @@ + +#include "openmc/progress_bar.h" + +#include +#include +#include + +#ifdef UNIX +#include +#endif + +#define BAR_WIDTH 72 + +bool is_terminal() { +#ifdef UNIX + return isatty(STDOUT_FILENO) != 0; +#else + return false; +#endif +} + +ProgressBar::ProgressBar() { + // initialize bar + set_value(0.0); +} + +void +ProgressBar::set_value(double val) { + + if (!is_terminal()) return; + + // set the bar percentage + if (val >= 100.0) { + bar.append("100"); + } else if (val <= 0.0) { + bar.append(" 0"); + } else { + std::stringstream ss; + ss << std::setfill(' ') << std::setw(3) << (int)val; + bar.append(ss.str()); + } + + bar.append("% |"); + // remaining width of the bar + int remaining_width = BAR_WIDTH - bar.size() - 2; + + // set the bar width + if (val >= 100.0) { + bar.append(remaining_width, '='); + } else if (val < 0.0) { + bar.append(remaining_width, ' '); + } else { + int width = (int)((double)remaining_width*val/100); + bar.append(width, '='); + bar.append(1, '>'); + bar.append(remaining_width-width-1, ' '); + } + + bar.append("|+"); + + // write the bar + std::cout << '\r' << bar << std::flush; + if (val >= 100.0) { std::cout << "\n"; } + + // reset the bar value + bar = ""; +} diff --git a/src/progress_header.F90 b/src/progress_header.F90 deleted file mode 100644 index 5f462e0fac..0000000000 --- a/src/progress_header.F90 +++ /dev/null @@ -1,102 +0,0 @@ -module progress_header - - use, intrinsic :: ISO_FORTRAN_ENV, only: OUTPUT_UNIT - - implicit none - -#ifdef UNIX - interface - function check_isatty(fd) bind(C, name = 'isatty') - use, intrinsic :: ISO_C_BINDING, only: c_int - integer(c_int) :: check_isatty - integer(c_int), value :: fd - end function check_isatty - end interface -#endif - -!=============================================================================== -! PROGRESSBAR -!=============================================================================== - - type ProgressBar - private - character(len=72) :: bar="???% | " // & - " |" - contains - procedure :: set_value => bar_set_value - end type ProgressBar - -contains - -!=============================================================================== -! IS_TERMINAL checks if output is currently being output to a terminal. Relies -! on a POSIX implementation of isatty, and defaults to false if that is not -! available -!=============================================================================== - - function is_terminal() result(istty) - logical :: istty - - istty = .true. - -#ifdef UNIX - if (check_isatty(1) == 0) istty = .false. -#else - istty = .false. -#endif - - end function is_terminal - -!=============================================================================== -! BAR_SET_VALUE prints the progress bar without advancing. The value is -! specified as percent completion, from 0 to 100. If the value is ever set to -! 100 or above, the bar is set to 100 and a newline is written. -!=============================================================================== - - subroutine bar_set_value(self, val) - - class(ProgressBar), intent(inout) :: self - real(8), intent(in) :: val - - integer :: i - - if (.not. is_terminal()) return - - ! set the percentage - if (val >= 100.) then - write(self % bar(1:3), "(I3)") 100 - else if (val <= 0.) then - write(self % bar(1:3), "(I3)") 0 - else - write(self % bar(1:3), "(I3)") int(val) - end if - - ! set the bar width - if (val >= 100.) then - do i=1,65 - write(self % bar(i+6:i+6), '(A)') '=' - end do - else - do i=1,int(dble(65)*val/100.) - write(self % bar(i+6:i+6), '(A)') '=' - end do - end if - - write(OUTPUT_UNIT, '(A1,A1,A72)', ADVANCE='no') '+', char(13), self % bar - flush(OUTPUT_UNIT) - - if (val >= 100.) then - - ! make new line - write(OUTPUT_UNIT, "(A)") "" - flush(OUTPUT_UNIT) - - ! reset the bar in case we want to use this instance again - self % bar = "???% | " // & - " |" - - end if - - end subroutine bar_set_value - -end module progress_header diff --git a/src/reaction_header.F90 b/src/reaction_header.F90 index 9d6add0f0a..0762846efc 100644 --- a/src/reaction_header.F90 +++ b/src/reaction_header.F90 @@ -74,22 +74,22 @@ module reaction_header pure function reaction_product_decay_rate(ptr, product) result(rate) bind(C) import C_PTR, C_INT, C_DOUBLE - type(C_PTR), value :: ptr - integer(C_INT), value :: product + type(C_PTR), intent(in), value :: ptr + integer(C_INT), intent(in), value :: product real(C_DOUBLE) :: rate end function pure function reaction_product_emission_mode(ptr, product) result(m) bind(C) import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT), value :: product + type(C_PTR), intent(in), value :: ptr + integer(C_INT), intent(in), value :: product integer(C_INT) :: m end function pure function reaction_product_particle(ptr, product) result(particle) bind(C) import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT), value :: product + type(C_PTR), intent(in), value :: ptr + integer(C_INT), intent(in), value :: product integer(C_INT) :: particle end function @@ -104,15 +104,15 @@ module reaction_header pure function reaction_product_yield(ptr, product, E) result(val) bind(C) import C_PTR, C_INT, C_DOUBLE - type(C_PTR), value :: ptr - integer(C_INT), value :: product - real(C_DOUBLE), value :: E + type(C_PTR), intent(in), value :: ptr + integer(C_INT), intent(in), value :: product + real(C_DOUBLE), intent(in), value :: E real(C_DOUBLE) :: val end function pure function reaction_products_size(ptr) result(sz) bind(C) import C_PTR, C_INT - type(C_PTR), value :: ptr + type(C_PTR), intent(in), value :: ptr integer(C_INT) :: sz end function diff --git a/src/scattdata.cpp b/src/scattdata.cpp index 8d71386243..21e7f9df7a 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -316,8 +316,8 @@ ScattDataLegendre::update_max_val() } // Calculate probability - double f = evaluate_legendre_c(dist[gin][i_gout].size() - 1, - dist[gin][i_gout].data(), mu); + double f = evaluate_legendre(dist[gin][i_gout].size() - 1, + dist[gin][i_gout].data(), mu); // if this is a new maximum, store it if (f > max_val[gin][i_gout]) max_val[gin][i_gout] = f; @@ -339,8 +339,8 @@ ScattDataLegendre::calc_f(int gin, int gout, double mu) f = 0.; } else { int i_gout = gout - gmin[gin]; - f = evaluate_legendre_c(dist[gin][i_gout].size() - 1, - dist[gin][i_gout].data(), mu); + f = evaluate_legendre(dist[gin][i_gout].size() - 1, + dist[gin][i_gout].data(), mu); } return f; } @@ -881,8 +881,8 @@ convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, tab.fmu[gin][i_gout].resize(n_mu); for (int imu = 0; imu < n_mu; imu++) { tab.fmu[gin][i_gout][imu] = - evaluate_legendre_c(leg.dist[gin][i_gout].size() - 1, - leg.dist[gin][i_gout].data(), tab.mu[imu]); + evaluate_legendre(leg.dist[gin][i_gout].size() - 1, + leg.dist[gin][i_gout].data(), tab.mu[imu]); } // Ensure positivity diff --git a/src/secondary_nbody.cpp b/src/secondary_nbody.cpp index 45f6d3cb86..dabd9de3a8 100644 --- a/src/secondary_nbody.cpp +++ b/src/secondary_nbody.cpp @@ -32,13 +32,13 @@ void NBodyPhaseSpace::sample(double E_in, double& E_out, double& mu) const double E_max = (Ap - 1.0)/Ap * (A_/(A_ + 1.0)*E_in + Q_); // x is essentially a Maxwellian distribution - double x = maxwell_spectrum_c(1.0); + double x = maxwell_spectrum(1.0); double y; double r1, r2, r3, r4, r5, r6; switch (n_bodies_) { case 3: - y = maxwell_spectrum_c(1.0); + y = maxwell_spectrum(1.0); break; case 4: r1 = prn(); diff --git a/src/settings.F90 b/src/settings.F90 index 5929bdd939..19ec3f7714 100644 --- a/src/settings.F90 +++ b/src/settings.F90 @@ -3,7 +3,6 @@ module settings use, intrinsic :: ISO_C_BINDING use constants - use set_header, only: SetInt implicit none @@ -67,9 +66,6 @@ module settings integer(C_INT32_T), bind(C) :: index_ufs_mesh ! Write source at end of simulation - logical(C_BOOL), bind(C) :: source_separate - logical(C_BOOL), bind(C) :: source_write - logical(C_BOOL), bind(C) :: source_latest ! Variance reduction settins logical(C_BOOL), bind(C) :: survival_biasing @@ -99,25 +95,10 @@ module settings ! Particle tracks logical(C_BOOL), bind(C) :: write_all_tracks - integer, allocatable :: track_identifiers(:,:) - - ! Particle restart run - logical(C_BOOL), bind(C) :: particle_restart_run - - ! Write out initial source - logical(C_BOOL), bind(C) :: write_initial_source ! Whether create fission neutrons or not. Only applied for MODE_FIXEDSOURCE logical(C_BOOL), bind(C) :: create_fission_neutrons - ! 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 @@ -128,7 +109,6 @@ module settings ! Various output options logical(C_BOOL), bind(C) :: output_summary - logical(C_BOOL), bind(C) :: output_tallies ! Resonance scattering settings logical(C_BOOL), bind(C) :: res_scat_on ! is resonance scattering treated? @@ -150,11 +130,14 @@ contains !=============================================================================== subroutine free_memory_settings() - if (allocated(res_scat_nuclides)) deallocate(res_scat_nuclides) - if (allocated(track_identifiers)) deallocate(track_identifiers) + interface + subroutine free_memory_settings_c() bind(C) + end subroutine + end interface - call statepoint_batch % clear() - call sourcepoint_batch % clear() + if (allocated(res_scat_nuclides)) deallocate(res_scat_nuclides) + + call free_memory_settings_c() end subroutine free_memory_settings end module settings diff --git a/src/settings.cpp b/src/settings.cpp index b5f2bf5c63..d4cea83bcc 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -9,14 +9,17 @@ #include "openmc/capi.h" #include "openmc/constants.h" +#include "openmc/container_util.h" #include "openmc/distribution.h" #include "openmc/distribution_multi.h" #include "openmc/distribution_spatial.h" +#include "openmc/eigenvalue.h" #include "openmc/error.h" #include "openmc/file_utils.h" #include "openmc/mesh.h" #include "openmc/output.h" #include "openmc/random_lcg.h" +#include "openmc/simulation.h" #include "openmc/source.h" #include "openmc/string_utils.h" #include "openmc/xml_interface.h" @@ -57,7 +60,7 @@ bool urr_ptables_on {true}; bool write_all_tracks {false}; bool write_initial_source {false}; bool dagmc {false}; - + std::string path_cross_sections; std::string path_input; std::string path_multipole; @@ -69,14 +72,15 @@ std::string path_statepoint; int32_t index_entropy_mesh {-1}; int32_t index_ufs_mesh {-1}; - +int32_t index_cmfd_mesh {-1}; + int32_t n_batches; int32_t n_inactive {0}; int32_t gen_per_batch {1}; int64_t n_particles {-1}; int electron_treatment {ELECTRON_TTB}; -double energy_cutoff[4] {0.0, 1000.0, 0.0, 0.0}; +std::array energy_cutoff {0.0, 1000.0, 0.0, 0.0}; int legendre_to_tabular_points {C_NONE}; int max_order {0}; int n_log_bins {8000}; @@ -85,13 +89,16 @@ int res_scat_method {RES_SCAT_ARES}; double res_scat_energy_min {0.01}; double res_scat_energy_max {1000.0}; int run_mode {-1}; +std::unordered_set sourcepoint_batch; +std::unordered_set statepoint_batch; int temperature_method {TEMPERATURE_NEAREST}; double temperature_tolerance {10.0}; double temperature_default {293.6}; -double temperature_range[2] {0.0, 0.0}; +std::array temperature_range {0.0, 0.0}; int trace_batch; int trace_gen; int64_t trace_particle; +std::vector> track_identifiers; int trigger_batch_interval {1}; int verbosity {7}; double weight_cutoff {0.25}; @@ -140,7 +147,10 @@ void get_run_parameters(pugi::xml_node node_base) gen_per_batch = std::stoi(get_node_value(node_base, "generations_per_batch")); } - // TODO: Preallocate space for keff and entropy by generation + // Preallocate space for keff and entropy by generation + int m = settings::n_max_batches * settings::gen_per_batch; + simulation::k_generation.reserve(m); + entropy.reserve(m); // Get the trigger information for keff if (check_for_node(node_base, "keff_trigger")) { @@ -195,7 +205,7 @@ void read_settings_xml() // Parse settings.xml file xml_document doc; - auto result = doc.load_file("settings.xml"); + auto result = doc.load_file(filename.c_str()); if (!result) { fatal_error("Error processing settings.xml file."); } @@ -218,7 +228,7 @@ void read_settings_xml() fatal_error("DAGMC mode unsupported for this build of OpenMC"); } #endif - + // To this point, we haven't displayed any output since we didn't know what // the verbosity is. Now that we checked for it, show the title if necessary if (openmc_master) { @@ -393,14 +403,14 @@ void read_settings_xml() // Number of OpenMP threads if (check_for_node(root, "threads")) { #ifdef _OPENMP - if (openmc_n_threads == 0) { - openmc_n_threads = std::stoi(get_node_value(root, "threads")); - if (openmc_n_threads < 1) { + if (simulation::n_threads == 0) { + simulation::n_threads = std::stoi(get_node_value(root, "threads")); + if (simulation::n_threads < 1) { std::stringstream msg; - msg << "Invalid number of threads: " << openmc_n_threads; + msg << "Invalid number of threads: " << simulation::n_threads; fatal_error(msg); } - omp_set_num_threads(openmc_n_threads); + omp_set_num_threads(simulation::n_threads); } #else if (openmc_master) warning("OpenMC was not compiled with OpenMP support; " @@ -414,7 +424,7 @@ void read_settings_xml() omp_set_num_threads(1); } #endif - + // ========================================================================== // EXTERNAL SOURCE @@ -490,7 +500,7 @@ void read_settings_xml() // Particle tracks if (check_for_node(root, "track")) { // Get values and make sure there are three per particle - auto temp = get_node_array(root, "track"); + auto temp = get_node_array(root, "track"); if (temp.size() % 3 != 0) { fatal_error("Number of integers specified in 'track' is not " "divisible by 3. Please provide 3 integers per particle to be " @@ -498,8 +508,11 @@ void read_settings_xml() } // Reshape into track_identifiers - //allocate(track_identifiers(3, n_tracks/3)) - //track_identifiers = reshape(temp_int_array, [3, n_tracks/3]) + int n_tracks = temp.size() / 3; + for (int i = 0; i < n_tracks; ++i) { + track_identifiers.push_back({temp[3*i], temp[3*i + 1], + temp[3*i + 2]}); + } } // Read meshes @@ -538,7 +551,7 @@ void read_settings_xml() // 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 - int n = std::ceil(std::pow(settings::n_particles / 20.0, 1.0/3.0)); + int n = std::ceil(std::pow(n_particles / 20.0, 1.0/3.0)); m.shape_ = {n, n, n}; m.n_dimension_ = 3; @@ -547,7 +560,7 @@ void read_settings_xml() } // Turn on Shannon entropy calculation - settings::entropy_on = true; + entropy_on = true; } // Uniform fission source weighting mesh @@ -580,18 +593,48 @@ void read_settings_xml() if (index_ufs_mesh >= 0) { // Turn on uniform fission source weighting - settings::ufs_on = true; + ufs_on = true; } - // TODO: Read + // Check if the user has specified to write state points + if (check_for_node(root, "state_point")) { + // Get pointer to state_point node + auto node_sp = root.child("state_point"); + + // Determine number of batches at which to store state points + if (check_for_node(node_sp, "batches")) { + // User gave specific batches to write state points + auto temp = get_node_array(node_sp, "batches"); + for (const auto& b : temp) { + statepoint_batch.insert(b); + } + } else { + // If neither were specified, write state point at last batch + statepoint_batch.insert(n_batches); + } + } else { + // If no tag was present, by default write state point at + // last batch only + statepoint_batch.insert(n_batches); + } // Check if the user has specified to write source points if (check_for_node(root, "source_point")) { // Get source_point node xml_node node_sp = root.child("source_point"); - // TODO: Read source point batches + // Determine batches at which to store source points + if (check_for_node(node_sp, "batches")) { + // User gave specific batches to write source points + auto temp = get_node_array(node_sp, "batches"); + for (const auto& b : temp) { + sourcepoint_batch.insert(b); + } + } else { + // If neither were specified, write source points with state points + sourcepoint_batch = statepoint_batch; + } // Check if the user has specified to write binary source file if (check_for_node(node_sp, "separate")) { @@ -608,10 +651,19 @@ void read_settings_xml() // If no tag was present, by default we keep source bank in // statepoint file and write it out at statepoints intervals source_separate = false; - // TODO: add defaults + sourcepoint_batch = statepoint_batch; } - // TODO: Check source points are subset + // If source is not seperate and is to be written out in the statepoint file, + // make sure that the sourcepoint batch numbers are contained in the + // statepoint list + if (!source_separate) { + for (const auto& b : sourcepoint_batch) { + if (!contains(statepoint_batch, b)) { + fatal_error("Sourcepoint batches are not a subset of statepoint batches."); + } + } + } // Check if the user has specified to not reduce tallies at the end of every // batch @@ -777,6 +829,11 @@ extern "C" { const char* openmc_path_particle_restart() { return settings::path_particle_restart.c_str(); } + + void free_memory_settings_c() { + settings::statepoint_batch.clear(); + settings::sourcepoint_batch.clear(); + } } } // namespace openmc diff --git a/src/simulation.F90 b/src/simulation.F90 index b1087e21d2..3eabbfecb4 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -7,414 +7,30 @@ module simulation #endif use bank_header, only: source_bank - use cmfd_execute, only: cmfd_init_batch, cmfd_tally_init, execute_cmfd - use cmfd_header, only: cmfd_on use constants, only: ZERO - use eigenvalue, only: calculate_average_keff, calculate_generation_keff, & - synchronize_bank, keff_generation, k_sum -#ifdef _OPENMP - use eigenvalue, only: join_bank_from_threads -#endif - use error, only: fatal_error, write_message - use geometry_header, only: n_cells + use error, only: fatal_error use material_header, only: n_materials, materials use message_passing - use mgxs_interface, only: energy_bins, energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides - use output, only: header, print_columns, & - print_batch_keff, print_generation, print_runtime, & - print_results, write_tallies - use particle_header use photon_header, only: micro_photon_xs, n_elements - use random_lcg, only: set_particle_seed use settings use simulation_header - use state_point, only: openmc_statepoint_write, write_source_point, load_state_point - use string, only: to_str - 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 + use tally_filter_header, only: filter_matches, n_filters, filter_match_pointer implicit none private - public :: openmc_next_batch - public :: openmc_simulation_init - public :: openmc_simulation_finalize - - integer(C_INT), parameter :: STATUS_EXIT_NORMAL = 0 - integer(C_INT), parameter :: STATUS_EXIT_MAX_BATCH = 1 - integer(C_INT), parameter :: STATUS_EXIT_ON_TRIGGER = 2 - - interface - subroutine openmc_simulation_init_c() bind(C) - end subroutine - - subroutine initialize_source() bind(C) - end subroutine - - function sample_external_source() result(site) bind(C) - import Bank - type(Bank) :: site - end function - end interface contains -!=============================================================================== -! OPENMC_NEXT_BATCH -!=============================================================================== - - function openmc_next_batch(status) result(err) bind(C) - integer(C_INT), intent(out), optional :: status - integer(C_INT) :: err - - type(Particle) :: p - integer(8) :: i_work - - err = 0 - - ! Make sure simulation has been initialized - if (.not. simulation_initialized) then - err = E_ALLOCATE - call set_errmsg("Simulation has not been initialized yet.") - return - end if - - call initialize_batch() - - ! ======================================================================= - ! LOOP OVER GENERATIONS - GENERATION_LOOP: do current_gen = 1, gen_per_batch - - call initialize_generation() - - ! Start timer for transport - call time_transport % start() - - ! ==================================================================== - ! LOOP OVER PARTICLES -!$omp parallel do schedule(runtime) firstprivate(p) copyin(tally_derivs) - PARTICLE_LOOP: do i_work = 1, work - current_work = i_work - - ! grab source particle from bank - call initialize_history(p, current_work) - - ! transport particle - call transport(p) - - end do PARTICLE_LOOP -!$omp end parallel do - - ! Accumulate time for transport - call time_transport % stop() - - call finalize_generation() - - end do GENERATION_LOOP - - call finalize_batch() - - ! Check simulation ending criteria - if (present(status)) then - if (current_batch == n_max_batches) then - status = STATUS_EXIT_MAX_BATCH - elseif (satisfy_triggers) then - status = STATUS_EXIT_ON_TRIGGER - else - status = STATUS_EXIT_NORMAL - end if - end if - - end function openmc_next_batch - -!=============================================================================== -! INITIALIZE_HISTORY -!=============================================================================== - - subroutine initialize_history(p, index_source) - - type(Particle), intent(inout) :: p - integer(8), intent(in) :: index_source - - integer(8) :: particle_seed ! unique index for particle - integer :: i - - ! set defaults - call particle_from_source(p, source_bank(index_source), run_CE, & - energy_bin_avg) - - ! set identifier for particle - p % id = work_index(rank) + index_source - - ! set random number seed - particle_seed = (total_gen + overall_generation() - 1)*n_particles + p % id - call set_particle_seed(particle_seed) - - ! set particle trace - trace = .false. - if (current_batch == trace_batch .and. current_gen == trace_gen .and. & - p % id == trace_particle) trace = .true. - - ! Set particle track. - p % write_track = .false. - if (write_all_tracks) then - p % write_track = .true. - else if (allocated(track_identifiers)) then - do i=1, size(track_identifiers(1,:)) - if (current_batch == track_identifiers(1,i) .and. & - ¤t_gen == track_identifiers(2,i) .and. & - &p % id == track_identifiers(3,i)) then - p % write_track = .true. - exit - end if - end do - end if - - end subroutine initialize_history - -!=============================================================================== -! INITIALIZE_BATCH -!=============================================================================== - - subroutine initialize_batch() - - integer :: i - - ! Increment current batch - current_batch = current_batch + 1 - - if (run_mode == MODE_FIXEDSOURCE) then - call write_message("Simulating batch " // trim(to_str(current_batch)) & - // "...", 6) - end if - - ! Reset total starting particle weight used for normalizing tallies - total_weight = ZERO - - if ((n_inactive > 0 .and. current_batch == 1) .or. & - (restart_run .and. restart_batch < n_inactive .and. current_batch == restart_batch + 1)) then - ! Turn on inactive timer - call time_inactive % start() - elseif ((current_batch == n_inactive + 1) .or. & - (restart_run .and. restart_batch > n_inactive .and. current_batch == restart_batch + 1)) then - ! Switch from inactive batch timer to active batch timer - call time_inactive % stop() - call time_active % start() - - do i = 1, n_tallies - tallies(i) % obj % active = .true. - end do - end if - - ! check CMFD initialize batch - if (run_mode == MODE_EIGENVALUE) then - if (cmfd_run) call cmfd_init_batch() - end if - - ! Add user tallies to active tallies list - call setup_active_tallies() - - end subroutine initialize_batch - -!=============================================================================== -! INITIALIZE_GENERATION -!=============================================================================== - - subroutine initialize_generation() - - interface - subroutine ufs_count_sites() bind(C) - end subroutine - end interface - - if (run_mode == MODE_EIGENVALUE) then - ! Reset number of fission bank sites - n_bank = 0 - - ! Count source sites if using uniform fission source weighting - if (ufs) call ufs_count_sites() - - ! Store current value of tracklength k - keff_generation = global_tallies(RESULT_VALUE, K_TRACKLENGTH) - end if - - end subroutine initialize_generation - -!=============================================================================== -! FINALIZE_GENERATION -!=============================================================================== - - subroutine finalize_generation() - - interface - subroutine fill_source_bank_fixedsource() bind(C) - end subroutine - - subroutine shannon_entropy() bind(C) - end subroutine - end interface - - ! Update global tallies with the omp private accumulation variables -!$omp parallel -!$omp critical - if (run_mode == MODE_EIGENVALUE) then - global_tallies(RESULT_VALUE, K_COLLISION) = & - global_tallies(RESULT_VALUE, K_COLLISION) + global_tally_collision - global_tallies(RESULT_VALUE, K_ABSORPTION) = & - global_tallies(RESULT_VALUE, K_ABSORPTION) + global_tally_absorption - global_tallies(RESULT_VALUE, K_TRACKLENGTH) = & - global_tallies(RESULT_VALUE, K_TRACKLENGTH) + global_tally_tracklength - end if - global_tallies(RESULT_VALUE, LEAKAGE) = & - global_tallies(RESULT_VALUE, LEAKAGE) + global_tally_leakage -!$omp end critical - - ! reset private tallies - if (run_mode == MODE_EIGENVALUE) then - global_tally_collision = ZERO - global_tally_absorption = ZERO - global_tally_tracklength = ZERO - end if - global_tally_leakage = ZERO -!$omp end parallel - - if (run_mode == MODE_EIGENVALUE) then -#ifdef _OPENMP - ! Join the fission bank from each thread into one global fission bank - call join_bank_from_threads() -#endif - - ! Distribute fission bank across processors evenly - call time_bank % start() - call synchronize_bank() - call time_bank % stop() - - ! Calculate shannon entropy - if (entropy_on) call shannon_entropy() - - ! Collect results and statistics - call calculate_generation_keff() - call calculate_average_keff() - - ! Write generation output - if (master .and. verbosity >= 7) then - if (current_gen /= gen_per_batch) then - call print_generation() - end if - end if - - elseif (run_mode == MODE_FIXEDSOURCE) then - ! For fixed-source mode, we need to sample the external source - call fill_source_bank_fixedsource() - end if - - end subroutine finalize_generation - -!=============================================================================== -! FINALIZE_BATCH handles synchronization and accumulation of tallies, -! calculation of Shannon entropy, getting single-batch estimate of keff, and -! turning on tallies when appropriate -!=============================================================================== - - subroutine finalize_batch() - - integer(C_INT) :: err -#ifdef OPENMC_MPI - integer :: mpi_err ! MPI error code -#endif - character(MAX_FILE_LEN) :: filename - - ! Reduce tallies onto master process and accumulate - call time_tallies % start() - call accumulate_tallies() - call time_tallies % stop() - - ! Reset global tally results - if (current_batch <= n_inactive) then - global_tallies(:,:) = ZERO - n_realizations = 0 - end if - - if (run_mode == MODE_EIGENVALUE) then - ! Perform CMFD calculation if on - if (cmfd_on) call execute_cmfd() - ! Write batch output - if (master .and. verbosity >= 7) call print_batch_keff() - end if - - ! Check_triggers - if (master) call check_triggers() -#ifdef OPENMC_MPI - call MPI_BCAST(satisfy_triggers, 1, MPI_LOGICAL, 0, & - mpi_intracomm, mpi_err) -#endif - if (satisfy_triggers .or. & - (trigger_on .and. current_batch == n_max_batches)) then - call statepoint_batch % add(current_batch) - end if - - ! Write out state point if it's been specified for this batch - if (statepoint_batch % contains(current_batch)) then - if (sourcepoint_batch % contains(current_batch) .and. source_write & - .and. .not. source_separate) then - err = openmc_statepoint_write(write_source=.true._C_BOOL) - else - err = openmc_statepoint_write(write_source=.false._C_BOOL) - end if - end if - - ! Write out a separate source point if it's been specified for this batch - if (sourcepoint_batch % contains(current_batch) .and. source_write & - .and. source_separate) call write_source_point() - - ! Write a continously-overwritten source point if requested. - if (source_latest) then - filename = trim(path_output) // 'source' // '.h5' - call write_source_point(filename) - end if - - end subroutine finalize_batch - - !=============================================================================== ! INITIALIZE_SIMULATION !=============================================================================== - function openmc_simulation_init() result(err) bind(C) - integer(C_INT) :: err + subroutine simulation_init_f() bind(C) integer :: i - err = 0 - - ! Skip if simulation has already been initialized - if (simulation_initialized) return - - ! Call initialization on C++ side - call openmc_simulation_init_c() - - ! 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() - - ! Activate the CMFD tallies - call cmfd_tally_init() - ! Set up material nuclide index mapping do i = 1, n_materials call materials(i) % init_nuclide_index() @@ -428,196 +44,36 @@ contains ! Allocate array for matching filter bins allocate(filter_matches(n_filters)) do i = 1, n_filters - allocate(filter_matches(i) % bins) - allocate(filter_matches(i) % weights) + filter_matches(i) % ptr = filter_match_pointer(i - 1) end do !$omp end parallel - ! Reset global variables -- this is done before loading state point (as that - ! will potentially populate k_generation and entropy) - current_batch = 0 - call k_generation % clear() - call entropy_clear() - need_depletion_rx = .false. - - ! If this is a restart run, load the state point data and binary source - ! file - if (restart_run) then - call load_state_point() - call write_message("Resuming simulation...", 6) - else - call initialize_source() - end if - - ! Display header - if (master) then - if (run_mode == MODE_FIXEDSOURCE) then - call header("FIXED SOURCE TRANSPORT SIMULATION", 3) - elseif (run_mode == MODE_EIGENVALUE) then - call header("K EIGENVALUE SIMULATION", 3) - if (verbosity >= 7) call print_columns() - end if - end if - - ! Set flag indicating initialization is done - simulation_initialized = .true. - - end function openmc_simulation_init + end subroutine !=============================================================================== ! FINALIZE_SIMULATION calculates tally statistics, writes tallies, and displays ! execution time and results !=============================================================================== - function openmc_simulation_finalize() result(err) bind(C) - integer(C_INT) :: err + subroutine simulation_finalize_f() bind(C) - integer :: i ! loop index -#ifdef OPENMC_MPI - integer :: n ! size of arrays - integer :: mpi_err ! MPI error code - integer :: count_per_filter ! number of result values for one filter bin - real(8) :: tempr(3) ! temporary array for communication -#ifdef OPENMC_MPIF08 - type(MPI_Datatype) :: result_block -#else - integer :: result_block -#endif -#endif - - interface - subroutine print_overlap_check() bind(C) - end subroutine print_overlap_check - end interface - - err = 0 - - ! Skip if simulation was never run - if (.not. simulation_initialized) return - - ! Stop active batch timer and start finalization timer - call time_active % stop() - call time_finalize % start() + integer :: i ! loop index ! Free up simulation-specific memory do i = 1, n_materials deallocate(materials(i) % mat_nuclide_index) end do !$omp parallel - do i = 1, size(filter_matches) - deallocate(filter_matches(i) % bins) - deallocate(filter_matches(i) % weights) - end do deallocate(micro_xs, micro_photon_xs, filter_matches) !$omp end parallel - ! Increment total number of generations - total_gen = total_gen + current_batch*gen_per_batch - -#ifdef OPENMC_MPI - ! Broadcast tally results so that each process has access to results - if (allocated(tallies)) then - do i = 1, size(tallies) - associate (results => tallies(i) % obj % results) - ! Create a new datatype that consists of all values for a given filter - ! bin and then use that to broadcast. This is done to minimize the - ! chance of the 'count' argument of MPI_BCAST exceeding 2**31 - n = size(results, 3) - count_per_filter = size(results, 1) * size(results, 2) - call MPI_TYPE_CONTIGUOUS(count_per_filter, MPI_DOUBLE, & - result_block, mpi_err) - call MPI_TYPE_COMMIT(result_block, mpi_err) - call MPI_BCAST(results, n, result_block, 0, mpi_intracomm, mpi_err) - call MPI_TYPE_FREE(result_block, mpi_err) - end associate - 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) -#endif - - ! Write tally results to tallies.out - if (output_tallies .and. master) call write_tallies() - - ! Deactivate all tallies - if (allocated(tallies)) then - do i = 1, n_tallies - tallies(i) % obj % active = .false. - end do - end if - - ! Stop timers and show timing statistics - call time_finalize%stop() - call time_total%stop() - if (master) then - if (verbosity >= 6) call print_runtime() - if (verbosity >= 4) call print_results() - end if - if (check_overlaps) call print_overlap_check() - - ! Reset flags - need_depletion_rx = .false. - simulation_initialized = .false. - - end function openmc_simulation_finalize - -!=============================================================================== -! 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 - - 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 + end subroutine !=============================================================================== ! ALLOCATE_BANKS allocates memory for the fission and source banks !=============================================================================== - subroutine allocate_banks() + subroutine allocate_banks() bind(C) integer :: alloc_err ! allocation error code diff --git a/src/simulation.cpp b/src/simulation.cpp index 74a3c14fc1..84ebb01921 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -1,14 +1,61 @@ #include "openmc/simulation.h" #include "openmc/capi.h" +#include "openmc/container_util.h" +#include "openmc/eigenvalue.h" +#include "openmc/error.h" #include "openmc/message_passing.h" +#include "openmc/output.h" +#include "openmc/particle.h" +#include "openmc/random_lcg.h" #include "openmc/settings.h" +#include "openmc/source.h" +#include "openmc/state_point.h" +#include "openmc/timer.h" +#include "openmc/tallies/filter.h" +#include "openmc/tallies/tally.h" + +#include +#include + +namespace openmc { + +// data/functions from Fortran side +extern "C" bool cmfd_on; + +extern "C" void accumulate_tallies(); +extern "C" void allocate_banks(); +extern "C" void check_triggers(); +extern "C" void cmfd_init_batch(); +extern "C" void cmfd_tally_init(); +extern "C" void configure_tallies(); +extern "C" void execute_cmfd(); +extern "C" void init_tally_routines(); +extern "C" void join_bank_from_threads(); +extern "C" void load_state_point(); +extern "C" void print_batch_keff(); +extern "C" void print_columns(); +extern "C" void print_generation(); +extern "C" void print_results(); +extern "C" void print_runtime(); +extern "C" void setup_active_tallies(); +extern "C" void simulation_init_f(); +extern "C" void simulation_finalize_f(); +extern "C" void transport(Particle* p); +extern "C" void write_tallies(); + +} // namespace openmc + +//============================================================================== +// C API functions +//============================================================================== // OPENMC_RUN encompasses all the main logic where iterations are performed // over the batches, generations, and histories in a fixed source or k-eigenvalue // calculation. -int openmc_run() { +int openmc_run() +{ openmc_simulation_init(); int err = 0; @@ -21,22 +68,432 @@ int openmc_run() { return err; } +int openmc_simulation_init() +{ + using namespace openmc; + + // Skip if simulation has already been initialized + if (simulation::initialized) return 0; + + // Determine how much work each process should do + calculate_work(); + + // Allocate array for matching filter bins +#pragma omp parallel + { + filter_matches.resize(n_filters); + } + + // Set up tally procedure pointers + init_tally_routines(); + + // Allocate source bank, and for eigenvalue simulations also allocate the + // fission bank + allocate_banks(); + + // Allocate tally results arrays if they're not allocated yet + configure_tallies(); + + // Activate the CMFD tallies + cmfd_tally_init(); + + // Call Fortran initialization + simulation_init_f(); + + // Reset global variables -- this is done before loading state point (as that + // will potentially populate k_generation and entropy) + simulation::current_batch = 0; + simulation::k_generation.clear(); + entropy.clear(); + simulation::need_depletion_rx = false; + + // If this is a restart run, load the state point data and binary source + // file + if (settings::restart_run) { + load_state_point(); + write_message("Resuming simulation...", 6); + } else { + initialize_source(); + } + + // Display header + if (mpi::master) { + if (settings::run_mode == RUN_MODE_FIXEDSOURCE) { + header("FIXED SOURCE TRANSPORT SIMULATION", 3); + } else if (settings::run_mode == RUN_MODE_EIGENVALUE) { + header("K EIGENVALUE SIMULATION", 3); + if (settings::verbosity >= 7) print_columns(); + } + } + + // Set flag indicating initialization is done + simulation::initialized = true; + return 0; +} + +int openmc_simulation_finalize() +{ + using namespace openmc; + + // Skip if simulation was never run + if (!simulation::initialized) return 0; + + // Stop active batch timer and start finalization timer + time_active.stop(); + time_finalize.start(); + +#pragma omp parallel + { + filter_matches.clear(); + } + + // Deallocate Fortran variables, set tallies to inactive + simulation_finalize_f(); + + // Increment total number of generations + simulation::total_gen += simulation::current_batch*settings::gen_per_batch; + +#ifdef OPENMC_MPI + broadcast_results(); +#endif + + // Write tally results to tallies.out + if (settings::output_tallies && mpi::master) write_tallies(); + + // Deactivate all tallies + for (int i = 1; i <= n_tallies; ++i) { + openmc_tally_set_active(i, false); + } + + // Stop timers and show timing statistics + time_finalize.stop(); + time_total.stop(); + if (mpi::master) { + if (settings::verbosity >= 6) print_runtime(); + if (settings::verbosity >= 4) print_results(); + } + if (settings::check_overlaps) print_overlap_check(); + + // Reset flags + simulation::need_depletion_rx = false; + simulation::initialized = false; + return 0; +} + +int openmc_next_batch(int* status) +{ + using namespace openmc; + using openmc::simulation::current_gen; + + // Make sure simulation has been initialized + if (!simulation::initialized) { + set_errmsg("Simulation has not been initialized yet."); + return OPENMC_E_ALLOCATE; + } + + initialize_batch(); + + // ======================================================================= + // LOOP OVER GENERATIONS + for (current_gen = 1; current_gen <= settings::gen_per_batch; ++current_gen) { + + initialize_generation(); + + // Start timer for transport + time_transport.start(); + + // ==================================================================== + // LOOP OVER PARTICLES + +#pragma omp parallel for schedule(runtime) + for (int64_t i_work = 1; i_work <= simulation::work; ++i_work) { + simulation::current_work = i_work; + + // grab source particle from bank + Particle p; + initialize_history(&p, simulation::current_work); + + // transport particle + transport(&p); + } + + // Accumulate time for transport + time_transport.stop(); + + finalize_generation(); + } + + finalize_batch(); + + // Check simulation ending criteria + if (status) { + if (simulation::current_batch == settings::n_max_batches) { + *status = STATUS_EXIT_MAX_BATCH; + } else if (simulation::satisfy_triggers) { + *status = STATUS_EXIT_ON_TRIGGER; + } else { + *status = STATUS_EXIT_NORMAL; + } + } + return 0; +} + namespace openmc { //============================================================================== // Global variables //============================================================================== +namespace simulation { + +int current_batch; +int current_gen; +int64_t current_work; +bool initialized {false}; +double keff {1.0}; +double keff_std; +double k_col_abs {0.0}; +double k_col_tra {0.0}; +double k_abs_tra {0.0}; +double log_spacing; +int n_lost_particles {0}; +bool need_depletion_rx {false}; +int restart_batch; +bool satisfy_triggers {false}; +int total_gen {0}; +int64_t work; + +std::vector k_generation; std::vector work_index; +// Threadprivate variables +bool trace; //!< flag to show debug information +#ifdef _OPENMP +int n_threads {-1}; //!< number of OpenMP threads +int thread_id; //!< ID of a given thread +#endif + +} // namespace simulation + //============================================================================== -// Functions +// Non-member functions //============================================================================== -void openmc_simulation_init_c() +void initialize_batch() { - // Determine how much work each process should do - calculate_work(); + // Increment current batch + ++simulation::current_batch; + + if (settings::run_mode == RUN_MODE_FIXEDSOURCE) { + int b = simulation::current_batch; + write_message("Simulating batch " + std::to_string(b), 6); + } + + // Reset total starting particle weight used for normalizing tallies + total_weight = 0.0; + + // Determine if this batch is the first inactive or active batch. + bool first_inactive = false; + bool first_active = false; + if (!settings::restart_run) { + first_inactive = settings::n_inactive > 0 && simulation::current_batch == 1; + first_active = simulation::current_batch == settings::n_inactive + 1; + } else if (simulation::current_batch == simulation::restart_batch + 1){ + first_inactive = simulation::restart_batch < settings::n_inactive; + first_active = !first_inactive; + } + + // Manage active/inactive timers and activate tallies if necessary. + if (first_inactive) { + time_inactive.start(); + } else if (first_active) { + time_inactive.stop(); + time_active.start(); + for (int i = 1; i <= n_tallies; ++i) { + // TODO: change one-based index + openmc_tally_set_active(i, true); + } + } + + // check CMFD initialize batch + if (settings::run_mode == RUN_MODE_EIGENVALUE) { + if (settings::cmfd_run) cmfd_init_batch(); + } + + // Add user tallies to active tallies list + setup_active_tallies(); +} + +void finalize_batch() +{ + // Reduce tallies onto master process and accumulate + time_tallies.start(); + accumulate_tallies(); + time_tallies.stop(); + + // Reset global tally results + if (simulation::current_batch <= settings::n_inactive) { + auto gt = global_tallies(); + std::fill(gt.begin(), gt.end(), 0.0); + n_realizations = 0; + } + + if (settings::run_mode == RUN_MODE_EIGENVALUE) { + // Perform CMFD calculation if on + if (cmfd_on) execute_cmfd(); + // Write batch output + if (mpi::master && settings::verbosity >= 7) print_batch_keff(); + } + + // Check_triggers + if (mpi::master) check_triggers(); +#ifdef OPENMC_MPI + MPI_Bcast(&simulation::satisfy_triggers, 1, MPI_C_BOOL, 0, mpi::intracomm); +#endif + if (simulation::satisfy_triggers || (settings::trigger_on && + simulation::current_batch == settings::n_max_batches)) { + settings::statepoint_batch.insert(simulation::current_batch); + } + + // Write out state point if it's been specified for this batch + if (contains(settings::statepoint_batch, simulation::current_batch)) { + if (contains(settings::sourcepoint_batch, simulation::current_batch) + && settings::source_write && !settings::source_separate) { + bool b = true; + openmc_statepoint_write(nullptr, &b); + } else { + bool b = false; + openmc_statepoint_write(nullptr, &b); + } + } + + // Write out a separate source point if it's been specified for this batch + if (contains(settings::sourcepoint_batch, simulation::current_batch) + && settings::source_write && settings::source_separate) { + write_source_point(nullptr); + } + + // Write a continously-overwritten source point if requested. + if (settings::source_latest) { + auto filename = settings::path_output + "source.h5"; + write_source_point(filename.c_str()); + } +} + +void initialize_generation() +{ + if (settings::run_mode == RUN_MODE_EIGENVALUE) { + // Reset number of fission bank sites + n_bank = 0; + + // Count source sites if using uniform fission source weighting + if (settings::ufs_on) ufs_count_sites(); + + // Store current value of tracklength k + keff_generation = global_tallies()(K_TRACKLENGTH, RESULT_VALUE); + } +} + +void finalize_generation() +{ + auto gt = global_tallies(); + + // Update global tallies with the omp private accumulation variables +#pragma omp parallel + { +#pragma omp critical(increment_global_tallies) + { + if (settings::run_mode == RUN_MODE_EIGENVALUE) { + gt(K_COLLISION, RESULT_VALUE) += global_tally_collision; + gt(K_ABSORPTION, RESULT_VALUE) += global_tally_absorption; + gt(K_TRACKLENGTH, RESULT_VALUE) += global_tally_tracklength; + } + gt(LEAKAGE, RESULT_VALUE) += global_tally_leakage; + } + + // reset threadprivate tallies + if (settings::run_mode == RUN_MODE_EIGENVALUE) { + global_tally_collision = 0.0; + global_tally_absorption = 0.0; + global_tally_tracklength = 0.0; + } + global_tally_leakage = 0.0; + } + + + if (settings::run_mode == RUN_MODE_EIGENVALUE) { +#ifdef _OPENMP + // Join the fission bank from each thread into one global fission bank + join_bank_from_threads(); +#endif + + // Distribute fission bank across processors evenly + synchronize_bank(); + + // Calculate shannon entropy + if (settings::entropy_on) shannon_entropy(); + + // Collect results and statistics + calculate_generation_keff(); + calculate_average_keff(); + + // Write generation output + if (mpi::master && settings::verbosity >= 7) { + if (simulation::current_gen != settings::gen_per_batch) { + print_generation(); + } + } + + } else if (settings::run_mode == RUN_MODE_FIXEDSOURCE) { + // For fixed-source mode, we need to sample the external source + fill_source_bank_fixedsource(); + } +} + +void initialize_history(Particle* p, int64_t index_source) +{ + // Get pointer to source bank + Bank* source_bank; + int64_t n; + openmc_source_bank(&source_bank, &n); + + // set defaults + p->from_source(&source_bank[index_source - 1]); + + // set identifier for particle + p->id = simulation::work_index[mpi::rank] + index_source; + + // set random number seed + int64_t particle_seed = (simulation::total_gen + overall_generation() - 1) + * settings::n_particles + p->id; + set_particle_seed(particle_seed); + + // set particle trace + simulation::trace = false; + if (simulation::current_batch == settings::trace_batch && + simulation::current_gen == settings::trace_gen && + p->id == settings::trace_particle) simulation::trace = true; + + // Set particle track. + p->write_track = false; + if (settings::write_all_tracks) { + p->write_track = true; + } else if (settings::track_identifiers.size() > 0) { + for (const auto& t : settings::track_identifiers) { + if (simulation::current_batch == t[0] && + simulation::current_gen == t[1] && + p->id == t[2]) { + p->write_track = true; + break; + } + } + } +} + +int overall_generation() +{ + using namespace simulation; + return settings::gen_per_batch*(current_batch - 1) + current_gen; } void calculate_work() @@ -48,19 +505,63 @@ void calculate_work() int64_t remainder = settings::n_particles % mpi::n_procs; int64_t i_bank = 0; - work_index.reserve(mpi::n_procs); - work_index.push_back(0); + simulation::work_index.resize(mpi::n_procs + 1); + simulation::work_index[0] = 0; for (int i = 0; i < mpi::n_procs; ++i) { // Number of particles for rank i int64_t work_i = i < remainder ? min_work + 1 : min_work; // Set number of particles - if (mpi::rank == i) openmc_work = work_i; + if (mpi::rank == i) simulation::work = work_i; // Set index into source bank for rank i i_bank += work_i; - work_index.push_back(i_bank); + simulation::work_index[i + 1] = i_bank; } } +#ifdef OPENMC_MPI +void broadcast_results() { + // Broadcast tally results so that each process has access to results + for (int i = 1; i <= n_tallies; ++i) { + // Create a new datatype that consists of all values for a given filter + // bin and then use that to broadcast. This is done to minimize the + // chance of the 'count' argument of MPI_BCAST exceeding 2**31 + auto results = tally_results(i); + + auto shape = results.shape(); + int count_per_filter = shape[1] * shape[2]; + MPI_Datatype result_block; + MPI_Type_contiguous(count_per_filter, MPI_DOUBLE, &result_block); + MPI_Type_commit(&result_block); + MPI_Bcast(results.data(), shape[0], result_block, 0, mpi::intracomm); + MPI_Type_free(&result_block); + } + + // Also broadcast global tally results + auto gt = global_tallies(); + MPI_Bcast(gt.data(), gt.size(), MPI_DOUBLE, 0, mpi::intracomm); + + // These guys are needed so that non-master processes can calculate the + // combined estimate of k-effective + double temp[] {simulation::k_col_abs, simulation::k_col_tra, + simulation::k_abs_tra}; + MPI_Bcast(temp, 3, MPI_DOUBLE, 0, mpi::intracomm); + simulation::k_col_abs = temp[0]; + simulation::k_col_tra = temp[1]; + simulation::k_abs_tra = temp[2]; +} + +#endif + +//============================================================================== +// Fortran compatibility +//============================================================================== + +extern "C" double k_generation(int i) { return simulation::k_generation.at(i - 1); } +extern "C" int k_generation_size() { return simulation::k_generation.size(); } +extern "C" void k_generation_clear() { simulation::k_generation.clear(); } +extern "C" void k_generation_reserve(int i) { simulation::k_generation.reserve(i); } +extern "C" int64_t work_index(int rank) { return simulation::work_index[rank]; } + } // namespace openmc diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index ad5513c618..1df4b0a4e3 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -13,84 +13,91 @@ module simulation_header ! GEOMETRY-RELATED VARIABLES ! Number of lost particles - integer(C_INT), bind(C, name='openmc_n_lost_particles') :: n_lost_particles = 0 + integer(C_INT), bind(C) :: n_lost_particles - real(8) :: log_spacing ! spacing on logarithmic grid + real(C_DOUBLE), bind(C) :: log_spacing ! spacing on logarithmic grid ! ============================================================================ ! SIMULATION VARIABLES - integer(C_INT), bind(C, name='openmc_current_batch') :: current_batch ! current batch - integer(C_INT), bind(C, name='openmc_current_gen') :: current_gen ! current generation within a batch - integer(C_INT), bind(C, name='openmc_total_gen') :: total_gen = 0 ! total number of generations simulated - logical(C_BOOL), bind(C, name='openmc_simulation_initialized') :: & - simulation_initialized = .false. - logical :: need_depletion_rx ! need to calculate depletion reaction rx? + integer(C_INT), bind(C) :: current_batch ! current batch + integer(C_INT), bind(C) :: current_gen ! current generation within a batch + integer(C_INT), bind(C) :: total_gen ! total number of generations simulated + logical(C_BOOL), bind(C) :: need_depletion_rx ! need to calculate depletion reaction rx? ! ============================================================================ ! TALLY PRECISION TRIGGER VARIABLES - logical :: satisfy_triggers = .false. ! whether triggers are satisfied + logical(C_BOOL), bind(C) :: satisfy_triggers ! whether triggers are satisfied - integer(C_INT64_T), bind(C, name='openmc_work') :: work ! number of particles per processor - integer(C_INT64_T), allocatable :: work_index(:) ! starting index in source bank for each process - integer(C_INT64_T), bind(C, name='openmc_current_work') :: current_work ! index in source bank of current history simulated + integer(C_INT64_T), bind(C) :: work ! number of particles per processor ! ============================================================================ ! K-EIGENVALUE SIMULATION VARIABLES ! Temporary k-effective values - type(VectorReal) :: k_generation ! single-generation estimates of k - real(C_DOUBLE), bind(C, name='openmc_keff') :: keff = ONE ! average k over active batches - real(C_DOUBLE), bind(C, name='openmc_keff_std') :: 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(C_DOUBLE), bind(C) :: keff ! average k over active batches + real(C_DOUBLE), bind(C) :: keff_std ! standard deviation of average k + real(C_DOUBLE), bind(C) :: k_col_abs ! sum over batches of k_collision * k_absorption + real(C_DOUBLE), bind(C) :: k_col_tra ! sum over batches of k_collision * k_tracklength + real(C_DOUBLE), bind(C) :: k_abs_tra ! sum over batches of k_absorption * k_tracklength ! ============================================================================ ! PARALLEL PROCESSING VARIABLES #ifdef _OPENMP - integer(C_INT), bind(C, name='openmc_n_threads') :: n_threads = NONE ! number of OpenMP threads - integer :: thread_id ! ID of a given thread + integer(C_INT), bind(C) :: n_threads ! number of OpenMP threads + integer(C_INT), bind(C) :: thread_id ! ID of a given thread #endif ! ============================================================================ ! MISCELLANEOUS VARIABLES - integer :: restart_batch + integer(C_INT), bind(C) :: restart_batch - logical(C_BOOL), bind(C, name='openmc_trace') :: trace + logical(C_BOOL), bind(C) :: trace -!$omp threadprivate(trace, thread_id, current_work) +!$omp threadprivate(trace, thread_id) interface subroutine entropy_clear() bind(C) end subroutine + + pure function overall_generation() result(gen) bind(C) + import C_INT + integer(C_INT) :: gen + end function overall_generation + + function k_generation(i) result(k) bind(C) + import C_DOUBLE, C_INT + integer(C_INT), value :: i + real(C_DOUBLE) :: k + end function + + function k_generation_size() result(sz) bind(C) + import C_INT + integer(C_INT) :: sz + end function + + subroutine k_generation_clear() bind(C) + end subroutine + + function work_index(rank) result(i) bind(C) + import C_INT, C_INT64_T + integer(C_INT), value :: rank + integer(C_INT64_T) :: i + end function end interface - contains -!=============================================================================== -! OVERALL_GENERATION determines the overall generation number -!=============================================================================== - - pure function overall_generation() result(gen) bind(C) - integer(C_INT) :: 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(work_index)) deallocate(work_index) - - call k_generation % clear() - call k_generation % shrink_to_fit() + call k_generation_clear() call entropy_clear() end subroutine free_memory_simulation diff --git a/src/source.cpp b/src/source.cpp index 666bbb7838..315ff2cdd8 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -264,17 +264,17 @@ void initialize_source() } // Read in the source bank - read_source_bank(file_id, work_index.data(), source_bank); + read_source_bank(file_id, source_bank); // Close file file_close(file_id); } else { // Generation source sites from specified distribution in user input - for (int64_t i = 0; i < openmc_work; ++i) { + for (int64_t i = 0; i < simulation::work; ++i) { // initialize random number seed - int64_t id = openmc_total_gen*settings::n_particles + - work_index[openmc::mpi::rank] + i + 1; + int64_t id = simulation::total_gen*settings::n_particles + + simulation::work_index[mpi::rank] + i + 1; set_particle_seed(id); // sample external source distribution @@ -287,13 +287,11 @@ void initialize_source() write_message("Writing out initial source...", 5); std::string filename = settings::path_output + "initial_source.h5"; hid_t file_id = file_open(filename, 'w', true); - write_source_bank(file_id, work_index.data(), source_bank); + write_source_bank(file_id, source_bank); file_close(file_id); } } -extern "C" double* rev_energy_bins_ptr(); - Bank sample_external_source() { // Set the random number generator to the source stream. @@ -320,11 +318,8 @@ Bank sample_external_source() // If running in MG, convert site % E to group if (!settings::run_CE) { - // Get pointer to rev_energy_bins array on Fortran side - double* rev_energy_bins = rev_energy_bins_ptr(); - - int n = num_energy_groups + 1; - site.E = lower_bound_index(rev_energy_bins, rev_energy_bins + n, site.E); + site.E = lower_bound_index(rev_energy_bins.begin(), rev_energy_bins.end(), + site.E); site.E = num_energy_groups - site.E; } @@ -352,11 +347,7 @@ extern "C" double total_source_strength() return strength; } -// Needed in fill_source_bank_fixedsource -extern "C" int overall_generation(); - -//! Fill source bank at end of generation for fixed source simulations -extern "C" void fill_source_bank_fixedsource() +void fill_source_bank_fixedsource() { if (settings::path_source.empty()) { // Get pointer to source bank @@ -364,10 +355,10 @@ extern "C" void fill_source_bank_fixedsource() int64_t n; openmc_source_bank(&source_bank, &n); - for (int64_t i = 0; i < openmc_work; ++i) { + for (int64_t i = 0; i < simulation::work; ++i) { // initialize random number seed - int64_t id = (openmc_total_gen + overall_generation()) * - settings::n_particles + work_index[openmc::mpi::rank] + i + 1; + int64_t id = (simulation::total_gen + overall_generation()) * + settings::n_particles + simulation::work_index[mpi::rank] + i + 1; set_particle_seed(id); // sample external source distribution diff --git a/src/state_point.F90 b/src/state_point.F90 index 91d0f10908..1286bb63d4 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -16,7 +16,6 @@ module state_point use bank_header, only: Bank use cmfd_header use constants - use eigenvalue, only: openmc_get_keff, k_sum use endf, only: reaction_name use error, only: fatal_error, warning, write_message use hdf5_interface @@ -36,17 +35,15 @@ module state_point implicit none interface - subroutine write_source_bank(group_id, work_index, bank_) bind(C) + subroutine write_source_bank(group_id, bank_) bind(C) import HID_T, C_INT64_T, Bank integer(HID_T), value :: group_id - integer(C_INT64_T), intent(in) :: work_index(*) type(Bank), intent(in) :: bank_(*) end subroutine write_source_bank - subroutine read_source_bank(group_id, work_index, bank_) bind(C) + subroutine read_source_bank(group_id, bank_) bind(C) import HID_T, C_INT64_T, Bank integer(HID_T), value :: group_id - integer(C_INT64_T), intent(in) :: work_index(*) type(Bank), intent(out) :: bank_(*) end subroutine read_source_bank end interface @@ -63,15 +60,13 @@ contains integer(C_INT) :: err logical :: write_source_ - integer :: i, j, k + integer :: i, j integer :: i_xs integer, allocatable :: id_array(:) integer(HID_T) :: file_id integer(HID_T) :: cmfd_group, tallies_group, tally_group, & filters_group, filter_group, derivs_group, & deriv_group, runtime_group - integer(C_INT) :: ignored_err - real(C_DOUBLE) :: k_combined(2) character(MAX_WORD_LEN), allocatable :: str_array(:) character(C_CHAR), pointer :: string(:) character(len=:, kind=C_CHAR), allocatable :: filename_ @@ -83,10 +78,14 @@ contains import HID_T integer(HID_T), value :: group end subroutine - subroutine entropy_to_hdf5(group) bind(C) + subroutine write_eigenvalue_hdf5(group) bind(C) import HID_T integer(HID_T), value :: group end subroutine + subroutine write_tally_results_nr(file_id) bind(C) + import HID_T + integer(HID_T), value :: file_id + end subroutine end interface err = 0 @@ -169,16 +168,7 @@ contains ! Write out information for eigenvalue run if (run_mode == MODE_EIGENVALUE) then - call write_dataset(file_id, "n_inactive", n_inactive) - call write_dataset(file_id, "generations_per_batch", gen_per_batch) - k = k_generation % size() - call write_dataset(file_id, "k_generation", k_generation % data(1:k)) - call entropy_to_hdf5(file_id) - 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) - ignored_err = openmc_get_keff(k_combined) - call write_dataset(file_id, "k_combined", k_combined) + call write_eigenvalue_hdf5(file_id) ! Write out CMFD info if (cmfd_on) then @@ -400,29 +390,29 @@ contains ! Write out the runtime metrics. runtime_group = create_group(file_id, "runtime") call write_dataset(runtime_group, "total initialization", & - time_initialize % get_value()) + time_initialize_elapsed()) call write_dataset(runtime_group, "reading cross sections", & time_read_xs % get_value()) call write_dataset(runtime_group, "simulation", & - time_inactive % get_value() + time_active % get_value()) + time_inactive_elapsed() + time_active_elapsed()) call write_dataset(runtime_group, "transport", & - time_transport % get_value()) + time_transport_elapsed()) if (run_mode == MODE_EIGENVALUE) then call write_dataset(runtime_group, "inactive batches", & - time_inactive % get_value()) + time_inactive_elapsed()) end if call write_dataset(runtime_group, "active batches", & - time_active % get_value()) + time_active_elapsed()) if (run_mode == MODE_EIGENVALUE) then call write_dataset(runtime_group, "synchronizing fission bank", & - time_bank % get_value()) + time_bank_elapsed()) call write_dataset(runtime_group, "sampling source sites", & - time_bank_sample % get_value()) + time_bank_sample_elapsed()) call write_dataset(runtime_group, "SEND-RECV source sites", & - time_bank_sendrecv % get_value()) + time_bank_sendrecv_elapsed()) end if call write_dataset(runtime_group, "accumulating tallies", & - time_tallies % get_value()) + time_tallies_elapsed()) if (cmfd_run) then call write_dataset(runtime_group, "CMFD", time_cmfd % get_value()) call write_dataset(runtime_group, "CMFD building matrices", & @@ -430,7 +420,7 @@ contains call write_dataset(runtime_group, "CMFD solving matrices", & time_cmfdsolve % get_value()) end if - call write_dataset(runtime_group, "total", time_total % get_value()) + call write_dataset(runtime_group, "total", time_total_elapsed()) call close_group(runtime_group) call file_close(file_id) @@ -447,180 +437,18 @@ contains if (master .or. parallel) then file_id = file_open(filename_, 'a', parallel=.true.) end if - call write_source_bank(file_id, work_index, source_bank) + call write_source_bank(file_id, source_bank) if (master .or. parallel) call file_close(file_id) end if end function openmc_statepoint_write -!=============================================================================== -! WRITE_SOURCE_POINT -!=============================================================================== - - subroutine write_source_point(filename) - character(MAX_FILE_LEN), intent(in), optional :: filename - - logical :: parallel - integer(HID_T) :: file_id - character(MAX_FILE_LEN) :: filename_ - - ! When using parallel HDF5, the file is written to collectively by all - ! processes. With MPI-only, the file is opened and written by the master - ! (note that the call to write_source_bank is by all processes since slave - ! processes need to send source bank data to the master. -#ifdef PHDF5 - parallel = .true. -#else - parallel = .false. -#endif - - if (present(filename)) then - filename_ = filename - else - filename_ = trim(path_output) // 'source.' // & - & zero_padded(current_batch, count_digits(n_max_batches)) - filename_ = trim(filename_) // '.h5' - end if - - if (master .or. parallel) then - file_id = file_open(filename_, 'w', parallel=.true.) - call write_attribute(file_id, "filetype", 'source') - end if - call write_source_bank(file_id, work_index, source_bank) - if (master .or. parallel) call file_close(file_id) - - end subroutine write_source_point - -!=============================================================================== -! WRITE_TALLY_RESULTS_NR -!=============================================================================== - - subroutine write_tally_results_nr(file_id) - integer(HID_T), intent(in) :: file_id - - integer :: i ! loop index - integer :: n ! number of filter bins - integer :: m ! number of score bins - integer :: n_bins ! total number of bins - integer(HID_T) :: tallies_group, tally_group - real(8), allocatable :: tally_temp(:,:,:) ! contiguous array of results - real(8), target :: global_temp(3,N_GLOBAL_TALLIES) -#ifdef OPENMC_MPI - integer :: mpi_err ! MPI error code - real(8) :: dummy ! temporary receive buffer for non-root reduces -#endif - type(TallyObject) :: dummy_tally - - ! ========================================================================== - ! COLLECT AND WRITE GLOBAL TALLIES - - if (master) then - ! Write number of realizations - call write_dataset(file_id, "n_realizations", n_realizations) - - ! Write number of global tallies - call write_dataset(file_id, "n_global_tallies", N_GLOBAL_TALLIES) - - tallies_group = open_group(file_id, "tallies") - end if - - -#ifdef OPENMC_MPI - ! 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(:,:) - end if - - ! Write out global tallies sum and sum_sq - call write_dataset(file_id, "global_tallies", global_temp) - end if - - if (active_tallies % size() > 0) then - ! Indicate that tallies are on - if (master) then - call write_attribute(file_id, "tallies_present", 1) - end if - - ! Write all tally results - TALLY_RESULTS: do i = 1, n_tallies - associate (t => tallies(i) % obj) - ! Determine size of tally results array - m = size(t % results, 2) - n = size(t % results, 3) - n_bins = m*n*2 - - ! Allocate array for storing sums and sums of squares, but - ! contiguously in memory for each - allocate(tally_temp(2,m,n)) - tally_temp(1,:,:) = t % results(RESULT_SUM,:,:) - tally_temp(2,:,:) = t % results(RESULT_SUM_SQ,:,:) - - if (master) then - tally_group = open_group(tallies_group, "tally " // & - trim(to_str(t % id))) - - ! The MPI_IN_PLACE specifier allows the master to copy values into - ! a receive buffer without having a temporary variable -#ifdef OPENMC_MPI - call MPI_REDUCE(MPI_IN_PLACE, tally_temp, n_bins, MPI_REAL8, & - MPI_SUM, 0, mpi_intracomm, mpi_err) -#endif - - ! At the end of the simulation, store the results back in the - ! regular TallyResults array - if (current_batch == n_max_batches .or. satisfy_triggers) then - t % results(RESULT_SUM,:,:) = tally_temp(1,:,:) - t % results(RESULT_SUM_SQ,:,:) = tally_temp(2,:,:) - end if - - ! Put in temporary tally result - allocate(dummy_tally % results(3,m,n)) - dummy_tally % results(RESULT_SUM,:,:) = tally_temp(1,:,:) - dummy_tally % results(RESULT_SUM_SQ,:,:) = tally_temp(2,:,:) - - ! Write reduced tally results to file - call dummy_tally % write_results_hdf5(tally_group) - - ! Deallocate temporary tally result - deallocate(dummy_tally % results) - else - ! Receive buffer not significant at other processors -#ifdef OPENMC_MPI - call MPI_REDUCE(tally_temp, dummy, n_bins, MPI_REAL8, MPI_SUM, & - 0, mpi_intracomm, mpi_err) -#endif - end if - - ! Deallocate temporary copy of tally results - deallocate(tally_temp) - - if (master) call close_group(tally_group) - end associate - end do TALLY_RESULTS - - if (master) call close_group(tallies_group) - else - ! Indicate that tallies are off - if (master) call write_dataset(file_id, "tallies_present", 0) - end if - - - end subroutine write_tally_results_nr - !=============================================================================== ! LOAD_STATE_POINT !=============================================================================== - subroutine load_state_point() + subroutine load_state_point() bind(C) integer :: i - integer :: n integer :: int_array(3) integer, allocatable :: array(:) integer(C_INT64_T) :: seed @@ -632,7 +460,12 @@ contains character(MAX_WORD_LEN) :: word interface - subroutine entropy_from_hdf5() bind(C) + subroutine read_eigenvalue_hdf5(group) bind(C) + import HID_T + integer(HID_T), value :: group + end subroutine + + subroutine restart_set_keff() bind(C) end subroutine end interface @@ -711,16 +544,7 @@ contains ! Read information specific to eigenvalue run if (run_mode == MODE_EIGENVALUE) then call read_dataset(int_array(1), file_id, "n_inactive") - call read_dataset(gen_per_batch, file_id, "generations_per_batch") - - n = restart_batch*gen_per_batch - call k_generation % resize(n) - call read_dataset(k_generation % data(1:n), file_id, "k_generation") - - call entropy_from_hdf5() - 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_eigenvalue_hdf5(file_id) ! Take maximum of statepoint n_inactive and input n_inactive n_inactive = max(n_inactive, int_array(1)) @@ -751,16 +575,7 @@ contains ! Set k_sum, keff, and current_batch based on whether restart file is part ! of active cycle or inactive cycle - if (restart_batch > n_inactive) then - do i = n_inactive + 1, restart_batch - k_sum(1) = k_sum(1) + k_generation % data(i) - k_sum(2) = k_sum(2) + k_generation % data(i)**2 - end do - n = gen_per_batch*n_realizations - keff = k_sum(1) / n - else - keff = k_generation % data(n) - end if + call restart_set_keff() current_batch = restart_batch ! Check to make sure source bank is present @@ -820,7 +635,7 @@ contains end if ! Write out source - call read_source_bank(file_id, work_index, source_bank) + call read_source_bank(file_id, source_bank) end if diff --git a/src/state_point.cpp b/src/state_point.cpp index dba0fe1b8d..620b5f71a7 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -1,20 +1,25 @@ #include "openmc/state_point.h" #include +#include // for setfill, setw +#include #include -#ifdef OPENMC_MPI -#include "mpi.h" -#endif +#include "xtensor/xbuilder.hpp" // for empty_like +#include "xtensor/xview.hpp" #include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/eigenvalue.h" #include "openmc/error.h" +#include "openmc/hdf5_interface.h" #include "openmc/message_passing.h" #include "openmc/settings.h" +#include "openmc/simulation.h" +#include "openmc/tallies/tally.h" namespace openmc { - hid_t h5banktype() { // Create type for array of 3 reals hsize_t dims[] {3}; @@ -32,9 +37,49 @@ hid_t h5banktype() { return banktype; } +void +write_source_point(const char* filename) +{ + // When using parallel HDF5, the file is written to collectively by all + // processes. With MPI-only, the file is opened and written by the master + // (note that the call to write_source_bank is by all processes since slave + // processes need to send source bank data to the master. +#ifdef PHDF5 + bool parallel = true; +#else + bool parallel = false; +#endif + + std::string filename_; + if (filename) { + filename_ = filename; + } else { + // Determine width for zero padding + int w = std::to_string(settings::n_max_batches).size(); + + std::stringstream s; + s << settings::path_output << "source." << std::setfill('0') + << std::setw(w) << simulation::current_batch << ".h5"; + filename_ = s.str(); + } + + hid_t file_id; + if (mpi::master || parallel) { + file_id = file_open(filename_, 'w', true); + write_attribute(file_id, "filetype", "source"); + } + + // Get pointer to source bank and write to file + Bank* source_bank; + int64_t n; + openmc_source_bank(&source_bank, &n); + write_source_bank(file_id, source_bank); + + if (mpi::master || parallel) file_close(file_id); +} void -write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) +write_source_bank(hid_t group_id, Bank* source_bank) { hid_t banktype = h5banktype(); @@ -46,11 +91,11 @@ write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); // Create another data space but for each proc individually - hsize_t count[] {static_cast(openmc_work)}; + hsize_t count[] {static_cast(simulation::work)}; hid_t memspace = H5Screate_simple(1, count, nullptr); // Select hyperslab for this dataspace - hsize_t start[] {static_cast(work_index[openmc::mpi::rank])}; + hsize_t start[] {static_cast(simulation::work_index[mpi::rank])}; H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); // Set up the property list for parallel writing @@ -77,24 +122,25 @@ write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) // Save source bank sites since the souce_bank array is overwritten below #ifdef OPENMC_MPI - std::vector temp_source {source_bank, source_bank + openmc_work}; + std::vector temp_source {source_bank, source_bank + simulation::work}; #endif - for (int i = 0; i < openmc::mpi::n_procs; ++i) { + for (int i = 0; i < mpi::n_procs; ++i) { // Create memory space - hsize_t count[] {static_cast(work_index[i+1] - work_index[i])}; + hsize_t count[] {static_cast(simulation::work_index[i+1] - + simulation::work_index[i])}; hid_t memspace = H5Screate_simple(1, count, nullptr); #ifdef OPENMC_MPI // Receive source sites from other processes if (i > 0) - MPI_Recv(source_bank, count[0], openmc::mpi::bank, i, i, - openmc::mpi::intracomm, MPI_STATUS_IGNORE); + MPI_Recv(source_bank, count[0], mpi::bank, i, i, + mpi::intracomm, MPI_STATUS_IGNORE); #endif // Select hyperslab for this dataspace dspace = H5Dget_space(dset); - hsize_t start[] {static_cast(work_index[i])}; + hsize_t start[] {static_cast(simulation::work_index[i])}; H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); // Write data to hyperslab @@ -113,8 +159,8 @@ write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) #endif } else { #ifdef OPENMC_MPI - MPI_Send(source_bank, openmc_work, openmc::mpi::bank, 0, openmc::mpi::rank, - openmc::mpi::intracomm); + MPI_Send(source_bank, simulation::work, mpi::bank, 0, mpi::rank, + mpi::intracomm); #endif } #endif @@ -123,7 +169,7 @@ write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) } -void read_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) +void read_source_bank(hid_t group_id, Bank* source_bank) { hid_t banktype = h5banktype(); @@ -131,20 +177,20 @@ void read_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) hid_t dset = H5Dopen(group_id, "source_bank", H5P_DEFAULT); // Create another data space but for each proc individually - hsize_t dims[] {static_cast(openmc_work)}; + hsize_t dims[] {static_cast(simulation::work)}; hid_t memspace = H5Screate_simple(1, dims, nullptr); // Make sure source bank is big enough hid_t dspace = H5Dget_space(dset); hsize_t dims_all[1]; H5Sget_simple_extent_dims(dspace, dims_all, nullptr); - if (work_index[openmc::mpi::n_procs] > dims_all[0]) { + if (simulation::work_index[mpi::n_procs] > dims_all[0]) { fatal_error("Number of source sites in source file is less " "than number of source particles per generation."); } // Select hyperslab for each process - hsize_t start[] {static_cast(work_index[openmc::mpi::rank])}; + hsize_t start[] {static_cast(simulation::work_index[mpi::rank])}; H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, dims, nullptr); #ifdef PHDF5 @@ -164,4 +210,124 @@ void read_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) H5Tclose(banktype); } +void write_tally_results_nr(hid_t file_id) +{ + // ========================================================================== + // COLLECT AND WRITE GLOBAL TALLIES + + hid_t tallies_group; + if (mpi::master) { + // Write number of realizations + write_dataset(file_id, "n_realizations", n_realizations); + + // Write number of global tallies + write_dataset(file_id, "n_global_tallies", N_GLOBAL_TALLIES); + + tallies_group = open_group(file_id, "tallies"); + } + + // Get pointer to global tallies + auto gt = global_tallies(); + +#ifdef OPENMC_MPI + // Reduce global tallies + xt::xtensor gt_reduced = xt::empty_like(gt); + MPI_Reduce(gt.data(), gt_reduced.data(), gt.size(), MPI_DOUBLE, + MPI_SUM, 0, mpi::intracomm); + + // Transfer values to value on master + if (mpi::master) { + if (simulation::current_batch == settings::n_max_batches || + simulation::satisfy_triggers) { + std::copy(gt_reduced.begin(), gt_reduced.end(), gt.begin()); + } + } +#endif + + // Write out global tallies sum and sum_sq + if (mpi::master) { + write_dataset(file_id, "global_tallies", gt); + } + + for (int i = 1; i <= n_tallies; ++i) { + // Skip any tallies that are not active + bool active; + openmc_tally_get_active(i, &active); + if (!active) continue; + + if (mpi::master && !object_exists(file_id, "tallies_present")) { + write_attribute(file_id, "tallies_present", 1); + } + + // Get view of accumulated tally values + auto results = tally_results(i); + auto values_view = xt::view(results, xt::all(), xt::all(), + xt::range(RESULT_SUM, RESULT_SUM_SQ + 1)); + + // Make copy of tally values in contiguous array + xt::xtensor values = values_view; + + if (mpi::master) { + // Open group for tally + int id; + openmc_tally_get_id(i, &id); + std::string groupname {"tally " + std::to_string(id)}; + hid_t tally_group = open_group(tallies_group, groupname.c_str()); + + // The MPI_IN_PLACE specifier allows the master to copy values into + // a receive buffer without having a temporary variable +#ifdef OPENMC_MPI + MPI_Reduce(MPI_IN_PLACE, values.data(), values.size(), MPI_DOUBLE, + MPI_SUM, 0, mpi::intracomm); +#endif + + // At the end of the simulation, store the results back in the + // regular TallyResults array + if (simulation::current_batch == settings::n_max_batches || + simulation::satisfy_triggers) { + values_view = values; + } + + // Put in temporary tally result + xt::xtensor results_copy = xt::zeros_like(results); + auto copy_view = xt::view(results_copy, xt::all(), xt::all(), + xt::range(RESULT_SUM, RESULT_SUM_SQ + 1)); + copy_view = values; + + // Write reduced tally results to file + auto shape = results_copy.shape(); + write_tally_results(tally_group, shape[0], shape[1], results_copy.data()); + + close_group(tally_group); + } else { + // Receive buffer not significant at other processors +#ifdef OPENMC_MPI + MPI_Reduce(values.data(), nullptr, values.size(), MPI_REAL8, MPI_SUM, + 0, mpi::intracomm); +#endif + } + } + + if (mpi::master) { + if (!object_exists(file_id, "tallies_present")) { + // Indicate that tallies are off + write_dataset(file_id, "tallies_present", 0); + } + } +} + +void restart_set_keff() +{ + if (simulation::restart_batch > settings::n_inactive) { + for (int i = settings::n_inactive; i < simulation::restart_batch; ++i) { + k_sum[0] += simulation::k_generation[i]; + k_sum[1] += std::pow(simulation::k_generation[i], 2); + } + int n = settings::gen_per_batch*n_realizations; + simulation::keff = k_sum[0] / n; + } else { + simulation::keff = simulation::k_generation.back(); + } +} + } // namespace openmc diff --git a/src/string_functions.cpp b/src/string_functions.cpp index f81b09aef4..27e14c9ba6 100644 --- a/src/string_functions.cpp +++ b/src/string_functions.cpp @@ -1,4 +1,5 @@ #include "openmc/string_functions.h" +#include namespace openmc { @@ -27,4 +28,13 @@ void to_lower(std::string& str) for (int i = 0; i < str.size(); i++) str[i] = std::tolower(str[i]); } +int word_count(std::string const& str) +{ + std::stringstream stream(str); + std::string dum; + int count = 0; + while (stream >> dum) {count++;} + return count; +} + } // namespace openmc diff --git a/src/surface.cpp b/src/surface.cpp index bf1c2e58ce..265a2f02ca 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -8,7 +8,7 @@ #include "openmc/error.h" #include "openmc/hdf5_interface.h" #include "openmc/xml_interface.h" - +#include "openmc/string_functions.h" namespace openmc { @@ -35,24 +35,6 @@ std::map surface_map; // Helper functions for reading the "coeffs" node of an XML surface element //============================================================================== -int word_count(const std::string &text) -{ - bool in_word = false; - int count {0}; - for (auto c = text.begin(); c != text.end(); c++) { - if (std::isspace(*c)) { - if (in_word) { - in_word = false; - count++; - } - } else { - in_word = true; - } - } - if (in_word) count++; - return count; -} - void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1) { // Check the given number of coefficients. diff --git a/src/surface_header.F90 b/src/surface_header.F90 index 9d7e114613..a2ef9c0563 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -2,7 +2,6 @@ module surface_header use, intrinsic :: ISO_C_BINDING - use dict_header, only: DictIntInt use hdf5_interface implicit none @@ -83,9 +82,6 @@ module surface_header type(Surface), allocatable, target :: surfaces(:) - ! Dictionary that maps user IDs to indices in 'surfaces' - type(DictIntInt) :: surface_dict - contains pure function surface_id(this) result(id) @@ -128,7 +124,6 @@ contains subroutine free_memory_surfaces() if (allocated(surfaces)) deallocate(surfaces) - call surface_dict % clear() call free_memory_surfaces_c() end subroutine free_memory_surfaces diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp new file mode 100644 index 0000000000..ef4ab345d6 --- /dev/null +++ b/src/tallies/filter.cpp @@ -0,0 +1,180 @@ +#include "openmc/tallies/filter.h" + +#include + +#include "openmc/capi.h" +#include "openmc/constants.h" // for MAX_LINE_LEN; +#include "openmc/error.h" +#include "openmc/tallies/filter_azimuthal.h" +#include "openmc/tallies/filter_cell.h" +#include "openmc/tallies/filter_cellborn.h" +#include "openmc/tallies/filter_cellfrom.h" +#include "openmc/tallies/filter_delayedgroup.h" +#include "openmc/tallies/filter_distribcell.h" +#include "openmc/tallies/filter_energyfunc.h" +#include "openmc/tallies/filter_energy.h" +#include "openmc/tallies/filter_legendre.h" +#include "openmc/tallies/filter_material.h" +#include "openmc/tallies/filter_mesh.h" +#include "openmc/tallies/filter_meshsurface.h" +#include "openmc/tallies/filter_mu.h" +#include "openmc/tallies/filter_particle.h" +#include "openmc/tallies/filter_polar.h" +#include "openmc/tallies/filter_sph_harm.h" +#include "openmc/tallies/filter_sptl_legendre.h" +#include "openmc/tallies/filter_surface.h" +#include "openmc/tallies/filter_universe.h" +#include "openmc/tallies/filter_zernike.h" + + +namespace openmc { + +//============================================================================== +// Global variables +//============================================================================== + +std::vector filter_matches; +std::vector> tally_filters; + +//============================================================================== +// Non-member functions +//============================================================================== + +void +free_memory_tally_c() +{ + #pragma omp parallel + { + filter_matches.clear(); + } + + tally_filters.clear(); +} + +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +extern "C" { + FilterMatch* filter_match_pointer(int indx) + {return &filter_matches[indx];} + + void + filter_match_bins_push_back(FilterMatch* match, int val) + {match->bins_.push_back(val);} + + void + filter_match_weights_push_back(FilterMatch* match, double val) + {match->weights_.push_back(val);} + + void + filter_match_bins_clear(FilterMatch* match) + {match->bins_.clear();} + + void + filter_match_weights_clear(FilterMatch* match) + {match->weights_.clear();} + + int + filter_match_bins_size(FilterMatch* match) + {return match->bins_.size();} + + int + filter_match_bins_data(FilterMatch* match, int indx) + {return match->bins_[indx-1];} + + double + filter_match_weights_data(FilterMatch* match, int indx) + {return match->weights_[indx-1];} + + void + filter_match_bins_set_data(FilterMatch* match, int indx, int val) + {match->bins_[indx-1] = val;} + + Filter* + allocate_filter(const char* type) + { + std::string type_ {type}; + if (type_ == "azimuthal") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "cell") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "cellborn") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "cellfrom") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "distribcell") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "delayedgroup") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "energyfunction") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "energy") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "energyout") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "legendre") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "material") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "mesh") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "meshsurface") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "mu") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "particle") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "polar") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "surface") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "spatiallegendre") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "sphericalharmonics") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "universe") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "zernike") { + tally_filters.push_back(std::make_unique()); + } else if (type_ == "zernikeradial") { + tally_filters.push_back(std::make_unique()); + } else { + return nullptr; + } + return tally_filters.back().get(); + } + + void filter_from_xml(Filter* filt, pugi::xml_node* node) + {filt->from_xml(*node);} + + void + filter_get_all_bins(Filter* filt, Particle* p, int estimator, + FilterMatch* match) + { + filt->get_all_bins(p, estimator, *match); + } + + void filter_to_statepoint(Filter* filt, hid_t group) + {filt->to_statepoint(group);} + + void filter_text_label(Filter* filt, int bin, char* label) + { + std::string label_str = filt->text_label(bin); + int i = 0; + for (; i < label_str.size() && i < MAX_LINE_LEN; i++) + label[i] = label_str[i]; + label[i] = '\0'; + } + + void filter_initialize(Filter* filt) {filt->initialize();} + + int filter_n_bins(Filter* filt) {return filt->n_bins_;} + + int mesh_filter_get_mesh(MeshFilter* filt) {return filt->mesh();} + + int sphharm_filter_get_cosine(SphericalHarmonicsFilter* filt) + {return static_cast(filt->cosine_);} +} + +} // namespace openmc diff --git a/src/tallies/filter_azimuthal.cpp b/src/tallies/filter_azimuthal.cpp new file mode 100644 index 0000000000..dc65284820 --- /dev/null +++ b/src/tallies/filter_azimuthal.cpp @@ -0,0 +1,74 @@ +#include "openmc/tallies/filter_azimuthal.h" + +#include +#include + +#include "openmc/constants.h" +#include "openmc/error.h" +#include "openmc/search.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +AzimuthalFilter::from_xml(pugi::xml_node node) +{ + auto bins = get_node_array(node, "bins"); + + if (bins.size() > 1) { + bins_ = bins; + + } else { + // 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 + + int n_angle = bins[0]; + + if (n_angle <= 1) fatal_error("Number of bins for azimuthal filter must " + "be greater than 1."); + + double d_angle = 2.0 * PI / n_angle; + bins_.resize(n_angle + 1); + for (int i = 0; i < n_angle; i++) bins_[i] = -PI + i * d_angle; + bins_[n_angle] = PI; + } + + n_bins_ = bins_.size() - 1; +} + +void +AzimuthalFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + double phi; + if (estimator == ESTIMATOR_TRACKLENGTH) { + phi = std::atan2(p->coord[0].uvw[1], p->coord[0].uvw[0]); + } else { + phi = std::atan2(p->last_uvw[1], p->last_uvw[0]); + } + + if (phi >= bins_.front() && phi <= bins_.back()) { + //TODO: off-by-one + auto bin = lower_bound_index(bins_.begin(), bins_.end(), phi) + 1; + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } +} + +void +AzimuthalFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", bins_); +} + +std::string +AzimuthalFilter::text_label(int bin) const +{ + std::stringstream out; + //TODO: off-by-one + out << "Azimuthal Angle [" << bins_[bin-1] << ", " << bins_[bin] << ")"; + return out.str(); +} + +} // namespace openmc diff --git a/src/tallies/filter_cell.cpp b/src/tallies/filter_cell.cpp new file mode 100644 index 0000000000..9e0a6721e5 --- /dev/null +++ b/src/tallies/filter_cell.cpp @@ -0,0 +1,92 @@ +#include "openmc/tallies/filter_cell.h" + +#include + +#include "openmc/capi.h" +#include "openmc/cell.h" +#include "openmc/error.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +CellFilter::from_xml(pugi::xml_node node) +{ + cells_ = get_node_array(node, "bins"); + n_bins_ = cells_.size(); +} + +void +CellFilter::initialize() +{ + // Convert cell IDs to indices of the global array. + for (auto& c : cells_) { + auto search = cell_map.find(c); + if (search != cell_map.end()) { + c = search->second; + } else { + std::stringstream err_msg; + err_msg << "Could not find cell " << c << " specified on tally filter."; + fatal_error(err_msg); + } + } + + // Populate the index->bin map. + for (int i = 0; i < cells_.size(); i++) { + map_[cells_[i]] = i; + } +} + +void +CellFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + for (int i = 0; i < p->n_coord; i++) { + auto search = map_.find(p->coord[i].cell); + if (search != map_.end()) { + //TODO: off-by-one + match.bins_.push_back(search->second + 1); + match.weights_.push_back(1.0); + } + } +} + +void +CellFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + std::vector cell_ids; + for (auto c : cells_) cell_ids.push_back(cells[c]->id_); + write_dataset(filter_group, "bins", cell_ids); +} + +std::string +CellFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Cell " + std::to_string(cells[cells_[bin-1]]->id_); +} + +//============================================================================== +// C-API functions +//============================================================================== + +extern "C" int +openmc_cell_filter_get_bins(int32_t index, int32_t** cells, int32_t* n) +{ + int err = verify_filter(index); + if (err) return err; + + auto filt = filter_from_f(index); + if (filt->type() != "cell") { + set_errmsg("Tried to get cells from a non-cell filter."); + return OPENMC_E_INVALID_TYPE; + } + + auto cell_filt = static_cast(filt); + *cells = cell_filt->cells_.data(); + *n = cell_filt->cells_.size(); + return 0; +} + +} // namespace openmc diff --git a/src/tallies/filter_cellborn.cpp b/src/tallies/filter_cellborn.cpp new file mode 100644 index 0000000000..4f86ad34cd --- /dev/null +++ b/src/tallies/filter_cellborn.cpp @@ -0,0 +1,26 @@ +#include "openmc/tallies/filter_cellborn.h" + +#include "openmc/cell.h" + +namespace openmc { + +void +CellbornFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + auto search = map_.find(p->cell_born); + if (search != map_.end()) { + //TODO: off-by-one + match.bins_.push_back(search->second + 1); + match.weights_.push_back(1.0); + } +} + +std::string +CellbornFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Birth Cell " + std::to_string(cells[cells_[bin-1]]->id_); +} + +} // namespace openmc diff --git a/src/tallies/filter_cellfrom.cpp b/src/tallies/filter_cellfrom.cpp new file mode 100644 index 0000000000..b4b1a57ef0 --- /dev/null +++ b/src/tallies/filter_cellfrom.cpp @@ -0,0 +1,28 @@ +#include "openmc/tallies/filter_cellfrom.h" + +#include "openmc/cell.h" + +namespace openmc { + +void +CellFromFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + for (int i = 0; i < p->last_n_coord; i++) { + auto search = map_.find(p->last_cell[i]); + if (search != map_.end()) { + //TODO: off-by-one + match.bins_.push_back(search->second + 1); + match.weights_.push_back(1.0); + } + } +} + +std::string +CellFromFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Cell from " + std::to_string(cells[cells_[bin-1]]->id_); +} + +} // namespace openmc diff --git a/src/tallies/filter_delayedgroup.cpp b/src/tallies/filter_delayedgroup.cpp new file mode 100644 index 0000000000..2b63027cf4 --- /dev/null +++ b/src/tallies/filter_delayedgroup.cpp @@ -0,0 +1,58 @@ +#include "openmc/tallies/filter_delayedgroup.h" + +#include "openmc/error.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +DelayedGroupFilter::from_xml(pugi::xml_node node) +{ + groups_ = get_node_array(node, "bins"); + n_bins_ = groups_.size(); + + // Make sure all the group index values are valid. + // TODO: do these need to be decremented for zero-based indexing? + for (auto group : groups_) { + if (group < 1) { + fatal_error("Encountered delayedgroup bin with index " + + std::to_string(group) + " which is less than 1"); + } else if (group > MAX_DELAYED_GROUPS) { + fatal_error("Encountered delayedgroup bin with index " + + std::to_string(group) + " which is greater than MAX_DELATED_GROUPS (" + + std::to_string(MAX_DELAYED_GROUPS) + ")"); + } + } +} + +void +DelayedGroupFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + //TODO: off-by-one + match.bins_.push_back(1); + match.weights_.push_back(1.0); +} + +void +DelayedGroupFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", groups_); +} + +std::string +DelayedGroupFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Delayed Group " + std::to_string(groups_[bin-1]); +} + +//============================================================================== +// Fortran interoperability +//============================================================================== + +extern "C" int delayedgroup_filter_groups(DelayedGroupFilter* filt, int i) +{return filt->groups_[i-1];} + +} // namespace openmc diff --git a/src/tallies/filter_distribcell.cpp b/src/tallies/filter_distribcell.cpp new file mode 100644 index 0000000000..39459672fc --- /dev/null +++ b/src/tallies/filter_distribcell.cpp @@ -0,0 +1,81 @@ +#include "openmc/tallies/filter_distribcell.h" + +#include "openmc/cell.h" +#include "openmc/error.h" +#include "openmc/geometry_aux.h" // For distribcell_path +#include "openmc/lattice.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +DistribcellFilter::from_xml(pugi::xml_node node) +{ + auto cells = get_node_array(node, "bins"); + if (cells.size() != 1) { + fatal_error("Only one cell can be specified per distribcell filter."); + } + cell_ = cells[0]; +} + +void +DistribcellFilter::initialize() +{ + // Convert the cell ID to an index of the global array. + auto search = cell_map.find(cell_); + if (search != cell_map.end()) { + cell_ = search->second; + n_bins_ = cells[cell_]->n_instances_; + } else { + std::stringstream err_msg; + err_msg << "Could not find cell " << cell_ + << " specified on tally filter."; + fatal_error(err_msg); + } +} + +void +DistribcellFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + int offset = 0; + auto distribcell_index = cells[cell_]->distribcell_index_; + for (int i = 0; i < p->n_coord; i++) { + auto& c {*cells[p->coord[i].cell]}; + if (c.type_ == FILL_UNIVERSE) { + offset += c.offset_[distribcell_index]; + } else if (c.type_ == FILL_LATTICE) { + auto& lat {*lattices[p->coord[i+1].lattice-1]}; + int i_xyz[3] {p->coord[i+1].lattice_x, + p->coord[i+1].lattice_y, + p->coord[i+1].lattice_z}; + if (lat.are_valid_indices(i_xyz)) { + offset += lat.offset(distribcell_index, i_xyz); + } + } + if (cell_ == p->coord[i].cell) { + //TODO: off-by-one + match.bins_.push_back(offset + 1); + match.weights_.push_back(1.0); + return; + } + } +} + +void +DistribcellFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", cells[cell_]->id_); +} + +std::string +DistribcellFilter::text_label(int bin) const +{ + auto map = cells[cell_]->distribcell_index_; + //TODO: off-by-one + auto path = distribcell_path(cell_, map, bin-1); + return "Distributed Cell " + path; +} + +} // namespace openmc diff --git a/src/tallies/filter_energy.cpp b/src/tallies/filter_energy.cpp new file mode 100644 index 0000000000..4c983b267e --- /dev/null +++ b/src/tallies/filter_energy.cpp @@ -0,0 +1,186 @@ +#include "openmc/tallies/filter_energy.h" + +#include "openmc/capi.h" +#include "openmc/constants.h" // For F90_NONE +#include "openmc/mgxs_interface.h" +#include "openmc/search.h" +#include "openmc/settings.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +//============================================================================== +// EnergyFilter implementation +//============================================================================== + +void +EnergyFilter::from_xml(pugi::xml_node node) +{ + bins_ = get_node_array(node, "bins"); + n_bins_ = bins_.size() - 1; + + // In MG mode, check if the filter bins match the transport 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 (!settings::run_CE) { + if (n_bins_ == num_energy_groups) { + matches_transport_groups_ = true; + for (auto i = 0; i < n_bins_ + 1; i++) { + if (rev_energy_bins[i] != bins_[i]) { + matches_transport_groups_ = false; + break; + } + } + } + } +} + +void +EnergyFilter::get_all_bins(const Particle* p, int estimator, FilterMatch& match) +const +{ + if (p->g != F90_NONE && matches_transport_groups_) { + if (estimator == ESTIMATOR_TRACKLENGTH) { + //TODO: off-by-one + match.bins_.push_back(num_energy_groups - p->g + 1); + } else { + //TODO: off-by-one + match.bins_.push_back(num_energy_groups - p->last_g + 1); + } + match.weights_.push_back(1.0); + + } else { + // Get the pre-collision energy of the particle. + auto E = p->last_E; + + // Bin the energy. + if (E >= bins_.front() && E <= bins_.back()) { + //TODO: off-by-one + auto bin = lower_bound_index(bins_.begin(), bins_.end(), E) + 1; + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } + } +} + +void +EnergyFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", bins_); +} + +std::string +EnergyFilter::text_label(int bin) const +{ + std::stringstream out; + //TODO: off-by-one + out << "Incoming Energy [" << bins_[bin-1] << ", " << bins_[bin] << ")"; + return out.str(); +} + +//============================================================================== +// EnergyoutFilter implementation +//============================================================================== + +void +EnergyoutFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + if (p->g != F90_NONE && matches_transport_groups_) { + match.bins_.push_back(num_energy_groups - p->g + 1); + match.weights_.push_back(1.0); + + } else { + if (p->E >= bins_.front() && p->E <= bins_.back()) { + //TODO: off-by-one + auto bin = lower_bound_index(bins_.begin(), bins_.end(), p->E) + 1; + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } + } +} + +std::string +EnergyoutFilter::text_label(int bin) const +{ + std::stringstream out; + //TODO: off-by-one + out << "Outgoing Energy [" << bins_[bin-1] << ", " << bins_[bin] << ")"; + return out.str(); +} + +//============================================================================== +// Fortran interoperability +//============================================================================== + +extern "C" bool energy_filter_matches_transport_groups(EnergyFilter* filt) +{return filt->matches_transport_groups_;} + +extern "C" int +energy_filter_search(EnergyFilter* filt, double val) +{ + if (val < filt->bins_.front() || val > filt->bins_.back()) { + return -1; + } else { + //TODO: off-by-one + return lower_bound_index(filt->bins_.begin(), filt->bins_.end(), val) + 1; + } +} + +//============================================================================== +// C-API functions +//============================================================================== + +extern"C" int +openmc_energy_filter_get_bins(int32_t index, double** energies, int32_t* n) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) return err; + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Tried to get energy bins on a non-energy filter."); + return OPENMC_E_INVALID_TYPE; + } + + // Output the bins. + *energies = filt->bins_.data(); + *n = filt->bins_.size(); + return 0; +} + +extern "C" int +openmc_energy_filter_set_bins(int32_t index, int32_t n, const double* energies) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) return err; + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Tried to set energy bins on a non-energy filter."); + return OPENMC_E_INVALID_TYPE; + } + + // Update the filter. + filt->bins_.clear(); + filt->bins_.resize(n); + for (int i = 0; i < n; i++) filt->bins_[i] = energies[i]; + filt->n_bins_ = n - 1; + filter_update_n_bins(index); + return 0; +} + +}// namespace openmc diff --git a/src/tallies/filter_energyfunc.cpp b/src/tallies/filter_energyfunc.cpp new file mode 100644 index 0000000000..b47e7b7b63 --- /dev/null +++ b/src/tallies/filter_energyfunc.cpp @@ -0,0 +1,69 @@ +#include "openmc/tallies/filter_energyfunc.h" + +#include // for setprecision +#include // for scientific +#include + +#include "openmc/error.h" +#include "openmc/search.h" +#include "openmc/settings.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +EnergyFunctionFilter::from_xml(pugi::xml_node node) +{ + if (!settings::run_CE) + fatal_error("EnergyFunction filters are only supported for " + "continuous-energy transport calculations"); + + if (!check_for_node(node, "energy")) + fatal_error("Energy grid not specified for EnergyFunction filter."); + + energy_ = get_node_array(node, "energy"); + + if (!check_for_node(node, "y")) + fatal_error("y values not specified for EnergyFunction filter."); + + y_ = get_node_array(node, "y"); +} + +void +EnergyFunctionFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + if (p->last_E >= energy_.front() && p->last_E <= energy_.back()) { + // Search for the incoming energy bin. + auto i = lower_bound_index(energy_.begin(), energy_.end(), p->last_E); + + // Compute the interpolation factor between the nearest bins. + double f = (p->last_E - energy_[i]) / (energy_[i+1] - energy_[i]); + + // Interpolate on the lin-lin grid. + //TODO: off-by-one + match.bins_.push_back(1); + match.weights_.push_back((1-f) * y_[i] + f * y_[i+1]); + } +} + +void +EnergyFunctionFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "energy", energy_); + write_dataset(filter_group, "y", y_); +} + +std::string +EnergyFunctionFilter::text_label(int bin) const +{ + std::stringstream out; + out << std::scientific << std::setprecision(1) + << "Energy Function f" + << "([ " << energy_.front() << ", ..., " << energy_.back() << "]) = " + << "[" << y_.front() << ", ..., " << y_.back() << "]"; + return out.str(); +} + +} // namespace openmc diff --git a/src/tallies/filter_legendre.cpp b/src/tallies/filter_legendre.cpp new file mode 100644 index 0000000000..67d3e21d44 --- /dev/null +++ b/src/tallies/filter_legendre.cpp @@ -0,0 +1,94 @@ +#include "openmc/tallies/filter_legendre.h" + +#include "openmc/capi.h" +#include "openmc/error.h" +#include "openmc/math_functions.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +LegendreFilter::from_xml(pugi::xml_node node) +{ + order_ = std::stoi(get_node_value(node, "order")); + n_bins_ = order_ + 1; +} + +void +LegendreFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + double wgt[n_bins_]; + calc_pn_c(order_, p->mu, wgt); + for (int i = 0; i < n_bins_; i++) { + //TODO: off-by-one + match.bins_.push_back(i + 1); + match.weights_.push_back(wgt[i]); + } +} + +void +LegendreFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "order", order_); +} + +std::string +LegendreFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Legendre expansion, P" + std::to_string(bin - 1); +} + +//============================================================================== +// C-API functions +//============================================================================== + +extern "C" int +openmc_legendre_filter_get_order(int32_t index, int* order) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) return err; + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Not a legendre filter."); + return OPENMC_E_INVALID_TYPE; + } + + // Output the order. + *order = filt->order_; + return 0; +} + +extern "C" int +openmc_legendre_filter_set_order(int32_t index, int order) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) return err; + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Not a legendre filter."); + return OPENMC_E_INVALID_TYPE; + } + + // Update the filter. + filt->order_ = order; + filt->n_bins_ = order + 1; + filter_update_n_bins(index); + return 0; +} + +} // namespace openmc diff --git a/src/tallies/filter_material.cpp b/src/tallies/filter_material.cpp new file mode 100644 index 0000000000..d36175e9b1 --- /dev/null +++ b/src/tallies/filter_material.cpp @@ -0,0 +1,124 @@ +#include "openmc/tallies/filter_material.h" + +#include + +#include "openmc/capi.h" +#include "openmc/error.h" +#include "openmc/material.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +MaterialFilter::from_xml(pugi::xml_node node) +{ + materials_ = get_node_array(node, "bins"); + n_bins_ = materials_.size(); +} + +void +MaterialFilter::initialize() +{ + // Convert material IDs to indices of the global array. + for (auto& m : materials_) { + auto search = material_map.find(m); + if (search != material_map.end()) { + m = search->second; + } else { + std::stringstream err_msg; + err_msg << "Could not find material " << m + << " specified on tally filter."; + fatal_error(err_msg); + } + } + + // Populate the index->bin map. + for (int i = 0; i < materials_.size(); i++) { + map_[materials_[i]] = i; + } +} + +void +MaterialFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + auto search = map_.find(p->material - 1); + if (search != map_.end()) { + //TODO: off-by-one + match.bins_.push_back(search->second + 1); + match.weights_.push_back(1.0); + } +} + +void +MaterialFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + std::vector material_ids; + for (auto c : materials_) material_ids.push_back(materials[c]->id_); + write_dataset(filter_group, "bins", material_ids); +} + +std::string +MaterialFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Material " + std::to_string(materials[materials_[bin-1]]->id_); +} + +//============================================================================== +// C-API functions +//============================================================================== + +extern "C" int +openmc_material_filter_get_bins(int32_t index, int32_t** bins, int32_t* n) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) return err; + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Tried to get material filter bins on a non-material filter."); + return OPENMC_E_INVALID_TYPE; + } + + // Output the bins. + *bins = filt->materials_.data(); + *n = filt->materials_.size(); + return 0; +} + +extern "C" int +openmc_material_filter_set_bins(int32_t index, int32_t n, const int32_t* bins) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) return err; + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Tried to set material filter bins on a non-material filter."); + return OPENMC_E_INVALID_TYPE; + } + + // Update the filter. + filt->materials_.clear(); + filt->materials_.resize(n); + for (int i = 0; i < n; i++) filt->materials_[i] = bins[i]; + filt->n_bins_ = filt->materials_.size(); + filt->map_.clear(); + for (int i = 0; i < n; i++) filt->map_[filt->materials_[i]] = i; + filter_update_n_bins(index); + return 0; +} + +} // namespace openmc diff --git a/src/tallies/filter_mesh.cpp b/src/tallies/filter_mesh.cpp new file mode 100644 index 0000000000..5ff54bcf5c --- /dev/null +++ b/src/tallies/filter_mesh.cpp @@ -0,0 +1,137 @@ +#include "openmc/tallies/filter_mesh.h" + +#include + +#include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/error.h" +#include "openmc/mesh.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +MeshFilter::from_xml(pugi::xml_node node) +{ + auto bins_ = get_node_array(node, "bins"); + if (bins_.size() != 1) { + fatal_error("Only one mesh can be specified per " + type() + + " mesh filter."); + } + + auto id = bins_[0]; + auto search = mesh_map.find(id); + if (search != mesh_map.end()) { + set_mesh(search->second); + } else{ + std::stringstream err_msg; + err_msg << "Could not find cell " << id << " specified on tally filter."; + fatal_error(err_msg); + } +} + +void +MeshFilter::get_all_bins(const Particle* p, int estimator, FilterMatch& match) +const +{ + if (estimator != ESTIMATOR_TRACKLENGTH) { + auto bin = meshes[mesh_]->get_bin(p->coord[0].xyz); + if (bin >= 0) { + //TODO: off-by-one + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } + } else { + meshes[mesh_]->bins_crossed(p, match.bins_, match.weights_); + } +} + +void +MeshFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", meshes[mesh_]->id_); +} + +std::string +MeshFilter::text_label(int bin) const +{ + auto& mesh = *meshes[mesh_]; + int n_dim = mesh.n_dimension_; + + int ijk[n_dim]; + mesh.get_indices_from_bin(bin, ijk); + + std::stringstream out; + out << "Mesh Index (" << ijk[0]; + if (n_dim > 1) out << ", " << ijk[1]; + if (n_dim > 2) out << ", " << ijk[2]; + out << ")"; + + return out.str(); +} + +void +MeshFilter::set_mesh(int32_t mesh) +{ + mesh_ = mesh; + n_bins_ = 1; + for (auto dim : meshes[mesh_]->shape_) n_bins_ *= dim; +} + +//============================================================================== +// C-API functions +//============================================================================== + +extern "C" int +openmc_mesh_filter_get_mesh(int32_t index, int32_t* index_mesh) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) return err; + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Tried to get mesh on a non-mesh filter."); + return OPENMC_E_INVALID_TYPE; + } + + // Output the mesh. + *index_mesh = filt->mesh(); + return 0; +} + +extern "C" int +openmc_mesh_filter_set_mesh(int32_t index, int32_t index_mesh) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) return err; + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Tried to set mesh on a non-mesh filter."); + return OPENMC_E_INVALID_TYPE; + } + + // Check the mesh index. + if (index_mesh < 0 || index_mesh >= meshes.size()) { + set_errmsg("Index in 'meshes' array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + // Update the filter. + filt->set_mesh(index_mesh); + filter_update_n_bins(index); + return 0; +} + +} // namespace openmc diff --git a/src/tallies/filter_meshsurface.cpp b/src/tallies/filter_meshsurface.cpp new file mode 100644 index 0000000000..af9bc53218 --- /dev/null +++ b/src/tallies/filter_meshsurface.cpp @@ -0,0 +1,95 @@ +#include "openmc/tallies/filter_meshsurface.h" + +#include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/error.h" +#include "openmc/mesh.h" + +namespace openmc { + +void +MeshSurfaceFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + meshes[mesh_]->surface_bins_crossed(p, match.bins_); + for (auto b : match.bins_) match.weights_.push_back(1.0); +} + +std::string +MeshSurfaceFilter::text_label(int bin) const +{ + auto& mesh = *meshes[mesh_]; + int n_dim = mesh.n_dimension_; + + // Get flattend mesh index and surface index. + //TODO: off-by-one + int i_mesh = (bin - 1) / (4 * n_dim) + 1; + int i_surf = ((bin - 1) % (4 * n_dim)) + 1; + + // Get mesh index part of label. + std::string out = MeshFilter::text_label(i_mesh); + + // Get surface part of label. + switch (i_surf) { + case OUT_LEFT: + out += " Outgoing, x-min"; + break; + case IN_LEFT: + out += " Incoming, x-min"; + break; + case OUT_RIGHT: + out += " Outgoing, x-max"; + break; + case IN_RIGHT: + out += " Incoming, x-max"; + break; + case OUT_BACK: + out += " Outgoing, y-min"; + break; + case IN_BACK: + out += " Incoming, y-min"; + break; + case OUT_FRONT: + out += " Outgoing, y-max"; + break; + case IN_FRONT: + out += " Incoming, y-max"; + break; + case OUT_BOTTOM: + out += " Outgoing, z-min"; + break; + case IN_BOTTOM: + out += " Incoming, z-min"; + break; + case OUT_TOP: + out += " Outgoing, z-max"; + break; + case IN_TOP: + out += " Incoming, z-max"; + break; + } + + return out; +} + +void +MeshSurfaceFilter::set_mesh(int32_t mesh) +{ + mesh_ = mesh; + n_bins_ = 4 * meshes[mesh_]->n_dimension_; + for (auto dim : meshes[mesh_]->shape_) n_bins_ *= dim; +} + +//============================================================================== +// C-API functions +//============================================================================== + +extern"C" int +openmc_meshsurface_filter_get_mesh(int32_t index, int32_t* index_mesh) +{return openmc_mesh_filter_get_mesh(index, index_mesh);} + +extern"C" int +openmc_meshsurface_filter_set_mesh(int32_t index, int32_t index_mesh) +{return openmc_mesh_filter_set_mesh(index, index_mesh);} + +} // namespace openmc diff --git a/src/tallies/filter_mu.cpp b/src/tallies/filter_mu.cpp new file mode 100644 index 0000000000..521deefe3d --- /dev/null +++ b/src/tallies/filter_mu.cpp @@ -0,0 +1,65 @@ +#include "openmc/tallies/filter_mu.h" + +#include + +#include "openmc/error.h" +#include "openmc/search.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +MuFilter::from_xml(pugi::xml_node node) +{ + auto bins = get_node_array(node, "bins"); + + if (bins.size() > 1) { + bins_ = bins; + + } else { + // 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 + + int n_angle = bins[0]; + + if (n_angle <= 1) fatal_error("Number of bins for mu filter must " + "be greater than 1."); + + double d_angle = 2.0 / n_angle; + bins_.resize(n_angle + 1); + for (int i = 0; i < n_angle; i++) bins_[i] = -1 + i * d_angle; + bins_[n_angle] = 1; + } + + n_bins_ = bins_.size() - 1; +} + +void +MuFilter::get_all_bins(const Particle* p, int estimator, FilterMatch& match) +const +{ + if (p->mu >= bins_.front() && p->mu <= bins_.back()) { + //TODO: off-by-one + auto bin = lower_bound_index(bins_.begin(), bins_.end(), p->mu) + 1; + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } +} + +void +MuFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", bins_); +} + +std::string +MuFilter::text_label(int bin) const +{ + std::stringstream out; + //TODO: off-by-one + out << "Change-in-Angle [" << bins_[bin-1] << ", " << bins_[bin] << ")"; + return out.str(); +} + +} // namespace openmc diff --git a/src/tallies/filter_particle.cpp b/src/tallies/filter_particle.cpp new file mode 100644 index 0000000000..27558c59b4 --- /dev/null +++ b/src/tallies/filter_particle.cpp @@ -0,0 +1,48 @@ +#include "openmc/tallies/filter_particle.h" + +#include "openmc/xml_interface.h" + +namespace openmc { + +void +ParticleFilter::from_xml(pugi::xml_node node) +{ + particles_ = get_node_array(node, "bins"); + n_bins_ = particles_.size(); +} + +void +ParticleFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + for (auto i = 0; i < particles_.size(); i++) { + if (particles_[i] == p->type) { + //TODO: off-by-one + match.bins_.push_back(i + 1); + match.weights_.push_back(1.0); + } + } +} + +void +ParticleFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", particles_); +} + +std::string +ParticleFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Particle " + std::to_string(particles_[bin-1]); +} + +//============================================================================== +// Fortran interoperability +//============================================================================== + +extern "C" int particle_filter_particles(ParticleFilter* filt, int i) +{return filt->particles_[i-1];} + +} // namespace openmc diff --git a/src/tallies/filter_polar.cpp b/src/tallies/filter_polar.cpp new file mode 100644 index 0000000000..50fc47999c --- /dev/null +++ b/src/tallies/filter_polar.cpp @@ -0,0 +1,73 @@ +#include "openmc/tallies/filter_polar.h" + +#include + +#include "openmc/constants.h" +#include "openmc/error.h" +#include "openmc/search.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +PolarFilter::from_xml(pugi::xml_node node) +{ + auto bins = get_node_array(node, "bins"); + + if (bins.size() > 1) { + bins_ = bins; + + } else { + // 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 + + int n_angle = bins[0]; + + if (n_angle <= 1) fatal_error("Number of bins for polar filter must " + "be greater than 1."); + + double d_angle = PI / n_angle; + bins_.resize(n_angle + 1); + for (int i = 0; i < n_angle; i++) bins_[i] = i * d_angle; + bins_[n_angle] = PI; + } + + n_bins_ = bins_.size() - 1; +} + +void +PolarFilter::get_all_bins(const Particle* p, int estimator, FilterMatch& match) +const +{ + double theta; + if (estimator == ESTIMATOR_TRACKLENGTH) { + theta = std::acos(p->coord[0].uvw[2]); + } else { + theta = std::acos(p->last_uvw[2]); + } + + if (theta >= bins_.front() && theta <= bins_.back()) { + //TODO: off-by-one + auto bin = lower_bound_index(bins_.begin(), bins_.end(), theta) + 1; + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } +} + +void +PolarFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "bins", bins_); +} + +std::string +PolarFilter::text_label(int bin) const +{ + std::stringstream out; + //TODO: off-by-one + out << "Polar Angle [" << bins_[bin-1] << ", " << bins_[bin] << ")"; + return out.str(); +} + +} // namespace openmc diff --git a/src/tallies/filter_sph_harm.cpp b/src/tallies/filter_sph_harm.cpp new file mode 100644 index 0000000000..001f291193 --- /dev/null +++ b/src/tallies/filter_sph_harm.cpp @@ -0,0 +1,183 @@ +#include "openmc/tallies/filter_sph_harm.h" + +#include // For pair + +#include "openmc/capi.h" +#include "openmc/error.h" +#include "openmc/math_functions.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +SphericalHarmonicsFilter::from_xml(pugi::xml_node node) +{ + order_ = std::stoi(get_node_value(node, "order")); + n_bins_ = (order_ + 1) * (order_ + 1); + + if (check_for_node(node, "cosine")) { + auto cos = get_node_value(node, "cosine", true); + if (cos == "scatter") { + cosine_ = SphericalHarmonicsCosine::scatter; + } else if (cos == "particle") { + cosine_ = SphericalHarmonicsCosine::particle; + } else { + std::stringstream err_msg; + err_msg << "Unrecognized cosine type, \"" << cos + << "\" in spherical harmonics filter"; + fatal_error(err_msg); + } + } +} + +void +SphericalHarmonicsFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + // Determine cosine term for scatter expansion if necessary + double wgt[order_ + 1]; + if (cosine_ == SphericalHarmonicsCosine::scatter) { + calc_pn_c(order_, p->mu, wgt); + } else { + for (int i = 0; i < order_ + 1; i++) wgt[i] = 1; + } + + // Find the Rn,m values + double rn[n_bins_]; + calc_rn_c(order_, p->last_uvw, rn); + + int j = 0; + for (int n = 0; n < order_ + 1; n++) { + // Calculate n-th order spherical harmonics for (u,v,w) + int num_nm = 2*n + 1; + + // Append the matching (bin,weight) for each moment + for (int i = 0; i < num_nm; i++) { + match.weights_.push_back(wgt[n] * rn[j]); + //TODO: off-by-one + match.bins_.push_back(++j); + } + } +} + +void +SphericalHarmonicsFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "order", order_); + if (cosine_ == SphericalHarmonicsCosine::scatter) { + write_dataset(filter_group, "cosine", "scatter"); + } else { + write_dataset(filter_group, "cosine", "particle"); + } +} + +std::string +SphericalHarmonicsFilter::text_label(int bin) const +{ + std::stringstream out; + for (int n = 0; n < order_ + 1; n++) { + if (bin <= (n + 1) * (n + 1)) { + //TODO: off-by-one + int m = (bin - n*n - 1) - n; + out << "Spherical harmonic expansion, Y" << n << "," << m; + return out.str(); + } + } +} + +//============================================================================== +// C-API functions +//============================================================================== + +std::pair +check_sphharm_filter(int32_t index) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) { + return {err, nullptr}; + } + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Not a spherical harmonics filter."); + err = OPENMC_E_INVALID_TYPE; + } + return {err, filt}; +} + +extern "C" int +openmc_sphharm_filter_get_order(int32_t index, int* order) +{ + // Check the filter. + auto check_result = check_sphharm_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Output the order. + *order = filt->order_; + return 0; +} + +extern "C" int +openmc_sphharm_filter_get_cosine(int32_t index, char cosine[]) +{ + // Check the filter. + auto check_result = check_sphharm_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Output the cosine. + if (filt->cosine_ == SphericalHarmonicsCosine::scatter) { + strcpy(cosine, "scatter"); + } else { + strcpy(cosine, "particle"); + } + return 0; +} + +extern "C" int +openmc_sphharm_filter_set_order(int32_t index, int order) +{ + // Check the filter. + auto check_result = check_sphharm_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Update the filter. + filt->order_ = order; + filt->n_bins_ = (order + 1) * (order + 1); + filter_update_n_bins(index); + return 0; +} + +extern "C" int +openmc_sphharm_filter_set_cosine(int32_t index, const char cosine[]) +{ + // Check the filter. + auto check_result = check_sphharm_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Update the filter. + if (strcmp(cosine, "scatter") == 0) { + filt->cosine_ = SphericalHarmonicsCosine::scatter; + } else if (strcmp(cosine, "particle") == 0) { + filt->cosine_ = SphericalHarmonicsCosine::particle; + } else { + set_errmsg("Invalid spherical harmonics cosine."); + return OPENMC_E_INVALID_ARGUMENT; + } + return 0; +} + +} // namespace openmc diff --git a/src/tallies/filter_sptl_legendre.cpp b/src/tallies/filter_sptl_legendre.cpp new file mode 100644 index 0000000000..df0d8cd236 --- /dev/null +++ b/src/tallies/filter_sptl_legendre.cpp @@ -0,0 +1,185 @@ +#include "openmc/tallies/filter_sptl_legendre.h" + +#include // For pair + +#include "openmc/capi.h" +#include "openmc/error.h" +#include "openmc/math_functions.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +SpatialLegendreFilter::from_xml(pugi::xml_node node) +{ + order_ = std::stoi(get_node_value(node, "order")); + + auto axis = get_node_value(node, "axis"); + if (axis == "x") { + axis_ = LegendreAxis::x; + } else if (axis == "y") { + axis_ = LegendreAxis::y; + } else if (axis == "z") { + axis_ = LegendreAxis::z; + } else { + fatal_error("Unrecognized axis on SpatialLegendreFilter"); + } + + min_ = std::stod(get_node_value(node, "min")); + max_ = std::stod(get_node_value(node, "max")); + + n_bins_ = order_ + 1; +} + +void +SpatialLegendreFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + // Get the coordinate along the axis of interest. + double x; + if (axis_ == LegendreAxis::x) { + x = p->coord[0].xyz[0]; + } else if (axis_ == LegendreAxis::y) { + x = p->coord[0].xyz[1]; + } else { + x = p->coord[0].xyz[2]; + } + + if (x >= min_ && x <= max_) { + // Compute the normalized coordinate value. + double x_norm = 2.0*(x - min_) / (max_ - min_) - 1.0; + + // Compute and return the Legendre weights. + double wgt[order_ + 1]; + calc_pn_c(order_, x_norm, wgt); + for (int i = 0; i < order_ + 1; i++) { + //TODO: off-by-one + match.bins_.push_back(i + 1); + match.weights_.push_back(wgt[i]); + } + } +} + +void +SpatialLegendreFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "order", order_); + if (axis_ == LegendreAxis::x) { + write_dataset(filter_group, "axis", "x"); + } else if (axis_ == LegendreAxis::y) { + write_dataset(filter_group, "axis", "y"); + } else { + write_dataset(filter_group, "axis", "z"); + } + write_dataset(filter_group, "min", min_); + write_dataset(filter_group, "max", max_); +} + +std::string +SpatialLegendreFilter::text_label(int bin) const +{ + std::stringstream out; + out << "Legendre expansion, "; + if (axis_ == LegendreAxis::x) { + out << "x"; + } else if (axis_ == LegendreAxis::y) { + out << "y"; + } else { + out << "z"; + } + //TODO: off-by-one + out << " axis, P" << std::to_string(bin - 1); + return out.str(); +} + +//============================================================================== +// C-API functions +//============================================================================== + +std::pair +check_sptl_legendre_filter(int32_t index) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) { + return {err, nullptr}; + } + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Not a spatial Legendre filter."); + err = OPENMC_E_INVALID_TYPE; + } + return {err, filt}; +} + +extern "C" int +openmc_spatial_legendre_filter_get_order(int32_t index, int* order) +{ + // Check the filter. + auto check_result = check_sptl_legendre_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Output the order. + *order = filt->order_; + return 0; +} + +extern "C" int +openmc_spatial_legendre_filter_get_params(int32_t index, int* axis, + double* min, double* max) +{ + // Check the filter. + auto check_result = check_sptl_legendre_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Output the params. + *axis = static_cast(filt->axis_); + *min = filt->min_; + *max = filt->max_; + return 0; +} + +extern "C" int +openmc_spatial_legendre_filter_set_order(int32_t index, int order) +{ + // Check the filter. + auto check_result = check_sptl_legendre_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Update the filter. + filt->order_ = order; + filt->n_bins_ = order + 1; + filter_update_n_bins(index); + return 0; +} + +extern "C" int +openmc_spatial_legendre_filter_set_params(int32_t index, const int* axis, + const double* min, const double* max) +{ + // Check the filter. + auto check_result = check_sptl_legendre_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Update the filter. + if (axis) filt->axis_ = static_cast(*axis); + if (min) filt->min_ = *min; + if (max) filt->max_ = *max; + return 0; +} + +} // namespace openmc diff --git a/src/tallies/filter_surface.cpp b/src/tallies/filter_surface.cpp new file mode 100644 index 0000000000..5458d20519 --- /dev/null +++ b/src/tallies/filter_surface.cpp @@ -0,0 +1,72 @@ +#include "openmc/tallies/filter_surface.h" + +#include + +#include "openmc/error.h" +#include "openmc/surface.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +SurfaceFilter::from_xml(pugi::xml_node node) +{ + surfaces_ = get_node_array(node, "bins"); + n_bins_ = surfaces_.size(); +} + +void +SurfaceFilter::initialize() +{ + // Convert surface IDs to indices of the global array. + for (auto& s : surfaces_) { + auto search = surface_map.find(s); + if (search != surface_map.end()) { + s = search->second; + } else { + std::stringstream err_msg; + err_msg << "Could not find surface " << s + << " specified on tally filter."; + fatal_error(err_msg); + } + } + + // Populate the index->bin map. + for (int i = 0; i < surfaces_.size(); i++) { + map_[surfaces_[i]] = i; + } +} + +void +SurfaceFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + auto search = map_.find(std::abs(p->surface)-1); + if (search != map_.end()) { + //TODO: off-by-one + match.bins_.push_back(search->second + 1); + if (p->surface < 0) { + match.weights_.push_back(-1.0); + } else { + match.weights_.push_back(1.0); + } + } +} + +void +SurfaceFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + std::vector surface_ids; + for (auto c : surfaces_) surface_ids.push_back(surfaces[c]->id_); + write_dataset(filter_group, "bins", surface_ids); +} + +std::string +SurfaceFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Surface " + std::to_string(surfaces[surfaces_[bin-1]]->id_); +} + +} // namespace openmc diff --git a/src/tallies/filter_universe.cpp b/src/tallies/filter_universe.cpp new file mode 100644 index 0000000000..05d8c3e0dd --- /dev/null +++ b/src/tallies/filter_universe.cpp @@ -0,0 +1,70 @@ +#include "openmc/tallies/filter_universe.h" + +#include + +#include "openmc/cell.h" +#include "openmc/error.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +void +UniverseFilter::from_xml(pugi::xml_node node) +{ + universes_ = get_node_array(node, "bins"); + n_bins_ = universes_.size(); +} + +void +UniverseFilter::initialize() +{ + // Convert universe IDs to indices of the global array. + for (auto& u : universes_) { + auto search = universe_map.find(u); + if (search != universe_map.end()) { + u = search->second; + } else { + std::stringstream err_msg; + err_msg << "Could not find universe " << u + << " specified on tally filter."; + fatal_error(err_msg); + } + } + + // Populate the index->bin map. + for (int i = 0; i < universes_.size(); i++) { + map_[universes_[i]] = i; + } +} + +void +UniverseFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + for (int i = 0; i < p->n_coord; i++) { + auto search = map_.find(p->coord[i].universe); + if (search != map_.end()) { + //TODO: off-by-one + match.bins_.push_back(search->second + 1); + match.weights_.push_back(1.0); + } + } +} + +void +UniverseFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + std::vector universe_ids; + for (auto u : universes_) universe_ids.push_back(universes[u]->id_); + write_dataset(filter_group, "bins", universe_ids); +} + +std::string +UniverseFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Universe " + std::to_string(universes[universes_[bin-1]]->id_); +} + +} // namespace openmc diff --git a/src/tallies/filter_zernike.cpp b/src/tallies/filter_zernike.cpp new file mode 100644 index 0000000000..a8f41cfeb5 --- /dev/null +++ b/src/tallies/filter_zernike.cpp @@ -0,0 +1,209 @@ +#include "openmc/tallies/filter_zernike.h" + +#include +#include +#include // For pair + +#include "openmc/capi.h" +#include "openmc/error.h" +#include "openmc/math_functions.h" +#include "openmc/xml_interface.h" + +namespace openmc { + +//============================================================================== +// ZernikeFilter implementation +//============================================================================== + +void +ZernikeFilter::from_xml(pugi::xml_node node) +{ + set_order(std::stoi(get_node_value(node, "order"))); + x_ = std::stod(get_node_value(node, "x")); + y_ = std::stod(get_node_value(node, "y")); + r_ = std::stod(get_node_value(node, "r")); +} + +void +ZernikeFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + // Determine the normalized (r,theta) coordinates. + double x = p->coord[0].xyz[0] - x_; + double y = p->coord[0].xyz[1] - y_; + double r = std::sqrt(x*x + y*y) / r_; + double theta = std::atan2(y, x); + + if (r <= 1.0) { + // Compute and return the Zernike weights. + double zn[n_bins_]; + calc_zn(order_, r, theta, zn); + for (int i = 0; i < n_bins_; i++) { + //TODO: off-by-one + match.bins_.push_back(i+1); + match.weights_.push_back(zn[i]); + } + } +} + +void +ZernikeFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + write_dataset(filter_group, "order", order_); + write_dataset(filter_group, "x", x_); + write_dataset(filter_group, "y", y_); + write_dataset(filter_group, "r", r_); +} + +std::string +ZernikeFilter::text_label(int bin) const +{ + std::stringstream out; + for (int n = 0; n < order_+1; n++) { + int last = (n + 1) * (n + 2) / 2; + //TODO: off-by-one + if (bin <= last) { + int first = last - n; + int m = -n + (bin - first) * 2; + out << "Zernike expansion, Z" << n << "," << m; + return out.str(); + } + } +} + +void +ZernikeFilter::set_order(int order) +{ + order_ = order; + n_bins_ = ((order+1) * (order+2)) / 2; +} + +//============================================================================== +// ZernikeRadialFilter implementation +//============================================================================== + +void +ZernikeRadialFilter::get_all_bins(const Particle* p, int estimator, + FilterMatch& match) const +{ + // Determine the normalized radius coordinate. + double x = p->coord[0].xyz[0] - x_; + double y = p->coord[0].xyz[1] - y_; + double r = std::sqrt(x*x + y*y) / r_; + + if (r <= 1.0) { + // Compute and return the Zernike weights. + double zn[n_bins_]; + calc_zn_rad(order_, r, zn); + for (int i = 0; i < n_bins_; i++) { + //TODO: off-by-one + match.bins_.push_back(i+1); + match.weights_.push_back(zn[i]); + } + } +} + +std::string +ZernikeRadialFilter::text_label(int bin) const +{ + //TODO: off-by-one + return "Zernike expansion, Z" + std::to_string(2*(bin-1)) + ",0"; +} + +void +ZernikeRadialFilter::set_order(int order) +{ + order_ = order; + n_bins_ = order / 2 + 1; +} + +//============================================================================== +// C-API functions +//============================================================================== + +std::pair +check_zernike_filter(int32_t index) +{ + // Make sure this is a valid index to an allocated filter. + int err = verify_filter(index); + if (err) { + return {err, nullptr}; + } + + // Get a pointer to the filter and downcast. + auto* filt_base = filter_from_f(index); + auto* filt = dynamic_cast(filt_base); + + // Check the filter type. + if (!filt) { + set_errmsg("Not a Zernike filter."); + err = OPENMC_E_INVALID_TYPE; + } + return {err, filt}; +} + +extern "C" int +openmc_zernike_filter_get_order(int32_t index, int* order) +{ + // Check the filter. + auto check_result = check_zernike_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Output the order. + *order = filt->order(); + return 0; +} + +extern "C" int +openmc_zernike_filter_get_params(int32_t index, double* x, double* y, + double* r) +{ + // Check the filter. + auto check_result = check_zernike_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Output the params. + *x = filt->x_; + *y = filt->y_; + *r = filt->r_; + return 0; +} + +extern "C" int +openmc_zernike_filter_set_order(int32_t index, int order) +{ + // Check the filter. + auto check_result = check_zernike_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Update the filter. + filt->set_order(order); + filter_update_n_bins(index); + return 0; +} + +extern "C" int +openmc_zernike_filter_set_params(int32_t index, const double* x, + const double* y, const double* r) +{ + // Check the filter. + auto check_result = check_zernike_filter(index); + auto err = check_result.first; + auto filt = check_result.second; + if (err) return err; + + // Update the filter. + if (x) filt->x_ = *x; + if (y) filt->y_ = *y; + if (r) filt->r_ = *r; + return 0; +} + +} // namespace openmc diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index a618f351e6..475c6a60c7 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -52,7 +52,7 @@ contains ! with the CE and MG modes. !=============================================================================== - subroutine init_tally_routines() + subroutine init_tally_routines() bind(C) if (run_CE) then score_general => score_general_ce score_analog_tally => score_analog_tally_ce @@ -2079,15 +2079,15 @@ contains 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 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 + 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 @@ -2106,10 +2106,10 @@ contains 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) + 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 ! ====================================================================== @@ -2166,7 +2166,7 @@ contains 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 + bins_size()) then filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 finished = .false. exit @@ -2225,15 +2225,15 @@ contains 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 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 + 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 @@ -2252,10 +2252,10 @@ contains 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) + 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 ! ====================================================================== @@ -2295,7 +2295,7 @@ contains 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 + bins_size()) then filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 finished = .false. exit @@ -2344,11 +2344,11 @@ contains integer :: d ! delayed group integer :: g ! another delayed group integer :: d_bin ! delayed group bin index - integer :: n ! number of energies on filter integer :: k ! loop index for bank sites integer :: l ! loop index for tally filters integer :: f ! index in filters array integer :: b ! index of filter bin + integer :: i_match ! matching bin index on energyout filter integer :: i_bin ! index of matching filter bin integer :: bin_energyout ! original outgoing energy bin integer :: i_filter ! index for matching filter bin combination @@ -2360,15 +2360,12 @@ contains ! save original outgoing energy bin and score index i = t % filter(t % find_filter(FILTER_ENERGYOUT)) i_bin = filter_matches(i) % i_bin - bin_energyout = filter_matches(i) % bins % data(i_bin) + bin_energyout = filter_matches(i) % bins_data(i_bin) ! declare the energyout filter type select type(eo_filt => filters(i) % obj) type is (EnergyoutFilter) - ! Get number of energies on filter - n = size(eo_filt % bins) - ! Since the creation of fission sites is weighted such that it is ! expected to create n_particles sites, we need to multiply the ! score by keff to get the true nu-fission rate. Otherwise, the sum @@ -2397,10 +2394,10 @@ contains ! modify the value so that g_out = 1 corresponds to the highest ! energy bin - g_out = size(eo_filt % bins) - g_out + g_out = eo_filt % n_bins - g_out + 1 ! change outgoing energy bin - filter_matches(i) % bins % data(i_bin) = g_out + call filter_matches(i) % bins_set_data(i_bin, g_out) else @@ -2412,12 +2409,11 @@ contains % E)) end if - ! check if outgoing energy is within specified range on filter - if (E_out < eo_filt % bins(1) .or. E_out > eo_filt % bins(n)) cycle - - ! change outgoing energy bin - filter_matches(i) % bins % data(i_bin) = & - binary_search(eo_filt % bins, n, E_out) + ! If this outgoing energy falls within the energyout filter's range, + ! set the appropriate filter_matches bin. + i_match = eo_filt % search(E_out) + if (i_match == -1) cycle + call filter_matches(i) % bins_set_data(i_bin, i_match) end if @@ -2428,8 +2424,8 @@ contains ! determine scoring index and weight for this filter combination i_filter = 1 do l = 1, size(t % filter) - i_filter = i_filter + (filter_matches(t % filter(l)) % bins % & - data(filter_matches(t % filter(l)) % i_bin) - 1) * & + i_filter = i_filter + (filter_matches(t % filter(l)) & + % bins_data(filter_matches(t % filter(l)) % i_bin) - 1) * & t % stride(l) end do @@ -2470,10 +2466,10 @@ contains do l = 1, size(t % filter) f = t % filter(l) b = filter_matches(f) % i_bin - i_filter = i_filter + (filter_matches(f) % bins % & - data(b) - 1) * t % stride(l) - filter_weight = filter_weight * filter_matches(f) % & - weights % data(b) + i_filter = i_filter + (filter_matches(f) & + % bins_data(b) - 1) * t % stride(l) + filter_weight = filter_weight * filter_matches(f) & + % weights_data(b) end do call score_fission_delayed_dg(t, d_bin, & @@ -2493,10 +2489,10 @@ contains do l = 1, size(t % filter) f = t % filter(l) b = filter_matches(f) % i_bin - i_filter = i_filter + (filter_matches(f) % bins % data(b) - 1) & + i_filter = i_filter + (filter_matches(f) % bins_data(b) - 1) & * t % stride(l) - filter_weight = filter_weight * filter_matches(f) % weights % & - data(b) + filter_weight = filter_weight * filter_matches(f) & + % weights_data(b) end do ! Add score to tally @@ -2509,7 +2505,7 @@ contains end select ! reset outgoing energy bin and score index - filter_matches(i) % bins % data(i_bin) = bin_energyout + call filter_matches(i) % bins_set_data(i_bin, bin_energyout) end subroutine score_fission_eout @@ -2534,14 +2530,14 @@ contains ! save original delayed group bin i_filt = t % filter(t % find_filter(FILTER_DELAYEDGROUP)) i_bin = filter_matches(i_filt) % i_bin - bin_original = filter_matches(i_filt) % bins % data(i_bin) - filter_matches(i_filt) % bins % data(i_bin) = d_bin + bin_original = filter_matches(i_filt) % bins_data(i_bin) + call filter_matches(i_filt) % bins_set_data(i_bin, d_bin) ! determine scoring index and weight on the modified matching bins filter_index = 1 do i = 1, size(t % filter) - filter_index = filter_index + (filter_matches(t % filter(i)) % bins % & - data(filter_matches(t % filter(i)) % i_bin) - 1) * t % stride(i) + filter_index = filter_index + (filter_matches(t % filter(i)) % & + bins_data(filter_matches(t % filter(i)) % i_bin) - 1) * t % stride(i) end do !$omp atomic @@ -2549,7 +2545,7 @@ contains t % results(RESULT_VALUE, score_index, filter_index) + score ! reset original delayed group bin - filter_matches(i_filt) % bins % data(i_bin) = bin_original + call filter_matches(i_filt) % bins_set_data(i_bin, bin_original) end subroutine score_fission_delayed_dg @@ -2595,15 +2591,15 @@ contains 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 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 + 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 @@ -2622,10 +2618,10 @@ contains 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) + 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 ! ====================================================================== @@ -2674,7 +2670,7 @@ contains 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 + bins_size()) then filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 finished = .false. exit @@ -2752,15 +2748,15 @@ contains 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 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 + 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 @@ -2779,10 +2775,10 @@ contains 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) + 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 ! ====================================================================== @@ -2831,7 +2827,7 @@ contains 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 + bins_size()) then filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 finished = .false. exit @@ -2898,15 +2894,15 @@ contains 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 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 + 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 @@ -2925,10 +2921,10 @@ contains 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) + 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 @@ -2961,7 +2957,7 @@ contains 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 + bins_size()) then filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 finished = .false. exit @@ -3769,7 +3765,7 @@ contains ! within the batch to a new random variable !=============================================================================== - subroutine accumulate_tallies() + subroutine accumulate_tallies() bind(C) integer :: i real(C_DOUBLE) :: k_col ! Copy of batch collision estimate of keff @@ -3778,6 +3774,11 @@ contains real(C_DOUBLE) :: val #ifdef OPENMC_MPI + interface + subroutine reduce_tally_results() bind(C) + end subroutine + end interface + ! Combine tally results onto master process if (reduce_tallies) call reduce_tally_results() #endif @@ -3821,72 +3822,11 @@ contains end subroutine accumulate_tallies -!=============================================================================== -! REDUCE_TALLY_RESULTS collects all the results from tallies onto one processor -!=============================================================================== - -#ifdef OPENMC_MPI - 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 - 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() - associate (t => tallies(active_tallies % data(i)) % obj) - - m = size(t % results, 2) - n = size(t % results, 3) - n_bins = m*n - - allocate(tally_temp(m,n), tally_temp2(m,n)) - - ! 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) - - 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 - - deallocate(tally_temp, tally_temp2) - end associate - end do - - ! 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 - global_tallies(RESULT_VALUE, :) = temp2 - else - global_tallies(RESULT_VALUE, :) = ZERO - end if - - ! We also need to determine the total starting weight of particles from the - ! last realization - 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 - !=============================================================================== ! SETUP_ACTIVE_TALLIES !=============================================================================== - subroutine setup_active_tallies() + subroutine setup_active_tallies() bind(C) integer :: i ! loop counter diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp new file mode 100644 index 0000000000..7f64832ec3 --- /dev/null +++ b/src/tallies/tally.cpp @@ -0,0 +1,111 @@ +#include "openmc/tallies/tally.h" + +#include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/message_passing.h" + +#include "xtensor/xadapt.hpp" +#include "xtensor/xbuilder.hpp" // for empty_like +#include "xtensor/xview.hpp" + +#include +#include + +namespace openmc { + +//============================================================================== +// Global variable definitions +//============================================================================== + +double global_tally_absorption; +double global_tally_collision; +double global_tally_tracklength; +double global_tally_leakage; + +//============================================================================== +// Non-member functions +//============================================================================== + +adaptor_type<2> global_tallies() +{ + // Get pointer to global tallies + double* buffer; + openmc_global_tallies(&buffer); + + // Adapt into xtensor + std::array shape = {N_GLOBAL_TALLIES, 3}; + std::size_t size {3*N_GLOBAL_TALLIES}; + + return xt::adapt(buffer, size, xt::no_ownership(), shape); +} + +adaptor_type<3> tally_results(int idx) +{ + // Get pointer to tally results + double* results; + std::array shape; + openmc_tally_results(idx, &results, shape.data()); + + // Adapt array into xtensor with no ownership + std::size_t size {shape[0] * shape[1] * shape[2]}; + return xt::adapt(results, size, xt::no_ownership(), shape); +} + +#ifdef OPENMC_MPI +void reduce_tally_results() +{ + for (int i = 1; i <= n_tallies; ++i) { + // Skip any tallies that are not active + bool active; + openmc_tally_get_active(i, &active); + if (!active) continue; + + // Get view of accumulated tally values + auto results = tally_results(i); + auto values_view = xt::view(results, xt::all(), xt::all(), RESULT_VALUE); + + // Make copy of tally values in contiguous array + xt::xtensor values = values_view; + xt::xtensor values_reduced = xt::empty_like(values); + + // Reduce contiguous set of tally results + MPI_Reduce(values.data(), values_reduced.data(), values.size(), + MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm); + + // Transfer values on master and reset on other ranks + if (mpi::master) { + values_view = values_reduced; + } else { + values_view = 0.0; + } + } + + // Get view of global tally values + auto gt = global_tallies(); + auto gt_values_view = xt::view(gt, xt::all(), RESULT_VALUE); + + // Make copy of values in contiguous array + xt::xtensor gt_values = gt_values_view; + xt::xtensor gt_values_reduced = xt::empty_like(gt_values); + + // Reduce contiguous data + MPI_Reduce(gt_values.data(), gt_values_reduced.data(), N_GLOBAL_TALLIES, + MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm); + + // Transfer values on master and reset on other ranks + if (mpi::master) { + gt_values_view = gt_values_reduced; + } else { + gt_values_view = 0.0; + } + + // We also need to determine the total starting weight of particles from the + // last realization + double weight_reduced; + MPI_Reduce(&total_weight, &weight_reduced, 1, MPI_DOUBLE, MPI_SUM, + 0, mpi::intracomm); + if (mpi::master) total_weight = weight_reduced; +} +#endif + +} // namespace openmc diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index 81b1958169..b8d5d60c0f 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -7,26 +7,14 @@ module tally_filter 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_legendre - use tally_filter_material use tally_filter_mesh use tally_filter_meshsurface - use tally_filter_mu use tally_filter_particle - use tally_filter_polar use tally_filter_sph_harm - use tally_filter_sptl_legendre - use tally_filter_surface - use tally_filter_universe - use tally_filter_zernike implicit none @@ -114,6 +102,14 @@ contains character(:), allocatable :: type_ + interface + function allocate_filter(type) result(ptr) bind(C) + import C_CHAR, C_PTR + character(kind=C_CHAR), intent(in) :: type(*) + type(C_PTR) :: ptr + end function allocate_filter + end interface + ! Convert C string to Fortran string type_ = to_f_string(type) @@ -131,7 +127,7 @@ contains case ('cellborn') allocate(CellbornFilter :: filters(index) % obj) case ('cellfrom') - allocate(CellfromFilter :: filters(index) % obj) + allocate(CellFromFilter :: filters(index) % obj) case ('delayedgroup') allocate(DelayedGroupFilter :: filters(index) % obj) case ('distribcell') @@ -172,6 +168,13 @@ contains err = E_UNASSIGNED call set_errmsg("Unknown filter type: " // trim(type_)) end select + + filters(index) % obj % ptr = allocate_filter(type) + if (.not. c_associated(filters(index) % obj % ptr)) then + err = E_UNASSIGNED + call set_errmsg("Could not allocate C++ tally filter") + end if + end if else err = E_OUT_OF_BOUNDS diff --git a/src/tallies/tally_filter_azimuthal.F90 b/src/tallies/tally_filter_azimuthal.F90 deleted file mode 100644 index fc7f15a17a..0000000000 --- a/src/tallies/tally_filter_azimuthal.F90 +++ /dev/null @@ -1,120 +0,0 @@ -module tally_filter_azimuthal - - use, intrinsic :: ISO_C_BINDING - - 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 deleted file mode 100644 index a394ed122b..0000000000 --- a/src/tallies/tally_filter_cell.F90 +++ /dev/null @@ -1,144 +0,0 @@ -module tally_filter_cell - - use, intrinsic :: ISO_C_BINDING - - 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 - public :: openmc_cell_filter_get_bins - -!=============================================================================== -! CELLFILTER specifies which geometric cells tally events reside in. -!=============================================================================== - - type, public, extends(TallyFilter) :: CellFilter - integer(C_INT32_T), 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 + 1) - 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 - -!=============================================================================== -! C API FUNCTIONS -!=============================================================================== - - function openmc_cell_filter_get_bins(index, cells, n) result(err) bind(C) - ! Return the cells associated with a cell filter - integer(C_INT32_T), value :: index - type(C_PTR), intent(out) :: cells - integer(C_INT32_T), intent(out) :: n - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (CellFilter) - cells = C_LOC(f % cells) - n = size(f % cells) - class default - err = E_INVALID_TYPE - call set_errmsg("Tried to get cells from a non-cell filter.") - end select - end if - end function openmc_cell_filter_get_bins - -end module tally_filter_cell diff --git a/src/tallies/tally_filter_cellborn.F90 b/src/tallies/tally_filter_cellborn.F90 deleted file mode 100644 index 37bf042020..0000000000 --- a/src/tallies/tally_filter_cellborn.F90 +++ /dev/null @@ -1,114 +0,0 @@ -module tally_filter_cellborn - - use, intrinsic :: ISO_C_BINDING - - 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 + 1) - 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 deleted file mode 100644 index a99aa68832..0000000000 --- a/src/tallies/tally_filter_cellfrom.F90 +++ /dev/null @@ -1,80 +0,0 @@ -module tally_filter_cellfrom - - use, intrinsic :: ISO_C_BINDING - - 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) + 1) - 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 index 1a662005bc..deebb0b1b0 100644 --- a/src/tallies/tally_filter_delayedgroup.F90 +++ b/src/tallies/tally_filter_delayedgroup.F90 @@ -2,13 +2,7 @@ module tally_filter_delayedgroup use, intrinsic :: ISO_C_BINDING - 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 @@ -20,67 +14,25 @@ module tally_filter_delayedgroup !=============================================================================== 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 + procedure :: groups 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) + function groups(this, i) result(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 + integer, intent(in) :: i + integer :: group + interface + function delayedgroup_filter_groups(filt, i) result(group) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: filt + integer(C_INT), value :: i + integer(C_INT) :: group + end function + end interface + group = delayedgroup_filter_groups(this % ptr, i) + end function groups end module tally_filter_delayedgroup diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index f35ffc3ec4..8082b43322 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -1,169 +1,21 @@ module tally_filter_distribcell - use, intrinsic :: ISO_C_BINDING - - 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 -!=============================================================================== -! 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 + 1) % type() == FILL_UNIVERSE) then - offset = offset + cells(p % coord(i) % cell + 1) & - % offset(distribcell_index) - elseif (cells(p % coord(i) % cell + 1) % type() == FILL_LATTICE) then - if (lattices(p % coord(i + 1) % lattice) % 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) & - % 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 + 1) 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) % n_instances() - else - call fatal_error("Could not find cell " // trim(to_str(id)) & - &// " specified on tally filter.") - end if + call this % initialize_cpp() + this % n_bins = this % n_bins_cpp() 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 - - label = '' - call find_offset(this % cell, bin-1, 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 -!=============================================================================== - - subroutine find_offset(i_cell, target_offset, path) - integer, intent(in) :: i_cell ! The target cell index - integer, intent(in) :: target_offset ! Target offset - character(*), intent(inout) :: path ! Path to offset - - integer :: map ! Index in maps vector - integer :: i ! Index over cells - - integer(C_INT) :: path_len - character(kind=C_CHAR), allocatable, target :: path_c(:) - - interface - function distribcell_path_len(target_cell, map, target_offset, root_univ)& - bind(C) result(len) - import C_INT32_T, C_INT - integer(C_INT32_T), intent(in), value :: target_cell, map, & - target_offset, root_univ - integer(C_INT) :: len - end function distribcell_path_len - - subroutine distribcell_path(target_cell, map, target_offset, root_univ, & - path) bind(C) - import C_INT32_T, C_CHAR - integer(C_INT32_T), intent(in), value :: target_cell, map, & - target_offset, root_univ - character(kind=C_CHAR), intent(out) :: path(*) - end subroutine distribcell_path - end interface - - ! Get the distribcell index for this cell - map = cells(i_cell) % distribcell_index() - - path_len = distribcell_path_len(i_cell-1, map, target_offset, & - root_universe) - allocate(path_c(path_len)) - call distribcell_path(i_cell-1, map, target_offset, root_universe, & - path_c) - do i = 1, min(path_len, MAX_LINE_LEN) - if (path_c(i) == C_NULL_CHAR) exit - path(i:i) = path_c(i) - 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 index f230d438e8..851de7ee8f 100644 --- a/src/tallies/tally_filter_energy.F90 +++ b/src/tallies/tally_filter_energy.F90 @@ -2,14 +2,6 @@ module tally_filter_energy use, intrinsic :: ISO_C_BINDING - use algorithm, only: binary_search - use constants - use error - use hdf5_interface - use mgxs_interface, only: num_energy_groups, rev_energy_bins - use particle_header, only: Particle - use settings, only: run_CE - use string, only: to_str use tally_filter_header use xml_interface @@ -18,20 +10,33 @@ module tally_filter_energy public :: openmc_energy_filter_get_bins public :: openmc_energy_filter_set_bins + interface + function openmc_energy_filter_get_bins(index, energies, n) result(err) bind(C) + import C_INT32_T, C_PTR, C_INT + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: energies + integer(C_INT32_T), intent(out) :: n + integer(C_INT) :: err + end function + function openmc_energy_filter_set_bins(index, n, energies) result(err) bind(C) + import C_INT32_T, C_DOUBLE, C_INT + 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 + end function + end interface + !=============================================================================== ! 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 + procedure :: search end type EnergyFilter !=============================================================================== @@ -41,11 +46,6 @@ module tally_filter_energy !=============================================================================== 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 @@ -58,194 +58,34 @@ contains class(EnergyFilter), intent(inout) :: this type(XMLNode), intent(in) :: node - integer :: n + interface + function energy_filter_matches_transport_groups(filt) result(matches) & + bind(C) + import C_PTR, C_BOOL + type(C_PTR), value :: filt + logical(C_BOOL) :: matches + end function + end interface - 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 == rev_energy_bins)) & - then - this % matches_transport_groups = .true. - end if - end if - end if + call this % from_xml_cpp(node) + this % n_bins = this % n_bins_cpp() + this % matches_transport_groups = & + energy_filter_matches_transport_groups(this % ptr) 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) + function search(this, val) result(bin) 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 - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (EnergyFilter) - energies = C_LOC(f % bins) - n = size(f % bins) - err = 0 - type is (EnergyoutFilter) - 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 - 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 = verify_filter(index) - if (err == 0) 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 - type is (EnergyoutFilter) - 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 - end if - end function openmc_energy_filter_set_bins + real(8), intent(in) :: val + integer :: bin + interface + function energy_filter_search(filt, val) result(bin) bind(C) + import C_PTR, C_DOUBLE, C_INT + type(C_PTR), value :: filt + real(C_DOUBLE), value :: val + integer(C_INT) :: bin + end function + end interface + bin = energy_filter_search(this % ptr, val) + end function search end module tally_filter_energy diff --git a/src/tallies/tally_filter_energyfunc.F90 b/src/tallies/tally_filter_energyfunc.F90 deleted file mode 100644 index 1402707b4d..0000000000 --- a/src/tallies/tally_filter_energyfunc.F90 +++ /dev/null @@ -1,124 +0,0 @@ -module tally_filter_energyfunc - - use, intrinsic :: ISO_C_BINDING - - 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 index d6aef4abd9..e79b2fa368 100644 --- a/src/tallies/tally_filter_header.F90 +++ b/src/tallies/tally_filter_header.F90 @@ -7,7 +7,6 @@ module tally_filter_header use error use hdf5_interface, only: HID_T use particle_header, only: Particle - use stl_vector, only: VectorInt, VectorReal use string, only: to_str use xml_interface, only: XMLNode @@ -20,19 +19,38 @@ module tally_filter_header public :: openmc_filter_set_id public :: openmc_get_filter_index public :: openmc_get_filter_next_id + public :: filter_match_pointer + + interface + function filter_match_pointer(indx) bind(C) result(ptr) + import C_PTR, C_INT + integer(C_INT), intent(in), value :: indx + type(C_PTR) :: ptr + end function filter_match_pointer + end interface !=============================================================================== ! TALLYFILTERMATCH stores every valid bin and weight for a filter !=============================================================================== type, public :: TallyFilterMatch + type(C_PTR) :: ptr + ! Index of the bin and weight being used in the current filter combination integer :: i_bin - type(VectorInt), pointer :: bins - type(VectorReal), pointer :: weights ! Indicates whether all valid bins for this filter have been found logical :: bins_present = .false. + + contains + procedure :: bins_push_back + procedure :: weights_push_back + procedure :: bins_clear + procedure :: weights_clear + procedure :: bins_size + procedure :: bins_data + procedure :: weights_data + procedure :: bins_set_data end type TallyFilterMatch !=============================================================================== @@ -44,66 +62,62 @@ module tally_filter_header type, public, abstract :: TallyFilter integer :: id integer :: n_bins = 0 + type(C_PTR) :: ptr 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 + procedure :: from_xml + procedure :: get_all_bins + procedure :: to_statepoint + procedure :: text_label + procedure :: initialize + procedure :: n_bins_cpp + procedure :: from_xml_cpp + procedure :: initialize_cpp 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_ - +! Pure C++ filters !=============================================================================== -! 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_ + type, public, extends(TallyFilter) :: AzimuthalFilter + end type -!=============================================================================== -! 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. + type, public, extends(TallyFilter) :: CellFilter + end type - 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_ + type, public, extends(TallyFilter) :: CellbornFilter + end type - end interface + type, public, extends(CellFilter) :: CellFromFilter + end type + + type, public, extends(TallyFilter) :: EnergyFunctionFilter + end type + + type, public, extends(TallyFilter) :: MaterialFilter + end type + + type, public, extends(TallyFilter) :: MuFilter + end type + + type, public, extends(TallyFilter) :: PolarFilter + end type + + type, public, extends(TallyFilter) :: SpatialLegendreFilter + end type + + type, public, extends(TallyFilter) :: SurfaceFilter + ! True if this filter is used for surface currents + logical :: current = .false. + end type + + type, public, extends(TallyFilter) :: UniverseFilter + end type + + type, public, extends(TallyFilter) :: ZernikeFilter + end type + + type, public, extends(ZernikeFilter) :: ZernikeRadialFilter + end type !=============================================================================== ! TALLYFILTERCONTAINER contains an allocatable TallyFilter object for arrays of @@ -130,12 +144,238 @@ module tally_filter_header 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. +! TallyFilterMatch implementation +!=============================================================================== - subroutine filter_initialize(this) + subroutine bins_push_back(this, val) + class(TallyFilterMatch), intent(inout) :: this + integer, intent(in) :: val + interface + subroutine filter_match_bins_push_back(ptr, val) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: ptr + integer(C_INT), intent(in), value :: val + end subroutine + end interface + call filter_match_bins_push_back(this % ptr, val) + end subroutine bins_push_back + + subroutine weights_push_back(this, val) + class(TallyFilterMatch), intent(inout) :: this + real(8), intent(in) :: val + interface + subroutine filter_match_weights_push_back(ptr, val) bind(C) + import C_PTR, C_DOUBLE + type(C_PTR), value :: ptr + real(C_DOUBLE), intent(in), value :: val + end subroutine + end interface + call filter_match_weights_push_back(this % ptr, val) + end subroutine weights_push_back + + subroutine bins_clear(this) + class(TallyFilterMatch), intent(inout) :: this + interface + subroutine filter_match_bins_clear(ptr) bind(C) + import C_PTR + type(C_PTR), value :: ptr + end subroutine + end interface + call filter_match_bins_clear(this % ptr) + end subroutine bins_clear + + subroutine weights_clear(this) + class(TallyFilterMatch), intent(inout) :: this + interface + subroutine filter_match_weights_clear(ptr) bind(C) + import C_PTR + type(C_PTR), value :: ptr + end subroutine + end interface + call filter_match_weights_clear(this % ptr) + end subroutine weights_clear + + function bins_size(this) result(len) + class(TallyFilterMatch), intent(inout) :: this + integer :: len + interface + function filter_match_bins_size(ptr) bind(C) result(len) + import C_PTR, C_INT + type(C_PTR), value :: ptr + integer(C_INT) :: len + end function + end interface + len = filter_match_bins_size(this % ptr) + end function bins_size + + function bins_data(this, indx) result(val) + class(TallyFilterMatch), intent(inout) :: this + integer, intent(in) :: indx + integer :: val + interface + function filter_match_bins_data(ptr, indx) bind(C) result(val) + import C_PTR, C_INT + type(C_PTR), value :: ptr + integer(C_INT), intent(in), value :: indx + integer(C_INT) :: val + end function + end interface + val = filter_match_bins_data(this % ptr, indx) + end function bins_data + + function weights_data(this, indx) result(val) + class(TallyFilterMatch), intent(inout) :: this + integer, intent(in) :: indx + real(8) :: val + interface + function filter_match_weights_data(ptr, indx) bind(C) result(val) + import C_PTR, C_INT, C_DOUBLE + type(C_PTR), value :: ptr + integer(C_INT), intent(in), value :: indx + real(C_DOUBLE) :: val + end function + end interface + val = filter_match_weights_data(this % ptr, indx) + end function weights_data + + subroutine bins_set_data(this, indx, val) + class(TallyFilterMatch), intent(inout) :: this + integer, intent(in) :: indx + integer, intent(in) :: val + interface + subroutine filter_match_bins_set_data(ptr, indx, val) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: ptr + integer(C_INT), value, intent(in) :: indx + integer(C_INT), value, intent(in) :: val + end subroutine + end interface + call filter_match_bins_set_data(this % ptr, indx, val) + end subroutine bins_set_data + +!=============================================================================== +! TallyFilter implementation +!=============================================================================== + + subroutine from_xml(this, node) class(TallyFilter), intent(inout) :: this - end subroutine filter_initialize + type(XMLNode), intent(in) :: node + call this % from_xml_cpp(node) + this % n_bins = this % n_bins_cpp() + end subroutine from_xml + + subroutine get_all_bins(this, p, estimator, match) + class(TallyFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + interface + subroutine filter_get_all_bins(filt, p, estimator, match) bind(C) + import C_PTR, Particle, C_INT + type(C_PTR), value :: filt + type(Particle), intent(in) :: p + integer(C_INT), intent(in), value :: estimator + type(C_PTR), value :: match + end subroutine filter_get_all_bins + end interface + call filter_get_all_bins(this % ptr, p, estimator, match % ptr) + end subroutine get_all_bins + + subroutine to_statepoint(this, filter_group) + class(TallyFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + interface + subroutine filter_to_statepoint(filt, filter_group) bind(C) + import C_PTR, HID_T + type(C_PTR), value :: filt + integer(HID_T), intent(in), value :: filter_group + end subroutine filter_to_statepoint + end interface + call filter_to_statepoint(this % ptr, filter_group) + end subroutine to_statepoint + + function text_label(this, bin) result(label) + class(TallyFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + character(kind=C_CHAR) :: label_(MAX_LINE_LEN+1) + integer :: i + interface + subroutine filter_text_label(filt, bin, label) bind(C) + import C_PTR, C_INT, C_CHAR + type(C_PTR), value :: filt + integer(C_INT), value :: bin + character(kind=C_CHAR) :: label(*) + end subroutine filter_text_label + end interface + call filter_text_label(this % ptr, bin, label_) + label = " " + do i = 1, MAX_LINE_LEN + if (label_(i) == C_NULL_CHAR) exit + label(i:i) = label_(i) + end do + end function text_label + + subroutine initialize(this) + class(TallyFilter), intent(inout) :: this + call this % initialize_cpp() + end subroutine initialize + + function n_bins_cpp(this) result(n_bins) + class(TallyFilter), intent(in) :: this + integer :: n_bins + interface + function filter_n_bins(filt) result(n_bins) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: filt + integer(C_INT) :: n_bins + end function filter_n_bins + end interface + n_bins = filter_n_bins(this % ptr) + end function n_bins_cpp + + subroutine from_xml_cpp(this, node) + class(TallyFilter), intent(inout) :: this + class(XMLNode), intent(in) :: node + interface + subroutine filter_from_xml(filt, node) bind(C) + import C_PTR + type(C_PTR), value :: filt + type(C_PTR) :: node + end subroutine filter_from_xml + end interface + call filter_from_xml(this % ptr, node % ptr) + end subroutine from_xml_cpp + + subroutine initialize_cpp(this) + class(TallyFilter), intent(inout) :: this + interface + subroutine filter_initialize(filt) bind(C) + import C_PTR + type(C_PTR), value :: filt + end subroutine filter_initialize + end interface + call filter_initialize(this % ptr) + end subroutine initialize_cpp + +!=============================================================================== +! FILTER_FROM_F given a Fortran index, return a pointer to a C++ filter. +!=============================================================================== + + function filter_from_f(index) result(filt) bind(C) + integer(C_INT32_T), intent(in), value :: index + type(C_PTR) :: filt + filt = filters(index) % obj % ptr + end function + +!=============================================================================== +! FILTER_UPDATE_N_BINS given a Fortran index, updates filt % n_bins using C++. +!=============================================================================== + + subroutine filter_update_n_bins(index) bind(C) + integer(C_INT32_T), intent(in), value :: index + filters(index) % obj % n_bins = filters(index) % obj % n_bins_cpp() + end subroutine !=============================================================================== ! FREE_MEMORY_TALLY_FILTER deallocates global arrays defined in this module @@ -153,8 +393,8 @@ contains ! array is sufficient and a filter object has already been allocated. !=============================================================================== - function verify_filter(index) result(err) - integer(C_INT32_T), intent(in) :: index + function verify_filter(index) result(err) bind(C) + integer(C_INT32_T), intent(in), value :: index integer(C_INT) :: err err = 0 diff --git a/src/tallies/tally_filter_legendre.F90 b/src/tallies/tally_filter_legendre.F90 index 4663df6519..e5eb34bc05 100644 --- a/src/tallies/tally_filter_legendre.F90 +++ b/src/tallies/tally_filter_legendre.F90 @@ -2,121 +2,20 @@ module tally_filter_legendre use, intrinsic :: ISO_C_BINDING - use constants - use error - use hdf5_interface - use math, only: calc_pn - use particle_header, only: Particle - use string, only: to_str use tally_filter_header - use xml_interface implicit none - private - public :: openmc_legendre_filter_get_order - public :: openmc_legendre_filter_set_order -!=============================================================================== -! LEGENDREFILTER gives Legendre moments of the change in scattering angle -!=============================================================================== + interface + function openmc_legendre_filter_set_order(index, order) result(err) bind(C) + import C_INT32_T, C_INT + integer(C_INT32_T), value :: index + integer(C_INT), value :: order + integer(C_INT) :: err + end function + end interface - type, public, extends(TallyFilter) :: LegendreFilter - integer(C_INT) :: order - contains - procedure :: from_xml - procedure :: get_all_bins - procedure :: to_statepoint - procedure :: text_label + type, extends(TallyFilter) :: LegendreFilter end type LegendreFilter -contains - -!=============================================================================== -! LegendreFilter methods -!=============================================================================== - - subroutine from_xml(this, node) - class(LegendreFilter), intent(inout) :: this - type(XMLNode), intent(in) :: node - - ! Get specified order - call get_node_value(node, "order", this % order) - this % n_bins = this % order + 1 - end subroutine from_xml - - subroutine get_all_bins(this, p, estimator, match) - class(LegendreFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - type(TallyFilterMatch), intent(inout) :: match - - integer :: i - real(C_DOUBLE) :: wgt(this % n_bins) - - call calc_pn(this % order, p % mu, wgt) - do i = 1, this % n_bins - call match % bins % push_back(i) - call match % weights % push_back(wgt(i)) - end do - end subroutine get_all_bins - - subroutine to_statepoint(this, filter_group) - class(LegendreFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "legendre") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "order", this % order) - end subroutine to_statepoint - - function text_label(this, bin) result(label) - class(LegendreFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Legendre expansion, P" // trim(to_str(bin - 1)) - end function text_label - -!=============================================================================== -! C API FUNCTIONS -!=============================================================================== - - function openmc_legendre_filter_get_order(index, order) result(err) bind(C) - ! Get the order of an expansion filter - integer(C_INT32_T), value :: index - integer(C_INT), intent(out) :: order - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (LegendreFilter) - order = f % order - class default - err = E_INVALID_TYPE - call set_errmsg("Tried to get order on a non-expansion filter.") - end select - end if - end function openmc_legendre_filter_get_order - - - function openmc_legendre_filter_set_order(index, order) result(err) bind(C) - ! Set the order of an expansion filter - integer(C_INT32_T), value :: index - integer(C_INT), value :: order - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (LegendreFilter) - f % order = order - f % n_bins = order + 1 - class default - err = E_INVALID_TYPE - call set_errmsg("Tried to set order on a non-expansion filter.") - end select - end if - end function openmc_legendre_filter_set_order - end module tally_filter_legendre diff --git a/src/tallies/tally_filter_material.F90 b/src/tallies/tally_filter_material.F90 deleted file mode 100644 index 51ab26594e..0000000000 --- a/src/tallies/tally_filter_material.F90 +++ /dev/null @@ -1,175 +0,0 @@ -module tally_filter_material - - use, intrinsic :: ISO_C_BINDING - - 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 - - err = verify_filter(index) - if (err == 0) 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 - 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 = verify_filter(index) - if (err == 0) 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 - 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 index dda0bc0ce2..626ed4b156 100644 --- a/src/tallies/tally_filter_mesh.F90 +++ b/src/tallies/tally_filter_mesh.F90 @@ -2,197 +2,37 @@ module tally_filter_mesh use, intrinsic :: ISO_C_BINDING - use constants - use dict_header, only: EMPTY - use error - use mesh_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 :: openmc_mesh_filter_get_mesh - 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. -!=============================================================================== + interface + function openmc_mesh_filter_set_mesh(index, index_mesh) result(err) bind(C) + import C_INT32_T, C_INT + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: index_mesh + integer(C_INT) :: err + end function + end interface 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 + procedure :: mesh => get_mesh end type MeshFilter contains - subroutine from_xml(this, node) - class(MeshFilter), intent(inout) :: this - type(XMLNode), intent(in) :: node - - integer :: i - integer :: id - integer :: n - integer(C_INT) :: err - type(RegularMesh) :: m - - 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 - err = openmc_get_mesh_index(id, this % mesh) - if (err /= 0) then - call fatal_error("Could not find mesh " // trim(to_str(id)) & - // " specified on filter.") - end if - - ! Determine number of bins - m = meshes(this % mesh) - this % n_bins = 1 - do i = 1, m % n_dimension() - this % n_bins = this % n_bins * m % dimension(i) - end do - 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 :: bin - type(RegularMesh) :: m - type(C_PTR) :: ptr_bins, ptr_weights - + function get_mesh(this) result(mesh) + class(MeshFilter) :: this + integer :: mesh interface - subroutine mesh_bins_crossed(m, p, bins, weights) bind(C) - import C_PTR, Particle - type(C_PTR), value :: m - type(Particle), intent(in) :: p - type(C_PTR), value :: bins - type(C_PTR), value :: weights - end subroutine + function mesh_filter_get_mesh(filt) result(index_mesh) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: filt + integer(C_INT) :: index_mesh + end function mesh_filter_get_mesh end interface - - ! 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. - call m % get_bin(p % coord(1) % xyz, bin) - if (bin >= 0) then - call match % bins % push_back(bin) - call match % weights % push_back(ONE) - end if - return - else - ptr_bins = C_LOC(match % bins) - ptr_weights = C_LOC(match % weights) - call mesh_bins_crossed(m % ptr, p, ptr_bins, ptr_weights) - end if - - 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 - - type(RegularMesh) :: m - - m = meshes(this % mesh) - call write_dataset(filter_group, "type", "mesh") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", m % 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) :: m - - 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 function text_label_mesh - -!=============================================================================== -! C API FUNCTIONS -!=============================================================================== - - function openmc_mesh_filter_get_mesh(index, index_mesh) result(err) bind(C) - ! Get the mesh for a mesh filter - integer(C_INT32_T), value, intent(in) :: index - integer(C_INT32_T), intent(out) :: index_mesh - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (MeshFilter) - index_mesh = f % mesh - class default - err = E_INVALID_TYPE - call set_errmsg("Tried to set mesh on a non-mesh filter.") - end select - end if - end function openmc_mesh_filter_get_mesh - - - 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 - - type(RegularMesh) :: m - integer :: i - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (MeshFilter) - if (index_mesh >= 0 .and. index_mesh < n_meshes()) then - f % mesh = index_mesh - f % n_bins = 1 - m = meshes(index_mesh) - do i = 1, m % n_dimension() - f % n_bins = f % n_bins * m % dimension(i) - end do - 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 - end if - end function openmc_mesh_filter_set_mesh + mesh = mesh_filter_get_mesh(this % ptr) + end function get_mesh end module tally_filter_mesh diff --git a/src/tallies/tally_filter_meshsurface.F90 b/src/tallies/tally_filter_meshsurface.F90 index f08bc35efb..574d2c8b7e 100644 --- a/src/tallies/tally_filter_meshsurface.F90 +++ b/src/tallies/tally_filter_meshsurface.F90 @@ -2,224 +2,21 @@ module tally_filter_meshsurface use, intrinsic :: ISO_C_BINDING - use constants - use error - use mesh_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 :: openmc_meshsurface_filter_get_mesh - public :: openmc_meshsurface_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. -!=============================================================================== + interface + function openmc_meshsurface_filter_set_mesh(index, index_mesh) result(err) & + bind(C) + import C_INT32_T, C_INT + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: index_mesh + integer(C_INT) :: err + end function + end interface - type, public, extends(TallyFilter) :: MeshSurfaceFilter - integer :: mesh - contains - procedure :: from_xml - procedure :: get_all_bins - procedure :: to_statepoint - procedure :: text_label + type, extends(TallyFilter) :: MeshSurfaceFilter end type MeshSurfaceFilter -contains - - subroutine from_xml(this, node) - class(MeshSurfaceFilter), intent(inout) :: this - type(XMLNode), intent(in) :: node - - integer :: i - integer :: id - integer :: n - integer(C_INT) :: err - type(RegularMesh) :: m - - n = node_word_count(node, "bins") - - if (n /= 1) call fatal_error("Only one mesh can be & - &specified per meshsurface filter.") - - ! Determine id of mesh - call get_node_value(node, "bins", id) - - ! Get pointer to mesh - err = openmc_get_mesh_index(id, this % mesh) - if (err /= 0) then - call fatal_error("Could not find mesh " // trim(to_str(id)) & - // " specified on filter.") - end if - - ! Determine number of bins - m = meshes(this % mesh) - this % n_bins = 4 * m % n_dimension() - do i = 1, m % n_dimension() - this % n_bins = this % n_bins * m % dimension(i) - end do - end subroutine from_xml - - subroutine get_all_bins(this, p, estimator, match) - class(MeshSurfaceFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - type(TallyFilterMatch), intent(inout) :: match - - type(RegularMesh) :: m - type(C_PTR) :: ptr_bins, ptr_weights - - interface - subroutine mesh_surface_bins_crossed(m, p, bins, weights) bind(C) - import C_PTR, Particle - type(C_PTR), value :: m - type(Particle), intent(in) :: p - type(C_PTR), value :: bins - type(C_PTR), value :: weights - end subroutine - end interface - - ! Get a pointer to the mesh. - m = meshes(this % mesh) - - ptr_bins = C_LOC(match % bins) - ptr_weights = C_LOC(match % weights) - call mesh_surface_bins_crossed(m % ptr, p, ptr_bins, ptr_weights) - - end subroutine get_all_bins - - subroutine to_statepoint(this, filter_group) - class(MeshSurfaceFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - type(RegularMesh) :: m - - m = meshes(this % mesh) - call write_dataset(filter_group, "type", "meshsurface") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", m % id()) - end subroutine to_statepoint - - function text_label(this, bin) result(label) - class(MeshSurfaceFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - integer :: i_mesh - integer :: i_surf - integer :: n_dim - integer, allocatable :: ijk(:) - type(RegularMesh) :: m - - m = meshes(this % mesh) - n_dim = m % n_dimension() - allocate(ijk(n_dim)) - - ! Get flattend mesh index and surface index - i_mesh = (bin - 1) / (4*n_dim) + 1 - i_surf = mod(bin - 1, 4*n_dim) + 1 - - ! Get mesh index part of label - call m % get_indices_from_bin(i_mesh, 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 - - ! Get surface part of label - select case (i_surf) - case (OUT_LEFT) - label = trim(label) // " Outgoing, x-min" - case (IN_LEFT) - label = trim(label) // " Incoming, x-min" - case (OUT_RIGHT) - label = trim(label) // " Outgoing, x-max" - case (IN_RIGHT) - label = trim(label) // " Incoming, x-max" - case (OUT_BACK) - label = trim(label) // " Outgoing, y-min" - case (IN_BACK) - label = trim(label) // " Incoming, y-min" - case (OUT_FRONT) - label = trim(label) // " Outgoing, y-max" - case (IN_FRONT) - label = trim(label) // " Incoming, y-max" - case (OUT_BOTTOM) - label = trim(label) // " Outgoing, z-min" - case (IN_BOTTOM) - label = trim(label) // " Incoming, z-min" - case (OUT_TOP) - label = trim(label) // " Outgoing, z-max" - case (IN_TOP) - label = trim(label) // " Incoming, z-max" - end select - end function text_label - -!=============================================================================== -! C API FUNCTIONS -!=============================================================================== - - function openmc_meshsurface_filter_get_mesh(index, index_mesh) result(err) bind(C) - ! Get the mesh for a mesh surface filter - integer(C_INT32_T), value, intent(in) :: index - integer(C_INT32_T), intent(out) :: index_mesh - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (MeshSurfaceFilter) - index_mesh = f % mesh - class default - err = E_INVALID_TYPE - call set_errmsg("Tried to set mesh on a non-mesh filter.") - end select - end if - end function openmc_meshsurface_filter_get_mesh - - - function openmc_meshsurface_filter_set_mesh(index, index_mesh) result(err) bind(C) - ! Set the mesh for a mesh surface filter - integer(C_INT32_T), value, intent(in) :: index - integer(C_INT32_T), value, intent(in) :: index_mesh - integer(C_INT) :: err - - integer :: i - integer :: n_dim - type(RegularMesh) :: m - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (MeshSurfaceFilter) - if (index_mesh >= 0 .and. index_mesh < n_meshes()) then - f % mesh = index_mesh - m = meshes(index_mesh) - n_dim = m % n_dimension() - f % n_bins = 4*n_dim - do i = 1, n_dim - f % n_bins = f % n_bins * m % dimension(i) - end do - 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 - end if - end function openmc_meshsurface_filter_set_mesh - end module tally_filter_meshsurface diff --git a/src/tallies/tally_filter_mu.F90 b/src/tallies/tally_filter_mu.F90 deleted file mode 100644 index 7f8bde9238..0000000000 --- a/src/tallies/tally_filter_mu.F90 +++ /dev/null @@ -1,110 +0,0 @@ -module tally_filter_mu - - use, intrinsic :: ISO_C_BINDING - - 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 index 5ac846310f..5b736fe5a6 100644 --- a/src/tallies/tally_filter_particle.F90 +++ b/src/tallies/tally_filter_particle.F90 @@ -2,12 +2,7 @@ module tally_filter_particle use, intrinsic :: ISO_C_BINDING - 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 @@ -17,62 +12,25 @@ module tally_filter_particle !=============================================================================== type, public, extends(TallyFilter) :: ParticleFilter - integer, allocatable :: particles(:) contains - procedure :: from_xml - procedure :: get_all_bins - procedure :: to_statepoint - procedure :: text_label + procedure :: particles 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) + function particles(this, i) result(ptype) 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 + integer, intent(in) :: i + integer :: ptype + interface + function particle_filter_particles(filt, i) result(ptype) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: filt + integer(C_INT), value :: i + integer(C_INT) :: ptype + end function + end interface + ptype = particle_filter_particles(this % ptr, i) + end function particles end module tally_filter_particle diff --git a/src/tallies/tally_filter_polar.F90 b/src/tallies/tally_filter_polar.F90 deleted file mode 100644 index 7cbb55f8b4..0000000000 --- a/src/tallies/tally_filter_polar.F90 +++ /dev/null @@ -1,118 +0,0 @@ -module tally_filter_polar - - use, intrinsic :: ISO_C_BINDING - - 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_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 4a1f432732..8848e9ace2 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -2,241 +2,32 @@ module tally_filter_sph_harm use, intrinsic :: ISO_C_BINDING - use constants - use error - use hdf5_interface - use math, only: calc_pn, calc_rn - use particle_header, only: Particle - use string, only: to_str, to_lower, to_f_string use tally_filter_header - use xml_interface implicit none private - public :: openmc_sphharm_filter_get_order - public :: openmc_sphharm_filter_get_cosine - public :: openmc_sphharm_filter_set_order - public :: openmc_sphharm_filter_set_cosine integer, public, parameter :: COSINE_SCATTER = 1 integer, public, parameter :: COSINE_PARTICLE = 2 -!=============================================================================== -! SPHERICALHARMONICSFILTER gives spherical harmonics expansion moments of a -! tally score -!=============================================================================== - type, public, extends(TallyFilter) :: SphericalHarmonicsFilter - integer :: order - integer :: cosine contains - procedure :: from_xml - procedure :: get_all_bins - procedure :: to_statepoint - procedure :: text_label + procedure :: cosine end type SphericalHarmonicsFilter contains -!=============================================================================== -! SphericalHarmonicsFilter methods -!=============================================================================== - - subroutine from_xml(this, node) - class(SphericalHarmonicsFilter), intent(inout) :: this - type(XMLNode), intent(in) :: node - - character(MAX_WORD_LEN) :: temp_str - - ! Get specified order - call get_node_value(node, "order", this % order) - this % n_bins = (this % order + 1)**2 - - ! Determine how cosine term is to be treated - if (check_for_node(node, "cosine")) then - call get_node_value(node, "cosine", temp_str) - select case (to_lower(temp_str)) - case ('scatter') - this % cosine = COSINE_SCATTER - case ('particle') - this % cosine = COSINE_PARTICLE - end select - else - this % cosine = COSINE_PARTICLE - end if - end subroutine from_xml - - subroutine get_all_bins(this, p, estimator, match) - class(SphericalHarmonicsFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - type(TallyFilterMatch), intent(inout) :: match - - integer :: i, j, n - integer :: num_nm - real(C_DOUBLE) :: wgt(this % order + 1) - real(C_DOUBLE) :: rn(this % n_bins) - - ! Determine cosine term for scatter expansion if necessary - if (this % cosine == COSINE_SCATTER) then - call calc_pn(this % order, p % mu, wgt) - else - wgt = ONE - end if - - ! Find the Rn,m values - call calc_rn(this % order, p % last_uvw, rn) - - j = 0 - do n = 0, this % order - ! Calculate n-th order spherical harmonics for (u,v,w) - num_nm = 2*n + 1 - - ! Append matching (bin,weight) for each moment - do i = 1, num_nm - j = j + 1 - call match % bins % push_back(j) - call match % weights % push_back(wgt(n + 1) * rn(j)) - end do - end do - - end subroutine get_all_bins - - subroutine to_statepoint(this, filter_group) - class(SphericalHarmonicsFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "sphericalharmonics") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "order", this % order) - if (this % cosine == COSINE_SCATTER) then - call write_dataset(filter_group, "cosine", "scatter") - else - call write_dataset(filter_group, "cosine", "particle") - end if - end subroutine to_statepoint - - function text_label(this, bin) result(label) - class(SphericalHarmonicsFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - integer :: n, m - - do n = 0, this % order - if (bin <= (n + 1)**2) then - m = (bin - n**2 - 1) - n - label = "Spherical harmonic expansion, Y" // trim(to_str(n)) // & - "," // trim(to_str(m)) - exit - end if - end do - end function text_label - -!=============================================================================== -! C API FUNCTIONS -!=============================================================================== - - function openmc_sphharm_filter_get_order(index, order) result(err) bind(C) - ! Get the order of an expansion filter - integer(C_INT32_T), value :: index - integer(C_INT), intent(out) :: order - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (SphericalHarmonicsFilter) - order = f % order - class default - err = E_INVALID_TYPE - call set_errmsg("Not a spherical harmonics filter.") - end select - end if - end function openmc_sphharm_filter_get_order - - - function openmc_sphharm_filter_get_cosine(index, cosine) result(err) bind(C) - ! Get the order of an expansion filter - integer(C_INT32_T), value :: index - character(kind=C_CHAR), intent(out) :: cosine(*) - integer(C_INT) :: err - - integer :: i - character(10) :: cosine_ - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (SphericalHarmonicsFilter) - select case (f % cosine) - case (COSINE_SCATTER) - cosine_ = 'scatter' - case (COSINE_PARTICLE) - cosine_ = 'particle' - end select - - ! Convert to C string - do i = 1, len_trim(cosine_) - cosine(i) = cosine_(i:i) - end do - cosine(len_trim(cosine_) + 1) = C_NULL_CHAR - - class default - err = E_INVALID_TYPE - call set_errmsg("Not a spherical harmonics filter.") - end select - end if - end function openmc_sphharm_filter_get_cosine - - - function openmc_sphharm_filter_set_order(index, order) result(err) bind(C) - ! Set the order of an expansion filter - integer(C_INT32_T), value :: index - integer(C_INT), value :: order - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (SphericalHarmonicsFilter) - f % order = order - f % n_bins = (order + 1)**2 - class default - err = E_INVALID_TYPE - call set_errmsg("Not a spherical harmonics filter.") - end select - end if - end function openmc_sphharm_filter_set_order - - - function openmc_sphharm_filter_set_cosine(index, cosine) result(err) bind(C) - ! Set the cosine parameter - integer(C_INT32_T), value :: index - character(kind=C_CHAR), intent(in) :: cosine(*) - integer(C_INT) :: err - - character(:), allocatable :: cosine_ - - ! Convert C string to Fortran string - cosine_ = to_f_string(cosine) - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (SphericalHarmonicsFilter) - select case (cosine_) - case ('scatter') - f % cosine = COSINE_SCATTER - case ('particle') - f % cosine = COSINE_PARTICLE - end select - - class default - err = E_INVALID_TYPE - call set_errmsg("Not a spherical harmonics filter.") - end select - end if - end function openmc_sphharm_filter_set_cosine + function cosine(this) result(val) + class(SphericalHarmonicsFilter) :: this + integer :: val + interface + function sphharm_filter_get_cosine(filt) result(val) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: filt + integer(C_INT) :: val + end function + end interface + val = sphharm_filter_get_cosine(this % ptr) + end function cosine end module tally_filter_sph_harm diff --git a/src/tallies/tally_filter_sptl_legendre.F90 b/src/tallies/tally_filter_sptl_legendre.F90 deleted file mode 100644 index 1bfbd0e3b5..0000000000 --- a/src/tallies/tally_filter_sptl_legendre.F90 +++ /dev/null @@ -1,225 +0,0 @@ -module tally_filter_sptl_legendre - - use, intrinsic :: ISO_C_BINDING - - use constants - use error - use hdf5_interface - use math, only: calc_pn - use particle_header, only: Particle - use string, only: to_str, to_lower - use tally_filter_header - use xml_interface - - implicit none - private - public :: openmc_spatial_legendre_filter_get_order - public :: openmc_spatial_legendre_filter_get_params - public :: openmc_spatial_legendre_filter_set_order - public :: openmc_spatial_legendre_filter_set_params - - integer, parameter :: AXIS_X = 1 - integer, parameter :: AXIS_Y = 2 - integer, parameter :: AXIS_Z = 3 - -!=============================================================================== -! SPATIALLEGENDREFILTER gives Legendre moments of the particle's normalized -! position along an axis -!=============================================================================== - - type, public, extends(TallyFilter) :: SpatialLegendreFilter - integer(C_INT) :: order - integer(C_INT) :: axis - real(C_DOUBLE) :: min - real(C_DOUBLE) :: max - contains - procedure :: from_xml - procedure :: get_all_bins - procedure :: to_statepoint - procedure :: text_label - end type SpatialLegendreFilter - -contains - -!=============================================================================== -! SpatialLegendreFilter methods -!=============================================================================== - - subroutine from_xml(this, node) - class(SpatialLegendreFilter), intent(inout) :: this - type(XMLNode), intent(in) :: node - - character(MAX_WORD_LEN) :: axis - - ! Get attributes from XML - call get_node_value(node, "order", this % order) - call get_node_value(node, "axis", axis) - select case (to_lower(axis)) - case ('x') - this % axis = AXIS_X - case ('y') - this % axis = AXIS_Y - case ('z') - this % axis = AXIS_Z - end select - call get_node_value(node, "min", this % min) - call get_node_value(node, "max", this % max) - - this % n_bins = this % order + 1 - end subroutine from_xml - - subroutine get_all_bins(this, p, estimator, match) - class(SpatialLegendreFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - type(TallyFilterMatch), intent(inout) :: match - - integer :: i - real(C_DOUBLE) :: wgt(this % n_bins) - real(C_DOUBLE) :: x ! Position on specified axis - real(C_DOUBLE) :: x_norm ! Normalized position - - x = p % coord(1) % xyz(this % axis) - if (this % min <= x .and. x <= this % max) then - ! Calculate normalized position between min and max - x_norm = TWO*(x - this % min)/(this % max - this % min) - ONE - - call calc_pn(this % order, x_norm, wgt) - do i = 1, this % n_bins - call match % bins % push_back(i) - call match % weights % push_back(wgt(i)) - end do - end if - end subroutine get_all_bins - - subroutine to_statepoint(this, filter_group) - class(SpatialLegendreFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - character(kind=C_CHAR) :: axis - - call write_dataset(filter_group, "type", "spatiallegendre") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "order", this % order) - select case (this % axis) - case (AXIS_X) - axis = 'x' - case (AXIS_Y) - axis = 'y' - case (AXIS_Z) - axis = 'z' - end select - call write_dataset(filter_group, 'axis', axis) - call write_dataset(filter_group, 'min', this % min) - call write_dataset(filter_group, 'max', this % max) - end subroutine to_statepoint - - function text_label(this, bin) result(label) - class(SpatialLegendreFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - character(1) :: axis - - select case (this % axis) - case (AXIS_X) - axis = 'x' - case (AXIS_Y) - axis = 'y' - case (AXIS_Z) - axis = 'z' - end select - label = "Legendre expansion, " // axis // " axis, P" // & - trim(to_str(bin - 1)) - end function text_label - -!=============================================================================== -! C API FUNCTIONS -!=============================================================================== - - function openmc_spatial_legendre_filter_get_order(index, order) result(err) bind(C) - ! Get the order of an expansion filter - integer(C_INT32_T), value :: index - integer(C_INT), intent(out) :: order - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (SpatialLegendreFilter) - order = f % order - class default - err = E_INVALID_TYPE - call set_errmsg("Not a spatial Legendre filter.") - end select - end if - end function openmc_spatial_legendre_filter_get_order - - - function openmc_spatial_legendre_filter_set_order(index, order) result(err) bind(C) - ! Set the order of an expansion filter - integer(C_INT32_T), value :: index - integer(C_INT), value :: order - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (SpatialLegendreFilter) - f % order = order - f % n_bins = order + 1 - class default - err = E_INVALID_TYPE - call set_errmsg("Not a spatial Legendre filter.") - end select - end if - end function openmc_spatial_legendre_filter_set_order - - - function openmc_spatial_legendre_filter_get_params(index, axis, min, max) & - result(err) bind(C) - ! Get the parameters for a spatial Legendre filter - integer(C_INT32_T), value :: index - integer(C_INT), intent(out) :: axis - real(C_DOUBLE), intent(out) :: min - real(C_DOUBLE), intent(out) :: max - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type(f => filters(index) % obj) - type is (SpatialLegendreFilter) - axis = f % axis - min = f % min - max = f % max - class default - err = E_INVALID_TYPE - call set_errmsg("Not a spatial Legendre filter.") - end select - end if - end function openmc_spatial_legendre_filter_get_params - - - function openmc_spatial_legendre_filter_set_params(index, axis, min, max) & - result(err) bind(C) - ! Set the parameters for a spatial Legendre filter - integer(C_INT32_T), value :: index - integer(C_INT), intent(in), optional :: axis - real(C_DOUBLE), intent(in), optional :: min - real(C_DOUBLE), intent(in), optional :: max - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type(f => filters(index) % obj) - type is (SpatialLegendreFilter) - if (present(axis)) f % axis = axis - if (present(min)) f % min = min - if (present(max)) f % max = max - class default - err = E_INVALID_TYPE - call set_errmsg("Not a spatial Legendre filter.") - end select - end if - end function openmc_spatial_legendre_filter_set_params - -end module tally_filter_sptl_legendre diff --git a/src/tallies/tally_filter_surface.F90 b/src/tallies/tally_filter_surface.F90 deleted file mode 100644 index ec642bc8de..0000000000 --- a/src/tallies/tally_filter_surface.F90 +++ /dev/null @@ -1,109 +0,0 @@ -module tally_filter_surface - - use, intrinsic :: ISO_C_BINDING - - 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)) % 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 deleted file mode 100644 index 9e0e09fcf3..0000000000 --- a/src/tallies/tally_filter_universe.F90 +++ /dev/null @@ -1,118 +0,0 @@ -module tally_filter_universe - - use, intrinsic :: ISO_C_BINDING - - 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) = universe_id(this % universes(i)) - 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(universe_id(this % universes(bin))) - end function text_label_universe - -end module tally_filter_universe diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 deleted file mode 100644 index 04515cf318..0000000000 --- a/src/tallies/tally_filter_zernike.F90 +++ /dev/null @@ -1,295 +0,0 @@ -module tally_filter_zernike - - use, intrinsic :: ISO_C_BINDING - - use constants - use error - use hdf5_interface - use math, only: calc_zn, calc_zn_rad - use particle_header, only: Particle - use string, only: to_str - use tally_filter_header - use xml_interface - - implicit none - private - public :: openmc_zernike_filter_get_order - public :: openmc_zernike_filter_get_params - public :: openmc_zernike_filter_set_order - public :: openmc_zernike_filter_set_params - -!=============================================================================== -! ZERNIKEFILTER gives Zernike polynomial moments of a particle's position -!=============================================================================== - - type, public, extends(TallyFilter) :: ZernikeFilter - integer :: order - real(8) :: x - real(8) :: y - real(8) :: r - contains - procedure :: calc_n_bins => calc_n_bins_zn - procedure :: from_xml => from_xml_zn - procedure :: get_all_bins => get_all_bins_zn - procedure :: to_statepoint => to_statepoint_zn - procedure :: text_label => text_label_zn - end type ZernikeFilter - -!=============================================================================== -! ZERNIKERADIALFILTER gives even order radial Zernike polynomial moments of a -! particle's position -!=============================================================================== - - type, public, extends(ZernikeFilter) :: ZernikeRadialFilter - contains - procedure :: calc_n_bins => calc_n_bins_zn_rad - ! Inherit from_xml from ZernikeFilter - procedure :: get_all_bins => get_all_bins_zn_rad - procedure :: to_statepoint => to_statepoint_zn_rad - procedure :: text_label => text_label_zn_rad - end type ZernikeRadialFilter - -contains - -!=============================================================================== -! ZernikeFilter methods -!=============================================================================== - - function calc_n_bins_zn(this) result(n_bins) - class(ZernikeFilter), intent(in) :: this - integer :: n - integer :: n_bins - - n = this % order - n_bins = ((n+1) * (n+2))/2 - end function calc_n_bins_zn - - subroutine from_xml_zn(this, node) - class(ZernikeFilter), intent(inout) :: this - type(XMLNode), intent(in) :: node - - integer :: n - - ! Get center of cylinder and radius - call get_node_value(node, "x", this % x) - call get_node_value(node, "y", this % y) - call get_node_value(node, "r", this % r) - - ! Get specified order - call get_node_value(node, "order", n) - this % order = n - this % n_bins = this % calc_n_bins() - end subroutine from_xml_zn - - subroutine get_all_bins_zn(this, p, estimator, match) - class(ZernikeFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - type(TallyFilterMatch), intent(inout) :: match - - integer :: i - real(8) :: x, y, r, theta - real(C_DOUBLE) :: zn(this % n_bins) - - ! Determine normalized (r,theta) positions - x = p % coord(1) % xyz(1) - this % x - y = p % coord(1) % xyz(2) - this % y - r = sqrt(x*x + y*y)/this % r - if (r <= ONE) then - theta = atan2(y, x) - - ! Get moments for Zernike polynomial orders 0..n - call calc_zn(this % order, r, theta, zn) - - do i = 1, this % n_bins - call match % bins % push_back(i) - call match % weights % push_back(zn(i)) - end do - endif - end subroutine get_all_bins_zn - - subroutine to_statepoint_zn(this, filter_group) - class(ZernikeFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "zernike") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "order", this % order) - call write_dataset(filter_group, "x", this % x) - call write_dataset(filter_group, "y", this % y) - call write_dataset(filter_group, "r", this % r) - end subroutine to_statepoint_zn - - function text_label_zn(this, bin) result(label) - class(ZernikeFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - integer :: n, m - integer :: first, last - - do n = 0, this % order - last = (n + 1)*(n + 2)/2 - if (bin <= last) then - first = last - n - m = -n + (bin - first)*2 - label = "Zernike expansion, Z" // trim(to_str(n)) // "," & - // trim(to_str(m)) - exit - end if - end do - end function text_label_zn - -!=============================================================================== -! ZernikeRadialFilter methods -!=============================================================================== - - function calc_n_bins_zn_rad(this) result(num_n_bins) - class(ZernikeRadialFilter), intent(in) :: this - integer :: n - integer :: num_n_bins - - n = this % order - num_n_bins = n/2 + 1 - end function calc_n_bins_zn_rad - - subroutine get_all_bins_zn_rad(this, p, estimator, match) - class(ZernikeRadialFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - type(TallyFilterMatch), intent(inout) :: match - - integer :: i - real(8) :: x, y, r - real(C_DOUBLE) :: zn_rad(this % n_bins) - - ! Determine normalized (r,theta) positions - x = p % coord(1) % xyz(1) - this % x - y = p % coord(1) % xyz(2) - this % y - r = sqrt(x*x + y*y)/this % r - if (r <= ONE) then - - ! Get moments for even order Zernike polynomial orders 0..n - call calc_zn_rad(this % order, r, zn_rad) - - do i = 1, this % n_bins - call match % bins % push_back(i) - call match % weights % push_back(zn_rad(i)) - end do - endif - end subroutine get_all_bins_zn_rad - - subroutine to_statepoint_zn_rad(this, filter_group) - class(ZernikeRadialFilter), intent(in) :: this - integer(HID_T), intent(in) :: filter_group - - call write_dataset(filter_group, "type", "zernikeradial") - call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "order", this % order) - call write_dataset(filter_group, "x", this % x) - call write_dataset(filter_group, "y", this % y) - call write_dataset(filter_group, "r", this % r) - end subroutine to_statepoint_zn_rad - - function text_label_zn_rad(this, bin) result(label) - class(ZernikeRadialFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - - label = "Zernike expansion, Z" // trim(to_str(2*(bin-1))) // ",0" - end function text_label_zn_rad - -!=============================================================================== -! C API FUNCTIONS -!=============================================================================== - - function openmc_zernike_filter_get_order(index, order) result(err) bind(C) - ! Get the order of an expansion filter - integer(C_INT32_T), value :: index - integer(C_INT), intent(out) :: order - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (ZernikeFilter) - order = f % order - type is (ZernikeRadialFilter) - order = f % order - class default - err = E_INVALID_TYPE - call set_errmsg("Not a Zernike filter.") - end select - end if - end function openmc_zernike_filter_get_order - - - function openmc_zernike_filter_get_params(index, x, y, r) result(err) bind(C) - ! Get the Zernike filter parameters - integer(C_INT32_T), value :: index - real(C_DOUBLE), intent(out) :: x - real(C_DOUBLE), intent(out) :: y - real(C_DOUBLE), intent(out) :: r - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - class is (ZernikeFilter) - x = f % x - y = f % y - r = f % r - class default - err = E_INVALID_TYPE - call set_errmsg("Not a Zernike filter.") - end select - end if - end function openmc_zernike_filter_get_params - - - function openmc_zernike_filter_set_order(index, order) result(err) bind(C) - ! Set the order of an expansion filter - integer(C_INT32_T), value :: index - integer(C_INT), value :: order - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - type is (ZernikeFilter) - f % order = order - f % n_bins = ((order + 1)*(order + 2))/2 - type is (ZernikeRadialFilter) - f % order = order - f % n_bins = order/2 + 1 - class default - err = E_INVALID_TYPE - call set_errmsg("Not a Zernike filter.") - end select - end if - end function openmc_zernike_filter_set_order - - - function openmc_zernike_filter_set_params(index, x, y, r) result(err) bind(C) - ! Set the Zernike filter parameters - integer(C_INT32_T), value :: index - real(C_DOUBLE), intent(in), optional :: x - real(C_DOUBLE), intent(in), optional :: y - real(C_DOUBLE), intent(in), optional :: r - integer(C_INT) :: err - - err = verify_filter(index) - if (err == 0) then - select type (f => filters(index) % obj) - class is (ZernikeFilter) - if (present(x)) f % x = x - if (present(y)) f % y = y - if (present(r)) f % r = r - class default - err = E_INVALID_TYPE - call set_errmsg("Not a Zernike filter.") - end select - end if - end function openmc_zernike_filter_set_params - -end module tally_filter_zernike diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index db84056812..0b80255d50 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -136,10 +136,10 @@ module tally_header ! 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 + real(C_DOUBLE), public, bind(C) :: global_tally_collision + real(C_DOUBLE), public, bind(C) :: global_tally_absorption + real(C_DOUBLE), public, bind(C) :: global_tally_tracklength + real(C_DOUBLE), public, bind(C) :: global_tally_leakage !$omp threadprivate(global_tally_collision, global_tally_absorption, & !$omp& global_tally_tracklength, global_tally_leakage) @@ -153,7 +153,7 @@ module tally_header ! Normalization for statistics integer(C_INT32_T), public, bind(C) :: n_realizations = 0 ! # of independent realizations - real(8), public :: total_weight ! total starting particle weight in realization + real(C_DOUBLE), public, bind(C) :: total_weight ! total starting particle weight in realization contains @@ -344,7 +344,7 @@ contains this % estimator = ESTIMATOR_ANALOG type is (SphericalHarmonicsFilter) j = FILTER_SPH_HARMONICS - if (filt % cosine == COSINE_SCATTER) then + if (filt % cosine() == COSINE_SCATTER) then this % estimator = ESTIMATOR_ANALOG end if type is (SpatialLegendreFilter) @@ -392,7 +392,7 @@ contains ! tallies.xml file. !=============================================================================== - subroutine configure_tallies() + subroutine configure_tallies() bind(C) integer :: i @@ -413,6 +413,13 @@ contains !=============================================================================== subroutine free_memory_tally() + interface + subroutine free_memory_tally_c() bind(C) + end subroutine free_memory_tally_c + end interface + + call free_memory_tally_c() + n_tallies = 0 if (allocated(tallies)) deallocate(tallies) call tally_dict % clear() @@ -681,14 +688,19 @@ contains ! 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_SIZE_T), 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) + + ! Note that shape is reversed since it is assumed to be used from + ! C/C++ code + shape_(1) = size(t % results, 3) + shape_(2) = size(t % results, 2) + shape_(3) = size(t % results, 1) err = 0 else err = E_ALLOCATE diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 27aa5ec50e..f5efd04378 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -29,7 +29,7 @@ contains ! and predicts the number of remainining batches to convergence. !=============================================================================== - subroutine check_triggers() + subroutine check_triggers() bind(C) implicit none @@ -173,8 +173,8 @@ contains ! Initialize bins, filter level do j = 1, size(t % filter) - call filter_matches(t % filter(j)) % bins % clear() - call filter_matches(t % filter(j)) % bins % push_back(0) + call filter_matches(t % filter(j)) % bins_clear() + call filter_matches(t % filter(j)) % bins_push_back(0) end do FILTER_LOOP: do filter_index = 1, t % n_filter_bins @@ -264,13 +264,13 @@ contains i_filter_surf = t % filter(t % find_filter(FILTER_SURFACE)) select type(filt => filters(i_filter_mesh) % obj) type is (MeshFilter) - m = meshes(filt % mesh) + m = meshes(filt % mesh()) end select ! 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 filter_matches(t % filter(j)) % bins_clear() + call filter_matches(t % filter(j)) % bins_push_back(1) end do ! determine how many energyin bins there are @@ -296,20 +296,20 @@ contains ! Get the indices for this cell call m % get_indices_from_bin(i, ijk) - filter_matches(i_filter_mesh) % bins % data(1) = i + call filter_matches(i_filter_mesh) % bins_set_data(1, i) do l = 1, n if (print_ebin) then - filter_matches(i_filter_ein) % bins % data(1) = l + call filter_matches(i_filter_ein) % bins_set_data(1, l) end if ! Left Surface - filter_matches(i_filter_surf) % bins % data(1) = OUT_LEFT + call filter_matches(i_filter_surf) % bins_set_data(1, OUT_LEFT) filter_index = 1 do j = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins % data(1) - 1) * t % stride(j) + bins_data(1) - 1) * t % stride(j) end do call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then @@ -321,11 +321,11 @@ contains trigger % variance = std_dev**2 ! Right Surface - filter_matches(i_filter_surf) % bins % data(1) = OUT_RIGHT + call filter_matches(i_filter_surf) % bins_set_data(1, OUT_RIGHT) filter_index = 1 do j = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins % data(1) - 1) * t % stride(j) + bins_data(1) - 1) * t % stride(j) end do call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then @@ -337,11 +337,11 @@ contains trigger % variance = trigger % std_dev**2 ! Back Surface - filter_matches(i_filter_surf) % bins % data(1) = OUT_BACK + call filter_matches(i_filter_surf) % bins_set_data(1, OUT_BACK) filter_index = 1 do j = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins % data(1) - 1) * t % stride(j) + bins_data(1) - 1) * t % stride(j) end do call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then @@ -353,11 +353,11 @@ contains trigger % variance = trigger % std_dev**2 ! Front Surface - filter_matches(i_filter_surf) % bins % data(1) = OUT_FRONT + call filter_matches(i_filter_surf) % bins_set_data(1, OUT_FRONT) filter_index = 1 do j = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins % data(1) - 1) * t % stride(j) + bins_data(1) - 1) * t % stride(j) end do call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then @@ -369,11 +369,11 @@ contains trigger % variance = trigger % std_dev**2 ! Bottom Surface - filter_matches(i_filter_surf) % bins % data(1) = OUT_BOTTOM + call filter_matches(i_filter_surf) % bins_set_data(1, OUT_BOTTOM) filter_index = 1 do j = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins % data(1) - 1) * t % stride(j) + bins_data(1) - 1) * t % stride(j) end do call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then @@ -385,11 +385,11 @@ contains trigger % variance = trigger % std_dev**2 ! Top Surface - filter_matches(i_filter_surf) % bins % data(1) = OUT_TOP + call filter_matches(i_filter_surf) % bins_set_data(1, OUT_TOP) filter_index = 1 do j = 1, size(t % filter) filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins % data(1) - 1) * t % stride(j) + bins_data(1) - 1) * t % stride(j) end do call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then diff --git a/src/timer.cpp b/src/timer.cpp new file mode 100644 index 0000000000..f1c2114467 --- /dev/null +++ b/src/timer.cpp @@ -0,0 +1,85 @@ +#include "openmc/timer.h" + +namespace openmc { + +//============================================================================== +// Timer implementation +//============================================================================== + +void Timer::start () +{ + running_ = true; + start_ = clock::now(); +} + +void Timer::stop() +{ + elapsed_ = elapsed(); + running_ = false; +} + +void Timer::reset() +{ + running_ = false; + elapsed_ = 0.0; +} + +double Timer::elapsed() +{ + if (running_) { + std::chrono::duration diff = clock::now() - start_; + return elapsed_ + diff.count(); + } else { + return elapsed_; + } +} + +//============================================================================== +// Global variables +//============================================================================== + +Timer time_active; +Timer time_bank; +Timer time_bank_sample; +Timer time_bank_sendrecv; +Timer time_finalize; +Timer time_inactive; +Timer time_initialize; +Timer time_tallies; +Timer time_total; +Timer time_transport; + +//============================================================================== +// Fortran compatibility +//============================================================================== + +extern "C" double time_active_elapsed() { return time_active.elapsed(); } +extern "C" double time_bank_elapsed() { return time_bank.elapsed(); } +extern "C" double time_bank_sample_elapsed() { return time_bank_sample.elapsed(); } +extern "C" double time_bank_sendrecv_elapsed() { return time_bank_sendrecv.elapsed(); } +extern "C" double time_finalize_elapsed() { return time_finalize.elapsed(); } +extern "C" double time_inactive_elapsed() { return time_inactive.elapsed(); } +extern "C" double time_initialize_elapsed() { return time_initialize.elapsed(); } +extern "C" double time_tallies_elapsed() { return time_tallies.elapsed(); } +extern "C" double time_total_elapsed() { return time_total.elapsed(); } +extern "C" double time_transport_elapsed() { return time_transport.elapsed(); } + +//============================================================================== +// Non-member functions +//============================================================================== + +void reset_timers() +{ + time_active.reset(); + time_bank.reset(); + time_bank_sample.reset(); + time_bank_sendrecv.reset(); + time_finalize.reset(); + time_inactive.reset(); + time_initialize.reset(); + time_tallies.reset(); + time_total.reset(); + time_transport.reset(); +} + +} // namespace openmc diff --git a/src/timer_header.F90 b/src/timer_header.F90 index a147a551df..2064f7ae66 100644 --- a/src/timer_header.F90 +++ b/src/timer_header.F90 @@ -1,9 +1,56 @@ module timer_header + use, intrinsic :: ISO_C_BINDING + use constants, only: ZERO implicit none + interface + function time_active_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_bank_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_bank_sample_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_bank_sendrecv_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_finalize_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_inactive_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_initialize_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_tallies_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_total_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + function time_transport_elapsed() result(t) bind(C) + import C_DOUBLE + real(C_DOUBLE) :: t + end function + subroutine reset_timers() bind(C) + end subroutine + end interface + !=============================================================================== ! TIMER represents a timer that can be started and stopped to measure how long ! different routines run. The intrinsic routine system_clock is used to measure @@ -25,18 +72,7 @@ module timer_header ! ============================================================================ ! 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 @@ -101,4 +137,12 @@ contains self % elapsed = ZERO end subroutine timer_reset +!=============================================================================== +! RESET_TIMERS resets timers on the Fortran side +!=============================================================================== + + subroutine reset_timers_f() bind(C) + call time_read_xs % reset() + end subroutine + end module timer_header diff --git a/src/tracking.F90 b/src/tracking.F90 index 8cf2fc05c7..038c7fdb54 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -17,7 +17,6 @@ module tracking use nuclide_header use particle_header use physics, only: collision - use physics_mg, only: collision_mg use random_lcg, only: prn, prn_set_stream use settings use simulation_header @@ -33,13 +32,23 @@ module tracking implicit none + interface + subroutine collision_mg(p, energy_bin_avg, material_xs) bind(C) + import Particle, C_DOUBLE, MaterialMacroXS + type(Particle), intent(inout) :: p + real(C_DOUBLE), intent(in) :: energy_bin_avg(*) + type(MaterialMacroXS), intent(in) :: material_xs + end subroutine collision_mg + + end interface + contains !=============================================================================== ! TRANSPORT encompasses the main logic for moving a particle through geometry. !=============================================================================== - subroutine transport(p) + subroutine transport(p) bind(C) type(Particle), intent(inout) :: p @@ -226,7 +235,7 @@ contains if (run_CE) then call collision(p) else - call collision_mg(p) + call collision_mg(p, energy_bin_avg, material_xs) end if ! Score collision estimator tallies -- this is done after a collision @@ -278,8 +287,7 @@ contains ! Check for secondary particles if this particle is dead if (.not. p % alive) then if (p % n_secondary > 0) then - call particle_from_source(p, p % secondary_bank(p % n_secondary), & - run_CE, energy_bin_avg) + call particle_from_source(p, p % secondary_bank(p % n_secondary)) p % n_secondary = p % n_secondary - 1 n_event = 0 @@ -313,7 +321,9 @@ contains real(8) :: norm ! "norm" of surface normal real(8) :: xyz(3) ! Saved global coordinate integer :: i_surface ! index in surfaces +#ifdef DAGMC integer :: i_cell ! index of new cell +#endif logical :: rotational ! if rotational periodic BC applied logical :: found ! particle found in universe? class(Surface), pointer :: surf diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index f7ded73611..5379a3b2e8 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -138,16 +138,35 @@ contains integer :: min_samples ! minimum number of samples per process integer :: remainder ! leftover samples from uneven divide #ifdef OPENMC_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 + integer(C_INT) :: n ! number of materials + integer(C_INT), allocatable :: data(:) ! array used to send number of hits #endif real(8) :: f ! fraction of hits real(8) :: var_f ! variance of fraction of hits real(8) :: volume_sample ! total volume of sampled region real(8) :: atoms(2, size(nuclides)) +#ifdef OPENMC_MPI + interface + subroutine send_int(buffer, count, dest, tag) bind(C) + import C_INT + integer(C_INT), intent(in) :: buffer + integer(C_INT), value :: count + integer(C_INT), value :: dest + integer(C_INT), value :: tag + end subroutine + + subroutine recv_int(buffer, count, source, tag) bind(C) + import C_INT + integer(C_INT), intent(out) :: buffer + integer(C_INT), value :: count + integer(C_INT), value :: source + integer(C_INT), value :: tag + end subroutine + end interface +#endif + ! Divide work over MPI processes min_samples = this % samples / n_procs remainder = mod(this % samples, n_procs) @@ -285,12 +304,10 @@ contains if (master) then #ifdef OPENMC_MPI do j = 1, n_procs - 1 - call MPI_RECV(n, 1, MPI_INTEGER, j, 0, mpi_intracomm, & - MPI_STATUS_IGNORE, mpi_err) + call recv_int(n, 1, j, 0) allocate(data(2*n)) - call MPI_RECV(data, 2*n, MPI_INTEGER, j, 1, mpi_intracomm, & - MPI_STATUS_IGNORE, mpi_err) + call recv_int(data(1), 2*n, j, 1) do k = 0, n - 1 do m = 1, master_indices(i_domain) % size() if (data(2*k + 1) == master_indices(i_domain) % data(m)) then @@ -353,8 +370,8 @@ contains data(2*k + 2) = master_hits(i_domain) % data(k + 1) end do - call MPI_SEND(n, 1, MPI_INTEGER, 0, 0, mpi_intracomm, mpi_err) - call MPI_SEND(data, 2*n, MPI_INTEGER, 0, 1, mpi_intracomm, mpi_err) + call send_int(n, 1, 0, 0) + call send_int(data(1), 2*n, 0, 1) deallocate(data) #endif end if diff --git a/tests/regression_tests/dagmc/inputs_true.dat b/tests/regression_tests/dagmc/inputs_true.dat new file mode 100644 index 0000000000..df8e3267e7 --- /dev/null +++ b/tests/regression_tests/dagmc/inputs_true.dat @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 0 + + + -4 -4 -4 4 4 4 + + + true + + + + + 1 + + + 1 + total + + diff --git a/tests/regression_tests/dagmc/materials.xml b/tests/regression_tests/dagmc/materials.xml deleted file mode 100644 index af1b505db2..0000000000 --- a/tests/regression_tests/dagmc/materials.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/tests/regression_tests/dagmc/settings.xml b/tests/regression_tests/dagmc/settings.xml deleted file mode 100644 index 5083623787..0000000000 --- a/tests/regression_tests/dagmc/settings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - eigenvalue - true - 5 - 0 - 100 - - - - -4 -4 -4 4 4 4 - - - - diff --git a/tests/regression_tests/dagmc/tallies.xml b/tests/regression_tests/dagmc/tallies.xml deleted file mode 100644 index 9b8b00cd63..0000000000 --- a/tests/regression_tests/dagmc/tallies.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - 1 - - - - 1 - total - - - diff --git a/tests/regression_tests/dagmc/test.py b/tests/regression_tests/dagmc/test.py index 943245d27e..6db936d12b 100644 --- a/tests/regression_tests/dagmc/test.py +++ b/tests/regression_tests/dagmc/test.py @@ -1,12 +1,49 @@ -from tests.testing_harness import TestHarness -import os -import pytest import openmc +import openmc.capi +from openmc.stats import Box + +import pytest +from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( not openmc.capi.dagmc_enabled, reason="DAGMC CAD geometry is not enabled.") def test_dagmc(): - harness = TestHarness('statepoint.5.h5') - harness.main() + model = openmc.model.Model() + + # settings + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 100 + + source = openmc.Source(space=Box([-4, -4, -4], + [ 4, 4, 4])) + model.settings.source = source + + model.settings.dagmc = True + + # tally + tally = openmc.Tally() + tally.scores = ['total'] + tally.filters = [openmc.CellFilter(1)] + model.tallies = [tally] + + # materials + u235 = openmc.Material() + u235.add_nuclide('U235', 1.0, 'ao') + u235.set_density('g/cc', 11) + u235.id = 40 + + water = openmc.Material() + water.add_nuclide('H1', 2.0, 'ao') + water.add_nuclide('O16', 1.0, 'ao') + water.add_s_alpha_beta('c_H_in_H2O') + water.set_density('g/cc', 1.0) + water.id = 41 + + mats = openmc.Materials([u235, water]) + model.materials = mats + + harness = PyAPITestHarness('statepoint.5.h5', model=model) + harness.main() diff --git a/tests/regression_tests/plot_voxel/__init__.py b/tests/regression_tests/plot_voxel/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/plot_voxel/geometry.xml b/tests/regression_tests/plot_voxel/geometry.xml new file mode 100644 index 0000000000..83619d9f78 --- /dev/null +++ b/tests/regression_tests/plot_voxel/geometry.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tests/regression_tests/plot_voxel/materials.xml b/tests/regression_tests/plot_voxel/materials.xml new file mode 100644 index 0000000000..90b3542675 --- /dev/null +++ b/tests/regression_tests/plot_voxel/materials.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/regression_tests/plot_voxel/plots.xml b/tests/regression_tests/plot_voxel/plots.xml new file mode 100644 index 0000000000..833329b427 --- /dev/null +++ b/tests/regression_tests/plot_voxel/plots.xml @@ -0,0 +1,10 @@ + + + + + 50 50 10 + 0. 0. 0. + 20 20 10 + + + diff --git a/tests/regression_tests/plot_voxel/results_true.dat b/tests/regression_tests/plot_voxel/results_true.dat new file mode 100644 index 0000000000..41193c9bc3 --- /dev/null +++ b/tests/regression_tests/plot_voxel/results_true.dat @@ -0,0 +1 @@ +20a0c3598bb698efa4bba65c5e55d71f4385e5b1bcf0067964e45001c12f5c0a729935a96409c760dbd3ec439ae6c676fce77d6e578b41f8f3e0ac68c9277acb \ No newline at end of file diff --git a/tests/regression_tests/plot_voxel/settings.xml b/tests/regression_tests/plot_voxel/settings.xml new file mode 100644 index 0000000000..adf256d2d4 --- /dev/null +++ b/tests/regression_tests/plot_voxel/settings.xml @@ -0,0 +1,13 @@ + + + + plot + + + 5 4 3 + -10 -10 -10 + 10 10 10 + + 1 + + diff --git a/tests/regression_tests/plot_voxel/test.py b/tests/regression_tests/plot_voxel/test.py new file mode 100644 index 0000000000..d2767e2f18 --- /dev/null +++ b/tests/regression_tests/plot_voxel/test.py @@ -0,0 +1,62 @@ +import glob +import hashlib +import os +from subprocess import check_call + +import h5py +import openmc +import pytest + +from tests.testing_harness import TestHarness +from tests.regression_tests import config + + +vtk = pytest.importorskip('vtk') +class PlotVoxelTestHarness(TestHarness): + """Specialized TestHarness for running OpenMC voxel plot tests.""" + def __init__(self, plot_names): + super().__init__(None) + self._plot_names = plot_names + + def _run_openmc(self): + openmc.plot_geometry(openmc_exec=config['exe']) + + check_call(['../../../scripts/openmc-voxel-to-vtk'] + + glob.glob('plot_4.h5')) + + def _test_output_created(self): + """Make sure *.ppm has been created.""" + for fname in self._plot_names: + assert os.path.exists(fname), 'Plot output file does not exist.' + + def _cleanup(self): + super()._cleanup() + for fname in self._plot_names: + if os.path.exists(fname): + os.remove(fname) + + def _get_results(self): + """Return a string hash of the plot files.""" + outstr = bytes() + + for fname in self._plot_names: + if fname.endswith('.h5'): + # Add voxel data to results + with h5py.File(fname, 'r') as fh: + outstr += fh.attrs['filetype'] + outstr += fh.attrs['num_voxels'].tostring() + outstr += fh.attrs['lower_left'].tostring() + outstr += fh.attrs['voxel_width'].tostring() + outstr += fh['data'].value.tostring() + + # Hash the information and return. + sha512 = hashlib.sha512() + sha512.update(outstr) + outstr = sha512.hexdigest() + + return outstr + + +def test_plot_voxel(): + harness = PlotVoxelTestHarness(('plot_4.h5', 'plot.vti')) + harness.main() diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index ae2d927191..059557dbf3 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -168,6 +168,8 @@ def test_tally(capi_init): openmc.capi.MaterialFilter(uid=1) mats = openmc.capi.materials f = openmc.capi.MaterialFilter([mats[2], mats[1]]) + assert f.bins[0] == mats[2] + assert f.bins[1] == mats[1] t.filters = [f] assert t.filters == [f] diff --git a/tests/unit_tests/test_data_decay.py b/tests/unit_tests/test_data_decay.py index be8ad7d64e..b925506dfb 100644 --- a/tests/unit_tests/test_data_decay.py +++ b/tests/unit_tests/test_data_decay.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from collections import Mapping +from collections.abc import Mapping import os from math import log diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index 34686b567f..46a9dbd02b 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from collections import Mapping +from collections.abc import Mapping import os import numpy as np diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index bbebcb34a2..3cf036aa2c 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from collections import Mapping, Callable +from collections.abc import Mapping, Callable import os import numpy as np diff --git a/tests/unit_tests/test_data_photon.py b/tests/unit_tests/test_data_photon.py index eecef73c4e..5036d08fd0 100644 --- a/tests/unit_tests/test_data_photon.py +++ b/tests/unit_tests/test_data_photon.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from collections import Mapping, Callable +from collections.abc import Mapping, Callable import os import numpy as np diff --git a/tests/unit_tests/test_data_thermal.py b/tests/unit_tests/test_data_thermal.py index 9def78b38d..7d7d4e964a 100644 --- a/tests/unit_tests/test_data_thermal.py +++ b/tests/unit_tests/test_data_thermal.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from collections import Callable +from collections.abc import Callable import os import numpy as np diff --git a/tools/ci/download-xs.sh b/tools/ci/download-xs.sh index ff430ab2c5..eed9e23c41 100755 --- a/tools/ci/download-xs.sh +++ b/tools/ci/download-xs.sh @@ -14,6 +14,6 @@ fi # Download multipole library if [[ ! -e $HOME/WMP_Library/092235.h5 ]]; then - wget -q https://github.com/mit-crpg/WMP_Library/releases/download/v1.0/WMP_Library_v1.0.tar.gz - tar -C $HOME -xzf WMP_Library_v1.0.tar.gz + wget -q https://github.com/mit-crpg/WMP_Library/releases/download/v1.1/WMP_Library_v1.1.tar.gz + tar -C $HOME -xzf WMP_Library_v1.1.tar.gz fi diff --git a/tools/ci/travis-install-dagmc.sh b/tools/ci/travis-install-dagmc.sh index 3cd5ee6ffc..ebe436210d 100755 --- a/tools/ci/travis-install-dagmc.sh +++ b/tools/ci/travis-install-dagmc.sh @@ -19,8 +19,10 @@ cd $HOME mkdir MOAB && cd MOAB git clone -b $MOAB_BRANCH $MOAB_REPO mkdir build && cd build -cmake ../moab -DENABLE_HDF5=ON -DCMAKE_INSTALL_PREFIX=$MOAB_INSTALL_DIR +cmake ../moab -DENABLE_HDF5=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$MOAB_INSTALL_DIR make -j && make -j test install +cmake ../moab -DBUILD_SHARED_LIBS=OFF +make -j install rm -rf $HOME/MOAB/moab export LD_LIBRARY_PATH=$MOAB_INSTALL_DIR/lib:$LD_LIBRARY_PATH @@ -28,7 +30,7 @@ export LD_LIBRARY_PATH=$MOAB_INSTALL_DIR/lib:$LD_LIBRARY_PATH mkdir DAGMC && cd DAGMC git clone -b $DAGMC_BRANCH $DAGMC_REPO mkdir build && cd build -cmake ../dagmc -DBUILD_TALLY=ON -DCMAKE_INSTALL_PREFIX=$DAGMC_INSTALL_DIR +cmake ../dagmc -DBUILD_TALLY=ON -DCMAKE_INSTALL_PREFIX=$DAGMC_INSTALL_DIR -DMOAB_DIR=$MOAB_INSTALL_DIR make -j install rm -rf $HOME/DAGMC/dagmc export LD_LIBRARY_PATH=$DAGMC_INSTALL_DIR/lib:$LD_LIBRARY_PATH diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index c10c222793..670f28f33b 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -23,8 +23,13 @@ fi # Build and install OpenMC executable python tools/ci/travis-install.py -# Install Python API in editable mode -pip install -e .[test] +if [[ $TRAVIS_PYTHON_VERSION == "3.7" ]]; then + # Install Python API in editable mode + pip install -e .[test] +else + # Conditionally install vtk + pip install -e .[test,vtk] +fi # For uploading to coveralls pip install coveralls