diff --git a/docs/source/pythonapi/examples/mgxs-part-i.ipynb b/docs/source/pythonapi/examples/mgxs-part-i.ipynb index b95bea462..7c5132100 100644 --- a/docs/source/pythonapi/examples/mgxs-part-i.ipynb +++ b/docs/source/pythonapi/examples/mgxs-part-i.ipynb @@ -383,6 +383,9 @@ "* `ScatterMatrixXS`\n", "* `NuScatterMatrixXS`\n", "* `Chi`\n", + "* `ChiPrompt`\n", + "* `InverseVelocity`\n", + "* `PromptNuFissionXS`\n", "\n", "These classes provide us with an interface to generate the tally inputs as well as perform post-processing of OpenMC's tally data to compute the respective multi-group cross sections. In this case, let's create the multi-group total, absorption and scattering cross sections with our 2-group structure." ] @@ -1164,21 +1167,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.2" + "pygments_lexer": "ipython2", + "version": "2.7.11" } }, "nbformat": 4, diff --git a/docs/source/pythonapi/examples/mgxs-part-iii.ipynb b/docs/source/pythonapi/examples/mgxs-part-iii.ipynb index 789366d3a..a1eaa7ad8 100644 --- a/docs/source/pythonapi/examples/mgxs-part-iii.ipynb +++ b/docs/source/pythonapi/examples/mgxs-part-iii.ipynb @@ -549,6 +549,9 @@ "* `ScatterMatrixXS` (`\"scatter matrix\"`)\n", "* `NuScatterMatrixXS` (`\"nu-scatter matrix\"`)\n", "* `Chi` (`\"chi\"`)\n", + "* `ChiPrompt` (`\"chi prompt\"`)\n", + "* `InverseVelocity` (`\"inverse-velocity\"`)\n", + "* `PromptNuFissionXS` (`\"prompt-nu-fission\"`)\n", "\n", "In this case, let's create the multi-group cross sections needed to run an OpenMOC simulation to verify the accuracy of our cross sections. In particular, we will define `\"transport\"`, `\"nu-fission\"`, `'\"fission\"`, `\"nu-scatter matrix\"` and `\"chi\"` cross sections for our `Library`.\n", "\n", @@ -571,7 +574,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we must specify the type of domain over which we would like the `Library` to compute multi-group cross sections. The domain type corresponds to the type of tally filter to be used in the tallies created to compute multi-group cross sections. At the present time, the `Library` supports `\"material,\"` `\"cell,\"` and `\"universe\"` domain types. We will use a `\"cell\"` domain type here to compute cross sections in each of the cells in the fuel assembly geometry.\n", + "Now we must specify the type of domain over which we would like the `Library` to compute multi-group cross sections. The domain type corresponds to the type of tally filter to be used in the tallies created to compute multi-group cross sections. At the present time, the `Library` supports `\"material\"`, `\"cell\"`, `\"universe\"`, and `\"mesh\"` domain types. We will use a `\"cell\"` domain type here to compute cross sections in each of the cells in the fuel assembly geometry.\n", "\n", "**Note:** By default, the `Library` class will instantiate `MGXS` objects for each and every domain (material, cell or universe) in the geometry of interest. However, one may specify a subset of these domains to the `Library.domains` property. In our case, we wish to compute multi-group cross sections in each and every cell since they will be needed in our downstream OpenMOC calculation on the identical combinatorial geometry mesh." ] @@ -1596,21 +1599,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.2" + "pygments_lexer": "ipython2", + "version": "2.7.11" } }, "nbformat": 4, diff --git a/docs/source/pythonapi/examples/mgxs-part-iv.ipynb b/docs/source/pythonapi/examples/mgxs-part-iv.ipynb index ae015b0a6..eaae92f7c 100644 --- a/docs/source/pythonapi/examples/mgxs-part-iv.ipynb +++ b/docs/source/pythonapi/examples/mgxs-part-iv.ipynb @@ -519,9 +519,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we must specify the type of domain over which we would like the `Library` to compute multi-group cross sections. The domain type corresponds to the type of tally filter to be used in the tallies created to compute multi-group cross sections. At the present time, the `Library` supports \"material,\" \"cell,\" and \"universe\" domain types. In this simple example, we wish to compute multi-group cross sections only for each material andtherefore will use a \"material\" domain type.\n", + "Now we must specify the type of domain over which we would like the `Library` to compute multi-group cross sections. The domain type corresponds to the type of tally filter to be used in the tallies created to compute multi-group cross sections. At the present time, the `Library` supports \"material\" \"cell\", \"universe\", and \"mesh\" domain types. In this simple example, we wish to compute multi-group cross sections only for each material and therefore will use a \"material\" domain type.\n", "\n", - "**Note:** By default, the `Library` class will instantiate `MGXS` objects for each and every domain (material, cell or universe) in the geometry of interest. However, one may specify a subset of these domains to the `Library.domains` property." + "**Note:** By default, the `Library` class will instantiate `MGXS` objects for each and every domain (material, cell, universe, or mesh) in the geometry of interest. However, one may specify a subset of these domains to the `Library.domains` property." ] }, { @@ -1437,21 +1437,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.2" + "pygments_lexer": "ipython2", + "version": "2.7.11" } }, "nbformat": 4, diff --git a/docs/source/pythonapi/examples/nuclear-data.ipynb b/docs/source/pythonapi/examples/nuclear-data.ipynb index 0c24079cf..82be4a113 100644 --- a/docs/source/pythonapi/examples/nuclear-data.ipynb +++ b/docs/source/pythonapi/examples/nuclear-data.ipynb @@ -32,7 +32,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The first thing we want to do is to read an ACE file into memory and instantiate a `IncidentNeutron` object. The easiest way to do this is with the `openmc.data.IncidentNeutron.from_ace(...)` factory method." + "## Importing from HDF5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `openmc.data` module can read OpenMC's HDF5-formatted data into Python objects. The easiest way to do this is with the `openmc.data.IncidentNeutron.from_hdf5(...)` factory method. Replace the `filename` variable below with a valid path to an HDF5 data file on your computer." ] }, { @@ -55,10 +62,10 @@ ], "source": [ "# Get filename for Gd-157\n", - "filename ='/opt/data/ace/nndc/293.6K/Gd_157_293.6K.ace'\n", + "filename ='/home/smharper/nuclear-data/nndc-hdf5/Gd157_71c.h5'\n", "\n", - "# Load ACE table into object\n", - "gd157 = openmc.data.IncidentNeutron.from_ace(filename)\n", + "# Load HDF5 data into object\n", + "gd157 = openmc.data.IncidentNeutron.from_hdf5(filename)\n", "gd157" ] }, @@ -66,7 +73,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now that we have our ACE table, we can look at its contents. Let's start off by plotting the total cross section. Reactions are indexed using their \"MT\" number -- a unique identifier for each reaction defined by the ENDF-6 format. The MT number for the total cross section is 1." + "## Cross sections" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From Python, it's easy to explore (and modify) the nuclear data. Let's start off by reading the total cross section. Reactions are indexed using their \"MT\" number -- a unique identifier for each reaction defined by the ENDF-6 format. The MT number for the total cross section is 1." ] }, { @@ -79,18 +93,129 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" + } + ], + "source": [ + "total = gd157[1]\n", + "total" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To find the cross section at a particular energy, 1 eV for example, simply call the reaction's `xs` attribute at that energy. Note that our nuclear data uses MeV as the unit of energy." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "142.6474702147809" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "total.xs(1e-6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `xs` attribute can also be called on an array of energies." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 142.64747021, 38.65417611, 175.40019668])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "total.xs([1e-6, 2e-6, 3e-6])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A quick way to plot cross sections is to use the `energy` attribute of `IncidentNeutron`. This gives an array of all the energy values used in cross section interpolation." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 1.00000000e-11, 1.03250000e-11, 1.06500000e-11, ...,\n", + " 1.95000000e+01, 1.99000000e+01, 2.00000000e+01])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gd157.energy" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" }, { "data": { - "image/png": "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\nAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -98,8 +223,7 @@ } ], "source": [ - "total = gd157[1]\n", - "plt.loglog(total.xs.x, total.xs.y)\n", + "plt.loglog(gd157.energy, total.xs(gd157.energy))\n", "plt.xlabel('Energy (MeV)')\n", "plt.ylabel('Cross section (b)')" ] @@ -115,7 +239,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -145,12 +269,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's suppose we want to look more closely at the (n,2n) reaction." + "Let's suppose we want to look more closely at the (n,2n) reaction. This reaction has an energy threshold" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -161,12 +285,64 @@ "text": [ "Threshold = 6.400881 MeV\n" ] + } + ], + "source": [ + "n2n = gd157[16]\n", + "print('Threshold = {} MeV'.format(n2n.threshold))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The (n,2n) cross section, like all basic cross sections, is represented by the `Tabulated1D` class. The energy and cross section values in the table can be directly accessed with the `x` and `y` attributes. Using the `x` and `y` has the nice benefit of automatically acounting for reaction thresholds." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "n2n.xs" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(6.400881, 20.0)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" }, { "data": { - "image/png": "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\n9zC0aV7vrgAAAABJRU5ErkJggg==\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -174,12 +350,10 @@ } ], "source": [ - "n2n = gd157[16]\n", "plt.plot(n2n.xs.x, n2n.xs.y)\n", "plt.xlabel('Energy (MeV)')\n", "plt.ylabel('Cross section (b)')\n", - "plt.xlim((n2n.xs.x[0], n2n.xs.x[-1]))\n", - "print('Threshold = {} MeV'.format(n2n.threshold))" + "plt.xlim((n2n.xs.x[0], n2n.xs.x[-1]))" ] }, { @@ -191,7 +365,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -203,7 +377,7 @@ " ]" ] }, - "execution_count": 6, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -214,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -222,10 +396,10 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 7, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -244,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -252,39 +426,39 @@ { "data": { "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" ] }, - "execution_count": 8, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -303,16 +477,16 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { - "image/png": "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\nMAzDMAyv/B9ZBCMoWOfF0wAAAABJRU5ErkJggg==\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -333,14 +507,42 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Unresolved resonance probability tables\n", - "\n", - "We can also look at unresolved resonance probability tables which are stored in a `ProbabilityTables` object. In the following example, we'll create a plot showing what the total cross section probability tables look like as a function of incoming energy." + "There is also `summed_reactions` attribute for cross sections (like total) which are built from summing up other cross sections." ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ]\n" + ] + } + ], + "source": [ + "pprint(list(gd157.summed_reactions.values()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the cross sections for these reactions are represented by the `Sum` class rather than `Tabulated1D`. They do not support the `x` and `y` attributes." + ] + }, + { + "cell_type": "code", + "execution_count": 17, "metadata": { "collapsed": false }, @@ -348,18 +550,49 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 10, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gd157[27].xs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unresolved resonance probability tables\n", + "\n", + "We can also look at unresolved resonance probability tables which are stored in a `ProbabilityTables` object. In the following example, we'll create a plot showing what the total cross section probability tables look like as a function of incoming energy." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -411,7 +644,7 @@ " color=cm(value)))\n", "\n", "# Overlay total cross section\n", - "ax.plot(total.xs.x, total.xs.y, 'k')\n", + "ax.plot(gd157.energy, total.xs(gd157.energy), 'k')\n", "\n", "# Make plot pretty and labeled\n", "ax.set_xlim(1e-6, 1e-1)\n", @@ -426,32 +659,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Exporting to HDF5\n", + "## Converting ACE to HDF5\n", "\n", - "To create an HDF5 nuclear data file for a nuclide, we can use the `export_to_hdf5()` method." + "The `openmc.data` package can also read ACE files and output HDF5 files. ACE files can be read with the `openmc.data.IncidentNeutron.from_ace(...)` factory method." ] }, { "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "gd157.export_to_hdf5('gd157.h5', 'w')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's see what's in the HDF5 file." - ] - }, - { - "cell_type": "code", - "execution_count": 12, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -459,29 +674,80 @@ { "data": { "text/plain": [ - "['Gd157.71c']" + "" ] }, - "execution_count": 12, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "h5file = h5py.File('gd157.h5', 'r')\n", - "list(h5file)" + "filename = '/home/smharper/nuclear-data/nndc/293.6K/Gd_157_293.6K.ace'\n", + "gd157_ace = openmc.data.IncidentNeutron.from_ace(filename)\n", + "gd157_ace" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "All reaction data is contained in the `reactions` group under a nuclide. While the group for each reaction is only labeled by its MT value, we can look at the group attributes to get a label for the reaction." + "We can store this formerly ACE data as HDF5 with the `export_to_hdf5()` method." ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 20, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "gd157_ace.export_to_hdf5('gd157.h5', 'w')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With few exceptions, the HDF5 file encodes the same data as the ACE file." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gd157_reconstructed = openmc.data.IncidentNeutron.from_hdf5('gd157.h5')\n", + "gd157_ace[16].xs.y - gd157_reconstructed[16].xs.y" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And one of the best parts of using HDF5 is that it is a widely used format with lots of third-party support. You can use `h5py`, for example, to inspect the data." + ] + }, + { + "cell_type": "code", + "execution_count": 22, "metadata": { "collapsed": false }, @@ -504,6 +770,7 @@ } ], "source": [ + "h5file = h5py.File('gd157.h5', 'r')\n", "main_group = h5file['Gd157.71c/reactions']\n", "for name, obj in sorted(list(main_group.items()))[:10]:\n", " if 'reaction_' in name:\n", @@ -512,7 +779,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -541,9 +808,10 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 24, "metadata": { - "collapsed": false + "collapsed": false, + "scrolled": true }, "outputs": [ { @@ -564,7 +832,7 @@ " 7.77740000e-01])" ] }, - "execution_count": 15, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -572,57 +840,25 @@ "source": [ "n2n_group['xs'].value" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that we can go the other direction, converting data from an HDF5 file into a `NeutronTable` object:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gd157_reconstructed = openmc.data.IncidentNeutron.from_hdf5('gd157.h5')\n", - "gd157[16].xs.y - gd157_reconstructed[16].xs.y" - ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 2", "language": "python", - "name": "python3" + "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.2" + "pygments_lexer": "ipython2", + "version": "2.7.12" } }, "nbformat": 4, diff --git a/docs/source/pythonapi/index.rst b/docs/source/pythonapi/index.rst index 5d45c799d..edb4f62d9 100644 --- a/docs/source/pythonapi/index.rst +++ b/docs/source/pythonapi/index.rst @@ -269,13 +269,16 @@ Multi-group Cross Sections openmc.mgxs.AbsorptionXS openmc.mgxs.CaptureXS openmc.mgxs.Chi + openmc.mgxs.ChiPrompt openmc.mgxs.FissionXS + openmc.mgxs.InverseVelocity openmc.mgxs.KappaFissionXS openmc.mgxs.MultiplicityMatrixXS openmc.mgxs.NuFissionXS openmc.mgxs.NuFissionMatrixXS openmc.mgxs.NuScatterXS openmc.mgxs.NuScatterMatrixXS + openmc.mgxs.PromptNuFissionXS openmc.mgxs.ScatterXS openmc.mgxs.ScatterMatrixXS openmc.mgxs.TotalXS diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index d6802075e..e530097de 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -1764,6 +1764,10 @@ The ```` element accepts the following sub-elements: | |fission. This score type is not used in the | | |multi-group :ref:`energy_mode`. | +----------------------+---------------------------------------------------+ + |prompt-nu-fission |Total production of prompt neutrons due to | + | |fission. This score type is not used in the | + | |multi-group :ref:`energy_mode`. | + +----------------------+---------------------------------------------------+ |nu-fission |Total production of neutrons due to fission. | +----------------------+---------------------------------------------------+ |nu-scatter, |These scores are similar in functionality to their | diff --git a/openmc/__init__.py b/openmc/__init__.py index 0bde0f584..557e13039 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -9,8 +9,8 @@ from openmc.plots import * from openmc.settings import * from openmc.surface import * from openmc.universe import * -from openmc.mgxs_library import * from openmc.mesh import * +from openmc.mgxs_library import * from openmc.filter import * from openmc.trigger import * from openmc.tallies import * diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 7c0dff99d..fe5683c06 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -212,7 +212,7 @@ def check_less_than(name, value, maximum, equality=False): raise ValueError(msg) def check_greater_than(name, value, minimum, equality=False): - """Ensure that an object's value is less than a given value. + """Ensure that an object's value is greater than a given value. Parameters ---------- diff --git a/openmc/data/data.py b/openmc/data/data.py index 21fffc8cb..0365b3794 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -122,10 +122,11 @@ ATOMIC_SYMBOL = {1: 'H', 2: 'He', 3: 'Li', 4: 'Be', 5: 'B', 6: 'C', 7: 'N', 114: 'Fl', 116: 'Lv'} ATOMIC_NUMBER = {value: key for key, value in ATOMIC_SYMBOL.items()} -REACTION_NAME = {1: '(n,total)', 2: '(n,elastic)', 4: '(n,level)', 5: '(n,misc)', 11: '(n,2nd)', - 16: '(n,2n)', 17: '(n,3n)', 18: '(n,fission)', 19: '(n,f)', - 20: '(n,nf)', 21: '(n,2nf)', 22: '(n,na)', 23: '(n,n3a)', - 24: '(n,2na)', 25: '(n,3na)', 28: '(n,np)', 29: '(n,n2a)', +REACTION_NAME = {1: '(n,total)', 2: '(n,elastic)', 4: '(n,level)', + 5: '(n,misc)', 11: '(n,2nd)', 16: '(n,2n)', 17: '(n,3n)', + 18: '(n,fission)', 19: '(n,f)', 20: '(n,nf)', 21: '(n,2nf)', + 22: '(n,na)', 23: '(n,n3a)', 24: '(n,2na)', 25: '(n,3na)', + 27: '(n,absorption)', 28: '(n,np)', 29: '(n,n2a)', 30: '(n,2n2a)', 32: '(n,nd)', 33: '(n,nt)', 34: '(n,nHe-3)', 35: '(n,nd2a)', 36: '(n,nt2a)', 37: '(n,4n)', 38: '(n,3nf)', 41: '(n,2np)', 42: '(n,3np)', 44: '(n,n2p)', 45: '(n,npa)', diff --git a/openmc/data/function.py b/openmc/data/function.py index e2efb4c10..bea6f5e9a 100644 --- a/openmc/data/function.py +++ b/openmc/data/function.py @@ -80,50 +80,46 @@ class Tabulated1D(object): # Get indices for interpolation idx = np.searchsorted(self.x, x, side='right') - 1 - # Find lowest valid index - i_low = np.searchsorted(idx, 0) - + # Loop over interpolation regions for k in range(len(self.breakpoints)): - # Determine which x values are within this interpolation range - i_high = np.searchsorted(idx, self.breakpoints[k] - 1) + # Get indices for the begining and ending of this region + i_begin = self.breakpoints[k-1] - 1 if k > 0 else 0 + i_end = self.breakpoints[k] - 1 - # Get x values and bounding (x,y) pairs - xk = x[i_low:i_high] - xi = self.x[idx[i_low:i_high]] - xi1 = self.x[idx[i_low:i_high] + 1] - yi = self.y[idx[i_low:i_high]] - yi1 = self.y[idx[i_low:i_high] + 1] + # Figure out which idx values lie within this region + contained = (idx >= i_begin) & (idx < i_end) + + xk = x[contained] # x values in this region + xi = self.x[idx[contained]] # low edge of corresponding bins + xi1 = self.x[idx[contained] + 1] # high edge of corresponding bins + yi = self.y[idx[contained]] + yi1 = self.y[idx[contained] + 1] if self.interpolation[k] == 1: # Histogram - y[i_low:i_high] = yi + y[contined] = yi elif self.interpolation[k] == 2: # Linear-linear - y[i_low:i_high] = yi + (xk - xi)/(xi1 - xi)*(yi1 - yi) + y[contained] = yi + (xk - xi)/(xi1 - xi)*(yi1 - yi) elif self.interpolation[k] == 3: # Linear-log - y[i_low:i_high] = yi + np.log(xk/xi)/np.log(xi1/xi)*(yi1 - yi) + y[contained] = yi + np.log(xk/xi)/np.log(xi1/xi)*(yi1 - yi) elif self.interpolation[k] == 4: # Log-linear - y[i_low:i_high] = yi*np.exp((xk - xi)/(xi1 - xi)*np.log(yi1/yi)) + y[contained] = yi*np.exp((xk - xi)/(xi1 - xi)*np.log(yi1/yi)) elif self.interpolation[k] == 5: # Log-log - y[i_low:i_high] = yi*np.exp(np.log(xk/xi)/np.log(xi1/xi)*np.log(yi1/yi)) + y[contained] = (yi*np.exp(np.log(xk/xi)/np.log(xi1/xi) + *np.log(yi1/yi))) - i_low = i_high - - # In some cases, the first/last point of x may be less than the first - # value of self.x due only to precision, so we check if they're close - # and set them equal if so. Otherwise, the interpolated value might be - # out of range (and thus zero) - if np.isclose(x[0], self.x[0], 1e-8): - y[0] = self.y[0] - if np.isclose(x[-1], self.x[-1], 1e-8): - y[-1] = self.y[-1] + # In some cases, x values might be outside the tabulated region due only + # to precision, so we check if they're close and set them equal if so. + y[np.isclose(x, self.x[0], atol=1e-14)] = self.y[0] + y[np.isclose(x, self.x[-1], atol=1e-14)] = self.y[-1] return y if iterable else y[0] diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index d2c9290b6..91cf1ba4b 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -346,6 +346,17 @@ class IncidentNeutron(object): tgroup = group['total_nu'] rx.derived_products.append(Product.from_hdf5(tgroup)) + # Build summed reactions. Start from the highest MT number because high + # MTs never depend on lower MTs. + for mt_sum in sorted(SUM_RULES, reverse=True): + if mt_sum not in data: + xs_components = [data[mt].xs for mt in SUM_RULES[mt_sum] + if mt in data] + if len(xs_components) > 0: + rxn = Reaction(mt_sum) + rxn.xs = Sum(xs_components) + data.summed_reactions[mt_sum] = rxn + # Read unresolved resonance probability tables if 'urr' in group: urr_group = group['urr'] diff --git a/openmc/data/reaction.py b/openmc/data/reaction.py index 62b02048b..ad707d276 100644 --- a/openmc/data/reaction.py +++ b/openmc/data/reaction.py @@ -400,7 +400,7 @@ class Reaction(object): # Read cross section if 'xs' in group: xs = group['xs'].value - rx.xs = Tabulated1D(energy, xs) + rx.xs = Tabulated1D(energy[rx.threshold_idx:], xs) # Determine number of products n_product = 0 diff --git a/openmc/filter.py b/openmc/filter.py index bc8b5bdf7..8a54e05ce 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -162,6 +162,11 @@ class Filter(object): if not isinstance(bins, Iterable): bins = [bins] + # If the bin is 0D numpy array, promote to 1D + elif isinstance(bins, np.ndarray): + if bins.shape == (): + bins.shape = (1,) + # If the bins are in a collection, convert it to a list else: bins = list(bins) @@ -563,7 +568,7 @@ class Filter(object): # Initialize dictionary to build Pandas Multi-index column filter_dict = {} - # Append Mesh ID as outermost index of mult-index + # Append Mesh ID as outermost index of multi-index mesh_key = 'mesh {0}'.format(self.mesh.id) # Find mesh dimensions - use 3D indices for simplicity diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index 179b47330..84571ce23 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -54,9 +54,9 @@ class Library(object): If true, computes cross sections for each nuclide in each domain mgxs_types : Iterable of str The types of cross sections in the library (e.g., ['total', 'scatter']) - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization - domains : Iterable of openmc.Material, openmc.Cell or openmc.Universe + domains : Iterable of openmc.Material, openmc.Cell, openmc.Universe or openmc.Mesh The spatial domain(s) for which MGXS in the Library are computed correction : {'P0', None} Apply the P0 correction to scattering matrices if set to 'P0' @@ -183,6 +183,8 @@ class Library(object): return self.openmc_geometry.get_all_material_cells() elif self.domain_type == 'universe': return self.openmc_geometry.get_all_universes() + elif self.domain_type == 'mesh': + raise ValueError('Unable to get domains for Mesh domain type') else: raise ValueError('Unable to get domains without a domain type') else: @@ -273,6 +275,12 @@ class Library(object): elif self.domain_type == 'universe': cv.check_iterable_type('domain', domains, openmc.Universe) all_domains = self.openmc_geometry.get_all_universes() + elif self.domain_type == 'mesh': + cv.check_iterable_type('domain', domains, openmc.Mesh) + + # The mesh and geometry are independent, so set all_domains + # to the input domains + all_domains = domains else: raise ValueError('Unable to set domains with domain ' 'type "{}"'.format(self.domain_type)) @@ -452,7 +460,7 @@ class Library(object): ---------- domain : Material or Cell or Universe or Integral The material, cell, or universe object of interest (or its ID) - mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', chi'} + mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', chi', 'chi-prompt', 'inverse-velocity', 'prompt-nu-fission'} The type of multi-group cross section object to return Returns @@ -474,6 +482,8 @@ class Library(object): cv.check_type('domain', domain, (openmc.Cell, Integral)) elif self.domain_type == 'universe': cv.check_type('domain', domain, (openmc.Universe, Integral)) + elif self.domain_type == 'mesh': + cv.check_type('domain', domain, (openmc.Mesh, Integral)) # Check that requested domain is included in library if isinstance(domain, Integral): diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 8e6d123f7..e1c2e6221 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -7,6 +7,7 @@ import os import sys import copy import abc +import itertools import numpy as np @@ -14,7 +15,6 @@ import openmc import openmc.checkvalue as cv from openmc.mgxs import EnergyGroups - if sys.version_info[0] >= 3: basestring = str @@ -34,21 +34,24 @@ MGXS_TYPES = ['total', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', - 'chi'] + 'chi', + 'chi-prompt', + 'inverse-velocity', + 'prompt-nu-fission'] # Supported domain types -# TODO: Implement Mesh domains DOMAIN_TYPES = ['cell', 'distribcell', 'universe', - 'material'] + 'material', + 'mesh'] # Supported domain classes -# TODO: Implement Mesh domains _DOMAINS = (openmc.Cell, openmc.Universe, - openmc.Material) + openmc.Material, + openmc.Mesh) class MGXS(object): @@ -63,9 +66,9 @@ class MGXS(object): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -83,9 +86,9 @@ class MGXS(object): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -112,9 +115,10 @@ class MGXS(object): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading - tally data from a statepoint file). + tally data from a statepoint file) and the number of mesh cells for + 'mesh' domain types. num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. @@ -260,6 +264,10 @@ class MGXS(object): # Create a domain Filter object domain_filter = openmc.Filter(self.domain_type, self.domain.id) + # If a mesh domain, give the mesh to the domain filter + if self.domain_type == 'mesh': + domain_filter.mesh = self.domain + # Create each Tally needed to compute the multi group cross section tally_metadata = zip(self.scores, self.tally_keys, self.filters) for score, key, filters in tally_metadata: @@ -375,6 +383,8 @@ class MGXS(object): self._domain_type = 'cell' elif isinstance(domain, openmc.Universe): self._domain_type = 'universe' + elif isinstance(domain, openmc.Mesh): + self._domain_type = 'mesh' @domain_type.setter def domain_type(self, domain_type): @@ -427,11 +437,11 @@ class MGXS(object): Parameters ---------- - mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', chi'} + mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'multiplicity matrix', 'nu-fission matrix', 'chi', 'chi-prompt', 'inverse-velocity', 'prompt-nu-fission'} The type of multi-group cross section object to return - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -482,6 +492,12 @@ class MGXS(object): mgxs = NuFissionMatrixXS(domain, domain_type, energy_groups) elif mgxs_type == 'chi': mgxs = Chi(domain, domain_type, energy_groups) + elif mgxs_type == 'chi-prompt': + mgxs = ChiPrompt(domain, domain_type, energy_groups) + elif mgxs_type == 'inverse-velocity': + mgxs = InverseVelocity(domain, domain_type, energy_groups) + elif mgxs_type == 'prompt-nu-fission': + mgxs = PromptNuFissionXS(domain, domain_type, energy_groups) mgxs.by_nuclide = by_nuclide mgxs.name = name @@ -666,6 +682,8 @@ class MGXS(object): self.domain = statepoint.summary.get_universe_by_id(self.domain.id) elif self.domain_type == 'material': self.domain = statepoint.summary.get_material_by_id(self.domain.id) + elif self.domain_type == 'mesh': + self.domain = statepoint.meshes[self.domain.id] else: msg = 'Unable to load data from a statepoint for domain type {0} ' \ 'which is not yet supported'.format(self.domain_type) @@ -673,7 +691,11 @@ class MGXS(object): # Use tally "slicing" to ensure that tallies correspond to our domain # NOTE: This is important if tally merging was used - if self.domain_type != 'distribcell': + if self.domain_type == 'mesh': + filters = [self.domain_type] + xyz = [range(1, x+1) for x in self.domain.dimension] + filter_bins = [tuple(itertools.product(*xyz))] + elif self.domain_type != 'distribcell': filters = [self.domain_type] filter_bins = [(self.domain.id,)] # Distribcell filters only accept single cell - neglect it when slicing @@ -704,7 +726,7 @@ class MGXS(object): def get_xs(self, groups='all', subdomains='all', nuclides='all', xs_type='macro', order_groups='increasing', value='mean', **kwargs): - """Returns an array of multi-group cross sections. + r"""Returns an array of multi-group cross sections. This method constructs a 2D NumPy array for the requested multi-group cross section data data for one or more energy groups and subdomains. @@ -747,12 +769,19 @@ class MGXS(object): cv.check_value('value', value, ['mean', 'std_dev', 'rel_err']) cv.check_value('xs_type', xs_type, ['macro', 'micro']) + # FIXME: Unable to get microscopic xs for mesh domain because the mesh + # cells do not know the nuclide densities in each mesh cell. + if self.domain_type == 'mesh' and xs_type == 'micro': + msg = 'Unable to get micro xs for mesh domain since the mesh ' \ + 'cells do not know the nuclide densities in each mesh cell.' + raise ValueError(msg) + filters = [] filter_bins = [] # Construct a collection of the domain filter bins if not isinstance(subdomains, basestring): - cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=2) + cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) for subdomain in subdomains: filters.append(self.domain_type) filter_bins.append((subdomain,)) @@ -972,16 +1001,16 @@ class MGXS(object): cv.check_iterable_type('energy_groups', groups, Integral) # Build lists of filters and filter bins to slice - if len(groups) == 0: - filters = [] - filter_bins = [] - else: - filter_bins = [] + filters = [] + filter_bins = [] + + if len(groups) != 0: + energy_bins = [] for group in groups: group_bounds = self.energy_groups.get_group_bounds(group) - filter_bins.append(group_bounds) - filter_bins = [tuple(filter_bins)] - filters = ['energy'] + energy_bins.append(group_bounds) + filter_bins.append(tuple(energy_bins)) + filters.append('energy') # Clone this MGXS to initialize the sliced version slice_xs = copy.deepcopy(self) @@ -1126,6 +1155,9 @@ class MGXS(object): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) + elif self.domain_type == 'mesh': + xyz = [range(1, x+1) for x in self.domain.dimension] + subdomains = list(itertools.product(*xyz)) else: subdomains = [self.domain.id] @@ -1148,6 +1180,9 @@ class MGXS(object): string += '{0: <16}=\t{1}\n'.format('\tDomain Type', self.domain_type) string += '{0: <16}=\t{1}\n'.format('\tDomain ID', self.domain.id) + # Generate the header for an individual XS + xs_header = '\tCross Sections [{0}]:'.format(self.get_units(xs_type)) + # If cross section data has not been computed, only print string header if self.tallies is None: print(string) @@ -1156,7 +1191,7 @@ class MGXS(object): # Loop over all subdomains for subdomain in subdomains: - if self.domain_type == 'distribcell': + if self.domain_type == 'distribcell' or self.domain_type == 'mesh': string += '{0: <16}=\t{1}\n'.format('\tSubdomain', subdomain) # Loop over all Nuclides @@ -1167,11 +1202,7 @@ class MGXS(object): string += '{0: <16}=\t{1}\n'.format('\tNuclide', nuclide) # Build header for cross section type - if xs_type == 'macro': - string += '{0: <16}\n'.format('\tCross Sections [cm^-1]:') - else: - string += '{0: <16}\n'.format('\tCross Sections [barns]:') - + string += '{0: <16}\n'.format(xs_header) template = '{0: <12}Group {1} [{2: <10} - {3: <10}MeV]:\t' # Loop over energy groups ranges @@ -1262,6 +1293,9 @@ class MGXS(object): elif self.domain_type == 'avg(distribcell)': domain_filter = self.xs_tally.find_filter('avg(distribcell)') subdomains = domain_filter.bins + elif self.domain_type == 'mesh': + xyz = [range(1, x+1) for x in self.domain.dimension] + subdomains = list(itertools.product(*xyz)) else: subdomains = [self.domain.id] @@ -1368,15 +1402,14 @@ class MGXS(object): # Get a Pandas DataFrame for the data df = self.get_pandas_dataframe(groups=groups, xs_type=xs_type) - # Capitalize column label strings - df.columns = df.columns.astype(str) - df.columns = map(str.title, df.columns) - # Export the data using Pandas IO API if format == 'csv': df.to_csv(filename + '.csv', index=False) elif format == 'excel': - df.to_excel(filename + '.xls', index=False) + if self.domain_type == 'mesh': + df.to_excel(filename + '.xls') + else: + df.to_excel(filename + '.xls', index=False) elif format == 'pickle': df.to_pickle(filename + '.pkl') elif format == 'latex': @@ -1457,7 +1490,10 @@ class MGXS(object): distribcell_paths=distribcell_paths) # Remove nuclide column since it is homogeneous and redundant - df.drop('nuclide', axis=1, inplace=True) + if self.domain_type == 'mesh': + df.drop('nuclide', axis=1, level=0, inplace=True) + else: + df.drop('nuclide', axis=1, inplace=True) # If the user requested a specific set of nuclides elif self.by_nuclide and nuclides != 'all': @@ -1471,7 +1507,10 @@ class MGXS(object): distribcell_paths=distribcell_paths) # Remove the score column since it is homogeneous and redundant - df = df.drop('score', axis=1) + if self.domain_type == 'mesh': + df = df.drop('score', axis=1, level=0) + else: + df = df.drop('score', axis=1) # Override energy groups bounds with indices all_groups = np.arange(self.num_groups, 0, -1, dtype=np.int) @@ -1527,9 +1566,34 @@ class MGXS(object): # Sort the dataframe by domain type id (e.g., distribcell id) and # energy groups such that data is from fast to thermal - df.sort_values(by=[self.domain_type] + columns, inplace=True) + if self.domain_type == 'mesh': + mesh_str = 'mesh {0}'.format(self.domain.id) + df.sort_values(by=[(mesh_str, 'x'), (mesh_str, 'y'), \ + (mesh_str, 'z')] + columns, inplace=True) + else: + df.sort_values(by=[self.domain_type] + columns, inplace=True) return df + def get_units(self, xs_type='macro'): + """This method returns the units of a MGXS based on a desired xs_type. + + Parameters + ---------- + xs_type: {'macro', 'micro'} + Return the macro or micro cross section units. + Defaults to 'macro'. + + Returns + ------- + str + A string representing the units of the MGXS. + + """ + + cv.check_value('xs_type', xs_type, ['macro', 'micro']) + + return 'cm^-1' if xs_type == 'macro' else 'barns' + class MatrixMGXS(MGXS): """An abstract multi-group cross section for some energy group structure @@ -1546,9 +1610,9 @@ class MatrixMGXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -1566,9 +1630,9 @@ class MatrixMGXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -1595,9 +1659,10 @@ class MatrixMGXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading - tally data from a statepoint file). + tally data from a statepoint file) and the number of mesh cells for + 'mesh' domain types. num_nuclides : int The number of nuclides for which the multi-group cross section is being tracked. This is unity if the by_nuclide attribute is False. @@ -1688,13 +1753,20 @@ class MatrixMGXS(MGXS): cv.check_value('value', value, ['mean', 'std_dev', 'rel_err']) cv.check_value('xs_type', xs_type, ['macro', 'micro']) + # FIXME: Unable to get microscopic xs for mesh domain because the mesh + # cells do not know the nuclide densities in each mesh cell. + if self.domain_type == 'mesh' and xs_type == 'micro': + msg = 'Unable to get micro xs for mesh domain since the mesh ' \ + 'cells do not know the nuclide densities in each mesh cell.' + raise ValueError(msg) + filters = [] filter_bins = [] # Construct a collection of the domain filter bins if not isinstance(subdomains, basestring): cv.check_iterable_type('subdomains', subdomains, Integral, - max_depth=2) + max_depth=3) for subdomain in subdomains: filters.append(self.domain_type) filter_bins.append((subdomain,)) @@ -1856,6 +1928,9 @@ class MatrixMGXS(MGXS): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) + elif self.domain_type == 'mesh': + xyz = [range(1, x+1) for x in self.domain.dimension] + subdomains = list(itertools.product(*xyz)) else: subdomains = [self.domain.id] @@ -1878,6 +1953,9 @@ class MatrixMGXS(MGXS): string += '{0: <16}=\t{1}\n'.format('\tDomain Type', self.domain_type) string += '{0: <16}=\t{1}\n'.format('\tDomain ID', self.domain.id) + # Generate the header for an individual XS + xs_header = '\tCross Sections [{0}]:'.format(self.get_units(xs_type)) + # If cross section data has not been computed, only print string header if self.tallies is None: print(string) @@ -1906,11 +1984,7 @@ class MatrixMGXS(MGXS): string += '{0: <16}=\t{1}\n'.format('\tNuclide', nuclide) # Build header for cross section type - if xs_type == 'macro': - string += '{0: <16}\n'.format('\tCross Sections [cm^-1]:') - else: - string += '{0: <16}\n'.format('\tCross Sections [barns]:') - + string += '{0: <16}\n'.format(xs_header) template = '{0: <12}Group {1} -> Group {2}:\t\t' # Loop over incoming/outgoing energy groups ranges @@ -1965,9 +2039,9 @@ class TotalXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -1985,9 +2059,9 @@ class TotalXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2016,7 +2090,7 @@ class TotalXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -2083,9 +2157,9 @@ class TransportXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -2103,9 +2177,9 @@ class TransportXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2134,7 +2208,7 @@ class TransportXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -2213,9 +2287,9 @@ class NuTransportXS(TransportXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -2233,9 +2307,9 @@ class NuTransportXS(TransportXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2264,7 +2338,7 @@ class NuTransportXS(TransportXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -2334,9 +2408,9 @@ class AbsorptionXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -2354,9 +2428,9 @@ class AbsorptionXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2385,7 +2459,7 @@ class AbsorptionXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -2450,9 +2524,9 @@ class CaptureXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -2470,9 +2544,9 @@ class CaptureXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2501,7 +2575,7 @@ class CaptureXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -2572,9 +2646,9 @@ class FissionXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -2592,9 +2666,9 @@ class FissionXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2623,7 +2697,7 @@ class FissionXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -2683,9 +2757,9 @@ class NuFissionXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -2703,9 +2777,9 @@ class NuFissionXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2734,7 +2808,7 @@ class NuFissionXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -2799,9 +2873,9 @@ class KappaFissionXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -2819,9 +2893,9 @@ class KappaFissionXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2850,7 +2924,7 @@ class KappaFissionXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -2912,9 +2986,9 @@ class ScatterXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -2932,9 +3006,9 @@ class ScatterXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -2963,7 +3037,7 @@ class ScatterXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -3027,9 +3101,9 @@ class NuScatterXS(MGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -3047,9 +3121,9 @@ class NuScatterXS(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -3078,7 +3152,7 @@ class NuScatterXS(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -3157,9 +3231,9 @@ class ScatterMatrixXS(MatrixMGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -3181,9 +3255,9 @@ class ScatterMatrixXS(MatrixMGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -3212,7 +3286,7 @@ class ScatterMatrixXS(MatrixMGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -3504,12 +3578,19 @@ class ScatterMatrixXS(MatrixMGXS): cv.check_value('value', value, ['mean', 'std_dev', 'rel_err']) cv.check_value('xs_type', xs_type, ['macro', 'micro']) + # FIXME: Unable to get microscopic xs for mesh domain because the mesh + # cells do not know the nuclide densities in each mesh cell. + if self.domain_type == 'mesh' and xs_type == 'micro': + msg = 'Unable to get micro xs for mesh domain since the mesh ' \ + 'cells do not know the nuclide densities in each mesh cell.' + raise ValueError(msg) + filters = [] filter_bins = [] # Construct a collection of the domain filter bins if not isinstance(subdomains, basestring): - cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=2) + cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) for subdomain in subdomains: filters.append(self.domain_type) filter_bins.append((subdomain,)) @@ -3654,9 +3735,12 @@ class ScatterMatrixXS(MatrixMGXS): df['moment'] = moments # Place the moment column before the mean column - mean_index = df.columns.get_loc('mean') columns = df.columns.tolist() - df = df[columns[:mean_index] + ['moment'] + columns[mean_index:-1]] + mean_index = [i for i, s in enumerate(columns) if 'mean' in s][0] + if self.domain_type == 'mesh': + df = df[columns[:mean_index] + [('moment', '')] + columns[mean_index:-1]] + else: + df = df[columns[:mean_index] + ['moment'] + columns[mean_index:-1]] # Select rows corresponding to requested scattering moment if moment != 'all': @@ -3696,6 +3780,9 @@ class ScatterMatrixXS(MatrixMGXS): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) + elif self.domain_type == 'mesh': + xyz = [range(1, x+1) for x in self.domain.dimension] + subdomains = list(itertools.product(*xyz)) else: subdomains = [self.domain.id] @@ -3723,6 +3810,9 @@ class ScatterMatrixXS(MatrixMGXS): string += '{0: <16}=\t{1}\n'.format('\tDomain Type', self.domain_type) string += '{0: <16}=\t{1}\n'.format('\tDomain ID', self.domain.id) + # Generate the header for an individual XS + xs_header = '\tCross Sections [{0}]:'.format(self.get_units(xs_type)) + # If cross section data has not been computed, only print string header if self.tallies is None: print(string) @@ -3751,11 +3841,7 @@ class ScatterMatrixXS(MatrixMGXS): string += '{0: <16}=\t{1}\n'.format('\tNuclide', nuclide) # Build header for cross section type - if xs_type == 'macro': - string += '{0: <16}\n'.format('\tCross Sections [cm^-1]:') - else: - string += '{0: <16}\n'.format('\tCross Sections [barns]:') - + string += '{0: <16}\n'.format(xs_header) template = '{0: <12}Group {1} -> Group {2}:\t\t' # Loop over incoming/outgoing energy groups ranges @@ -3806,9 +3892,9 @@ class NuScatterMatrixXS(ScatterMatrixXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -3830,9 +3916,9 @@ class NuScatterMatrixXS(ScatterMatrixXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -3861,7 +3947,7 @@ class NuScatterMatrixXS(ScatterMatrixXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -3932,9 +4018,9 @@ class MultiplicityMatrixXS(MatrixMGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -3952,9 +4038,9 @@ class MultiplicityMatrixXS(MatrixMGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -3983,7 +4069,7 @@ class MultiplicityMatrixXS(MatrixMGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -4079,9 +4165,9 @@ class NuFissionMatrixXS(MatrixMGXS): Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -4099,9 +4185,9 @@ class NuFissionMatrixXS(MatrixMGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -4130,7 +4216,7 @@ class NuFissionMatrixXS(MatrixMGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -4183,20 +4269,20 @@ class Chi(MGXS): .. math:: - \langle \nu\sigma_{f,\rightarrow g} \phi \rangle &= \int_{r \in V} dr + \langle \nu\sigma_{f,g' \rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_{E_g}^{E_{g-1}} dE \; \chi(E) \nu\sigma_f (r, E') \psi(r, E', \Omega')\\ \langle \nu\sigma_f \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_0^\infty dE \; \chi(E) \nu\sigma_f (r, E') \psi(r, E', \Omega') \\ - \chi_g &= \frac{\langle \nu\sigma_{f,\rightarrow g} \phi \rangle}{\langle - \nu\sigma_f \phi \rangle} + \chi_g &= \frac{\langle \nu\sigma_{f,g' \rightarrow g} \phi \rangle} + {\langle \nu\sigma_f \phi \rangle} Parameters ---------- - domain : openmc.Material or openmc.Cell or openmc.Universe + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh The domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} The domain type for spatial homogenization groups : openmc.mgxs.EnergyGroups The energy group structure for energy condensation @@ -4214,9 +4300,9 @@ class Chi(MGXS): Reaction type (e.g., 'total', 'nu-fission', etc.) by_nuclide : bool If true, computes cross sections for each nuclide in domain - domain : Material or Cell or Universe + domain : Material or Cell or Universe or Mesh Domain for spatial homogenization - domain_type : {'material', 'cell', 'distribcell', 'universe'} + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation @@ -4245,7 +4331,7 @@ class Chi(MGXS): is None unless the multi-group cross section has been computed. num_subdomains : int The number of subdomains is unity for 'material', 'cell' and 'universe' - domain types. When the This is equal to the number of cell instances + domain types. This is equal to the number of cell instances for 'distribcell' domain types (it is equal to unity prior to loading tally data from a statepoint file). num_nuclides : int @@ -4395,7 +4481,7 @@ class Chi(MGXS): """ if not self.can_merge(other): - raise ValueError('Unable to merge Chi') + raise ValueError('Unable to merge a Chi MGXS') # Create deep copy of tally to return as merged tally merged_mgxs = copy.deepcopy(self) @@ -4414,7 +4500,7 @@ class Chi(MGXS): # The nuclides must be mutually exclusive for nuclide in self.nuclides: if nuclide in other.nuclides: - msg = 'Unable to merge Chi with shared nuclides' + msg = 'Unable to merge a Chi MGXS with shared nuclides' raise ValueError(msg) # Concatenate lists of nuclides for the merged MGXS @@ -4473,12 +4559,19 @@ class Chi(MGXS): cv.check_value('value', value, ['mean', 'std_dev', 'rel_err']) cv.check_value('xs_type', xs_type, ['macro', 'micro']) + # FIXME: Unable to get microscopic xs for mesh domain because the mesh + # cells do not know the nuclide densities in each mesh cell. + if self.domain_type == 'mesh' and xs_type == 'micro': + msg = 'Unable to get micro xs for mesh domain since the mesh ' \ + 'cells do not know the nuclide densities in each mesh cell.' + raise ValueError(msg) + filters = [] filter_bins = [] # Construct a collection of the domain filter bins if not isinstance(subdomains, basestring): - cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=2) + cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) for subdomain in subdomains: filters.append(self.domain_type) filter_bins.append((subdomain,)) @@ -4618,3 +4711,390 @@ class Chi(MGXS): df['std. dev.'] *= np.tile(densities, tile_factor) return df + + def get_units(self, xs_type='macro'): + """Returns the units of Chi. + + This method returns the units of Chi, which is "%" for both macro + and micro xs types. + + Parameters + ---------- + xs_type: {'macro', 'micro'} + Return the macro or micro cross section units. + Defaults to 'macro'. + + Returns + ------- + str + A string representing the units of Chi. + + """ + + cv.check_value('xs_type', xs_type, ['macro', 'micro']) + + # Chi has the same units (%) for both macro and micro + return '%' + + +class ChiPrompt(Chi): + r"""The prompt fission spectrum. + + This class can be used for both OpenMC input generation and tally data + post-processing to compute spatially-homogenized and energy-integrated + multi-group cross sections for multi-group neutronics calculations. At a + minimum, one needs to set the :attr:`ChiPrompt.energy_groups` and + :attr:`ChiPrompt.domain` properties. Tallies for the flux and appropriate + reaction rates over the specified domain are generated automatically via the + :attr:`ChiPrompt.tallies` property, which can then be appended to a + :class:`openmc.Tallies` instance. + + For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the + necessary data to compute multi-group cross sections from a + :class:`openmc.StatePoint` instance. The derived multi-group cross section + can then be obtained from the :attr:`ChiPrompt.xs_tally` property. + + For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the + fission spectrum is calculated as: + + .. math:: + + \langle \nu^p \sigma_{f,g' \rightarrow g} \phi \rangle &= \int_{r \in V} + dr \int_{4\pi} d\Omega' \int_0^\infty dE' \int_{E_g}^{E_{g-1}} dE \; + \chi(E) \nu^p \sigma_f (r, E') \psi(r, E', \Omega')\\ + \langle \nu^p \sigma_f \phi \rangle &= \int_{r \in V} dr \int_{4\pi} + d\Omega' \int_0^\infty dE' \int_0^\infty dE \; \chi(E) \nu^p \sigma_f (r, + E') \psi(r, E', \Omega') \\ + \chi_g^p &= \frac{\langle \nu^p \sigma_{f,g' \rightarrow g} \phi \rangle} + {\langle \nu^p \sigma_f \phi \rangle} + + Parameters + ---------- + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + The domain for spatial homogenization + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} + The domain type for spatial homogenization + groups : openmc.mgxs.EnergyGroups + The energy group structure for energy condensation + by_nuclide : bool + If true, computes cross sections for each nuclide in domain + name : str, optional + Name of the multi-group cross section. Used as a label to identify + tallies in OpenMC 'tallies.xml' file. + + Attributes + ---------- + name : str, optional + Name of the multi-group cross section + rxn_type : str + Reaction type (e.g., 'total', 'nu-fission', etc.) + by_nuclide : bool + If true, computes cross sections for each nuclide in domain + domain : Material or Cell or Universe or Mesh + Domain for spatial homogenization + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} + Domain type for spatial homogenization + energy_groups : openmc.mgxs.EnergyGroups + Energy group structure for energy condensation + tally_trigger : openmc.Trigger + An (optional) tally precision trigger given to each tally used to + compute the cross section + scores : list of str + The scores in each tally used to compute the multi-group cross section + filters : list of openmc.Filter + The filters in each tally used to compute the multi-group cross section + tally_keys : list of str + The keys into the tallies dictionary for each tally used to compute + the multi-group cross section + estimator : {'tracklength', 'analog'} + The tally estimator used to compute the multi-group cross section + tallies : collections.OrderedDict + OpenMC tallies needed to compute the multi-group cross section. The keys + are strings listed in the :attr:`ChiPrompt.tally_keys` property and + values are instances of :class:`openmc.Tally`. + rxn_rate_tally : openmc.Tally + Derived tally for the reaction rate tally used in the numerator to + compute the multi-group cross section. This attribute is None + unless the multi-group cross section has been computed. + xs_tally : openmc.Tally + Derived tally for the multi-group cross section. This attribute + is None unless the multi-group cross section has been computed. + num_subdomains : int + The number of subdomains is unity for 'material', 'cell' and 'universe' + domain types. This is equal to the number of cell instances + for 'distribcell' domain types (it is equal to unity prior to loading + tally data from a statepoint file). + num_nuclides : int + The number of nuclides for which the multi-group cross section is + being tracked. This is unity if the by_nuclide attribute is False. + nuclides : Iterable of str or 'sum' + The optional user-specified nuclides for which to compute cross + sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides + are not specified by the user, all nuclides in the spatial domain + are included. This attribute is 'sum' if by_nuclide is false. + sparse : bool + Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format + for compressed data storage + loaded_sp : bool + Whether or not a statepoint file has been loaded with tally data + derived : bool + Whether or not the MGXS is merged from one or more other MGXS + hdf5_key : str + The key used to index multi-group cross sections in an HDF5 data store + + """ + + def __init__(self, domain=None, domain_type=None, + groups=None, by_nuclide=False, name=''): + super(ChiPrompt, self).__init__(domain, domain_type, groups, by_nuclide, name) + self._rxn_type = 'chi-prompt' + + @property + def scores(self): + return ['prompt-nu-fission', 'prompt-nu-fission'] + + +class InverseVelocity(MGXS): + r"""An inverse velocity multi-group cross section. + + This class can be used for both OpenMC input generation and tally data + post-processing to compute spatially-homogenized and energy-integrated + multi-group neutron inverse velocities for multi-group neutronics + calculations. The units of inverse velocity are seconds per centimeter. At a + minimum, one needs to set the :attr:`InverseVelocity.energy_groups` and + :attr:`InverseVelocity.domain` properties. Tallies for the flux and + appropriate reaction rates over the specified domain are generated + automatically via the :attr:`InverseVelocity.tallies` property, which can + then be appended to a :class:`openmc.Tallies` instance. + + For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the + necessary data to compute multi-group cross sections from a + :class:`openmc.StatePoint` instance. The derived multi-group cross section + can then be obtained from the :attr:`InverseVelocity.xs_tally` property. + + For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the + neutron inverse velocities are calculated by tallying the flux-weighted + inverse velocity and the flux. The inverse velocity is then the + flux-weighted inverse velocity divided by the flux: + + .. math:: + + \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; + \frac{\psi (r, E, \Omega)}{v (r, E)}}{\int_{r \in V} dr \int_{4\pi} + d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)} + + Parameters + ---------- + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + The domain for spatial homogenization + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} + The domain type for spatial homogenization + groups : openmc.mgxs.EnergyGroups + The energy group structure for energy condensation + by_nuclide : bool + If true, computes cross sections for each nuclide in domain + name : str, optional + Name of the multi-group cross section. Used as a label to identify + tallies in OpenMC 'tallies.xml' file. + + Attributes + ---------- + name : str, optional + Name of the multi-group cross section + rxn_type : str + Reaction type (e.g., 'total', 'nu-fission', etc.) + by_nuclide : bool + If true, computes cross sections for each nuclide in domain + domain : Material or Cell or Universe or Mesh + Domain for spatial homogenization + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} + Domain type for spatial homogenization + energy_groups : openmc.mgxs.EnergyGroups + Energy group structure for energy condensation + tally_trigger : openmc.Trigger + An (optional) tally precision trigger given to each tally used to + compute the cross section + scores : list of str + The scores in each tally used to compute the multi-group cross section + filters : list of openmc.Filter + The filters in each tally used to compute the multi-group cross section + tally_keys : list of str + The keys into the tallies dictionary for each tally used to compute + the multi-group cross section + estimator : {'tracklength', 'analog'} + The tally estimator used to compute the multi-group cross section + tallies : collections.OrderedDict + OpenMC tallies needed to compute the multi-group cross section. The keys + are strings listed in the :attr:`InverseVelocity.tally_keys` property + and values are instances of :class:`openmc.Tally`. + rxn_rate_tally : openmc.Tally + Derived tally for the reaction rate tally used in the numerator to + compute the multi-group cross section. This attribute is None + unless the multi-group cross section has been computed. + xs_tally : openmc.Tally + Derived tally for the multi-group cross section. This attribute + is None unless the multi-group cross section has been computed. + num_subdomains : int + The number of subdomains is unity for 'material', 'cell' and 'universe' + domain types. This is equal to the number of cell instances + for 'distribcell' domain types (it is equal to unity prior to loading + tally data from a statepoint file). + num_nuclides : int + The number of nuclides for which the multi-group cross section is + being tracked. This is unity if the by_nuclide attribute is False. + nuclides : Iterable of str or 'sum' + The optional user-specified nuclides for which to compute cross + sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides + are not specified by the user, all nuclides in the spatial domain + are included. This attribute is 'sum' if by_nuclide is false. + sparse : bool + Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format + for compressed data storage + loaded_sp : bool + Whether or not a statepoint file has been loaded with tally data + derived : bool + Whether or not the MGXS is merged from one or more other MGXS + hdf5_key : str + The key used to index multi-group cross sections in an HDF5 data store + + """ + + def __init__(self, domain=None, domain_type=None, + groups=None, by_nuclide=False, name=''): + super(InverseVelocity, self).__init__(domain, domain_type, + groups, by_nuclide, name) + self._rxn_type = 'inverse-velocity' + + def get_units(self, xs_type='macro'): + """Returns the units of InverseVelocity. + + This method returns the units of an InverseVelocity based on a desired + xs_type. + + Parameters + ---------- + xs_type: {'macro', 'micro'} + Return the macro or micro cross section units. + Defaults to 'macro'. + + Returns + ------- + str + A string representing the units of the InverseVelocity. + + """ + + if xs_type == 'macro': + return 'second/cm' + else: + raise ValueError('Unable to return the units of InverseVelocity for' + ' xs_type other than "macro"') + + +class PromptNuFissionXS(MGXS): + r"""A prompt fission neutron production multi-group cross section. + + This class can be used for both OpenMC input generation and tally data + post-processing to compute spatially-homogenized and energy-integrated + multi-group cross sections for multi-group neutronics calculations. At a + minimum, one needs to set the :attr:`PromptNuFissionXS.energy_groups` and + :attr:`PromptNuFissionXS.domain` properties. Tallies for the flux and + appropriate reaction rates over the specified domain are generated + automatically via the :attr:`PromptNuFissionXS.tallies` property, which can + then be appended to a :class:`openmc.Tallies` instance. + + For post-processing, the :meth:`MGXS.load_from_statepoint` will pull in the + necessary data to compute multi-group cross sections from a + :class:`openmc.StatePoint` instance. The derived multi-group cross section + can then be obtained from the :attr:`PromptNuFissionXS.xs_tally` property. + + For a spatial domain :math:`V` and energy group :math:`[E_g,E_{g-1}]`, the + fission spectrum is calculated as: + + .. math:: + + \frac{\int_{r \in V} dr \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; + \nu\sigma_f^p (r, E) \psi (r, E, \Omega)}{\int_{r \in V} dr \int_{4\pi} + d\Omega \int_{E_g}^{E_{g-1}} dE \; \psi (r, E, \Omega)}. + + Parameters + ---------- + domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.Mesh + The domain for spatial homogenization + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} + The domain type for spatial homogenization + groups : openmc.mgxs.EnergyGroups + The energy group structure for energy condensation + by_nuclide : bool + If true, computes cross sections for each nuclide in domain + name : str, optional + Name of the multi-group cross section. Used as a label to identify + tallies in OpenMC 'tallies.xml' file. + + Attributes + ---------- + name : str, optional + Name of the multi-group cross section + rxn_type : str + Reaction type (e.g., 'total', 'nu-fission', etc.) + by_nuclide : bool + If true, computes cross sections for each nuclide in domain + domain : Material or Cell or Universe or Mesh + Domain for spatial homogenization + domain_type : {'material', 'cell', 'distribcell', 'universe', 'mesh'} + Domain type for spatial homogenization + energy_groups : openmc.mgxs.EnergyGroups + Energy group structure for energy condensation + tally_trigger : openmc.Trigger + An (optional) tally precision trigger given to each tally used to + compute the cross section + scores : list of str + The scores in each tally used to compute the multi-group cross section + filters : list of openmc.Filter + The filters in each tally used to compute the multi-group cross section + tally_keys : list of str + The keys into the tallies dictionary for each tally used to compute + the multi-group cross section + estimator : {'tracklength', 'analog'} + The tally estimator used to compute the multi-group cross section + tallies : collections.OrderedDict + OpenMC tallies needed to compute the multi-group cross section. The keys + are strings listed in the :attr:`PromptNuFissionXS.tally_keys` property + and values are instances of :class:`openmc.Tally`. + rxn_rate_tally : openmc.Tally + Derived tally for the reaction rate tally used in the numerator to + compute the multi-group cross section. This attribute is None + unless the multi-group cross section has been computed. + xs_tally : openmc.Tally + Derived tally for the multi-group cross section. This attribute + is None unless the multi-group cross section has been computed. + num_subdomains : int + The number of subdomains is unity for 'material', 'cell' and 'universe' + domain types. This is equal to the number of cell instances + for 'distribcell' domain types (it is equal to unity prior to loading + tally data from a statepoint file). + num_nuclides : int + The number of nuclides for which the multi-group cross section is + being tracked. This is unity if the by_nuclide attribute is False. + nuclides : Iterable of str or 'sum' + The optional user-specified nuclides for which to compute cross + sections (e.g., 'U-238', 'O-16'). If by_nuclide is True but nuclides + are not specified by the user, all nuclides in the spatial domain + are included. This attribute is 'sum' if by_nuclide is false. + sparse : bool + Whether or not the MGXS' tallies use SciPy's LIL sparse matrix format + for compressed data storage + loaded_sp : bool + Whether or not a statepoint file has been loaded with tally data + derived : bool + Whether or not the MGXS is merged from one or more other MGXS + hdf5_key : str + The key used to index multi-group cross sections in an HDF5 data store + + """ + + def __init__(self, domain=None, domain_type=None, + groups=None, by_nuclide=False, name=''): + super(PromptNuFissionXS, self).__init__(domain, domain_type, groups, + by_nuclide, name) + self._rxn_type = 'prompt-nu-fission' diff --git a/openmc/tallies.py b/openmc/tallies.py index 79e7c56bc..c68b0faac 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1293,7 +1293,7 @@ class Tally(object): # Create list of 2- or 3-tuples tuples for mesh cell bins if self_filter.type == 'mesh': dimension = self_filter.mesh.dimension - xyz = map(lambda x: np.arange(1, x+1), dimension) + xyz = [range(1, x+1) for x in dimension] bins = list(itertools.product(*xyz)) # Create list of 2-tuples for energy boundary bins diff --git a/src/cmfd_data.F90 b/src/cmfd_data.F90 index 347351e31..0f1d9420b 100644 --- a/src/cmfd_data.F90 +++ b/src/cmfd_data.F90 @@ -6,6 +6,7 @@ module cmfd_data !============================================================================== use constants + use tally_filter, only: MeshFilter implicit none private @@ -94,7 +95,10 @@ contains ! Associate tallies and mesh t => cmfd_tallies(1) - i_mesh = t % filters(t % find_filter(FILTER_MESH)) % int_bins(1) + select type(filt => t % filters(t % find_filter(FILTER_MESH)) % obj) + type is (MeshFilter) + i_mesh = filt % mesh + end select m => meshes(i_mesh) ! Set mesh widths @@ -109,7 +113,10 @@ contains ! Associate tallies and mesh t => cmfd_tallies(ital) - i_mesh = t % filters(t % find_filter(FILTER_MESH)) % int_bins(1) + select type(filt => t % filters(t % find_filter(FILTER_MESH)) % obj) + type is (MeshFilter) + i_mesh = filt % mesh + end select m => meshes(i_mesh) i_filter_mesh = t % find_filter(FILTER_MESH) @@ -138,7 +145,7 @@ contains TALLY: if (ital == 1) then ! Reset all bins to 1 - matching_bins(1:t%n_filters) = 1 + matching_bins(1:size(t % filters)) = 1 ! Set ijk as mesh indices ijk = (/ i, j, k /) @@ -152,7 +159,8 @@ contains end if ! Calculate score index from bins - score_index = sum((matching_bins(1:t%n_filters) - 1) * t%stride) + 1 + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t%stride) + 1 ! Get flux flux = t % results(1,score_index) % sum @@ -181,7 +189,7 @@ contains INGROUP: do g = 1, ng ! Reset all bins to 1 - matching_bins(1:t%n_filters) = 1 + matching_bins(1:size(t % filters)) = 1 ! Set ijk as mesh indices ijk = (/ i, j, k /) @@ -198,7 +206,8 @@ contains end if ! Calculate score index from bins - score_index = sum((matching_bins(1:t%n_filters) - 1) * t%stride) + 1 + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t%stride) + 1 ! Get scattering cmfd % scattxs(h,g,i,j,k) = t % results(1,score_index) % sum /& @@ -220,7 +229,7 @@ contains else if (ital == 3) then ! Initialize and filter for energy - matching_bins(1:t%n_filters) = 1 + matching_bins(1:size(t % filters)) = 1 if (i_filter_ein > 0) then matching_bins(i_filter_ein) = ng - h + 1 end if @@ -229,60 +238,72 @@ contains matching_bins(i_filter_mesh) = mesh_indices_to_bin(m, & (/ i-1, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_RIGHT - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! outgoing + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t%stride) + 1 ! outgoing cmfd % current(1,h,i,j,k) = t % results(1,score_index) % sum matching_bins(i_filter_surf) = OUT_RIGHT - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! incoming + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! incoming cmfd % current(2,h,i,j,k) = t % results(1,score_index) % sum ! Right surface matching_bins(i_filter_mesh) = mesh_indices_to_bin(m, & (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_RIGHT - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! incoming + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! incoming cmfd % current(3,h,i,j,k) = t % results(1,score_index) % sum matching_bins(i_filter_surf) = OUT_RIGHT - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! outgoing + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! outgoing cmfd % current(4,h,i,j,k) = t % results(1,score_index) % sum ! Back surface matching_bins(i_filter_mesh) = mesh_indices_to_bin(m, & (/ i, j-1, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_FRONT - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! outgoing + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! outgoing cmfd % current(5,h,i,j,k) = t % results(1,score_index) % sum matching_bins(i_filter_surf) = OUT_FRONT - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! incoming + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! incoming cmfd % current(6,h,i,j,k) = t % results(1,score_index) % sum ! Front surface matching_bins(i_filter_mesh) = mesh_indices_to_bin(m, & (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_FRONT - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! incoming + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! incoming cmfd % current(7,h,i,j,k) = t % results(1,score_index) % sum matching_bins(i_filter_surf) = OUT_FRONT - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! outgoing + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! outgoing cmfd % current(8,h,i,j,k) = t % results(1,score_index) % sum ! Bottom surface matching_bins(i_filter_mesh) = mesh_indices_to_bin(m, & (/ i, j, k-1 /) + 1, .true.) matching_bins(i_filter_surf) = IN_TOP - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! outgoing + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! outgoing cmfd % current(9,h,i,j,k) = t % results(1,score_index) % sum matching_bins(i_filter_surf) = OUT_TOP - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! incoming + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! incoming cmfd % current(10,h,i,j,k) = t % results(1,score_index) % sum ! Top surface matching_bins(i_filter_mesh) = mesh_indices_to_bin(m, & (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_TOP - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! incoming + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! incoming cmfd % current(11,h,i,j,k) = t % results(1,score_index) % sum matching_bins(i_filter_surf) = OUT_TOP - score_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 ! outgoing + score_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! outgoing cmfd % current(12,h,i,j,k) = t % results(1,score_index) % sum end if TALLY diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index f69c09fe1..baa08e76e 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -267,14 +267,16 @@ contains use mesh_header, only: RegularMesh use string use tally, only: setup_active_cmfdtallies - use tally_header, only: TallyObject, TallyFilter + use tally_header, only: TallyObject + use tally_filter_header + use tally_filter use tally_initialize, only: add_tallies use xml_interface type(Node), pointer :: doc ! pointer to XML doc info character(MAX_LINE_LEN) :: temp_str ! temp string - integer :: i ! loop counter + integer :: i, j ! loop counter integer :: n ! size of arrays in mesh specification integer :: ng ! number of energy groups (default 1) integer :: n_filters ! number of filters @@ -283,7 +285,7 @@ contains real(8) :: rarray3(3) ! temp double array type(TallyObject), pointer :: t type(RegularMesh), pointer :: m - type(TallyFilter) :: filters(N_FILTER_TYPES) ! temporary filters + type(TallyFilterContainer) :: filters(N_FILTER_TYPES) ! temporary filters type(Node), pointer :: node_mesh ! Set global variables if they are 0 (this can happen if there is no tally @@ -410,21 +412,25 @@ contains ! Set up mesh filter n_filters = 1 - filters(n_filters) % type = FILTER_MESH - filters(n_filters) % n_bins = product(m % dimension) - allocate(filters(n_filters) % int_bins(1)) - filters(n_filters) % int_bins(1) = n_user_meshes + 1 + allocate(MeshFilter :: filters(n_filters) % obj) + select type (filt => filters(n_filters) % obj) + type is (MeshFilter) + filt % n_bins = product(m % dimension) + filt % mesh = n_user_meshes + 1 + end select t % find_filter(FILTER_MESH) = n_filters ! Read and set incoming energy mesh filter if (check_for_node(node_mesh, "energy")) then n_filters = n_filters + 1 - filters(n_filters) % type = FILTER_ENERGYIN - ng = get_arraysize_double(node_mesh, "energy") - filters(n_filters) % n_bins = ng - 1 - allocate(filters(n_filters) % real_bins(ng)) - call get_node_array(node_mesh, "energy", & - filters(n_filters) % real_bins) + allocate(EnergyFilter :: filters(n_filters) % obj) + select type (filt => filters(n_filters) % obj) + type is (EnergyFilter) + ng = get_arraysize_double(node_mesh, "energy") + filt % n_bins = ng - 1 + allocate(filt % bins(ng)) + call get_node_array(node_mesh, "energy", filt % bins) + end select t % find_filter(FILTER_ENERGYIN) = n_filters end if @@ -448,9 +454,10 @@ contains t % type = TALLY_VOLUME ! Allocate and set filters - t % n_filters = n_filters allocate(t % filters(n_filters)) - t % filters = filters(1:n_filters) + do j = 1, n_filters + call move_alloc(filters(j) % obj, t % filters(j) % obj) + end do ! Allocate scoring bins allocate(t % score_bins(3)) @@ -481,23 +488,22 @@ contains ! read and set outgoing energy mesh filter if (check_for_node(node_mesh, "energy")) then n_filters = n_filters + 1 - filters(n_filters) % type = FILTER_ENERGYOUT - ng = get_arraysize_double(node_mesh, "energy") - filters(n_filters) % n_bins = ng - 1 - allocate(filters(n_filters) % real_bins(ng)) - call get_node_array(node_mesh, "energy", & - filters(n_filters) % real_bins) + allocate(EnergyoutFilter :: filters(n_filters) % obj) + select type (filt => filters(n_filters) % obj) + type is (EnergyoutFilter) + ng = get_arraysize_double(node_mesh, "energy") + filt % n_bins = ng - 1 + allocate(filt % bins(ng)) + call get_node_array(node_mesh, "energy", filt % bins) + end select t % find_filter(FILTER_ENERGYOUT) = n_filters end if ! Allocate and set filters - t % n_filters = n_filters allocate(t % filters(n_filters)) - t % filters = filters(1:n_filters) - - ! deallocate filters bins array - if (check_for_node(node_mesh, "energy")) & - deallocate(filters(n_filters) % real_bins) + do j = 1, n_filters + call move_alloc(filters(j) % obj, t % filters(j) % obj) + end do ! Allocate macro reactions allocate(t % score_bins(2)) @@ -522,25 +528,25 @@ contains ! Add extra filter for surface n_filters = n_filters + 1 - filters(n_filters) % type = FILTER_SURFACE - filters(n_filters) % n_bins = 2 * m % n_dimension - allocate(filters(n_filters) % int_bins(2 * m % n_dimension)) - if (m % n_dimension == 2) then - filters(n_filters) % int_bins = (/ IN_RIGHT, OUT_RIGHT, IN_FRONT, & - OUT_FRONT /) - elseif (m % n_dimension == 3) then - filters(n_filters) % int_bins = (/ IN_RIGHT, OUT_RIGHT, IN_FRONT, & - OUT_FRONT, IN_TOP, OUT_TOP /) - end if + allocate(SurfaceFilter :: filters(n_filters) % obj) + select type(filt => filters(n_filters) % obj) + type is(SurfaceFilter) + filt % n_bins = 2 * m % n_dimension + allocate(filt % surfaces(2 * m % n_dimension)) + if (m % n_dimension == 2) then + filt % surfaces = (/ IN_RIGHT, OUT_RIGHT, IN_FRONT, OUT_FRONT /) + elseif (m % n_dimension == 3) then + filt % surfaces = (/ IN_RIGHT, OUT_RIGHT, IN_FRONT, OUT_FRONT, & + IN_TOP, OUT_TOP /) + end if + end select t % find_filter(FILTER_SURFACE) = n_filters ! Allocate and set filters - t % n_filters = n_filters allocate(t % filters(n_filters)) - t % filters = filters(1:n_filters) - - ! Deallocate filters bins array - deallocate(filters(n_filters) % int_bins) + do j = 1, n_filters + call move_alloc(filters(j) % obj, t % filters(j) % obj) + end do ! Allocate macro reactions allocate(t % score_bins(1)) @@ -558,15 +564,10 @@ contains ! We need to increase the dimension by one since we also need ! currents coming into and out of the boundary mesh cells. i_filter_mesh = t % find_filter(FILTER_MESH) - t % filters(i_filter_mesh) % n_bins = product(m % dimension + 1) + t % filters(i_filter_mesh) % obj % n_bins = product(m % dimension + 1) end if - ! Deallocate filter bins - deallocate(filters(1) % int_bins) - if (check_for_node(node_mesh, "energy")) & - deallocate(filters(2) % real_bins) - end do ! Put cmfd tallies into active tally array and turn tallies on diff --git a/src/constants.F90 b/src/constants.F90 index 5447b5cc2..4164285e2 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -289,7 +289,7 @@ module constants EVENT_ABSORB = 2 ! Tally score type - integer, parameter :: N_SCORE_TYPES = 22 + integer, parameter :: N_SCORE_TYPES = 23 integer, parameter :: & SCORE_FLUX = -1, & ! flux SCORE_TOTAL = -2, & ! total reaction rate @@ -310,9 +310,10 @@ module constants SCORE_NU_SCATTER_YN = -17, & ! angular flux-weighted nu-scattering moment (0:N) SCORE_EVENTS = -18, & ! number of events SCORE_DELAYED_NU_FISSION = -19, & ! delayed neutron production rate - SCORE_INVERSE_VELOCITY = -20, & ! flux-weighted inverse velocity - SCORE_FISS_Q_PROMPT = -21, & ! prompt fission Q-value - SCORE_FISS_Q_RECOV = -22 ! recoverable fission Q-value + SCORE_PROMPT_NU_FISSION = -20, & ! prompt neutron production rate + SCORE_INVERSE_VELOCITY = -21, & ! flux-weighted inverse velocity + SCORE_FISS_Q_PROMPT = -22, & ! prompt fission Q-value + SCORE_FISS_Q_RECOV = -23 ! recoverable fission Q-value ! Maximum scattering order supported integer, parameter :: MAX_ANG_ORDER = 10 diff --git a/src/endf.F90 b/src/endf.F90 index ad5e97a03..a836a5439 100644 --- a/src/endf.F90 +++ b/src/endf.F90 @@ -42,6 +42,8 @@ contains string = "nu-fission" case (SCORE_DELAYED_NU_FISSION) string = "delayed-nu-fission" + case (SCORE_PROMPT_NU_FISSION) + string = "prompt-nu-fission" case (SCORE_KAPPA_FISSION) string = "kappa-fission" case (SCORE_CURRENT) diff --git a/src/geometry.F90 b/src/geometry.F90 index 767e2db10..6d4ca7767 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -499,7 +499,7 @@ contains end if ! Set previous coordinate going slightly past surface crossing - p % last_xyz = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw + p % last_xyz_current = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw ! Diagnostic message if (verbosity >= 10 .or. trace) then @@ -563,7 +563,7 @@ contains end if ! Set previous coordinate going slightly past surface crossing - p % last_xyz = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw + p % last_xyz_current = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw ! Diagnostic message if (verbosity >= 10 .or. trace) then diff --git a/src/global.F90 b/src/global.F90 index 1b1e5632a..1558c050e 100644 --- a/src/global.F90 +++ b/src/global.F90 @@ -14,7 +14,7 @@ module global use set_header, only: SetInt use surface_header, only: SurfaceContainer use source_header, only: SourceDistribution - use tally_header, only: TallyObject, TallyMap, TallyResult + use tally_header, only: TallyObject, TallyResult use trigger_header, only: KTrigger use timer_header, only: Timer @@ -137,6 +137,7 @@ module global type(RegularMesh), allocatable, target :: meshes(:) type(TallyObject), allocatable, target :: tallies(:) integer, allocatable :: matching_bins(:) + real(8), allocatable :: filter_weights(:) ! Pointers for different tallies type(TallyObject), pointer :: user_tallies(:) => null() @@ -176,9 +177,6 @@ module global !$omp threadprivate(global_tally_collision, global_tally_absorption, & !$omp& global_tally_tracklength, global_tally_leakage) - ! Tally map structure - type(TallyMap), allocatable :: tally_maps(:) - integer :: n_meshes = 0 ! # of structured meshes integer :: n_user_meshes = 0 ! # of structured user meshes integer :: n_tallies = 0 ! # of tallies @@ -443,7 +441,8 @@ module global type(Nuclide0K), allocatable, target :: nuclides_0K(:) ! 0K nuclides info !$omp threadprivate(micro_xs, material_xs, fission_bank, n_bank, & -!$omp& trace, thread_id, current_work, matching_bins) +!$omp& trace, thread_id, current_work, matching_bins, & +!$omp& filter_weights) contains @@ -509,7 +508,7 @@ contains if (allocated(meshes)) deallocate(meshes) if (allocated(tallies)) deallocate(tallies) if (allocated(matching_bins)) deallocate(matching_bins) - if (allocated(tally_maps)) deallocate(tally_maps) + if (allocated(filter_weights)) deallocate(filter_weights) ! Deallocate fission and source bank and entropy !$omp parallel diff --git a/src/initialize.F90 b/src/initialize.F90 index 37eeb1f3e..99adf967f 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -22,8 +22,9 @@ module initialize use state_point, only: load_state_point use string, only: to_str, starts_with, ends_with, str_to_int use summary, only: write_summary - use tally_header, only: TallyObject, TallyResult, TallyFilter + use tally_header, only: TallyObject, TallyResult use tally_initialize,only: configure_tallies + use tally_filter use tally, only: init_tally_routines #ifdef MPI @@ -711,76 +712,15 @@ contains ! ======================================================================= ! ADJUST INDICES FOR EACH TALLY FILTER - FILTER_LOOP: do j = 1, t%n_filters - - select case (t%filters(j)%type) - case (FILTER_DISTRIBCELL) - do k = 1, size(t%filters(j)%int_bins) - id = t%filters(j)%int_bins(k) - if (cell_dict%has_key(id)) then - t%filters(j)%int_bins(k) = cell_dict%get_key(id) - else - call fatal_error("Could not find cell " // trim(to_str(id)) // & - " specified on tally " // trim(to_str(t%id))) - end if - - end do - case (FILTER_CELL, FILTER_CELLBORN) - - do k = 1, t%filters(j)%n_bins - id = t%filters(j)%int_bins(k) - if (cell_dict%has_key(id)) then - t%filters(j)%int_bins(k) = cell_dict%get_key(id) - else - call fatal_error("Could not find cell " // trim(to_str(id)) & - &// " specified on tally " // trim(to_str(t%id))) - end if - end do - - case (FILTER_SURFACE) + FILTER_LOOP: do j = 1, size(t % filters) + select type(filt => t % filters(j) % obj) + type is (SurfaceFilter) ! Check if this is a surface filter only for surface currents - if (any(t%score_bins == SCORE_CURRENT)) cycle FILTER_LOOP - - do k = 1, t%filters(j)%n_bins - id = t%filters(j)%int_bins(k) - if (surface_dict%has_key(id)) then - t%filters(j)%int_bins(k) = surface_dict%get_key(id) - else - call fatal_error("Could not find surface " // trim(to_str(id)) & - &// " specified on tally " // trim(to_str(t%id))) - end if - end do - - case (FILTER_UNIVERSE) - - do k = 1, t%filters(j)%n_bins - id = t%filters(j)%int_bins(k) - if (universe_dict%has_key(id)) then - t%filters(j)%int_bins(k) = universe_dict%get_key(id) - else - call fatal_error("Could not find universe " // trim(to_str(id)) & - &// " specified on tally " // trim(to_str(t%id))) - end if - end do - - case (FILTER_MATERIAL) - - do k = 1, t%filters(j)%n_bins - id = t%filters(j)%int_bins(k) - if (material_dict%has_key(id)) then - t%filters(j)%int_bins(k) = material_dict%get_key(id) - else - call fatal_error("Could not find material " // trim(to_str(id)) & - &// " specified on tally " // trim(to_str(t%id))) - end if - end do - - case (FILTER_MESH) - - ! The mesh filter already has been set to the index in meshes rather - ! than the user-specified id, so it doesn't need to be changed. - + if (.not. any(t % score_bins == SCORE_CURRENT)) & + call filt % initialize() + class default + call filt % initialize() end select end do FILTER_LOOP @@ -894,14 +834,11 @@ contains ! We need distribcell if any tallies have distribcell filters. do i = 1, n_tallies - do j = 1, tallies(i) % n_filters - if (tallies(i) % filters(j) % type == FILTER_DISTRIBCELL) then + do j = 1, size(tallies(i) % filters) + select type(filt => tallies(i) % filters(j) % obj) + type is (DistribcellFilter) distribcell_active = .true. - if (size(tallies(i) % filters(j) % int_bins) > 1) then - call fatal_error("A distribcell filter was specified with & - &multiple bins. This feature is not supported.") - end if - end if + end select end do end do @@ -924,13 +861,12 @@ contains ! Set the number of bins in all distribcell filters. do i = 1, n_tallies - do j = 1, tallies(i) % n_filters - associate (filt => tallies(i) % filters(j)) - if (filt % type == FILTER_DISTRIBCELL) then - ! Set the number of bins to the number of instances of the cell. - filt % n_bins = cells(filt % int_bins(1)) % instances - end if - end associate + do j = 1, size(tallies(i) % filters) + select type(filt => tallies(i) % filters(j) % obj) + type is (DistribcellFilter) + ! Set the number of bins to the number of instances of the cell. + filt % n_bins = cells(filt % cell) % instances + end select end do end do @@ -990,10 +926,11 @@ contains ! List all cells referenced in distribcell filters. do i = 1, n_tallies - do j = 1, tallies(i) % n_filters - if (tallies(i) % filters(j) % type == FILTER_DISTRIBCELL) then - call cell_list % add(tallies(i) % filters(j) % int_bins(1)) - end if + do j = 1, size(tallies(i) % filters) + select type(filt => tallies(i) % filters(j) % obj) + type is (DistribcellFilter) + call cell_list % add(filt % cell) + end select end do end do diff --git a/src/input_xml.F90 b/src/input_xml.F90 index f661927d7..330f0e6c5 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -24,7 +24,8 @@ module input_xml use stl_vector, only: VectorInt, VectorReal, VectorChar use string, only: to_lower, to_str, str_to_int, str_to_real, & starts_with, ends_with, tokenize, split_string - use tally_header, only: TallyObject, TallyFilter + use tally_header, only: TallyObject + use tally_filter use tally_initialize, only: add_tallies use xml_interface @@ -2667,7 +2668,7 @@ contains type(ElemKeyValueCI), pointer :: pair_list type(TallyObject), pointer :: t type(RegularMesh), pointer :: m - type(TallyFilter), allocatable :: filters(:) ! temporary filters + type(TallyFilterContainer), allocatable :: filters(:) ! temporary filters type(Node), pointer :: doc => null() type(Node), pointer :: node_mesh => null() type(Node), pointer :: node_tal => null() @@ -2907,115 +2908,122 @@ contains call get_node_list(node_tal, "filter", node_filt_list) n_filters = get_list_size(node_filt_list) - if (n_filters /= 0) then + ! Allocate filters array + allocate(t % filters(n_filters)) - ! Allocate filters array - t % n_filters = n_filters - allocate(t % filters(n_filters)) + READ_FILTERS: do j = 1, n_filters + ! Get pointer to filter xml node + call get_list_item(node_filt_list, j, node_filt) - READ_FILTERS: do j = 1, n_filters - ! Get pointer to filter xml node - call get_list_item(node_filt_list, j, node_filt) + ! Convert filter type to lower case + temp_str = '' + if (check_for_node(node_filt, "type")) & + call get_node_value(node_filt, "type", temp_str) + temp_str = to_lower(temp_str) - ! Convert filter type to lower case - temp_str = '' - if (check_for_node(node_filt, "type")) & - call get_node_value(node_filt, "type", temp_str) - temp_str = to_lower(temp_str) - - ! Determine number of bins - if (check_for_node(node_filt, "bins")) then - if (temp_str == 'energy' .or. temp_str == 'energyout' .or. & - temp_str == 'mu' .or. temp_str == 'polar' .or. & - temp_str == 'azimuthal') then - n_words = get_arraysize_double(node_filt, "bins") - else - n_words = get_arraysize_integer(node_filt, "bins") - end if + ! Determine number of bins + if (check_for_node(node_filt, "bins")) then + if (temp_str == 'energy' .or. temp_str == 'energyout' .or. & + temp_str == 'mu' .or. temp_str == 'polar' .or. & + temp_str == 'azimuthal') then + n_words = get_arraysize_double(node_filt, "bins") else - call fatal_error("Bins not set in filter on tally " & - // trim(to_str(t % id))) + n_words = get_arraysize_integer(node_filt, "bins") end if + else + call fatal_error("Bins not set in filter on tally " & + // trim(to_str(t % id))) + end if - ! Determine type of filter - select case (temp_str) + ! Determine type of filter + select case (temp_str) - case ('distribcell') - - ! Set type of filter - t % filters(j) % type = FILTER_DISTRIBCELL - - ! Going to add new filters to this tally if n_words > 1 + case ('distribcell') + ! Allocate and declare the filter type + allocate(DistribcellFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (DistribcellFilter) + if (n_words /= 1) call fatal_error("Only one cell can be & + &specified per distribcell filter.") + ! Store bins + call get_node_value(node_filt, "bins", filt % cell) + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_DISTRIBCELL) = j + case ('cell') + ! Allocate and declare the filter type + allocate(CellFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (CellFilter) ! Allocate and store bins - allocate(t % filters(j) % int_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % int_bins) - - case ('cell') - ! Set type of filter - t % filters(j) % type = FILTER_CELL - - ! Set number of bins - t % filters(j) % n_bins = n_words + filt % n_bins = n_words + allocate(filt % cells(n_words)) + call get_node_array(node_filt, "bins", filt % cells) + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_CELL) = j + case ('cellborn') + ! Allocate and declare the filter type + allocate(CellbornFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (CellbornFilter) ! Allocate and store bins - allocate(t % filters(j) % int_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % int_bins) - - case ('cellborn') - ! Set type of filter - t % filters(j) % type = FILTER_CELLBORN - - ! Set number of bins - t % filters(j) % n_bins = n_words + filt % n_bins = n_words + allocate(filt % cells(n_words)) + call get_node_array(node_filt, "bins", filt % cells) + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_CELLBORN) = j + case ('material') + ! Allocate and declare the filter type + allocate(MaterialFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (MaterialFilter) ! Allocate and store bins - allocate(t % filters(j) % int_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % int_bins) - - case ('material') - ! Set type of filter - t % filters(j) % type = FILTER_MATERIAL - - ! Set number of bins - t % filters(j) % n_bins = n_words + filt % n_bins = n_words + allocate(filt % materials(n_words)) + call get_node_array(node_filt, "bins", filt % materials) + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_MATERIAL) = j + case ('universe') + ! Allocate and declare the filter type + allocate(UniverseFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (UniverseFilter) ! Allocate and store bins - allocate(t % filters(j) % int_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % int_bins) - - case ('universe') - ! Set type of filter - t % filters(j) % type = FILTER_UNIVERSE - - ! Set number of bins - t % filters(j) % n_bins = n_words + filt % n_bins = n_words + allocate(filt % universes(n_words)) + call get_node_array(node_filt, "bins", filt % universes) + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_UNIVERSE) = j + case ('surface') + call fatal_error("Surface filter is not yet supported!") + ! Allocate and declare the filter type + allocate(SurfaceFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (SurfaceFilter) ! Allocate and store bins - allocate(t % filters(j) % int_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % int_bins) + filt % n_bins = n_words + allocate(filt % surfaces(n_words)) + call get_node_array(node_filt, "bins", filt % surfaces) + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_SURFACE) = j - case ('surface') - call fatal_error("Surface filter is not yet supported!") - - ! Set type of filter - t % filters(j) % type = FILTER_SURFACE - - ! Set number of bins - t % filters(j) % n_bins = n_words - - ! Allocate and store bins - allocate(t % filters(j) % int_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % int_bins) - - case ('mesh') - ! Set type of filter - t % filters(j) % type = FILTER_MESH - - ! Check to make sure multiple meshes weren't given - if (n_words /= 1) then - call fatal_error("Can only have one mesh filter specified.") - end if + case ('mesh') + ! Allocate and declare the filter type + allocate(MeshFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (MeshFilter) + if (n_words /= 1) call fatal_error("Only one mesh can be & + &specified per mesh filter.") ! Determine id of mesh call get_node_value(node_filt, "bins", id) @@ -3032,214 +3040,220 @@ contains ! Determine number of bins -- this is assuming that the tally is ! a volume tally and not a surface current tally. If it is a ! surface current tally, the number of bins will get reset later - t % filters(j) % n_bins = product(m % dimension) + filt % n_bins = product(m % dimension) - ! Allocate and store index of mesh - allocate(t % filters(j) % int_bins(1)) - t % filters(j) % int_bins(1) = i_mesh - - case ('energy') - ! Set type of filter - t % filters(j) % type = FILTER_ENERGYIN - - ! Set number of bins - t % filters(j) % n_bins = n_words - 1 + ! Store the index of the mesh + filt % mesh = i_mesh + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_MESH) = j + case ('energy') + ! Allocate and declare the filter type + allocate(EnergyFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (EnergyFilter) ! Allocate and store bins - allocate(t % filters(j) % real_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % real_bins) + filt % n_bins = n_words - 1 + allocate(filt % bins(n_words)) + call get_node_array(node_filt, "bins", filt % bins) - ! We can save tallying time if we know that the tally bins - ! match the energy group structure. In that case, the matching bin + ! We can save tallying time if we know that the tally bins match + ! the energy group structure. In that case, the matching bin ! index is simply the group (after flipping for the different ! ordering of the library and tallying systems). if (.not. run_CE) then if (n_words == energy_groups + 1) then - if (all(t % filters(j) % real_bins == & - energy_bins(energy_groups + 1:1:-1))) & - t % energy_matches_groups = .true. + if (all(filt % bins == energy_bins(energy_groups + 1:1:-1))) & + then + filt % matches_transport_groups = .true. + end if end if end if + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_ENERGYIN) = j - case ('energyout') - ! Set type of filter - t % filters(j) % type = FILTER_ENERGYOUT - - ! Set number of bins - t % filters(j) % n_bins = n_words - 1 - + case ('energyout') + ! Allocate and declare the filter type + allocate(EnergyoutFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (EnergyoutFilter) ! Allocate and store bins - allocate(t % filters(j) % real_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % real_bins) + filt % n_bins = n_words - 1 + allocate(filt % bins(n_words)) + call get_node_array(node_filt, "bins", filt % bins) - ! We can save tallying time if we know that the tally bins - ! match the energy group structure. In that case, the matching bin + ! We can save tallying time if we know that the tally bins match + ! the energy group structure. In that case, the matching bin ! index is simply the group (after flipping for the different ! ordering of the library and tallying systems). if (.not. run_CE) then if (n_words == energy_groups + 1) then - if (all(t % filters(j) % real_bins == & - energy_bins(energy_groups + 1:1:-1))) & - t % energyout_matches_groups = .true. + if (all(filt % bins == energy_bins(energy_groups + 1:1:-1))) & + then + filt % matches_transport_groups = .true. + end if end if end if + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_ENERGYOUT) = j - ! Set to analog estimator - t % estimator = ESTIMATOR_ANALOG + ! Set to analog estimator + t % estimator = ESTIMATOR_ANALOG - case ('delayedgroup') - ! Check to see if running in MG mode, because if so, the current - ! system isnt set up yet to support delayed group data and thus - ! these tallies - if (.not. run_CE) then - call fatal_error("delayedgroup filter on tally " & - // trim(to_str(t % id)) // " not yet supported& - & for multi-group mode.") - end if - - ! Set type of filter - t % filters(j) % type = FILTER_DELAYEDGROUP - - ! Set number of bins - t % filters(j) % n_bins = n_words + case ('delayedgroup') + ! Check to see if running in MG mode, because if so, the current + ! system isnt set up yet to support delayed group data and thus + ! these tallies + if (.not. run_CE) then + call fatal_error("delayedgroup filter on tally " & + // trim(to_str(t % id)) // " not yet supported& + & for multi-group mode.") + end if + ! Allocate and declare the filter type + allocate(DelayedGroupFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (DelayedGroupFilter) ! Allocate and store bins - allocate(t % filters(j) % int_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % int_bins) + filt % n_bins = n_words + allocate(filt % groups(n_words)) + call get_node_array(node_filt, "bins", filt % groups) - ! Check bins to make sure all are between 1 and MAX_DELAYED_GROUPS + ! Check that bins are all are between 1 and MAX_DELAYED_GROUPS do d = 1, n_words - if (t % filters(j) % int_bins(d) < 1 .or. & - t % filters(j) % int_bins(d) > MAX_DELAYED_GROUPS) then + if (filt % groups(d) < 1 .or. & + filt % groups(d) > MAX_DELAYED_GROUPS) then call fatal_error("Encountered delayedgroup bin with index " & - // trim(to_str(t % filters(j) % int_bins(d))) // " that is& - & outside the range of 1 to MAX_DELAYED_GROUPS ( " & + // trim(to_str(filt % groups(d))) // " that is outside & + &the range of 1 to MAX_DELAYED_GROUPS ( " & // trim(to_str(MAX_DELAYED_GROUPS)) // ")") end if end do + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_DELAYEDGROUP) = j - case ('mu') - ! Set type of filter - t % filters(j) % type = FILTER_MU - - ! Set number of bins - t % filters(j) % n_bins = n_words - 1 - + case ('mu') + ! Allocate and declare the filter type + allocate(MuFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (MuFilter) ! Allocate and store bins - allocate(t % filters(j) % real_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % real_bins) + filt % n_bins = n_words - 1 + allocate(filt % bins(n_words)) + call get_node_array(node_filt, "bins", filt % bins) - ! Allow a user to input a lone number which will mean that - ! you subivide [-1,1] evenly with the input being the number of bins + ! Allow a user to input a lone number which will mean that you + ! subdivide [-1,1] evenly with the input being the number of bins if (n_words == 1) then - Nangle = int(t % filters(j) % real_bins(1)) + Nangle = int(filt % bins(1)) if (Nangle > 1) then - t % filters(j) % n_bins = Nangle + filt % n_bins = Nangle dangle = TWO / real(Nangle,8) - deallocate(t % filters(j) % real_bins) - allocate(t % filters(j) % real_bins(Nangle + 1)) + deallocate(filt % bins) + allocate(filt % bins(Nangle + 1)) do iangle = 1, Nangle - t % filters(j) % real_bins(iangle) = -ONE + (iangle - 1) * dangle + filt % bins(iangle) = -ONE + (iangle - 1) * dangle end do - t % filters(j) % real_bins(Nangle + 1) = ONE + filt % bins(Nangle + 1) = ONE else call fatal_error("Number of bins for mu filter must be& - & greater than 1 on tally " // trim(to_str(t % id)) // ".") + & greater than 1 on tally " & + // trim(to_str(t % id)) // ".") end if - end if + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_MU) = j - ! Set to analog estimator - t % estimator = ESTIMATOR_ANALOG - - case ('polar') - ! Set type of filter - t % filters(j) % type = FILTER_POLAR - - ! Set number of bins - t % filters(j) % n_bins = n_words - 1 + ! Set to analog estimator + t % estimator = ESTIMATOR_ANALOG + case ('polar') + ! Allocate and declare the filter type + allocate(PolarFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (PolarFilter) ! Allocate and store bins - allocate(t % filters(j) % real_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % real_bins) + filt % n_bins = n_words - 1 + allocate(filt % bins(n_words)) + call get_node_array(node_filt, "bins", filt % bins) - ! Allow a user to input a lone number which will mean that - ! you subivide [0,pi] evenly with the input being the number of bins + ! Allow a user to input a lone number which will mean that you + ! subdivide [0,pi] evenly with the input being the number of bins if (n_words == 1) then - Nangle = int(t % filters(j) % real_bins(1)) + Nangle = int(filt % bins(1)) if (Nangle > 1) then - t % filters(j) % n_bins = Nangle + filt % n_bins = Nangle dangle = PI / real(Nangle,8) - deallocate(t % filters(j) % real_bins) - allocate(t % filters(j) % real_bins(Nangle + 1)) + deallocate(filt % bins) + allocate(filt % bins(Nangle + 1)) do iangle = 1, Nangle - t % filters(j) % real_bins(iangle) = (iangle - 1) * dangle + filt % bins(iangle) = (iangle - 1) * dangle end do - t % filters(j) % real_bins(Nangle + 1) = PI + filt % bins(Nangle + 1) = PI else - call fatal_error("Number of bins for polar filter must be& - & greater than 1 on tally " // trim(to_str(t % id)) // ".") + call fatal_error("Number of bins for mu filter must be& + & greater than 1 on tally " & + // trim(to_str(t % id)) // ".") end if - end if + end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_POLAR) = j - case ('azimuthal') - ! Set type of filter - t % filters(j) % type = FILTER_AZIMUTHAL - - ! Set number of bins - t % filters(j) % n_bins = n_words - 1 - + case ('azimuthal') + ! Allocate and declare the filter type + allocate(AzimuthalFilter::t % filters(j) % obj) + select type (filt => t % filters(j) % obj) + type is (AzimuthalFilter) ! Allocate and store bins - allocate(t % filters(j) % real_bins(n_words)) - call get_node_array(node_filt, "bins", t % filters(j) % real_bins) + filt % n_bins = n_words - 1 + allocate(filt % bins(n_words)) + call get_node_array(node_filt, "bins", filt % bins) - ! Allow a user to input a lone number which will mean that - ! you sub-divide [-pi,pi) evenly with the input being the number of + ! Allow a user to input a lone number which will mean that you + ! subdivide [-pi,pi) evenly with the input being the number of ! bins if (n_words == 1) then - Nangle = int(t % filters(j) % real_bins(1)) + Nangle = int(filt % bins(1)) if (Nangle > 1) then - t % filters(j) % n_bins = Nangle + filt % n_bins = Nangle dangle = TWO * PI / real(Nangle,8) - deallocate(t % filters(j) % real_bins) - allocate(t % filters(j) % real_bins(Nangle + 1)) + deallocate(filt % bins) + allocate(filt % bins(Nangle + 1)) do iangle = 1, Nangle - t % filters(j) % real_bins(iangle) = -PI + (iangle - 1) * dangle + filt % bins(iangle) = -PI + (iangle - 1) * dangle end do - t % filters(j) % real_bins(Nangle + 1) = PI + filt % bins(Nangle + 1) = PI else - call fatal_error("Number of bins for azimuthal filter must be& - & greater than 1 on tally " // trim(to_str(t % id)) // ".") + call fatal_error("Number of bins for mu filter must be& + & greater than 1 on tally " & + // trim(to_str(t % id)) // ".") end if - end if - - case default - ! Specified tally filter is invalid, raise error - call fatal_error("Unknown filter type '" & - // trim(temp_str) // "' on tally " & - // trim(to_str(t % id)) // ".") - end select + ! Set the filter index in the tally find_filter array + t % find_filter(FILTER_AZIMUTHAL) = j - ! Set find_filter, e.g. if filter(3) has type FILTER_CELL, then - ! find_filter(FILTER_CELL) would be set to 3. + case default + ! Specified tally filter is invalid, raise error + call fatal_error("Unknown filter type '" & + // trim(temp_str) // "' on tally " & + // trim(to_str(t % id)) // ".") - t % find_filter(t % filters(j) % type) = j + end select - end do READ_FILTERS + end do READ_FILTERS - ! Check that both cell and surface weren't specified - if (t % find_filter(FILTER_CELL) > 0 .and. & - t % find_filter(FILTER_SURFACE) > 0) then - call fatal_error("Cannot specify both cell and surface filters for & - &tally " // trim(to_str(t % id))) - end if - - else - ! No filters were specified - t % n_filters = 0 + ! Check that both cell and surface weren't specified + if (t % find_filter(FILTER_CELL) > 0 .and. & + t % find_filter(FILTER_SURFACE) > 0) then + call fatal_error("Cannot specify both cell and surface filters for & + &tally " // trim(to_str(t % id))) end if ! ======================================================================= @@ -3275,8 +3289,9 @@ contains if (trim(sarray(j)) == 'total') then ! Check if a delayedgroup filter is present for this tally - do l = 1, t % n_filters - if (t % filters(l) % type == FILTER_DELAYEDGROUP) then + do l = 1, size(t % filters) + select type(filt => t % filters(l) % obj) + type is (DelayedGroupFilter) call warning("A delayedgroup filter was used on a total & &nuclide tally. Cross section libraries are not & &guaranteed to have the same delayed group structure & @@ -3285,7 +3300,7 @@ contains &all isotopes while the JEFF 3.1.1 library has the same & &delayed group structure across all isotopes. Use with & &caution!") - end if + end select end do t % nuclide_bins(j) = -1 @@ -3334,8 +3349,9 @@ contains t % n_nuclide_bins = 1 ! Check if a delayedgroup filter is present for this tally - do l = 1, t % n_filters - if (t % filters(l) % type == FILTER_DELAYEDGROUP) then + do l = 1, size(t % filters) + select type(filt => t % filters(l) % obj) + type is (DelayedGroupFilter) call warning("A delayedgroup filter was used on a total nuclide & &tally. Cross section libraries are not guaranteed to have the& & same delayed group structure across all isotopes. In & @@ -3343,7 +3359,7 @@ contains &group structure across all isotopes while the JEFF 3.1.1 & &library has the same delayed group structure across all & &isotopes. Use with caution!") - end if + end select end do end if @@ -3619,6 +3635,12 @@ contains ! Set tally estimator to analog t % estimator = ESTIMATOR_ANALOG end if + case ('prompt-nu-fission') + t % score_bins(j) = SCORE_PROMPT_NU_FISSION + if (t % find_filter(FILTER_ENERGYOUT) > 0) then + ! Set tally estimator to analog + t % estimator = ESTIMATOR_ANALOG + end if ! Disallow for MG mode since data not present if (.not. run_CE) then @@ -3657,36 +3679,42 @@ contains &filter.") end if - ! Get pointer to mesh - i_mesh = t % filters(k) % int_bins(1) - m => meshes(i_mesh) + ! Declare the type of the mesh filter + select type(filt => t % filters(k) % obj) + type is (MeshFilter) - ! We need to increase the dimension by one since we also need - ! currents coming into and out of the boundary mesh cells. - t % filters(k) % n_bins = product(m % dimension + 1) + ! Get pointer to mesh + i_mesh = filt % mesh + m => meshes(i_mesh) + + ! We need to increase the dimension by one since we also need + ! currents coming into and out of the boundary mesh cells. + filt % n_bins = product(m % dimension + 1) + end select ! Copy filters to temporary array - allocate(filters(t % n_filters + 1)) - filters(1:t % n_filters) = t % filters + allocate(filters(size(t % filters) + 1)) + filters(1:size(t % filters)) = t % filters ! Move allocation back -- filters becomes deallocated during ! this call call move_alloc(FROM=filters, TO=t%filters) ! Add surface filter - t % n_filters = t % n_filters + 1 - t % filters(t % n_filters) % type = FILTER_SURFACE - t % filters(t % n_filters) % n_bins = 2 * m % n_dimension - allocate(t % filters(t % n_filters) % int_bins(& - 2 * m % n_dimension)) - if (m % n_dimension == 2) then - t % filters(t % n_filters) % int_bins = (/ IN_RIGHT, & - OUT_RIGHT, IN_FRONT, OUT_FRONT /) - elseif (m % n_dimension == 3) then - t % filters(t % n_filters) % int_bins = (/ IN_RIGHT, & - OUT_RIGHT, IN_FRONT, OUT_FRONT, IN_TOP, OUT_TOP /) - end if - t % find_filter(FILTER_SURFACE) = t % n_filters + n_filters = size(t % filters) + allocate(SurfaceFilter :: t % filters(n_filters) % obj) + select type (filt => t % filters(size(t % filters)) % obj) + type is (SurfaceFilter) + filt % n_bins = 2 * m % n_dimension + allocate(filt % surfaces(2 * m % n_dimension)) + if (m % n_dimension == 2) then + filt % surfaces = (/ IN_RIGHT, OUT_RIGHT, IN_FRONT, OUT_FRONT /) + elseif (m % n_dimension == 3) then + filt % surfaces = (/ IN_RIGHT, OUT_RIGHT, IN_FRONT, OUT_FRONT,& + IN_TOP, OUT_TOP /) + end if + end select + t % find_filter(FILTER_SURFACE) = size(t % filters) case ('events') t % score_bins(j) = SCORE_EVENTS @@ -4396,9 +4424,11 @@ contains &meshlines on plot " // trim(to_str(pl % id))) end if - i_mesh = cmfd_tallies(1) % & - filters(cmfd_tallies(1) % find_filter(FILTER_MESH)) % & - int_bins(1) + select type(filt => cmfd_tallies(1) % & + filters(cmfd_tallies(1) % find_filter(FILTER_MESH)) % obj) + type is (MeshFilter) + i_mesh = filt % mesh + end select pl % meshlines_mesh => meshes(i_mesh) case ('entropy') diff --git a/src/mesh.F90 b/src/mesh.F90 index 4ec84345b..0b227f4f5 100644 --- a/src/mesh.F90 +++ b/src/mesh.F90 @@ -290,34 +290,42 @@ contains ! Check if line intersects left surface -- calculate the intersection point ! y - yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0) - if (yi >= ym0 .and. yi < ym1) then - intersects = .true. - return + if ((x0 < xm0 .and. x1 > xm0) .or. (x0 > xm0 .and. x1 < xm0)) then + yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0) + if (yi >= ym0 .and. yi < ym1) then + intersects = .true. + return + end if end if ! Check if line intersects back surface -- calculate the intersection point ! x - xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0) - if (xi >= xm0 .and. xi < xm1) then - intersects = .true. - return + if ((y0 < ym0 .and. y1 > ym0) .or. (y0 > ym0 .and. y1 < ym0)) then + xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0) + if (xi >= xm0 .and. xi < xm1) then + intersects = .true. + return + end if end if ! Check if line intersects right surface -- calculate the intersection ! point y - yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0) - if (yi >= ym0 .and. yi < ym1) then - intersects = .true. - return + if ((x0 < xm1 .and. x1 > xm1) .or. (x0 > xm1 .and. x1 < xm1)) then + yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0) + if (yi >= ym0 .and. yi < ym1) then + intersects = .true. + return + end if end if ! Check if line intersects front surface -- calculate the intersection point ! x - xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0) - if (xi >= xm0 .and. xi < xm1) then - intersects = .true. - return + if ((y0 < ym1 .and. y1 > ym1) .or. (y0 > ym1 .and. y1 < ym1)) then + xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0) + if (xi >= xm0 .and. xi < xm1) then + intersects = .true. + return + end if end if end function mesh_intersects_2d @@ -359,56 +367,68 @@ contains ! Check if line intersects left surface -- calculate the intersection point ! (y,z) - yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0) - zi = z0 + (xm0 - x0) * (z1 - z0) / (x1 - x0) - if (yi >= ym0 .and. yi < ym1 .and. zi >= zm0 .and. zi < zm1) then - intersects = .true. - return + if ((x0 < xm0 .and. x1 > xm0) .or. (x0 > xm0 .and. x1 < xm0)) then + yi = y0 + (xm0 - x0) * (y1 - y0) / (x1 - x0) + zi = z0 + (xm0 - x0) * (z1 - z0) / (x1 - x0) + if (yi >= ym0 .and. yi < ym1 .and. zi >= zm0 .and. zi < zm1) then + intersects = .true. + return + end if end if ! Check if line intersects back surface -- calculate the intersection point ! (x,z) - xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0) - zi = z0 + (ym0 - y0) * (z1 - z0) / (y1 - y0) - if (xi >= xm0 .and. xi < xm1 .and. zi >= zm0 .and. zi < zm1) then - intersects = .true. - return + if ((y0 < ym0 .and. y1 > ym0) .or. (y0 > ym0 .and. y1 < ym0)) then + xi = x0 + (ym0 - y0) * (x1 - x0) / (y1 - y0) + zi = z0 + (ym0 - y0) * (z1 - z0) / (y1 - y0) + if (xi >= xm0 .and. xi < xm1 .and. zi >= zm0 .and. zi < zm1) then + intersects = .true. + return + end if end if ! Check if line intersects bottom surface -- calculate the intersection ! point (x,y) - xi = x0 + (zm0 - z0) * (x1 - x0) / (z1 - z0) - yi = y0 + (zm0 - z0) * (y1 - y0) / (z1 - z0) - if (xi >= xm0 .and. xi < xm1 .and. yi >= ym0 .and. yi < ym1) then - intersects = .true. - return + if ((z0 < zm0 .and. z1 > zm0) .or. (z0 > zm0 .and. z1 < zm0)) then + xi = x0 + (zm0 - z0) * (x1 - x0) / (z1 - z0) + yi = y0 + (zm0 - z0) * (y1 - y0) / (z1 - z0) + if (xi >= xm0 .and. xi < xm1 .and. yi >= ym0 .and. yi < ym1) then + intersects = .true. + return + end if end if ! Check if line intersects right surface -- calculate the intersection point ! (y,z) - yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0) - zi = z0 + (xm1 - x0) * (z1 - z0) / (x1 - x0) - if (yi >= ym0 .and. yi < ym1 .and. zi >= zm0 .and. zi < zm1) then - intersects = .true. - return + if ((x0 < xm1 .and. x1 > xm1) .or. (x0 > xm1 .and. x1 < xm1)) then + yi = y0 + (xm1 - x0) * (y1 - y0) / (x1 - x0) + zi = z0 + (xm1 - x0) * (z1 - z0) / (x1 - x0) + if (yi >= ym0 .and. yi < ym1 .and. zi >= zm0 .and. zi < zm1) then + intersects = .true. + return + end if end if ! Check if line intersects front surface -- calculate the intersection point ! (x,z) - xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0) - zi = z0 + (ym1 - y0) * (z1 - z0) / (y1 - y0) - if (xi >= xm0 .and. xi < xm1 .and. zi >= zm0 .and. zi < zm1) then - intersects = .true. - return + if ((y0 < ym1 .and. y1 > ym1) .or. (y0 > ym1 .and. y1 < ym1)) then + xi = x0 + (ym1 - y0) * (x1 - x0) / (y1 - y0) + zi = z0 + (ym1 - y0) * (z1 - z0) / (y1 - y0) + if (xi >= xm0 .and. xi < xm1 .and. zi >= zm0 .and. zi < zm1) then + intersects = .true. + return + end if end if ! Check if line intersects top surface -- calculate the intersection point ! (x,y) - xi = x0 + (zm1 - z0) * (x1 - x0) / (z1 - z0) - yi = y0 + (zm1 - z0) * (y1 - y0) / (z1 - z0) - if (xi >= xm0 .and. xi < xm1 .and. yi >= ym0 .and. yi < ym1) then - intersects = .true. - return + if ((z0 < zm1 .and. z1 > zm1) .or. (z0 > zm1 .and. z1 < zm1)) then + xi = x0 + (zm1 - z0) * (x1 - x0) / (z1 - z0) + yi = y0 + (zm1 - z0) * (y1 - y0) / (z1 - z0) + if (xi >= xm0 .and. xi < xm1 .and. yi >= ym0 .and. yi < ym1) then + intersects = .true. + return + end if end if end function mesh_intersects_3d diff --git a/src/output.F90 b/src/output.F90 index 35524a9a3..2ed25092e 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -17,6 +17,7 @@ module output use sab_header, only: SAlphaBeta use string, only: to_upper, to_str use tally_header, only: TallyObject + use tally_filter implicit none @@ -736,7 +737,6 @@ contains integer :: k ! loop index for scoring bins integer :: n ! loop index for nuclides integer :: l ! loop index for user scores - integer :: type ! type of tally filter integer :: indent ! number of spaces to preceed output integer :: filter_index ! index in results array for filters integer :: score_index ! scoring bin index @@ -747,7 +747,6 @@ contains real(8) :: t_value ! t-values for confidence intervals real(8) :: alpha ! significance level for CI character(MAX_FILE_LEN) :: filename ! name of output file - character(16) :: filter_name(N_FILTER_TYPES) ! names of tally filters 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 @@ -756,21 +755,6 @@ contains ! Skip if there are no tallies if (n_tallies == 0) return - ! Initialize names for tally filter types - filter_name(FILTER_UNIVERSE) = "Universe" - filter_name(FILTER_MATERIAL) = "Material" - filter_name(FILTER_DISTRIBCELL) = "Distributed Cell" - filter_name(FILTER_CELL) = "Cell" - filter_name(FILTER_CELLBORN) = "Birth Cell" - filter_name(FILTER_SURFACE) = "Surface" - filter_name(FILTER_MESH) = "Mesh" - filter_name(FILTER_ENERGYIN) = "Incoming Energy" - filter_name(FILTER_ENERGYOUT) = "Outgoing Energy" - filter_name(FILTER_MU) = "Change-in-Angle" - filter_name(FILTER_POLAR) = "Polar Angle" - filter_name(FILTER_AZIMUTHAL) = "Azimuthal Angle" - filter_name(FILTER_DELAYEDGROUP) = "Delayed Group" - ! Initialize names for scores score_names(abs(SCORE_FLUX)) = "Flux" score_names(abs(SCORE_TOTAL)) = "Total Reaction Rate" @@ -790,6 +774,7 @@ contains score_names(abs(SCORE_NU_SCATTER_PN)) = "Scattering Prod. Rate Moment" score_names(abs(SCORE_NU_SCATTER_YN)) = "Scattering Prod. Rate Moment" score_names(abs(SCORE_DELAYED_NU_FISSION)) = "Delayed-Nu-Fission Rate" + score_names(abs(SCORE_PROMPT_NU_FISSION)) = "Prompt-Nu-Fission Rate" score_names(abs(SCORE_INVERSE_VELOCITY)) = "Flux-Weighted Inverse Velocity" score_names(abs(SCORE_FISS_Q_PROMPT)) = "Prompt fission power" score_names(abs(SCORE_FISS_Q_RECOV)) = "Recoverable fission power" @@ -842,14 +827,14 @@ contains ! to be used for a given tally. ! Initialize bins, filter level, and indentation - matching_bins(1:t%n_filters) = 0 + matching_bins(1:size(t % filters)) = 0 j = 1 indent = 0 print_bin: do find_bin: do ! Check for no filters - if (t % n_filters == 0) exit find_bin + if (size(t % filters) == 0) exit find_bin ! Increment bin combination matching_bins(j) = matching_bins(j) + 1 @@ -857,7 +842,7 @@ contains ! ================================================================= ! REACHED END OF BINS FOR THIS FILTER, MOVE TO NEXT FILTER - if (matching_bins(j) > t % filters(j) % n_bins) then + if (matching_bins(j) > t % filters(j) % obj % n_bins) then ! If this is the first filter, then exit if (j == 1) exit print_bin @@ -870,12 +855,11 @@ contains else ! Check if this is last filter - if (j == t % n_filters) exit find_bin + if (j == size(t % filters)) exit find_bin ! Print current filter information - type = t % filters(j) % type - write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & - trim(filter_name(type)), trim(get_label(t, j)) + write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & + trim(t % filters(j) % obj % text_label(matching_bins(j))) indent = indent + 2 j = j + 1 end if @@ -883,25 +867,25 @@ contains end do find_bin ! Print filter information - if (t % n_filters > 0) then - type = t % filters(j) % type - write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & - trim(filter_name(type)), trim(get_label(t, j)) + if (size(t % filters) > 0) then + write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & + trim(t % filters(j) % obj % text_label(matching_bins(j))) end if ! Determine scoring index for this bin combination -- note that unlike ! in the score_tally subroutine, we have to use max(bins,1) since all ! bins below the lowest filter level will be zeros - if (t % n_filters > 0) then - filter_index = sum((max(matching_bins(1:t%n_filters),1) - 1) * t % stride) + 1 + if (size(t % filters) > 0) then + filter_index = sum((max(matching_bins(1:size(t % filters)),1) - 1) & + * t % stride) + 1 else filter_index = 1 end if ! Write results for this filter bin combination score_index = 0 - if (t % n_filters > 0) indent = indent + 2 + if (size(t % filters) > 0) indent = indent + 2 do n = 1, t % n_nuclide_bins ! Write label for nuclide i_nuclide = t % nuclide_bins(n) @@ -973,7 +957,7 @@ contains end do indent = indent - 2 - if (t % n_filters == 0) exit print_bin + if (size(t % filters) == 0) exit print_bin end do print_bin @@ -1010,16 +994,19 @@ contains ! Get pointer to mesh i_filter_mesh = t % find_filter(FILTER_MESH) i_filter_surf = t % find_filter(FILTER_SURFACE) - m => meshes(t % filters(i_filter_mesh) % int_bins(1)) + select type(filt => t % filters(i_filter_mesh) % obj) + type is (MeshFilter) + m => meshes(filt % mesh) + end select ! initialize bins array - matching_bins(1:t%n_filters) = 1 + matching_bins(1:size(t % filters)) = 1 ! determine how many energy in bins there are i_filter_ein = t % find_filter(FILTER_ENERGYIN) if (i_filter_ein > 0) then print_ebin = .true. - n = t % filters(i_filter_ein) % n_bins + n = t % filters(i_filter_ein) % obj % n_bins else print_ebin = .false. n = 1 @@ -1042,22 +1029,25 @@ contains matching_bins(i_filter_ein) = l ! Write incoming energy bin - write(UNIT=unit_tally, FMT='(3X,A,1X,A)') & - "Incoming Energy", trim(get_label(t, i_filter_ein)) + write(UNIT=unit_tally, FMT='(3X,A)') & + trim(t % filters(i_filter_ein) % obj % text_label( & + matching_bins(i_filter_ein))) end if ! Left Surface matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, (/ i-1, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_RIGHT - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Left", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_RIGHT - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Left", & to_str(t % results(1,filter_index) % sum), & @@ -1067,14 +1057,16 @@ contains matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_RIGHT - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Right", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_RIGHT - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Right", & to_str(t % results(1,filter_index) % sum), & @@ -1084,14 +1076,16 @@ contains matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, (/ i, j-1, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_FRONT - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Back", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_FRONT - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Back", & to_str(t % results(1,filter_index) % sum), & @@ -1101,14 +1095,16 @@ contains matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_FRONT - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Front", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_FRONT - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Front", & to_str(t % results(1,filter_index) % sum), & @@ -1118,14 +1114,16 @@ contains matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, (/ i, j, k-1 /) + 1, .true.) matching_bins(i_filter_surf) = IN_TOP - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Bottom", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_TOP - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Bottom", & to_str(t % results(1,filter_index) % sum), & @@ -1135,14 +1133,16 @@ contains matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_TOP - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Top", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_TOP - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Top", & to_str(t % results(1,filter_index) % sum), & @@ -1155,329 +1155,4 @@ contains end subroutine write_surface_current -!=============================================================================== -! GET_LABEL returns a label for a cell/surface/etc given a tally, filter type, -! and corresponding bin -!=============================================================================== - - function get_label(t, i_filter) result(label) - type(TallyObject), intent(in) :: t ! tally object - integer, intent(in) :: i_filter ! index in filters array - character(MAX_LINE_LEN) :: label ! user-specified identifier - - integer :: i ! index in cells/surfaces/etc array - integer :: bin - integer :: offset - integer, allocatable :: ijk(:) ! indices in mesh - real(8) :: E0 ! lower bound for energy bin - real(8) :: E1 ! upper bound for energy bin - type(RegularMesh), pointer :: m - type(Universe), pointer :: univ - - bin = matching_bins(i_filter) - - select case(t % filters(i_filter) % type) - case (FILTER_UNIVERSE) - i = t % filters(i_filter) % int_bins(bin) - label = to_str(universes(i) % id) - case (FILTER_MATERIAL) - i = t % filters(i_filter) % int_bins(bin) - label = to_str(materials(i) % id) - case (FILTER_CELL, FILTER_CELLBORN) - i = t % filters(i_filter) % int_bins(bin) - label = to_str(cells(i) % id) - case (FILTER_DISTRIBCELL) - label = '' - univ => universes(BASE_UNIVERSE) - offset = 0 - call find_offset(t % filters(i_filter) % int_bins(1), & - univ, bin-1, offset, label) - case (FILTER_SURFACE) - i = t % filters(i_filter) % int_bins(bin) - label = to_str(surfaces(i)%obj%id) - case (FILTER_MESH) - m => meshes(t % filters(i_filter) % int_bins(1)) - allocate(ijk(m % n_dimension)) - call bin_to_mesh_indices(m, bin, ijk) - if (m % n_dimension == 2) then - label = "Index (" // trim(to_str(ijk(1))) // ", " // & - trim(to_str(ijk(2))) // ")" - elseif (m % n_dimension == 3) then - label = "Index (" // trim(to_str(ijk(1))) // ", " // & - trim(to_str(ijk(2))) // ", " // trim(to_str(ijk(3))) // ")" - end if - case (FILTER_ENERGYIN, FILTER_ENERGYOUT, FILTER_MU, FILTER_POLAR, & - FILTER_AZIMUTHAL) - E0 = t % filters(i_filter) % real_bins(bin) - E1 = t % filters(i_filter) % real_bins(bin + 1) - label = "[" // trim(to_str(E0)) // ", " // trim(to_str(E1)) // ")" - case (FILTER_DELAYEDGROUP) - i = t % filters(i_filter) % int_bins(bin) - label = to_str(i) - end select - - end function get_label - -!=============================================================================== -! FIND_OFFSET uses a given map number, a target cell ID, and a target offset -! to build a string which is the path from the base universe to the target cell -! with the given offset -!=============================================================================== - - recursive subroutine find_offset(goal, univ, final, offset, path) - - integer, intent(in) :: goal ! The target cell index - type(Universe), intent(in) :: univ ! Universe to begin search - integer, intent(in) :: final ! Target offset - integer, intent(inout) :: offset ! Current offset - character(*), intent(inout) :: path ! Path to offset - - integer :: map ! Index in maps vector - integer :: i, j ! Index over cells - integer :: k, l, m ! Indices in lattice - integer :: old_k, old_l, old_m ! Previous indices in lattice - integer :: n_x, n_y, n_z ! Lattice cell array dimensions - integer :: n ! Number of cells to search - integer :: cell_index ! Index in cells array - integer :: lat_offset ! Offset from lattice - integer :: temp_offset ! Looped sum of offsets - logical :: this_cell = .false. ! Advance in this cell? - logical :: later_cell = .false. ! Fill cells after this one? - type(Cell), pointer :: c ! Pointer to current cell - type(Universe), pointer :: next_univ ! Next universe to loop through - class(Lattice), pointer :: lat ! Pointer to current lattice - - ! Get the distribcell index for this cell - map = cells(goal) % distribcell_index - - n = univ % n_cells - - ! Write to the geometry stack - if (univ%id == 0) then - path = trim(path) // to_str(univ%id) - else - path = trim(path) // "->" // to_str(univ%id) - end if - - ! Look through all cells in this universe - do i = 1, n - ! If the cell matches the goal and the offset matches final, write to the - ! geometry stack - if (univ % cells(i) == goal .and. offset == final) then - c => cells(univ % cells(i)) - path = trim(path) // "->" // to_str(c % id) - return - end if - end do - - ! Find the fill cell or lattice cell that we need to enter - do i = 1, n - - later_cell = .false. - - cell_index = univ % cells(i) - c => cells(cell_index) - - this_cell = .false. - - ! If we got here, we still think the target is in this universe - ! or further down, but it's not this exact cell. - ! Compare offset to next cell to see if we should enter this cell - if (i /= n) then - - do j = i+1, n - - cell_index = univ % cells(j) - c => cells(cell_index) - - ! Skip normal cells which do not have offsets - if (c % type == CELL_NORMAL) then - cycle - end if - - ! Break loop once we've found the next cell with an offset - exit - end do - - ! Ensure we didn't just end the loop by iteration - if (c % type /= CELL_NORMAL) then - - ! There are more cells in this universe that it could be in - later_cell = .true. - - ! Two cases, lattice or fill cell - if (c % type == CELL_FILL) then - temp_offset = c % offset(map) - - ! Get the offset of the first lattice location - else - lat => lattices(c % fill) % obj - temp_offset = lat % offset(map, 1, 1, 1) - end if - - ! If the final offset is in the range of offset - temp_offset+offset - ! then the goal is in this cell - if (final < temp_offset + offset) then - this_cell = .true. - end if - end if - end if - - if (n == 1 .and. c % type /= CELL_NORMAL) then - this_cell = .true. - end if - - if (.not. later_cell) then - this_cell = .true. - end if - - ! Get pointer to THIS cell because target must be in this cell - if (this_cell) then - - cell_index = univ % cells(i) - c => cells(cell_index) - - path = trim(path) // "->" // to_str(c%id) - - ! ==================================================================== - ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - if (c % type == CELL_FILL) then - - ! Enter this cell to update the current offset - offset = c % offset(map) + offset - - next_univ => universes(c % fill) - call find_offset(goal, next_univ, final, offset, path) - return - - ! ==================================================================== - ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type == CELL_LATTICE) then - - ! Set current lattice - lat => lattices(c % fill) % obj - - select type (lat) - - ! ================================================================== - ! RECTANGULAR LATTICES - type is (RectLattice) - - ! Write to the geometry stack - path = trim(path) // "->" // to_str(lat%id) - - n_x = lat % n_cells(1) - n_y = lat % n_cells(2) - n_z = lat % n_cells(3) - old_m = 1 - old_l = 1 - old_k = 1 - - ! Loop over lattice coordinates - do k = 1, n_x - do l = 1, n_y - do m = 1, n_z - - if (final >= lat % offset(map, k, l, m) + offset) then - if (k == n_x .and. l == n_y .and. m == n_z) then - ! This is last lattice cell, so target must be here - lat_offset = lat % offset(map, k, l, m) - offset = offset + lat_offset - next_univ => universes(lat % universes(k, l, m)) - path = trim(path) // "(" // trim(to_str(k)) // & - "," // trim(to_str(l)) // "," // & - trim(to_str(m)) // ")" - call find_offset(goal, next_univ, final, offset, path) - return - else - old_m = m - old_l = l - old_k = k - cycle - end if - else - ! Target is at this lattice position - lat_offset = lat % offset(map, old_k, old_l, old_m) - offset = offset + lat_offset - next_univ => universes(lat % universes(old_k, old_l, old_m)) - path = trim(path) // "(" // trim(to_str(old_k)) // & - "," // trim(to_str(old_l)) // "," // & - trim(to_str(old_m)) // ")" - call find_offset(goal, next_univ, final, offset, path) - return - end if - - end do - end do - end do - - ! ================================================================== - ! HEXAGONAL LATTICES - type is (HexLattice) - - ! Write to the geometry stack - path = trim(path) // "->" // to_str(lat%id) - - n_z = lat % n_axial - n_y = 2 * lat % n_rings - 1 - n_x = 2 * lat % n_rings - 1 - old_m = 1 - old_l = 1 - old_k = 1 - - ! Loop over lattice coordinates - do m = 1, n_z - do l = 1, n_y - do k = 1, n_x - - ! This array position is never used - if (k + l < lat % n_rings + 1) then - cycle - ! This array position is never used - else if (k + l > 3*lat % n_rings - 1) then - cycle - end if - - if (final >= lat % offset(map, k, l, m) + offset) then - if (k == lat % n_rings .and. l == n_y .and. m == n_z) then - ! This is last lattice cell, so target must be here - lat_offset = lat % offset(map, k, l, m) - offset = offset + lat_offset - next_univ => universes(lat % universes(k, l, m)) - path = trim(path) // "(" // & - trim(to_str(k - lat % n_rings)) // "," // & - trim(to_str(l - lat % n_rings)) // "," // & - trim(to_str(m)) // ")" - call find_offset(goal, next_univ, final, offset, path) - return - else - old_m = m - old_l = l - old_k = k - cycle - end if - else - ! Target is at this lattice position - lat_offset = lat % offset(map, old_k, old_l, old_m) - offset = offset + lat_offset - next_univ => universes(lat % universes(old_k, old_l, old_m)) - path = trim(path) // "(" // & - trim(to_str(old_k - lat % n_rings)) // "," // & - trim(to_str(old_l - lat % n_rings)) // "," // & - trim(to_str(old_m)) // ")" - call find_offset(goal, next_univ, final, offset, path) - return - end if - - end do - end do - end do - - end select - - end if - end if - end do - end subroutine find_offset - end module output diff --git a/src/particle_header.F90 b/src/particle_header.F90 index a313c6ed5..ee854aea3 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -59,10 +59,13 @@ module particle_header logical :: alive ! is particle alive? ! Pre-collision physical data - real(8) :: last_xyz(3) ! previous coordinates - real(8) :: last_uvw(3) ! previous direction coordinates - real(8) :: last_wgt ! pre-collision particle weight - real(8) :: absorb_wgt ! weight absorbed for survival biasing + real(8) :: last_xyz_current(3) ! coordinates of the last collision or + ! reflective/periodic surface crossing + ! for current tallies + real(8) :: last_xyz(3) ! previous coordinates + real(8) :: last_uvw(3) ! previous direction coordinates + real(8) :: last_wgt ! pre-collision particle weight + real(8) :: absorb_wgt ! weight absorbed for survival biasing ! What event last took place logical :: fission ! did the particle cause implicit fission @@ -193,20 +196,21 @@ contains call this % initialize() ! copy attributes from source bank site - this % wgt = src % wgt - this % last_wgt = src % wgt - this % coord(1) % xyz = src % xyz - this % coord(1) % uvw = src % uvw - this % last_xyz = src % xyz - this % last_uvw = src % uvw + this % wgt = src % wgt + this % last_wgt = src % wgt + this % coord(1) % xyz = src % xyz + this % coord(1) % uvw = src % uvw + this % last_xyz_current = src % xyz + this % last_xyz = src % xyz + this % last_uvw = src % uvw if (run_CE) then - this % E = src % E + this % E = src % E else - this % g = int(src % E) - this % last_g = int(src % E) - this % E = energy_bin_avg(this % g) + this % g = int(src % E) + this % last_g = int(src % E) + this % E = energy_bin_avg(this % g) end if - this % last_E = this % E + this % last_E = this % E end subroutine initialize_from_source diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index 846d37160..a1c32f723 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -107,11 +107,12 @@ contains end if ! Set particle last attributes - p % last_wgt = p % wgt - p % last_xyz = p % coord(1)%xyz - p % last_uvw = p % coord(1)%uvw - p % last_E = p % E - p % last_g = p % g + p % last_wgt = p % wgt + p % last_xyz_current = p % coord(1)%xyz + p % last_xyz = p % coord(1)%xyz + p % last_uvw = p % coord(1)%uvw + p % last_E = p % E + p % last_g = p % g ! Close hdf5 file call file_close(file_id) diff --git a/src/state_point.F90 b/src/state_point.F90 index 87ef4ead7..abe034897 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -237,57 +237,13 @@ contains end select call write_dataset(tally_group, "n_realizations", & tally % n_realizations) - call write_dataset(tally_group, "n_filters", tally % n_filters) + call write_dataset(tally_group, "n_filters", size(tally % filters)) ! Write filter information - FILTER_LOOP: do j = 1, tally % n_filters + FILTER_LOOP: do j = 1, size(tally % filters) filter_group = create_group(tally_group, "filter " // & trim(to_str(j))) - - ! Write name of type - select case (tally % filters(j) % type) - case(FILTER_UNIVERSE) - call write_dataset(filter_group, "type", "universe") - case(FILTER_MATERIAL) - call write_dataset(filter_group, "type", "material") - case(FILTER_CELL) - call write_dataset(filter_group, "type", "cell") - case(FILTER_CELLBORN) - call write_dataset(filter_group, "type", "cellborn") - case(FILTER_SURFACE) - call write_dataset(filter_group, "type", "surface") - case(FILTER_MESH) - call write_dataset(filter_group, "type", "mesh") - case(FILTER_ENERGYIN) - call write_dataset(filter_group, "type", "energy") - case(FILTER_ENERGYOUT) - call write_dataset(filter_group, "type", "energyout") - case(FILTER_MU) - call write_dataset(filter_group, "type", "mu") - case(FILTER_POLAR) - call write_dataset(filter_group, "type", "polar") - case(FILTER_AZIMUTHAL) - call write_dataset(filter_group, "type", "azimuthal") - case(FILTER_DISTRIBCELL) - call write_dataset(filter_group, "type", "distribcell") - case(FILTER_DELAYEDGROUP) - call write_dataset(filter_group, "type", "delayedgroup") - end select - - call write_dataset(filter_group, "n_bins", & - tally % filters(j) % n_bins) - if (tally % filters(j) % type == FILTER_ENERGYIN .or. & - tally % filters(j) % type == FILTER_ENERGYOUT .or. & - tally % filters(j) % type == FILTER_MU .or. & - tally % filters(j) % type == FILTER_POLAR .or. & - tally % filters(j) % type == FILTER_AZIMUTHAL) then - call write_dataset(filter_group, "bins", & - tally % filters(j) % real_bins) - else - call write_dataset(filter_group, "bins", & - tally % filters(j) % int_bins) - end if - + call tally % filters(j) % obj % to_statepoint(filter_group) call close_group(filter_group) end do FILTER_LOOP diff --git a/src/summary.F90 b/src/summary.F90 index 9ad9aa23c..b02336d97 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -3,7 +3,7 @@ module summary use constants use endf, only: reaction_name use geometry_header, only: Cell, Universe, Lattice, RectLattice, & - &HexLattice, BASE_UNIVERSE + &HexLattice use global use hdf5_interface use material_header, only: Material @@ -13,7 +13,6 @@ module summary use surface_header use string, only: to_str use tally_header, only: TallyObject - use output, only: find_offset use hdf5 @@ -584,10 +583,6 @@ contains type(RegularMesh), pointer :: m type(TallyObject), pointer :: t - integer :: offset ! distibcell offset - character(MAX_LINE_LEN), allocatable :: paths(:) ! distribcell paths array - character(MAX_LINE_LEN) :: path ! distribcell path - tallies_group = create_group(file_id, "tallies") ! Write total number of meshes @@ -628,74 +623,11 @@ contains call write_dataset(tally_group, "name", t%name) ! Write number of filters - call write_dataset(tally_group, "n_filters", t%n_filters) + call write_dataset(tally_group, "n_filters", size(t % filters)) - FILTER_LOOP: do j = 1, t % n_filters + FILTER_LOOP: do j = 1, size(t % filters) filter_group = create_group(tally_group, "filter " // trim(to_str(j))) - - ! Write number of bins for this filter - call write_dataset(filter_group, "n_bins", t % filters(j) % n_bins) - - ! Write filter bins - if (t % filters(j) % type == FILTER_ENERGYIN .or. & - t % filters(j)% type == FILTER_ENERGYOUT .or. & - t % filters(j) % type == FILTER_MU .or. & - t % filters(j) % type == FILTER_POLAR .or. & - t % filters(j) % type == FILTER_AZIMUTHAL) then - call write_dataset(filter_group, "bins", t % filters(j) % real_bins) - else - call write_dataset(filter_group, "bins", t % filters(j) % int_bins) - end if - - ! Write paths to reach each distribcell instance - if (t % filters(j) % type == FILTER_DISTRIBCELL) then - ! Allocate array of strings for each distribcell path - allocate(paths(t % filters(j) % n_bins)) - - ! Store path for each distribcell instance - do k = 1, t % filters(j) % n_bins - path = '' - offset = 1 - call find_offset(t % filters(j) % int_bins(1), & - universes(BASE_UNIVERSE), k, offset, path) - paths(k) = path - end do - - ! Write array of distribcell paths to summary file - call write_dataset(filter_group, "paths", paths) - deallocate(paths) - end if - - ! Write name of type - select case (t%filters(j)%type) - case(FILTER_UNIVERSE) - call write_dataset(filter_group, "type", "universe") - case(FILTER_MATERIAL) - call write_dataset(filter_group, "type", "material") - case(FILTER_CELL) - call write_dataset(filter_group, "type", "cell") - case(FILTER_CELLBORN) - call write_dataset(filter_group, "type", "cellborn") - case(FILTER_SURFACE) - call write_dataset(filter_group, "type", "surface") - case(FILTER_MESH) - call write_dataset(filter_group, "type", "mesh") - case(FILTER_ENERGYIN) - call write_dataset(filter_group, "type", "energy") - case(FILTER_ENERGYOUT) - call write_dataset(filter_group, "type", "energyout") - case(FILTER_DISTRIBCELL) - call write_dataset(filter_group, "type", "distribcell") - case(FILTER_MU) - call write_dataset(filter_group, "type", "mu") - case(FILTER_POLAR) - call write_dataset(filter_group, "type", "polar") - case(FILTER_AZIMUTHAL) - call write_dataset(filter_group, "type", "azimuthal") - case(FILTER_DELAYEDGROUP) - call write_dataset(filter_group, "type", "delayedgroup") - end select - + call t % filters(j) % obj % to_summary(filter_group) call close_group(filter_group) end do FILTER_LOOP diff --git a/src/tally.F90 b/src/tally.F90 index 8d14b1c2a..ab630ceb2 100644 --- a/src/tally.F90 +++ b/src/tally.F90 @@ -14,7 +14,8 @@ module tally use particle_header, only: LocalCoord, Particle use search, only: binary_search use string, only: to_str - use tally_header, only: TallyResult, TallyMapItem, TallyMapElement + use tally_header, only: TallyResult + use tally_filter #ifdef MPI use message_passing @@ -28,7 +29,6 @@ module tally procedure(score_general_), pointer :: score_general => null() procedure(score_analog_tally_), pointer :: score_analog_tally => null() - procedure(get_scoring_bins_), pointer :: get_scoring_bins => null() abstract interface subroutine score_general_(p, t, start_index, filter_index, i_nuclide, & @@ -48,13 +48,6 @@ module tally import Particle type(Particle), intent(in) :: p end subroutine score_analog_tally_ - - subroutine get_scoring_bins_(p, i_tally, found_bin) - import Particle - type(Particle), intent(in) :: p - integer, intent(in) :: i_tally - logical, intent(out) :: found_bin - end subroutine get_scoring_bins_ end interface contains @@ -68,18 +61,18 @@ contains if (run_CE) then score_general => score_general_ce score_analog_tally => score_analog_tally_ce - get_scoring_bins => get_scoring_bins_ce else score_general => score_general_mg score_analog_tally => score_analog_tally_mg - get_scoring_bins => get_scoring_bins_mg end if end subroutine init_tally_routines !=============================================================================== ! SCORE_GENERAL* adds scores to the tally array for the given filter and -! nuclide. This will work for either analog or tracklength tallies. Note that -! atom_density and flux are not used for analog tallies. +! nuclide. This function is called by all volume tallies. For analog tallies, +! the flux estimate depends on the score type so the flux argument is really +! just used for filter weights. The atom_density argument is not used for +! analog tallies. !=============================================================================== subroutine score_general_ce(p, t, start_index, filter_index, i_nuclide, & @@ -137,7 +130,7 @@ contains else score = p % last_wgt end if - score = score / material_xs % total + score = score / material_xs % total * flux else ! For flux, we need no cross section @@ -153,9 +146,9 @@ contains if (survival_biasing) then ! We need to account for the fact that some weight was already ! absorbed - score = p % last_wgt + p % absorb_wgt + score = p % last_wgt + p % absorb_wgt * flux else - score = p % last_wgt + score = p % last_wgt * flux end if else @@ -190,7 +183,7 @@ contains ! Score the flux weighted inverse velocity with velocity in units of ! cm/s score = score / material_xs % total & - / (sqrt(TWO * E / (MASS_NEUTRON_MEV)) * C_LIGHT * 100.0_8) + / (sqrt(TWO * E / (MASS_NEUTRON_MEV)) * C_LIGHT * 100.0_8) * flux else ! For inverse velocity, we don't need a cross section. The velocity is @@ -206,7 +199,7 @@ contains ! Since only scattering events make it here, again we can use ! the weight entering the collision as the estimator for the ! reaction rate - score = p % last_wgt + score = p % last_wgt * flux else ! Note SCORE_SCATTER_N not available for tracklength/collision. @@ -229,7 +222,7 @@ contains ! Since only scattering events make it here, again we can use ! the weight entering the collision as the estimator for the ! reaction rate - score = p % last_wgt + score = p % last_wgt * flux case (SCORE_SCATTER_YN) @@ -242,7 +235,7 @@ contains ! Since only scattering events make it here, again we can use ! the weight entering the collision as the estimator for the ! reaction rate - score = p % last_wgt + score = p % last_wgt * flux case (SCORE_NU_SCATTER, SCORE_NU_SCATTER_N) @@ -256,7 +249,7 @@ contains (p % event_MT >= N_N1 .and. p % event_MT <= N_NC)) then ! Don't waste time on very common reactions we know have multiplicities ! of one. - score = p % last_wgt + score = p % last_wgt * flux else m = nuclides(p%event_nuclide)%reaction_index% & get_key(p % event_MT) @@ -266,11 +259,11 @@ contains select type (yield => rxn % products(1) % yield) type is (Constant1D) ! Grab the yield from the reaction - score = p % last_wgt * yield % y + score = p % last_wgt * yield % y * flux class default ! the yield was already incorporated in to p % wgt per the ! scattering routine - score = p % wgt + score = p % wgt * flux end select end associate end if @@ -290,7 +283,7 @@ contains (p % event_MT >= N_N1 .and. p % event_MT <= N_NC)) then ! Don't waste time on very common reactions we know have multiplicities ! of one. - score = p % last_wgt + score = p % last_wgt * flux else m = nuclides(p%event_nuclide)%reaction_index% & get_key(p % event_MT) @@ -300,11 +293,11 @@ contains select type (yield => rxn % products(1) % yield) type is (Constant1D) ! Grab the yield from the reaction - score = p % last_wgt * yield % y + score = p % last_wgt * yield % y * flux class default ! the yield was already incorporated in to p % wgt per the ! scattering routine - score = p % wgt + score = p % wgt * flux end select end associate end if @@ -324,7 +317,7 @@ contains (p % event_MT >= N_N1 .and. p % event_MT <= N_NC)) then ! Don't waste time on very common reactions we know have multiplicities ! of one. - score = p % last_wgt + score = p % last_wgt * flux else m = nuclides(p%event_nuclide)%reaction_index% & get_key(p % event_MT) @@ -334,11 +327,11 @@ contains select type (yield => rxn % products(1) % yield) type is (Constant1D) ! Grab the yield from the reaction - score = p % last_wgt * yield % y + score = p % last_wgt * yield % y * flux class default ! the yield was already incorporated in to p % wgt per the ! scattering routine - score = p % wgt + score = p % wgt * flux end select end associate end if @@ -349,13 +342,13 @@ contains if (survival_biasing) then ! No absorption events actually occur if survival biasing is on -- ! just use weight absorbed in survival biasing - score = p % absorb_wgt + score = p % absorb_wgt * flux else ! Skip any event where the particle wasn't absorbed if (p % event == EVENT_SCATTER) cycle SCORE_LOOP ! All fission and absorption events will contribute here, so we ! can just use the particle's weight entering the collision - score = p % last_wgt + score = p % last_wgt * flux end if else @@ -375,7 +368,7 @@ contains ! fission if (micro_xs(p % event_nuclide) % absorption > ZERO) then score = p % absorb_wgt * micro_xs(p % event_nuclide) % fission & - / micro_xs(p % event_nuclide) % absorption + / micro_xs(p % event_nuclide) % absorption * flux else score = ZERO end if @@ -386,7 +379,7 @@ contains ! particle's weight entering the collision as the estimate for the ! fission reaction rate score = p % last_wgt * micro_xs(p % event_nuclide) % fission & - / micro_xs(p % event_nuclide) % absorption + / micro_xs(p % event_nuclide) % absorption * flux end if else @@ -407,7 +400,7 @@ contains ! neutrons were emitted with different energies, multiple ! outgoing energy bins may have been scored to. The following ! logic treats this special case and results to multiple bins - call score_fission_eout_ce(p, t, score_index) + call score_fission_eout_ce(p, t, score_index, score_bin) cycle SCORE_LOOP end if end if @@ -417,7 +410,7 @@ contains ! nu-fission if (micro_xs(p % event_nuclide) % absorption > ZERO) then score = p % absorb_wgt * micro_xs(p % event_nuclide) % & - nu_fission / micro_xs(p % event_nuclide) % absorption + nu_fission / micro_xs(p % event_nuclide) % absorption * flux else score = ZERO end if @@ -429,7 +422,7 @@ contains ! score the number of particles that were banked in the fission ! bank. Since this was weighted by 1/keff, we multiply by keff ! to get the proper score. - score = keff * p % wgt_bank + score = keff * p % wgt_bank * flux end if else @@ -441,6 +434,74 @@ contains end if + case (SCORE_PROMPT_NU_FISSION) + if (t % estimator == ESTIMATOR_ANALOG) then + if (survival_biasing .or. p % fission) then + if (t % find_filter(FILTER_ENERGYOUT) > 0) then + ! Normally, we only need to make contributions to one scoring + ! bin. However, in the case of fission, since multiple fission + ! neutrons were emitted with different energies, multiple + ! outgoing energy bins may have been scored to. The following + ! logic treats this special case and results to multiple bins + call score_fission_eout_ce(p, t, score_index, score_bin) + cycle SCORE_LOOP + end if + end if + if (survival_biasing) then + ! No fission events occur if survival biasing is on -- need to + ! calculate fraction of absorptions that would have resulted in + ! prompt-nu-fission + if (micro_xs(p % event_nuclide) % absorption > ZERO) then + score = p % absorb_wgt * micro_xs(p % event_nuclide) % fission & + * nuclides(p % event_nuclide) % nu(E, EMISSION_PROMPT) & + / micro_xs(p % event_nuclide) % absorption + else + score = ZERO + end if + else + ! Skip any non-fission events + if (.not. p % fission) cycle SCORE_LOOP + ! If there is no outgoing energy filter, than we only need to + ! score to one bin. For the score to be 'analog', we need to + ! score the number of particles that were banked in the fission + ! bank as prompt neutrons. Since this was weighted by 1/keff, we + ! multiply by keff to get the proper score. + score = keff * p % wgt_bank * (ONE - sum(p % n_delayed_bank) & + / real(p % n_bank, 8)) + end if + + else + ! make sure the correct energy is used + if (t % estimator == ESTIMATOR_TRACKLENGTH) then + E = p % E + else + E = p % last_E + end if + + if (i_nuclide > 0) then + score = micro_xs(i_nuclide) % fission * nuclides(i_nuclide) % & + nu(E, EMISSION_PROMPT) * atom_density * flux + else + + score = ZERO + + ! Loop over all nuclides in the current material + do l = 1, materials(p % material) % n_nuclides + + ! Get atom density + atom_density_ = materials(p % material) % atom_density(l) + + ! Get index in nuclides array + i_nuc = materials(p % material) % nuclide(l) + + ! Accumulate the contribution from each nuclide + score = score + micro_xs(i_nuc) % fission * nuclides(i_nuc) % & + nu(E, EMISSION_PROMPT) * atom_density_ * flux + end do + end if + end if + + case (SCORE_DELAYED_NU_FISSION) ! make sure the correct energy is used @@ -461,7 +522,7 @@ contains ! neutrons were emitted with different energies, multiple ! outgoing energy bins may have been scored to. The following ! logic treats this special case and results to multiple bins - call score_fission_delayed_eout(p, t, score_index) + call score_fission_eout_ce(p, t, score_index, score_bin) cycle SCORE_LOOP end if end if @@ -473,23 +534,28 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then + select type(filt => t % filters(dg_filter) % obj) + type is (DelayedGroupFilter) - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, t % filters(dg_filter) % n_bins + ! Loop over all delayed group bins and tally to them + ! individually + do d_bin = 1, filt % n_bins - ! Get the delayed group for this bin - d = t % filters(dg_filter) % int_bins(d_bin) + ! Get the delayed group for this bin + d = filt % groups(d_bin) - ! Compute the yield for this delayed group - yield = nuclides(p % event_nuclide) % nu(E, EMISSION_DELAYED, d) + ! Compute the yield for this delayed group + yield = nuclides(p % event_nuclide) & + % nu(E, EMISSION_DELAYED, d) - ! Compute the score and tally to bin - score = p % absorb_wgt * yield * micro_xs(p % event_nuclide) & - % fission / micro_xs(p % event_nuclide) % absorption - call score_fission_delayed_dg(t, d_bin, score, score_index) - end do - cycle SCORE_LOOP + ! Compute the score and tally to bin + score = p % absorb_wgt * yield & + * micro_xs(p % event_nuclide) % fission & + / micro_xs(p % event_nuclide) % absorption + call score_fission_delayed_dg(t, d_bin, score, score_index) + end do + cycle SCORE_LOOP + end select else ! If the delayed group filter is not present, compute the score ! by multiplying the absorbed weight by the fraction of the @@ -513,18 +579,22 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then + select type(filt => t % filters(dg_filter) % obj) + type is (DelayedGroupFilter) - ! Loop over all delayed group bins and tally to them individually - do d_bin = 1, t % filters(dg_filter) % n_bins + ! Loop over all delayed group bins and tally to them + ! individually + do d_bin = 1, filt % n_bins - ! Get the delayed group for this bin - d = t % filters(dg_filter) % int_bins(d_bin) + ! Get the delayed group for this bin + d = filt % groups(d_bin) - ! Compute the score and tally to bin - score = keff * p % wgt_bank / p % n_bank * p % n_delayed_bank(d) - call score_fission_delayed_dg(t, d_bin, score, score_index) - end do - cycle SCORE_LOOP + ! Compute the score and tally to bin + score = keff * p % wgt_bank / p % n_bank * p % n_delayed_bank(d) + call score_fission_delayed_dg(t, d_bin, score, score_index) + end do + cycle SCORE_LOOP + end select else ! Add the contribution from all delayed groups @@ -538,22 +608,26 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then + select type(filt => t % filters(dg_filter) % obj) + type is (DelayedGroupFilter) - ! Loop over all delayed group bins and tally to them individually - do d_bin = 1, t % filters(dg_filter) % n_bins + ! Loop over all delayed group bins and tally to them + ! individually + do d_bin = 1, filt % n_bins - ! Get the delayed group for this bin - d = t % filters(dg_filter) % int_bins(d_bin) + ! Get the delayed group for this bin + d = filt % groups(d_bin) - ! Compute the yield for this delayed group - yield = nuclides(i_nuclide) % nu(E, EMISSION_DELAYED, d) + ! Compute the yield for this delayed group + yield = nuclides(i_nuclide) % nu(E, EMISSION_DELAYED, d) - ! Compute the score and tally to bin - score = micro_xs(i_nuclide) % fission * yield * & - atom_density * flux - call score_fission_delayed_dg(t, d_bin, score, score_index) - end do - cycle SCORE_LOOP + ! Compute the score and tally to bin + score = micro_xs(i_nuclide) % fission * yield * & + atom_density * flux + call score_fission_delayed_dg(t, d_bin, score, score_index) + end do + cycle SCORE_LOOP + end select else ! If the delayed group filter is not present, compute the score @@ -567,31 +641,35 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then + select type(filt => t % filters(dg_filter) % obj) + type is (DelayedGroupFilter) - ! Loop over all nuclides in the current material - do l = 1, materials(p % material) % n_nuclides + ! Loop over all nuclides in the current material + do l = 1, materials(p % material) % n_nuclides - ! Get atom density - atom_density_ = materials(p % material) % atom_density(l) + ! Get atom density + atom_density_ = materials(p % material) % atom_density(l) - ! Get index in nuclides array - i_nuc = materials(p % material) % nuclide(l) + ! Get index in nuclides array + i_nuc = materials(p % material) % nuclide(l) - ! Loop over all delayed group bins and tally to them individually - do d_bin = 1, t % filters(dg_filter) % n_bins + ! Loop over all delayed group bins and tally to them + ! individually + do d_bin = 1, filt % n_bins - ! Get the delayed group for this bin - d = t % filters(dg_filter) % int_bins(d_bin) + ! Get the delayed group for this bin + d = filt % groups(d_bin) - ! Get the yield for the desired nuclide and delayed group - yield = nuclides(i_nuc) % nu(E, EMISSION_DELAYED, d) + ! Get the yield for the desired nuclide and delayed group + yield = nuclides(i_nuc) % nu(E, EMISSION_DELAYED, d) - ! Compute the score and tally to bin - score = micro_xs(i_nuc) % fission * yield * atom_density_ * flux - call score_fission_delayed_dg(t, d_bin, score, score_index) + ! Compute the score and tally to bin + score = micro_xs(i_nuc) % fission * yield * atom_density_ * flux + call score_fission_delayed_dg(t, d_bin, score, score_index) + end do end do - end do - cycle SCORE_LOOP + cycle SCORE_LOOP + end select else score = ZERO @@ -685,7 +763,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then ! Check if event MT matches if (p % event_MT /= ELASTIC) cycle SCORE_LOOP - score = p % last_wgt + score = p % last_wgt * flux else if (i_nuclide > 0) then @@ -817,7 +895,7 @@ contains ! Any other score is assumed to be a MT number. Thus, we just need ! to check if it matches the MT number of the event if (p % event_MT /= score_bin) cycle SCORE_LOOP - score = p % last_wgt + score = p % last_wgt * flux else ! Any other cross section has to be calculated on-the-fly. For @@ -973,7 +1051,7 @@ contains else score = p % last_wgt end if - score = score / material_xs % total + score = score / material_xs % total * flux else ! For flux, we need no cross section @@ -996,7 +1074,7 @@ contains if (i_nuclide > 0) then score = score * atom_density * & nucxs % get_xs('total', p_g, UVW=p_uvw) / & - matxs % get_xs('total', p_g, UVW=p_uvw) + matxs % get_xs('total', p_g, UVW=p_uvw) * flux end if else @@ -1022,7 +1100,7 @@ contains else score = p % last_wgt end if - score = score * inverse_velocities(p_g) / material_xs % total + score = score * inverse_velocities(p_g) / material_xs % total * flux else ! For inverse velocity, we need no cross section @@ -1045,7 +1123,7 @@ contains ! Since only scattering events make it here, again we can use ! the weight entering the collision as the estimator for the ! reaction rate - score = p % last_wgt + score = p % last_wgt * flux ! Since we transport based on material data, the angle selected ! was not selected from the f(mu) for the nuclide. Therefore @@ -1088,7 +1166,7 @@ contains ! For scattering production, we need to use the pre-collision ! weight times the multiplicity as the estimate for the number of ! neutrons exiting a reaction with neutrons in the exit channel - score = p % wgt + score = p % wgt * flux ! Since we transport based on material data, the angle selected ! was not selected from the f(mu) for the nuclide. Therefore @@ -1118,13 +1196,13 @@ contains if (survival_biasing) then ! No absorption events actually occur if survival biasing is on -- ! just use weight absorbed in survival biasing - score = p % absorb_wgt + score = p % absorb_wgt * flux else ! Skip any event where the particle wasn't absorbed if (p % event == EVENT_SCATTER) cycle SCORE_LOOP ! All fission and absorption events will contribute here, so we ! can just use the particle's weight entering the collision - score = p % last_wgt + score = p % last_wgt * flux end if if (i_nuclide > 0) then score = score * atom_density * & @@ -1147,24 +1225,24 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! fission - score = p % absorb_wgt + score = p % absorb_wgt * flux else ! Skip any non-absorption events if (p % event == EVENT_SCATTER) cycle SCORE_LOOP ! All fission events will contribute, so again we can use ! particle's weight entering the collision as the estimate for the ! fission reaction rate - score = p % last_wgt + score = p % last_wgt * flux end if if (i_nuclide > 0) then - score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) - else - score = score * & - matxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) - end if + score = score * atom_density * & + nucxs % get_xs('fission', p_g, UVW=p_uvw) / & + matxs % get_xs('absorption', p_g, UVW=p_uvw) + else + score = score * & + matxs % get_xs('fission', p_g, UVW=p_uvw) / & + matxs % get_xs('absorption', p_g, UVW=p_uvw) + end if else if (i_nuclide > 0) then score = nucxs % get_xs('fission', p_g, UVW=p_uvw) * & @@ -1194,7 +1272,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - score = p % absorb_wgt + score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & nucxs % get_xs('nu_fission', p_g, UVW=p_uvw) / & @@ -1212,7 +1290,7 @@ contains ! score the number of particles that were banked in the fission ! bank. Since this was weighted by 1/keff, we multiply by keff ! to get the proper score. - score = keff * p % wgt_bank + score = keff * p % wgt_bank * flux if (i_nuclide > 0) then score = score * atom_density * & nucxs % get_xs('fission', p_g, UVW=p_uvw) / & @@ -1236,14 +1314,14 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! fission - score = p % absorb_wgt + score = p % absorb_wgt * flux else ! Skip any non-absorption events if (p % event == EVENT_SCATTER) cycle SCORE_LOOP ! All fission events will contribute, so again we can use ! particle's weight entering the collision as the estimate for the ! fission reaction rate - score = p % last_wgt + score = p % last_wgt * flux end if if (i_nuclide > 0) then score = score * atom_density * & @@ -1459,11 +1537,12 @@ contains integer :: i integer :: i_tally + integer :: i_filt integer :: k ! loop index for nuclide bins ! position during the loop integer :: filter_index ! single index for single bin integer :: i_nuclide ! index in nuclides array - logical :: found_bin ! scoring bin found? + real(8) :: filter_weight ! combined weight of all filters type(TallyObject), pointer :: t ! A loop over all tallies is necessary because we need to simultaneously @@ -1474,63 +1553,103 @@ contains i_tally = active_analog_tallies % get_item(i) t => tallies(i_tally) - ! ======================================================================= - ! DETERMINE SCORING BIN COMBINATION + ! Find the first bin in each filter. There may be more than one matching + ! bin per filter, but we'll deal with those later. + do i_filt = 1, size(t % filters) + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + NO_BIN_FOUND, matching_bins(i_filt), filter_weights(i_filt)) + ! 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 (matching_bins(i_filt) == NO_BIN_FOUND) cycle TALLY_LOOP + end do - call get_scoring_bins(p, i_tally, found_bin) - if (.not. found_bin) cycle + ! ======================================================================== + ! Loop until we've covered all valid bins on each of the filters. - ! ======================================================================= - ! CALCULATE RESULTS AND ACCUMULATE TALLY + FILTER_LOOP: do - ! If we have made it here, we have a scoring combination of bins for this - ! tally -- now we need to determine where in the results array we should - ! be accumulating the tally values + ! Determine scoring index and weight for this filter combination + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 + filter_weight = product(filter_weights(:size(t % filters))) - ! Determine scoring index for this filter combination - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + ! ====================================================================== + ! Nuclide logic - ! Check for nuclide bins - k = 0 - NUCLIDE_LOOP: do while (k < t % n_nuclide_bins) + ! Check for nuclide bins + k = 0 + NUCLIDE_LOOP: do while (k < t % n_nuclide_bins) - ! Increment the index in the list of nuclide bins - k = k + 1 + ! Increment the index in the list of nuclide bins + k = k + 1 + + if (t % all_nuclides) then + ! In the case that the user has requested to tally all nuclides, we + ! can take advantage of the fact that we know exactly how nuclide + ! bins correspond to nuclide indices. + if (k == 1) then + ! If we just entered, set the nuclide bin index to the index in + ! the nuclides array since this will match the index in the + ! nuclide bin array. + k = p % event_nuclide + elseif (k == p % event_nuclide + 1) then + ! After we've tallied the individual nuclide bin, we also need + ! to contribute to the total material bin which is the last bin + k = n_nuclides_total + 1 + else + ! After we've tallied in both the individual nuclide bin and the + ! total material bin, we're done + exit + end if - if (t % all_nuclides) then - ! In the case that the user has requested to tally all nuclides, we - ! can take advantage of the fact that we know exactly how nuclide - ! bins correspond to nuclide indices. - if (k == 1) then - ! If we just entered, set the nuclide bin index to the index in - ! the nuclides array since this will match the index in the - ! nuclide bin array. - k = p % event_nuclide - elseif (k == p % event_nuclide + 1) then - ! After we've tallied the individual nuclide bin, we also need - ! to contribute to the total material bin which is the last bin - k = n_nuclides_total + 1 else - ! After we've tallied in both the individual nuclide bin and the - ! total material bin, we're done - exit + ! If the user has explicitly specified nuclides (or specified + ! none), we need to search through the nuclide bin list one by + ! one. First we need to get the value of the nuclide bin + i_nuclide = t % nuclide_bins(k) + + ! Now compare the value against that of the colliding nuclide. + if (i_nuclide /= p % event_nuclide .and. i_nuclide /= -1) cycle end if - else - ! If the user has explicitly specified nuclides (or specified - ! none), we need to search through the nuclide bin list one by - ! one. First we need to get the value of the nuclide bin - i_nuclide = t % nuclide_bins(k) + ! Determine score for each bin + call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & + i_nuclide, ZERO, filter_weight) - ! Now compare the value against that of the colliding nuclide. - if (i_nuclide /= p % event_nuclide .and. i_nuclide /= -1) cycle - end if + end do NUCLIDE_LOOP - ! Determine score for each bin - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, ZERO, ZERO) + ! ====================================================================== + ! Filter logic - end do NUCLIDE_LOOP + ! If there are no filters, then we are done. + if (size(t % filters) == 0) exit FILTER_LOOP + + ! Increment the filter bins, starting with the last filter. If we get a + ! NO_BIN_FOUND for the last filter, it means we finished all valid bins + ! for that filter, but next-to-last filter might have more than one + ! valid bin so we need to increment that one as well, and so on. + do i_filt = size(t % filters), 1, -1 + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + matching_bins(i_filt), matching_bins(i_filt), & + filter_weights(i_filt)) + if (matching_bins(i_filt) /= NO_BIN_FOUND) exit + end do + + ! If we got all NO_BIN_FOUNDs, then we have finished all valid bins for + ! each of the filters. Exit the loop. + if (all(matching_bins(:size(t % filters)) == NO_BIN_FOUND)) & + exit FILTER_LOOP + + ! Reset all the filters with NO_BIN_FOUND. This will set them back to + ! their first valid bin. + do i_filt = 1, size(t % filters) + if (matching_bins(i_filt) == NO_BIN_FOUND) then + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + matching_bins(i_filt), matching_bins(i_filt), & + filter_weights(i_filt)) + end if + end do + end do FILTER_LOOP ! If the user has specified that we can assume all tallies are spatially ! separate, this implies that once a tally has been scored to, we needn't @@ -1552,14 +1671,15 @@ contains integer :: i, m integer :: i_tally + integer :: i_filt integer :: k ! loop index for nuclide bins ! position during the loop integer :: filter_index ! single index for single bin integer :: i_nuclide ! index in nuclides array - logical :: found_bin ! scoring bin found? + real(8) :: filter_weight ! combined weight of all filters + real(8) :: atom_density type(TallyObject), pointer :: t type(Material), pointer :: mat - real(8) :: atom_density ! A loop over all tallies is necessary because we need to simultaneously ! determine different filter bins for the same tally in order to score to it @@ -1573,44 +1693,84 @@ contains ! nuclides are in the material mat => materials(p % material) - ! ======================================================================= - ! DETERMINE SCORING BIN COMBINATION + ! Find the first bin in each filter. There may be more than one matching + ! bin per filter, but we'll deal with those later. + do i_filt = 1, size(t % filters) + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + NO_BIN_FOUND, matching_bins(i_filt), filter_weights(i_filt)) + ! 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 (matching_bins(i_filt) == NO_BIN_FOUND) cycle TALLY_LOOP + end do - call get_scoring_bins(p, i_tally, found_bin) - if (.not. found_bin) cycle + ! ======================================================================== + ! Loop until we've covered all valid bins on each of the filters. - ! ======================================================================= - ! CALCULATE RESULTS AND ACCUMULATE TALLY + FILTER_LOOP: do - ! If we have made it here, we have a scoring combination of bins for this - ! tally -- now we need to determine where in the results array we should - ! be accumulating the tally values + ! Determine scoring index and weight for this filter combination + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 + filter_weight = product(filter_weights(:size(t % filters))) - ! Determine scoring index for this filter combination - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + ! ====================================================================== + ! Nuclide logic - ! Check for nuclide bins - k = 0 - NUCLIDE_LOOP: do while (k < t % n_nuclide_bins) + ! Check for nuclide bins + k = 0 + NUCLIDE_LOOP: do while (k < t % n_nuclide_bins) - ! Increment the index in the list of nuclide bins - k = k + 1 + ! Increment the index in the list of nuclide bins + k = k + 1 - i_nuclide = t % nuclide_bins(k) + i_nuclide = t % nuclide_bins(k) - ! Check to see if this nuclide was in the material of our collision. - do m = 1, mat % n_nuclides - if (mat % nuclide(m) == i_nuclide) then - atom_density = mat % atom_density(m) - exit - end if + ! Check to see if this nuclide was in the material of our collision. + do m = 1, mat % n_nuclides + if (mat % nuclide(m) == i_nuclide) then + atom_density = mat % atom_density(m) + exit + end if + end do + + ! Determine score for each bin + call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & + i_nuclide, atom_density, filter_weight) + + end do NUCLIDE_LOOP + + ! ====================================================================== + ! Filter logic + + ! If there are no filters, then we are done. + if (size(t % filters) == 0) exit FILTER_LOOP + + ! Increment the filter bins, starting with the last filter. If we get a + ! NO_BIN_FOUND for the last filter, it means we finished all valid bins + ! for that filter, but next-to-last filter might have more than one + ! valid bin so we need to increment that one as well, and so on. + do i_filt = size(t % filters), 1, -1 + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + matching_bins(i_filt), matching_bins(i_filt), & + filter_weights(i_filt)) + if (matching_bins(i_filt) /= NO_BIN_FOUND) exit end do - ! Determine score for each bin - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, atom_density, ZERO) + ! If we got all NO_BIN_FOUNDs, then we have finished all valid bins for + ! each of the filters. Exit the loop. + if (all(matching_bins(:size(t % filters)) == NO_BIN_FOUND)) & + exit FILTER_LOOP - end do NUCLIDE_LOOP + ! Reset all the filters with NO_BIN_FOUND. This will set them back to + ! their first valid bin. + do i_filt = 1, size(t % filters) + if (matching_bins(i_filt) == NO_BIN_FOUND) then + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + matching_bins(i_filt), matching_bins(i_filt), & + filter_weights(i_filt)) + end if + end do + end do FILTER_LOOP ! If the user has specified that we can assume all tallies are spatially ! separate, this implies that once a tally has been scored to, we needn't @@ -1633,16 +1793,23 @@ contains ! neutrons produced with different energies. !=============================================================================== - subroutine score_fission_eout_ce(p, t, i_score) - type(Particle), intent(in) :: p + subroutine score_fission_eout_ce(p, t, i_score, score_bin) + + type(Particle), intent(in) :: p type(TallyObject), intent(inout) :: t - integer, intent(in) :: i_score ! index for score + integer, intent(in) :: i_score ! index for score + integer, intent(in) :: score_bin integer :: i ! index of outgoing energy filter + integer :: j ! index of delayedgroup filter + 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 :: bin_energyout ! original outgoing energy bin integer :: i_filter ! index for matching filter bin combination + real(8) :: filter_weight ! combined weight of all filters real(8) :: score ! actual score real(8) :: E_out ! energy of fission bank site @@ -1650,37 +1817,93 @@ contains i = t % find_filter(FILTER_ENERGYOUT) bin_energyout = matching_bins(i) - ! Get number of energies on filter - n = size(t % filters(i) % real_bins) + ! declare the energyout filter type + select type(eo_filt => t % filters(i) % obj) + type is (EnergyoutFilter) - ! 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 - ! of all nu-fission rates would be ~1.0. + ! Get number of energies on filter + n = size(eo_filt % bins) - ! loop over number of particles banked - do k = 1, p % n_bank - ! determine score based on bank site weight and keff - score = keff * fission_bank(n_bank - p % n_bank + k) % wgt + ! 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 + ! of all nu-fission rates would be ~1.0. - ! determine outgoing energy from fission bank - E_out = fission_bank(n_bank - p % n_bank + k) % E + ! loop over number of particles banked + do k = 1, p % n_bank - ! check if outgoing energy is within specified range on filter - if (E_out < t % filters(i) % real_bins(1) .or. & - E_out > t % filters(i) % real_bins(n)) cycle + ! get the delayed group + g = fission_bank(n_bank - p % n_bank + k) % delayed_group - ! change outgoing energy bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, n, E_out) + ! determine score based on bank site weight and keff + score = keff * fission_bank(n_bank - p % n_bank + k) % wgt - ! determine scoring index - i_filter = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + ! determine outgoing energy from fission bank + E_out = fission_bank(n_bank - p % n_bank + k) % E - ! Add score to tally + ! 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 + Matching_bins(i) = binary_search(eo_filt % bins, n, E_out) + + ! Case for tallying prompt neutrons + if (score_bin == SCORE_NU_FISSION .or. & + (score_bin == SCORE_PROMPT_NU_FISSION .and. g == 0)) then + + ! determine scoring index and weight for this filter combination + i_filter = sum((matching_bins(1:size(t%filters)) - 1) * t % stride) & + + 1 + filter_weight = product(filter_weights(:size(t % filters))) + + ! Add score to tally !$omp atomic - t % results(i_score, i_filter) % value = & - t % results(i_score, i_filter) % value + score - end do + t % results(i_score, i_filter) % value = & + t % results(i_score, i_filter) % value + score * filter_weight + + ! Case for tallying delayed emissions + else if (score_bin == SCORE_DELAYED_NU_FISSION .and. g /= 0) then + + ! Get the index of delayed group filter + j = t % find_filter(FILTER_DELAYEDGROUP) + + ! if the delayed group filter is present, tally to corresponding + ! delayed group bin if it exists + if (j > 0) then + + ! declare the delayed group filter type + select type(dg_filt => t % filters(j) % obj) + type is (DelayedGroupFilter) + + ! loop over delayed group bins until the corresponding bin is + ! found + do d_bin = 1, dg_filt % n_bins + d = dg_filt % groups(d_bin) + + ! check whether the delayed group of the particle is equal to + ! the delayed group of this bin + if (d == g) then + call score_fission_delayed_dg(t, d_bin, score, i_score) + end if + end do + end select + + ! if the delayed group filter is not present, add score to tally + else + + ! determine scoring index and weight for this filter combination + i_filter = sum((matching_bins(1:size(t%filters)) - 1) * t % stride)& + + 1 + filter_weight = product(filter_weights(:size(t % filters))) + + ! Add score to tally +!$omp atomic + t % results(i_score, i_filter) % value = & + t % results(i_score, i_filter) % value + score * filter_weight + end if + end if + end do + end select ! reset outgoing energy bin and score index matching_bins(i) = bin_energyout @@ -1699,6 +1922,7 @@ contains integer :: k ! loop index for bank sites integer :: bin_energyout ! original outgoing energy bin integer :: i_filter ! index for matching filter bin combination + real(8) :: filter_weight ! combined weight of all filters real(8) :: score ! actual score integer :: gout ! energy group of fission bank site integer :: gin ! energy group of incident particle @@ -1708,158 +1932,68 @@ contains i = t % find_filter(FILTER_ENERGYOUT) bin_energyout = matching_bins(i) - ! Get number of energies on filter - n = size(t % filters(i) % real_bins) + ! Declare the filter type + select type(filt => t % filters(i) % obj) + type is (EnergyoutFilter) - ! 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 - ! of all nu-fission rates would be ~1.0. + ! Get number of energies on filter + n = size(filt % bins) - ! loop over number of particles banked - do k = 1, p % n_bank - ! determine score based on bank site weight and keff - score = keff * fission_bank(n_bank - p % n_bank + k) % wgt - if (i_nuclide > 0) then - if (survival_biasing) then - gin = p % g - else - gin = p % last_g + ! 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 + ! of all nu-fission rates would be ~1.0. + + ! loop over number of particles banked + do k = 1, p % n_bank + ! determine score based on bank site weight and keff + score = keff * fission_bank(n_bank - p % n_bank + k) % wgt + if (i_nuclide > 0) then + if (survival_biasing) then + gin = p % g + else + gin = p % last_g + end if + score = score * atom_density * & + nuclides_MG(i_nuclide) % obj % get_xs('fission', gin, & + UVW=p % last_uvw) / & + macro_xs(p % material) % obj % get_xs('fission', gin, & + UVW=p % last_uvw) end if - score = score * atom_density * & - nuclides_MG(i_nuclide) % obj % get_xs('fission', gin, & - UVW=p % last_uvw) / & - macro_xs(p % material) % obj % get_xs('fission', gin, & - UVW=p % last_uvw) - end if - if (t % energyout_matches_groups) then - ! determine outgoing energy from fission bank - gout = int(fission_bank(n_bank - p % n_bank + k) % E) + if (filt % matches_transport_groups) then + ! determine outgoing energy from fission bank + gout = int(fission_bank(n_bank - p % n_bank + k) % E) - ! change outgoing energy bin - matching_bins(i) = gout - else - ! determine outgoing energy from fission bank - E_out = energy_bin_avg(int(fission_bank(n_bank - p % n_bank + k) % E)) + ! change outgoing energy bin + matching_bins(i) = gout + else + ! determine outgoing energy from fission bank + E_out = energy_bin_avg(int(fission_bank(n_bank - p % n_bank + k) % E)) - ! check if outgoing energy is within specified range on filter - if (E_out < t % filters(i) % real_bins(1) .or. & - E_out > t % filters(i) % real_bins(n)) cycle + ! check if outgoing energy is within specified range on filter + if (E_out < filt % bins(1) .or. E_out > filt % bins(n)) cycle - ! change outgoing energy bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, n, E_out) - end if + ! change outgoing energy bin + matching_bins(i) = binary_search(filt % bins, n, E_out) + end if - ! determine scoring index - i_filter = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + ! determine scoring index and weight for this filter combination + i_filter = sum((matching_bins(1:size(t%filters)) - 1) * t % stride) + 1 + filter_weight = product(filter_weights(:size(t % filters))) - ! Add score to tally + ! Add score to tally !$omp atomic - t % results(i_score, i_filter) % value = & - t % results(i_score, i_filter) % value + score - end do + t % results(i_score, i_filter) % value = & + t % results(i_score, i_filter) % value + score * filter_weight + end do ! reset outgoing energy bin and score index matching_bins(i) = bin_energyout + end select end subroutine score_fission_eout_mg -!=============================================================================== -! SCORE_FISSION_DELAYED_EOUT handles a special case where we need to store -! delayed neutron production rate with an outgoing energy filter (think of a -! fission matrix). In this case, we may need to score to multiple bins if there -! were multiple neutrons produced with different energies. -!=============================================================================== - - subroutine score_fission_delayed_eout(p, t, i_score) - - type(Particle), intent(in) :: p - type(TallyObject), intent(inout) :: t - integer, intent(in) :: i_score ! index for score - - integer :: i ! index of outgoing energy filter - integer :: j ! index of delayedgroup filter - 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 :: bin_energyout ! original outgoing energy bin - integer :: i_filter ! index for matching filter bin combination - real(8) :: score ! actual score - real(8) :: E_out ! energy of fission bank site - - ! Save original outgoing energy bin - i = t % find_filter(FILTER_ENERGYOUT) - bin_energyout = matching_bins(i) - - ! Get the index of delayed group filter - j = t % find_filter(FILTER_DELAYEDGROUP) - - ! Get number of energies on filter - n = size(t % filters(i) % real_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 delayed-nu-fission rate. - - ! loop over number of particles banked - do k = 1, p % n_bank - - ! get the delayed group - g = fission_bank(n_bank - p % n_bank + k) % delayed_group - - ! check if the particle was born delayed - if (g /= 0) then - - ! determine score based on bank site weight and keff - score = keff * fission_bank(n_bank - p % n_bank + k) % wgt - - ! determine outgoing energy from fission bank - E_out = fission_bank(n_bank - p % n_bank + k) % E - - ! check if outgoing energy is within specified range on filter - if (E_out < t % filters(i) % real_bins(1) .or. & - E_out > t % filters(i) % real_bins(n)) cycle - - ! change outgoing energy bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, n, E_out) - - ! if the delayed group filter is present, tally to corresponding - ! delayed group bin if it exists - if (j > 0) then - - ! loop over delayed group bins until the corresponding bin is found - do d_bin = 1, t % filters(j) % n_bins - d = t % filters(j) % int_bins(d_bin) - - ! check whether the delayed group of the particle is equal to the - ! delayed group of this bin - if (d == g) then - call score_fission_delayed_dg(t, d_bin, score, i_score) - end if - end do - - ! if the delayed group filter is not present, add score to tally - else - - ! determine scoring index - i_filter = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - - ! Add score to tally -!$omp atomic - t % results(i_score, i_filter) % value = & - t % results(i_score, i_filter) % value + score - end if - end if - end do - - ! reset outgoing energy bin - matching_bins(i) = bin_energyout - - end subroutine score_fission_delayed_eout - !=============================================================================== ! SCORE_FISSION_DELAYED_DG helper function used to increment the tally when a ! delayed group filter is present. @@ -1868,23 +2002,26 @@ contains subroutine score_fission_delayed_dg(t, d_bin, score, score_index) type(TallyObject), intent(inout) :: t - integer, intent(in) :: score_index ! index for score integer, intent(in) :: d_bin ! delayed group bin index + real(8), intent(in) :: score ! actual score + integer, intent(in) :: score_index ! index for score integer :: bin_original ! original bin index integer :: filter_index ! index for matching filter bin combination - real(8) :: score ! actual score + real(8) :: filter_weight ! combined weight of all filters ! save original delayed group bin bin_original = matching_bins(t % find_filter(FILTER_DELAYEDGROUP)) matching_bins(t % find_filter(FILTER_DELAYEDGROUP)) = d_bin - ! Compute the filter index based on the modified matching_bins - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + ! determine scoring index and weight on the modified matching_bins + filter_index = sum((matching_bins(1:size(t % filters)) - 1) * t % stride) & + + 1 + filter_weight = product(filter_weights(:size(t % filters))) !$omp atomic t % results(score_index, filter_index) % value = & - t % results(score_index, filter_index) % value + score + t % results(score_index, filter_index) % value + score * filter_weight ! reset original delayed group bin matching_bins(t % find_filter(FILTER_DELAYEDGROUP)) = bin_original @@ -1905,13 +2042,14 @@ contains integer :: i integer :: i_tally + integer :: i_filt integer :: j ! loop index for scoring bins integer :: k ! loop index for nuclide bins integer :: filter_index ! single index for single bin integer :: i_nuclide ! index in nuclides array (from bins) real(8) :: flux ! tracklength estimate of flux real(8) :: atom_density ! atom density of single nuclide in atom/b-cm - logical :: found_bin ! scoring bin found? + real(8) :: filter_weight ! combined weight of all filters type(TallyObject), pointer :: t type(Material), pointer :: mat @@ -1926,70 +2064,103 @@ contains i_tally = active_tracklength_tallies % get_item(i) t => tallies(i_tally) - ! Check if this tally has a mesh filter -- if so, we treat it separately - ! since multiple bins can be scored to with a single track + ! Find the first bin in each filter. There may be more than one matching + ! bin per filter, but we'll deal with those later. + do i_filt = 1, size(t % filters) + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + NO_BIN_FOUND, matching_bins(i_filt), filter_weights(i_filt)) + ! 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 (matching_bins(i_filt) == NO_BIN_FOUND) cycle TALLY_LOOP + end do - if (t % find_filter(FILTER_MESH) > 0) then - call score_tl_on_mesh(p, i_tally, distance) - cycle - end if + ! ======================================================================== + ! Loop until we've covered all valid bins on each of the filters. - ! ======================================================================= - ! DETERMINE SCORING BIN COMBINATION + FILTER_LOOP: do - call get_scoring_bins(p, i_tally, found_bin) - if (.not. found_bin) cycle + ! Determine scoring index and weight for this filter combination + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 + filter_weight = product(filter_weights(:size(t % filters))) - ! ======================================================================= - ! CALCULATE RESULTS AND ACCUMULATE TALLY + ! ====================================================================== + ! Nuclide logic - ! If we have made it here, we have a scoring combination of bins for this - ! tally -- now we need to determine where in the results array we should - ! be accumulating the tally values - - ! Determine scoring index for this filter combination - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - - if (t % all_nuclides) then - if (p % material /= MATERIAL_VOID) then - call score_all_nuclides(p, i_tally, flux, filter_index) - end if - else - - NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins - ! Get index of nuclide in nuclides array - i_nuclide = t % nuclide_bins(k) - - if (i_nuclide > 0) then - if (p % material /= MATERIAL_VOID) then - ! Get pointer to current material - mat => materials(p % material) - - ! Determine if nuclide is actually in material - NUCLIDE_MAT_LOOP: do j = 1, mat % n_nuclides - ! If index of nuclide matches the j-th nuclide listed in the - ! material, break out of the loop - if (i_nuclide == mat % nuclide(j)) exit - - ! If we've reached the last nuclide in the material, it means - ! the specified nuclide to be tallied is not in this material - if (j == mat % n_nuclides) then - cycle NUCLIDE_BIN_LOOP - end if - end do NUCLIDE_MAT_LOOP - - atom_density = mat % atom_density(j) - else - atom_density = ZERO - end if + if (t % all_nuclides) then + if (p % material /= MATERIAL_VOID) then + call score_all_nuclides(p, i_tally, flux, filter_index) end if + else - ! Determine score for each bin - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, atom_density, flux) + NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins + ! Get index of nuclide in nuclides array + i_nuclide = t % nuclide_bins(k) - end do NUCLIDE_BIN_LOOP - end if + if (i_nuclide > 0) then + if (p % material /= MATERIAL_VOID) then + ! Get pointer to current material + mat => materials(p % material) + + ! Determine if nuclide is actually in material + NUCLIDE_MAT_LOOP: do j = 1, mat % n_nuclides + ! If index of nuclide matches the j-th nuclide listed in the + ! material, break out of the loop + if (i_nuclide == mat % nuclide(j)) exit + + ! If we've reached the last nuclide in the material, it means + ! the specified nuclide to be tallied is not in this material + if (j == mat % n_nuclides) then + cycle NUCLIDE_BIN_LOOP + end if + end do NUCLIDE_MAT_LOOP + + atom_density = mat % atom_density(j) + else + atom_density = ZERO + end if + end if + + ! Determine score for each bin + call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & + i_nuclide, atom_density, flux * filter_weight) + + end do NUCLIDE_BIN_LOOP + + end if + + ! ====================================================================== + ! Filter logic + + ! If there are no filters, then we are done. + if (size(t % filters) == 0) exit FILTER_LOOP + + ! Increment the filter bins, starting with the last filter. If we get a + ! NO_BIN_FOUND for the last filter, it means we finished all valid bins + ! for that filter, but next-to-last filter might have more than one + ! valid bin so we need to increment that one as well, and so on. + do i_filt = size(t % filters), 1, -1 + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + matching_bins(i_filt), matching_bins(i_filt), & + filter_weights(i_filt)) + if (matching_bins(i_filt) /= NO_BIN_FOUND) exit + end do + + ! If we got all NO_BIN_FOUNDs, then we have finished all valid bins for + ! each of the filters. Exit the loop. + if (all(matching_bins(:size(t % filters)) == NO_BIN_FOUND)) & + exit FILTER_LOOP + + ! Reset all the filters with NO_BIN_FOUND. This will set them back to + ! their first valid bin. + do i_filt = 1, size(t % filters) + if (matching_bins(i_filt) == NO_BIN_FOUND) then + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + matching_bins(i_filt), matching_bins(i_filt), & + filter_weights(i_filt)) + end if + end do + end do FILTER_LOOP ! If the user has specified that we can assume all tallies are spatially ! separate, this implies that once a tally has been scored to, we needn't @@ -2005,289 +2176,6 @@ contains end subroutine score_tracklength_tally -!=============================================================================== -! SCORE_TL_ON_MESH calculate fluxes and reaction rates based on the track-length -! estimate of the flux specifically for tallies that have mesh filters. For -! these tallies, it is possible to score to multiple mesh cells for each track. -!=============================================================================== - - subroutine score_tl_on_mesh(p, i_tally, d_track) - - type(Particle), intent(in) :: p - integer, intent(in) :: i_tally - real(8), intent(in) :: d_track - - integer :: i ! loop index for filter/score bins - integer :: j ! loop index for direction - integer :: k ! loop index for mesh cell crossings - integer :: b ! loop index for nuclide bins - integer :: ijk0(3) ! indices of starting coordinates - integer :: ijk1(3) ! indices of ending coordinates - integer :: ijk_cross(3) ! indices of mesh cell crossed - integer :: n_cross ! number of surface crossings - integer :: filter_index ! single index for single bin - integer :: i_nuclide ! index in nuclides array - integer :: i_filter_mesh ! index of mesh filter in filters array - real(8) :: atom_density ! density of individual nuclide in atom/b-cm - real(8) :: flux ! tracklength estimate of flux - real(8) :: uvw(3) ! cosine of angle of particle - real(8) :: xyz0(3) ! starting/intermediate coordinates - real(8) :: xyz1(3) ! ending coordinates of particle - real(8) :: xyz_cross(3) ! coordinates of next boundary - real(8) :: d(3) ! distance to each bounding surface - real(8) :: distance ! distance traveled in mesh cell - logical :: found_bin ! was a scoring bin found? - logical :: start_in_mesh ! starting coordinates inside mesh? - logical :: end_in_mesh ! ending coordinates inside mesh? - real(8) :: theta - real(8) :: phi - type(TallyObject), pointer :: t - type(RegularMesh), pointer :: m - type(Material), pointer :: mat - - t => tallies(i_tally) - matching_bins(1:t%n_filters) = 1 - - ! ========================================================================== - ! CHECK IF THIS TRACK INTERSECTS THE MESH - - ! Copy starting and ending location of particle - xyz0 = p % coord(1) % xyz - (d_track - TINY_BIT) * p % coord(1) % uvw - xyz1 = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw - - ! Get index for mesh filter - i_filter_mesh = t % find_filter(FILTER_MESH) - - ! Determine indices for starting and ending location - m => meshes(t % filters(i_filter_mesh) % int_bins(1)) - call get_mesh_indices(m, xyz0, ijk0(:m % n_dimension), start_in_mesh) - call get_mesh_indices(m, xyz1, ijk1(:m % n_dimension), end_in_mesh) - - ! Check if start or end is in mesh -- if not, check if track still - ! intersects with mesh - if ((.not. start_in_mesh) .and. (.not. end_in_mesh)) then - if (m % n_dimension == 2) then - if (.not. mesh_intersects_2d(m, xyz0, xyz1)) return - else - if (.not. mesh_intersects_3d(m, xyz0, xyz1)) return - end if - end if - - ! Reset starting and ending location - xyz0 = p % coord(1) % xyz - d_track * p % coord(1) % uvw - xyz1 = p % coord(1) % xyz - - ! ========================================================================= - ! CHECK FOR SCORING COMBINATION FOR FILTERS OTHER THAN MESH - - FILTER_LOOP: do i = 1, t % n_filters - - select case (t % filters(i) % type) - case (FILTER_UNIVERSE) - ! determine next universe bin - ! TODO: Account for multiple universes when performing this filter - matching_bins(i) = get_next_bin(FILTER_UNIVERSE, & - p % coord(p % n_coord) % universe, i_tally) - - case (FILTER_MATERIAL) - matching_bins(i) = get_next_bin(FILTER_MATERIAL, & - p % material, i_tally) - - case (FILTER_CELL) - ! determine next cell bin - do j = 1, p % n_coord - position(FILTER_CELL) = 0 - matching_bins(i) = get_next_bin(FILTER_CELL, & - p % coord(j) % cell, i_tally) - if (matching_bins(i) /= NO_BIN_FOUND) exit - end do - - case (FILTER_CELLBORN) - ! determine next cellborn bin - matching_bins(i) = get_next_bin(FILTER_CELLBORN, & - p % cell_born, i_tally) - - case (FILTER_SURFACE) - ! determine next surface bin - matching_bins(i) = get_next_bin(FILTER_SURFACE, & - p % surface, i_tally) - - case (FILTER_ENERGYIN) - ! determine incoming energy bin - k = t % filters(i) % n_bins - - ! check if energy of the particle is within energy bins - if (p % E < t % filters(i) % real_bins(1) .or. & - p % E > t % filters(i) % real_bins(k + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find incoming energy bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - k + 1, p % E) - end if - - case (FILTER_POLAR) - ! Get theta value - theta = acos(p % coord(1) % uvw(3)) - - ! determine polar angle bin - k = t % filters(i) % n_bins - - ! check if particle is within polar angle bins - if (theta < t % filters(i) % real_bins(1) .or. & - theta > t % filters(i) % real_bins(k + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find polar angle bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - k + 1, theta) - end if - - case (FILTER_AZIMUTHAL) - ! make sure the correct direction vector is used - phi = atan2(p % coord(1) % uvw(2), p % coord(1) % uvw(1)) - - ! determine mu bin - k = t % filters(i) % n_bins - - ! check if particle is within azimuthal angle bins - if (phi < t % filters(i) % real_bins(1) .or. & - phi > t % filters(i) % real_bins(k + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find azimuthal angle bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - k + 1, phi) - end if - - end select - - ! Check if no matching bin was found - if (matching_bins(i) == NO_BIN_FOUND) return - - end do FILTER_LOOP - - ! ========================================================================== - ! DETERMINE WHICH MESH CELLS TO SCORE TO - - ! Calculate number of surface crossings - n_cross = sum(abs(ijk1(:m % n_dimension) - ijk0(:m % n_dimension))) + 1 - - ! Copy particle's direction - uvw = p % coord(1) % uvw - - ! Bounding coordinates - do j = 1, m % n_dimension - if (uvw(j) > 0) then - xyz_cross(j) = m % lower_left(j) + ijk0(j) * m % width(j) - else - xyz_cross(j) = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) - end if - end do - - MESH_LOOP: do k = 1, n_cross - found_bin = .false. - - ! Calculate distance to each bounding surface. We need to treat special - ! case where the cosine of the angle is zero since this would result in a - ! divide-by-zero. - - if (k == n_cross) xyz_cross = xyz1 - - do j = 1, m % n_dimension - if (uvw(j) == 0) then - d(j) = INFINITY - else - d(j) = (xyz_cross(j) - xyz0(j))/uvw(j) - end if - end do - - ! Determine the closest bounding surface of the mesh cell by calculating - ! the minimum distance - - j = minloc(d(:m % n_dimension), 1) - distance = d(j) - - ! Now use the minimum distance and diretion of the particle to determine - ! which surface was crossed - - if (all(ijk0(:m % n_dimension) >= 1) .and. all(ijk0(:m % n_dimension) <= m % dimension)) then - ijk_cross = ijk0 - found_bin = .true. - end if - - ! Increment indices and determine new crossing point - if (uvw(j) > 0) then - ijk0(j) = ijk0(j) + 1 - xyz_cross(j) = xyz_cross(j) + m % width(j) - else - ijk0(j) = ijk0(j) - 1 - xyz_cross(j) = xyz_cross(j) - m % width(j) - end if - - ! ======================================================================= - ! SCORE TO THIS MESH CELL - - if (found_bin) then - ! Calculate track-length estimate of flux - flux = p % wgt * distance - - ! Determine mesh bin - matching_bins(i_filter_mesh) = mesh_indices_to_bin(m, ijk_cross) - - ! Determining scoring index - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - - if (t % all_nuclides) then - if (p % material /= MATERIAL_VOID) then - ! Score reaction rates for each nuclide in material - call score_all_nuclides(p, i_tally, flux, filter_index) - end if - else - NUCLIDE_BIN_LOOP: do b = 1, t % n_nuclide_bins - ! Get index of nuclide in nuclides array - i_nuclide = t % nuclide_bins(b) - - if (i_nuclide > 0) then - if (p % material /= MATERIAL_VOID) then - ! Get pointer to current material - mat => materials(p % material) - - ! Determine if nuclide is actually in material - NUCLIDE_MAT_LOOP: do j = 1, mat % n_nuclides - ! If index of nuclide matches the j-th nuclide listed in - ! the material, break out of the loop - if (i_nuclide == mat % nuclide(j)) exit - - ! If we've reached the last nuclide in the material, it - ! means the specified nuclide to be tallied is not in this - ! material - if (j == mat % n_nuclides) then - cycle NUCLIDE_BIN_LOOP - end if - end do NUCLIDE_MAT_LOOP - - atom_density = mat % atom_density(j) - else - atom_density = ZERO - end if - end if - - ! Determine score for each bin - call score_general(p, t, (b-1)*t % n_score_bins, filter_index, & - i_nuclide, atom_density, flux) - - end do NUCLIDE_BIN_LOOP - end if - end if - - ! Calculate new coordinates - xyz0 = xyz0 + distance * uvw - - end do MESH_LOOP - - end subroutine score_tl_on_mesh - !=============================================================================== ! SCORE_COLLISION_TALLY calculates fluxes and reaction rates based on the ! 1/Sigma_t estimate of the flux. This is triggered after every collision. It @@ -2302,6 +2190,7 @@ contains integer :: i integer :: i_tally + integer :: i_filt integer :: j ! loop index for scoring bins integer :: k ! loop index for nuclide bins integer :: filter_index ! single index for single bin @@ -2309,7 +2198,7 @@ contains real(8) :: flux ! collision estimate of flux real(8) :: atom_density ! atom density of single nuclide ! in atom/b-cm - logical :: found_bin ! scoring bin found? + real(8) :: filter_weight ! combined weight of all filters type(TallyObject), pointer :: t type(Material), pointer :: mat @@ -2329,62 +2218,103 @@ contains i_tally = active_collision_tallies % get_item(i) t => tallies(i_tally) - ! ======================================================================= - ! DETERMINE SCORING BIN COMBINATION + ! Find the first bin in each filter. There may be more than one matching + ! bin per filter, but we'll deal with those later. + do i_filt = 1, size(t % filters) + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + NO_BIN_FOUND, matching_bins(i_filt), filter_weights(i_filt)) + ! 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 (matching_bins(i_filt) == NO_BIN_FOUND) cycle TALLY_LOOP + end do - call get_scoring_bins(p, i_tally, found_bin) - if (.not. found_bin) cycle + ! ======================================================================== + ! Loop until we've covered all valid bins on each of the filters. - ! ======================================================================= - ! CALCULATE RESULTS AND ACCUMULATE TALLY + FILTER_LOOP: do - ! If we have made it here, we have a scoring combination of bins for this - ! tally -- now we need to determine where in the results array we should - ! be accumulating the tally values + ! Determine scoring index and weight for this filter combination + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 + filter_weight = product(filter_weights(:size(t % filters))) - ! Determine scoring index for this filter combination - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + ! ====================================================================== + ! Nuclide logic - if (t % all_nuclides) then - if (p % material /= MATERIAL_VOID) then - call score_all_nuclides(p, i_tally, flux, filter_index) - end if - else - - NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins - ! Get index of nuclide in nuclides array - i_nuclide = t % nuclide_bins(k) - - if (i_nuclide > 0) then - if (p % material /= MATERIAL_VOID) then - ! Get pointer to current material - mat => materials(p % material) - - ! Determine if nuclide is actually in material - NUCLIDE_MAT_LOOP: do j = 1, mat % n_nuclides - ! If index of nuclide matches the j-th nuclide listed in the - ! material, break out of the loop - if (i_nuclide == mat % nuclide(j)) exit - - ! If we've reached the last nuclide in the material, it means - ! the specified nuclide to be tallied is not in this material - if (j == mat % n_nuclides) then - cycle NUCLIDE_BIN_LOOP - end if - end do NUCLIDE_MAT_LOOP - - atom_density = mat % atom_density(j) - else - atom_density = ZERO - end if + if (t % all_nuclides) then + if (p % material /= MATERIAL_VOID) then + call score_all_nuclides(p, i_tally, flux, filter_index) end if + else - ! Determine score for each bin - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, atom_density, flux) + NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins + ! Get index of nuclide in nuclides array + i_nuclide = t % nuclide_bins(k) - end do NUCLIDE_BIN_LOOP - end if + if (i_nuclide > 0) then + if (p % material /= MATERIAL_VOID) then + ! Get pointer to current material + mat => materials(p % material) + + ! Determine if nuclide is actually in material + NUCLIDE_MAT_LOOP: do j = 1, mat % n_nuclides + ! If index of nuclide matches the j-th nuclide listed in the + ! material, break out of the loop + if (i_nuclide == mat % nuclide(j)) exit + + ! If we've reached the last nuclide in the material, it means + ! the specified nuclide to be tallied is not in this material + if (j == mat % n_nuclides) then + cycle NUCLIDE_BIN_LOOP + end if + end do NUCLIDE_MAT_LOOP + + atom_density = mat % atom_density(j) + else + atom_density = ZERO + end if + end if + + ! Determine score for each bin + call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & + i_nuclide, atom_density, flux * filter_weight) + + end do NUCLIDE_BIN_LOOP + + end if + + ! ====================================================================== + ! Filter logic + + ! If there are no filters, then we are done. + if (size(t % filters) == 0) exit FILTER_LOOP + + ! Increment the filter bins, starting with the last filter. If we get a + ! NO_BIN_FOUND for the last filter, it means we finished all valid bins + ! for that filter, but next-to-last filter might have more than one + ! valid bin so we need to increment that one as well, and so on. + do i_filt = size(t % filters), 1, -1 + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + matching_bins(i_filt), matching_bins(i_filt), & + filter_weights(i_filt)) + if (matching_bins(i_filt) /= NO_BIN_FOUND) exit + end do + + ! If we got all NO_BIN_FOUNDs, then we have finished all valid bins for + ! each of the filters. Exit the loop. + if (all(matching_bins(:size(t % filters)) == NO_BIN_FOUND)) & + exit FILTER_LOOP + + ! Reset all the filters with NO_BIN_FOUND. This will set them back to + ! their first valid bin. + do i_filt = 1, size(t % filters) + if (matching_bins(i_filt) == NO_BIN_FOUND) then + call t % filters(i_filt) % obj % get_next_bin(p, t % estimator, & + matching_bins(i_filt), matching_bins(i_filt), & + filter_weights(i_filt)) + end if + end do + end do FILTER_LOOP ! If the user has specified that we can assume all tallies are spatially ! separate, this implies that once a tally has been scored to, we needn't @@ -2400,433 +2330,6 @@ contains end subroutine score_collision_tally -!=============================================================================== -! GET_SCORING_BINS determines a combination of filter bins that should be scored -! for a tally based on the particle's current attributes. -!=============================================================================== - - subroutine get_scoring_bins_ce(p, i_tally, found_bin) - - type(Particle), intent(in) :: p - integer, intent(in) :: i_tally - logical, intent(out) :: found_bin - - integer :: i ! loop index for filters - integer :: j - integer :: n ! number of bins for single filter - integer :: offset ! offset for distribcell - integer :: distribcell_index ! index in distribcell arrays - real(8) :: E ! particle energy - real(8) :: theta, phi ! Polar and Azimuthal Angles, respectively - type(TallyObject), pointer :: t - type(RegularMesh), pointer :: m - - found_bin = .true. - t => tallies(i_tally) - matching_bins(1:t%n_filters) = 1 - - FILTER_LOOP: do i = 1, t % n_filters - - select case (t % filters(i) % type) - case (FILTER_MESH) - ! determine mesh bin - m => meshes(t % filters(i) % int_bins(1)) - - ! Determine if we're in the mesh first - call get_mesh_bin(m, p % coord(1) % xyz, matching_bins(i)) - - case (FILTER_UNIVERSE) - ! determine next universe bin - ! TODO: Account for multiple universes when performing this filter - matching_bins(i) = get_next_bin(FILTER_UNIVERSE, & - p % coord(p % n_coord) % universe, i_tally) - - case (FILTER_MATERIAL) - if (p % material == MATERIAL_VOID) then - matching_bins(i) = NO_BIN_FOUND - else - matching_bins(i) = get_next_bin(FILTER_MATERIAL, & - p % material, i_tally) - endif - - case (FILTER_CELL) - ! determine next cell bin - do j = 1, p % n_coord - position(FILTER_CELL) = 0 - matching_bins(i) = get_next_bin(FILTER_CELL, & - p % coord(j) % cell, i_tally) - if (matching_bins(i) /= NO_BIN_FOUND) exit - end do - - case (FILTER_DISTRIBCELL) - ! determine next distribcell bin - distribcell_index = cells(t % filters(i) % int_bins(1)) & - % distribcell_index - matching_bins(i) = NO_BIN_FOUND - offset = 0 - do j = 1, p % n_coord - if (cells(p % coord(j) % cell) % type == CELL_FILL) then - offset = offset + cells(p % coord(j) % cell) % & - offset(distribcell_index) - elseif(cells(p % coord(j) % cell) % type == CELL_LATTICE) then - if (lattices(p % coord(j + 1) % lattice) % obj & - % are_valid_indices([& - p % coord(j + 1) % lattice_x, & - p % coord(j + 1) % lattice_y, & - p % coord(j + 1) % lattice_z])) then - offset = offset + lattices(p % coord(j + 1) % lattice) % obj % & - offset(distribcell_index, & - p % coord(j + 1) % lattice_x, & - p % coord(j + 1) % lattice_y, & - p % coord(j + 1) % lattice_z) - end if - end if - if (t % filters(i) % int_bins(1) == p % coord(j) % cell) then - matching_bins(i) = offset + 1 - exit - end if - end do - - case (FILTER_CELLBORN) - ! determine next cellborn bin - matching_bins(i) = get_next_bin(FILTER_CELLBORN, & - p % cell_born, i_tally) - - case (FILTER_SURFACE) - ! determine next surface bin - matching_bins(i) = get_next_bin(FILTER_SURFACE, & - p % surface, i_tally) - - case (FILTER_ENERGYIN) - ! determine incoming energy bin - n = t % filters(i) % n_bins - - ! make sure the correct energy is used - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - E = p % E - else - E = p % last_E - end if - - ! check if energy of the particle is within energy bins - if (E < t % filters(i) % real_bins(1) .or. & - E > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find incoming energy bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, E) - end if - - case (FILTER_ENERGYOUT) - ! determine outgoing energy bin - n = t % filters(i) % n_bins - - ! check if energy of the particle is within energy bins - if (p % E < t % filters(i) % real_bins(1) .or. & - p % E > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find incoming energy bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, p % E) - end if - - case (FILTER_DELAYEDGROUP) - - if (survival_biasing .and. t % find_filter(FILTER_ENERGYOUT) <= 0) then - matching_bins(i) = 1 - elseif (active_tracklength_tallies % size() > 0) then - matching_bins(i) = 1 - else - if (p % delayed_group == 0) then - matching_bins = NO_BIN_FOUND - else - matching_bins(i) = p % delayed_group - end if - end if - - case (FILTER_MU) - ! determine mu bin - n = t % filters(i) % n_bins - - ! check if particle is within mu bins - if (p % mu < t % filters(i) % real_bins(1) .or. & - p % mu > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find mu bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, p % mu) - end if - - case (FILTER_POLAR) - ! make sure the correct direction vector is used - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - theta = acos(p % coord(1) % uvw(3)) - else - theta = acos(p % last_uvw(3)) - end if - - ! determine polar angle bin - n = t % filters(i) % n_bins - - ! check if particle is within polar angle bins - if (theta < t % filters(i) % real_bins(1) .or. & - theta > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find polar angle bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, theta) - end if - - case (FILTER_AZIMUTHAL) - ! make sure the correct direction vector is used - if (t % 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 - ! determine mu bin - n = t % filters(i) % n_bins - - ! check if particle is within azimuthal angle bins - if (phi < t % filters(i) % real_bins(1) .or. & - phi > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find azimuthal angle bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, phi) - end if - - end select - - ! If the current filter didn't match, exit this subroutine - if (matching_bins(i) == NO_BIN_FOUND) then - found_bin = .false. - return - end if - - end do FILTER_LOOP - - end subroutine get_scoring_bins_ce - - subroutine get_scoring_bins_mg(p, i_tally, found_bin) - - type(Particle), intent(in) :: p - integer, intent(in) :: i_tally - logical, intent(out) :: found_bin - - integer :: i ! loop index for filters - integer :: j - integer :: n ! number of bins for single filter - integer :: distribcell_index ! index in distribcell arrays - integer :: offset ! offset for distribcell - real(8) :: theta, phi ! Polar and Azimuthal Angles, respectively - real(8) :: E - type(TallyObject), pointer :: t - type(RegularMesh), pointer :: m - - found_bin = .true. - t => tallies(i_tally) - matching_bins(1:t%n_filters) = 1 - - FILTER_LOOP: do i = 1, t % n_filters - - select case (t % filters(i) % type) - case (FILTER_MESH) - ! determine mesh bin - m => meshes(t % filters(i) % int_bins(1)) - - ! Determine if we're in the mesh first - call get_mesh_bin(m, p % coord(1) % xyz, matching_bins(i)) - - case (FILTER_UNIVERSE) - ! determine next universe bin - ! TODO: Account for multiple universes when performing this filter - matching_bins(i) = get_next_bin(FILTER_UNIVERSE, & - p % coord(p % n_coord) % universe, i_tally) - - case (FILTER_MATERIAL) - if (p % material == MATERIAL_VOID) then - matching_bins(i) = NO_BIN_FOUND - else - matching_bins(i) = get_next_bin(FILTER_MATERIAL, & - p % material, i_tally) - endif - - case (FILTER_CELL) - ! determine next cell bin - do j = 1, p % n_coord - position(FILTER_CELL) = 0 - matching_bins(i) = get_next_bin(FILTER_CELL, & - p % coord(j) % cell, i_tally) - if (matching_bins(i) /= NO_BIN_FOUND) exit - end do - - case (FILTER_DISTRIBCELL) - ! determine next distribcell bin - distribcell_index = cells(t % filters(i) % int_bins(1)) & - % distribcell_index - matching_bins(i) = NO_BIN_FOUND - offset = 0 - do j = 1, p % n_coord - if (cells(p % coord(j) % cell) % type == CELL_FILL) then - offset = offset + cells(p % coord(j) % cell) % & - offset(distribcell_index) - elseif(cells(p % coord(j) % cell) % type == CELL_LATTICE) then - if (lattices(p % coord(j + 1) % lattice) % obj & - % are_valid_indices([& - p % coord(j + 1) % lattice_x, & - p % coord(j + 1) % lattice_y, & - p % coord(j + 1) % lattice_z])) then - offset = offset + lattices(p % coord(j + 1) % lattice) % obj % & - offset(distribcell_index, & - p % coord(j + 1) % lattice_x, & - p % coord(j + 1) % lattice_y, & - p % coord(j + 1) % lattice_z) - end if - end if - if (t % filters(i) % int_bins(1) == p % coord(j) % cell) then - matching_bins(i) = offset + 1 - exit - end if - end do - - case (FILTER_CELLBORN) - ! determine next cellborn bin - matching_bins(i) = get_next_bin(FILTER_CELLBORN, & - p % cell_born, i_tally) - - case (FILTER_SURFACE) - ! determine next surface bin - matching_bins(i) = get_next_bin(FILTER_SURFACE, & - p % surface, i_tally) - - case (FILTER_ENERGYIN) - if (t % energy_matches_groups) then - ! make sure the correct energy group is used - ! Since all groups are filters, the filter bin is the group - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - matching_bins(i) = p % g - else - matching_bins(i) = p % last_g - end if - ! Tallies are ordered in increasing groups, group indices - ! however are the opposite, so switch - matching_bins(i) = energy_groups - matching_bins(i) + 1 - else - ! make sure the correct energy is used - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - E = p % E - else - E = p % last_E - end if - n = t % filters(i) % n_bins - - ! check if energy of the particle is within energy bins - if (E < t % filters(i) % real_bins(1) .or. & - E > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find incoming energy bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, E) - end if - end if - - case (FILTER_ENERGYOUT) - if (t % energyout_matches_groups) then - ! Since all groups are filters, the filter bin is the group - matching_bins(i) = p % g - - ! Tallies are ordered in increasing groups, group indices - ! however are the opposite, so switch - matching_bins(i) = energy_groups - matching_bins(i) + 1 - else - ! determine outgoing energy bin - n = t % filters(i) % n_bins - - ! check if energy of the particle is within energy bins - if (p % E < t % filters(i) % real_bins(1) .or. & - p % E > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find incoming energy bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, p % E) - end if - end if - - case (FILTER_MU) - ! determine mu bin - n = t % filters(i) % n_bins - - ! check if particle is within mu bins - if (p % mu < t % filters(i) % real_bins(1) .or. & - p % mu > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find mu bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, p % mu) - end if - - case (FILTER_POLAR) - ! make sure the correct direction vector is used - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - theta = acos(p % coord(1) % uvw(3)) - else - theta = acos(p % last_uvw(3)) - end if - - ! determine polar angle bin - n = t % filters(i) % n_bins - - ! check if particle is within polar angle bins - if (theta < t % filters(i) % real_bins(1) .or. & - theta > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find polar angle bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, theta) - end if - - case (FILTER_AZIMUTHAL) - ! make sure the correct direction vector is used - if (t % 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 - ! determine mu bin - n = t % filters(i) % n_bins - - ! check if particle is within azimuthal angle bins - if (phi < t % filters(i) % real_bins(1) .or. & - phi > t % filters(i) % real_bins(n + 1)) then - matching_bins(i) = NO_BIN_FOUND - else - ! search to find azimuthal angle bin - matching_bins(i) = binary_search(t % filters(i) % real_bins, & - n + 1, phi) - end if - - end select - - ! If the current filter didn't match, exit this subroutine - if (matching_bins(i) == NO_BIN_FOUND) then - found_bin = .false. - return - end if - - end do FILTER_LOOP - - end subroutine get_scoring_bins_mg - !=============================================================================== ! SCORE_SURFACE_CURRENT tallies surface crossings in a mesh tally by manually ! determining which mesh surfaces were crossed @@ -2843,7 +2346,6 @@ contains integer :: ijk0(3) ! indices of starting coordinates integer :: ijk1(3) ! indices of ending coordinates integer :: n_cross ! number of surface crossings - integer :: n ! number of incoming energy bins integer :: filter_index ! index of scoring bin integer :: i_filter_mesh ! index of mesh filter in filters array integer :: i_filter_surf ! index of surface filter in filters @@ -2853,6 +2355,7 @@ contains real(8) :: xyz_cross(3) ! coordinates of bounding surfaces real(8) :: d(3) ! distance to each bounding surface real(8) :: distance ! actual distance traveled + real(8) :: filt_score ! score applied by filters logical :: start_in_mesh ! particle's starting xyz in mesh? logical :: end_in_mesh ! particle's ending xyz in mesh? logical :: x_same ! same starting/ending x index (i) @@ -2863,7 +2366,7 @@ contains TALLY_LOOP: do i = 1, active_current_tallies % size() ! Copy starting and ending location of particle - xyz0 = p % last_xyz + xyz0 = p % last_xyz_current xyz1 = p % coord(1) % xyz ! Get pointer to tally @@ -2874,8 +2377,13 @@ contains i_filter_mesh = t % find_filter(FILTER_MESH) i_filter_surf = t % find_filter(FILTER_SURFACE) + ! Get pointer to mesh + select type(filt => t % filters(i_filter_mesh) % obj) + type is (MeshFilter) + m => meshes(filt % mesh) + end select + ! Determine indices for starting and ending location - m => meshes(t % filters(i_filter_mesh) % int_bins(1)) call get_mesh_indices(m, xyz0, ijk0(:m % n_dimension), start_in_mesh) call get_mesh_indices(m, xyz1, ijk1(:m % n_dimension), end_in_mesh) @@ -2898,19 +2406,13 @@ contains ! Copy particle's direction uvw = p % coord(1) % uvw - ! determine incoming energy bin + ! Determine incoming energy bin. We need to tell the energy filter this + ! is a tracklength tally so it uses the pre-collision energy. j = t % find_filter(FILTER_ENERGYIN) if (j > 0) then - n = t % filters(j) % n_bins - ! check if energy of the particle is within energy bins - if (p % E < t % filters(j) % real_bins(1) .or. & - p % E > t % filters(j) % real_bins(n + 1)) then - cycle - end if - - ! search to find incoming energy bin - matching_bins(j) = binary_search(t % filters(j) % real_bins, & - n + 1, p % E) + call t % filters(i) % obj % get_next_bin(p, ESTIMATOR_TRACKLENGTH, & + & NO_BIN_FOUND, matching_bins(j), filt_score) + if (matching_bins(j) == NO_BIN_FOUND) cycle end if ! ======================================================================= @@ -2929,7 +2431,8 @@ contains matching_bins(i_filter_surf) = OUT_TOP matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, ijk0 + 1, .true.) - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 !$omp atomic t % results(1, filter_index) % value = & t % results(1, filter_index) % value + p % wgt @@ -2942,7 +2445,8 @@ contains matching_bins(i_filter_surf) = IN_TOP matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, ijk0 + 1, .true.) - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 !$omp atomic t % results(1, filter_index) % value = & t % results(1, filter_index) % value + p % wgt @@ -2959,7 +2463,8 @@ contains matching_bins(i_filter_surf) = OUT_FRONT matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, ijk0 + 1, .true.) - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 !$omp atomic t % results(1, filter_index) % value = & t % results(1, filter_index) % value + p % wgt @@ -2972,7 +2477,8 @@ contains matching_bins(i_filter_surf) = IN_FRONT matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, ijk0 + 1, .true.) - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 !$omp atomic t % results(1, filter_index) % value = & t % results(1, filter_index) % value + p % wgt @@ -2989,7 +2495,8 @@ contains matching_bins(i_filter_surf) = OUT_RIGHT matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, ijk0 + 1, .true.) - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 !$omp atomic t % results(1, filter_index) % value = & t % results(1, filter_index) % value + p % wgt @@ -3002,7 +2509,8 @@ contains matching_bins(i_filter_surf) = IN_RIGHT matching_bins(i_filter_mesh) = & mesh_indices_to_bin(m, ijk0 + 1, .true.) - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 !$omp atomic t % results(1, filter_index) % value = & t % results(1, filter_index) % value + p % wgt @@ -3118,7 +2626,8 @@ contains ! Determine scoring index if (matching_bins(i_filter_surf) > 0) then - filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 + filter_index = sum((matching_bins(1:size(t % filters)) - 1) & + * t % stride) + 1 ! Check for errors if (filter_index <= 0 .or. filter_index > & @@ -3140,61 +2649,6 @@ contains end subroutine score_surface_current -!=============================================================================== -! GET_NEXT_BIN determines the next scoring bin for a particular filter variable -!=============================================================================== - - function get_next_bin(filter_type, filter_value, i_tally) result(bin) - - integer, intent(in) :: filter_type ! e.g. FILTER_MATERIAL - integer, intent(in) :: filter_value ! value of filter, e.g. material 3 - integer, intent(in) :: i_tally ! index of tally - integer :: bin ! index of filter - - integer :: i_tally_check - integer :: n - - ! If there are no scoring bins for this item, then return immediately - if (.not. allocated(tally_maps(filter_type) % items(filter_value) % elements)) then - bin = NO_BIN_FOUND - return - end if - - ! Check how many elements there are for this item - n = size(tally_maps(filter_type) % items(filter_value) % elements) - - do - ! Increment position in elements - position(filter_type) = position(filter_type) + 1 - - ! If we've reached the end of the array, there is no more bin to score to - if (position(filter_type) > n) then - position(filter_type) = 0 - bin = NO_BIN_FOUND - return - end if - - i_tally_check = tally_maps(filter_type) % items(filter_value) % & - elements(position(filter_type)) % index_tally - - if (i_tally_check > i_tally) then - ! Since the index being checked against is greater than the index we - ! need (and the tally indices were added to elements sequentially), we - ! know that no more bins will be scoring bins for this tally - position(filter_type) = 0 - bin = NO_BIN_FOUND - return - elseif (i_tally_check == i_tally) then - ! Found a match - bin = tally_maps(filter_type) % items(filter_value) % & - elements(position(filter_type)) % index_bin - return - end if - - end do - - end function get_next_bin - !=============================================================================== ! SYNCHRONIZE_TALLIES accumulates the sum of the contributions from each history ! within the batch to a new random variable diff --git a/src/tally_filter.F90 b/src/tally_filter.F90 new file mode 100644 index 000000000..c0e1c8853 --- /dev/null +++ b/src/tally_filter.F90 @@ -0,0 +1,1491 @@ +module tally_filter + + use constants, only: ONE, NO_BIN_FOUND, FP_PRECISION + use dict_header, only: DictIntInt + use geometry_header, only: BASE_UNIVERSE, RectLattice, HexLattice + use global + use hdf5_interface + use mesh_header, only: RegularMesh + use mesh, only: get_mesh_bin, bin_to_mesh_indices, & + get_mesh_indices, mesh_indices_to_bin, & + mesh_intersects_2d, mesh_intersects_3d + use particle_header, only: Particle + use search, only: binary_search + use string, only: to_str + use tally_filter_header, only: TallyFilter, TallyFilterContainer + + use hdf5, only: HID_T + + implicit none + +!=============================================================================== +! MESHFILTER indexes the location of particle events to a regular mesh. For +! tracklength tallies, it will produce multiple valid bins and the bin weight +! will correspond to the fraction of the track length that lies in that bin. +!=============================================================================== + type, extends(TallyFilter) :: MeshFilter + integer :: mesh + contains + procedure :: get_next_bin => get_next_bin_mesh + procedure :: to_statepoint => to_statepoint_mesh + procedure :: text_label => text_label_mesh + end type MeshFilter + +!=============================================================================== +! UNIVERSEFILTER specifies which geometric universes tally events reside in. +!=============================================================================== + type, extends(TallyFilter) :: UniverseFilter + integer, allocatable :: universes(:) + type(DictIntInt) :: map + contains + procedure :: get_next_bin => get_next_bin_universe + procedure :: to_statepoint => to_statepoint_universe + procedure :: text_label => text_label_universe + procedure :: initialize => initialize_universe + end type UniverseFilter + +!=============================================================================== +! MATERIAL specifies which material tally events reside in. +!=============================================================================== + type, extends(TallyFilter) :: MaterialFilter + integer, allocatable :: materials(:) + type(DictIntInt) :: map + contains + procedure :: get_next_bin => get_next_bin_material + procedure :: to_statepoint => to_statepoint_material + procedure :: text_label => text_label_material + procedure :: initialize => initialize_material + end type MaterialFilter + +!=============================================================================== +! CELLFILTER specifies which geometric cells tally events reside in. +!=============================================================================== + type, extends(TallyFilter) :: CellFilter + integer, allocatable :: cells(:) + type(DictIntInt) :: map + contains + procedure :: get_next_bin => get_next_bin_cell + procedure :: to_statepoint => to_statepoint_cell + procedure :: text_label => text_label_cell + procedure :: initialize => initialize_cell + end type CellFilter + +!=============================================================================== +! DISTRIBCELLFILTER specifies which distributed geometric cells tally events +! reside in. +!=============================================================================== + type, extends(TallyFilter) :: DistribcellFilter + integer :: cell + contains + procedure :: get_next_bin => get_next_bin_distribcell + procedure :: to_statepoint => to_statepoint_distribcell + procedure :: to_summary => to_summary_distribcell + procedure :: text_label => text_label_distribcell + procedure :: initialize => initialize_distribcell + end type DistribcellFilter + +!=============================================================================== +! CELLBORNFILTER specifies which cell the particle was born in. +!=============================================================================== + type, extends(TallyFilter) :: CellbornFilter + integer, allocatable :: cells(:) + type(DictIntInt) :: map + contains + procedure :: get_next_bin => get_next_bin_cellborn + procedure :: to_statepoint => to_statepoint_cellborn + procedure :: text_label => text_label_cellborn + procedure :: initialize => initialize_cellborn + end type CellbornFilter + +!=============================================================================== +! SURFACEFILTER is currently not implemented for usual geometric surfaces, but +! it is used as a placeholder for mesh surfaces used in current tallies. +!=============================================================================== + type, extends(TallyFilter) :: SurfaceFilter + integer, allocatable :: surfaces(:) + contains + procedure :: get_next_bin => get_next_bin_surface + procedure :: to_statepoint => to_statepoint_surface + procedure :: text_label => text_label_surface + procedure :: initialize => initialize_surface + end type SurfaceFilter + +!=============================================================================== +! ENERGYFILTER bins the incident neutron energy. +!=============================================================================== + type, extends(TallyFilter) :: EnergyFilter + real(8), allocatable :: bins(:) + + ! True if transport group number can be used directly to get bin number + logical :: matches_transport_groups = .false. + + contains + procedure :: get_next_bin => get_next_bin_energy + procedure :: to_statepoint => to_statepoint_energy + procedure :: text_label => text_label_energy + end type EnergyFilter + +!=============================================================================== +! ENERGYOUTFILTER bins the outgoing neutron energy. Only scattering events use +! the get_next_bin functionality. Nu-fission tallies manually iterate over the +! filter bins. +!=============================================================================== + type, extends(TallyFilter) :: EnergyoutFilter + real(8), allocatable :: bins(:) + + ! True if transport group number can be used directly to get bin number + logical :: matches_transport_groups = .false. + + contains + procedure :: get_next_bin => get_next_bin_energyout + procedure :: to_statepoint => to_statepoint_energyout + procedure :: text_label => text_label_energyout + end type EnergyoutFilter + +!=============================================================================== +! DELAYEDGROUPFILTER bins outgoing fission neutrons in their delayed groups. +! The get_next_bin functionality is not actually used. The bins are manually +! iterated over in the scoring subroutines. +!=============================================================================== + type, extends(TallyFilter) :: DelayedGroupFilter + integer, allocatable :: groups(:) + contains + procedure :: get_next_bin => get_next_bin_dg + procedure :: to_statepoint => to_statepoint_dg + procedure :: text_label => text_label_dg + end type DelayedGroupFilter + +!=============================================================================== +! MUFILTER bins the incoming-outgoing direction cosine. This is only used for +! scatter reactions. +!=============================================================================== + type, extends(TallyFilter) :: MuFilter + real(8), allocatable :: bins(:) + contains + procedure :: get_next_bin => get_next_bin_mu + procedure :: to_statepoint => to_statepoint_mu + procedure :: text_label => text_label_mu + end type MuFilter + +!=============================================================================== +! POLARFILTER bins the incident neutron polar angle (relative to the global +! z-axis). +!=============================================================================== + type, extends(TallyFilter) :: PolarFilter + real(8), allocatable :: bins(:) + contains + procedure :: get_next_bin => get_next_bin_polar + procedure :: to_statepoint => to_statepoint_polar + procedure :: text_label => text_label_polar + end type PolarFilter + +!=============================================================================== +! AZIMUTHALFILTER bins the incident neutron azimuthal angle (relative to the +! global xy-plane). +!=============================================================================== + type, extends(TallyFilter) :: AzimuthalFilter + real(8), allocatable :: bins(:) + contains + procedure :: get_next_bin => get_next_bin_azimuthal + procedure :: to_statepoint => to_statepoint_azimuthal + procedure :: text_label => text_label_azimuthal + end type AzimuthalFilter + +contains + +!=============================================================================== +! METHODS: for a description of these methods, see their counterparts bound to +! the abstract TallyFilter class. +!=============================================================================== + +!=============================================================================== +! MeshFilter methods +!=============================================================================== + subroutine get_next_bin_mesh(this, p, estimator, current_bin, next_bin, & + weight) + class(MeshFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer, parameter :: MAX_SEARCH_ITER = 100 ! Maximum number of times we can + ! can loop while trying to find + ! the first intersection. + + integer :: j ! loop index for direction + integer :: ijk0(3) ! indices of starting coordinates + integer :: ijk1(3) ! indices of ending coordinates + integer :: search_iter ! loop count for intersection search + real(8) :: uvw(3) ! cosine of angle of particle + real(8) :: xyz0(3) ! starting/intermediate coordinates + real(8) :: xyz1(3) ! ending coordinates of particle + real(8) :: xyz_cross ! coordinates of next boundary + real(8) :: d(3) ! distance to each bounding surface + real(8) :: total_distance ! distance of entire particle track + real(8) :: distance ! distance traveled in mesh cell + logical :: start_in_mesh ! starting coordinates inside mesh? + logical :: end_in_mesh ! ending coordinates inside mesh? + type(RegularMesh), pointer :: m + + ! Get a pointer to the mesh. + m => meshes(this % mesh) + + if (estimator /= ESTIMATOR_TRACKLENGTH) then + ! If this is an analog or collision tally, then there can only be one + ! valid mesh bin. + if (current_bin == NO_BIN_FOUND) then + call get_mesh_bin(m, p % coord(1) % xyz, next_bin) + else + next_bin = NO_BIN_FOUND + end if + weight = ONE + + else + ! A track can span multiple mesh bins so we need to handle a lot of + ! intersection logic for tracklength tallies. + + ! Copy the starting and ending coordinates of the particle. Offset these + ! just a bit for the purposes of determining if there was an intersection + ! in case the mesh surfaces coincide with lattice/geometric surfaces which + ! might produce finite-precision errors. + xyz0 = p % last_xyz + TINY_BIT * p % coord(1) % uvw + xyz1 = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw + + ! Determine indices for starting and ending location. + call get_mesh_indices(m, xyz0, ijk0(:m % n_dimension), start_in_mesh) + call get_mesh_indices(m, xyz1, ijk1(:m % n_dimension), end_in_mesh) + + ! If this is the first iteration of the filter loop, check if the track + ! intersects any part of the mesh. + if (current_bin == NO_BIN_FOUND) then + if ((.not. start_in_mesh) .and. (.not. end_in_mesh)) then + if (m % n_dimension == 2) then + if (.not. mesh_intersects_2d(m, xyz0, xyz1)) then + next_bin = NO_BIN_FOUND + return + end if + else + if (.not. mesh_intersects_3d(m, xyz0, xyz1)) then + next_bin = NO_BIN_FOUND + return + end if + end if + end if + end if + + ! Copy the un-modified coordinates the particle direction. + xyz0 = p % last_xyz + xyz1 = p % coord(1) % xyz + uvw = p % coord(1) % uvw + + ! Compute the length of the entire track. + total_distance = sqrt(sum((xyz1 - xyz0)**2)) + + ! If we're looking for the first valid bin, check to see if the particle + ! starts inside the mesh. + if (current_bin == NO_BIN_FOUND) then + if (any(ijk0(:m % n_dimension) < 1) & + .or. any(ijk0(:m % n_dimension) > m % dimension)) then + + ! The particle does not start in the mesh so keep iterating the ijk0 + ! indices to cross the nearest mesh surface until we've found a valid + ! bin. MAX_SEARCH_ITER prevents an infinite loop. + search_iter = 0 + do while (any(ijk0(:m % n_dimension) < 1) & + .or. any(ijk0(:m % n_dimension) > m % dimension)) + if (search_iter == MAX_SEARCH_ITER) call fatal_error("Failed to & + &find a mesh intersection on a tally mesh filter.") + + do j = 1, m % n_dimension + if (abs(uvw(j)) < FP_PRECISION) then + d(j) = INFINITY + else if (uvw(j) > 0) then + xyz_cross = m % lower_left(j) + ijk0(j) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + else + xyz_cross = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + end if + end do + j = minloc(d(:m % n_dimension), 1) + if (uvw(j) > ZERO) then + ijk0(j) = ijk0(j) + 1 + else + ijk0(j) = ijk0(j) - 1 + end if + end do + distance = d(j) + xyz0 = xyz0 + distance * uvw + + end if + end if + + ! ======================================================================== + ! If we've already scored some mesh bins, figure out which mesh cell is + ! next and where the particle enters that cell. + + if (current_bin /= NO_BIN_FOUND) then + ! Get the indices to the last bin. + call bin_to_mesh_indices(m, current_bin, ijk0(:m % n_dimension)) + + ! If the particle track ends in that bin, then we are done. + if (all(ijk0(:m % n_dimension) == ijk1(:m % n_dimension))) then + next_bin = NO_BIN_FOUND + return + end if + + ! Figure out which face of the previous mesh cell our track exits, i.e. + ! the closest surface of that cell for which + ! dot(p % uvw, face_normal) > 0. + do j = 1, m % n_dimension + if (abs(uvw(j)) < FP_PRECISION) then + d(j) = INFINITY + else if (uvw(j) > 0) then + xyz_cross = m % lower_left(j) + ijk0(j) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + else + xyz_cross = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + end if + end do + j = minloc(d(:m % n_dimension), 1) + + ! Translate the starting coordintes by the distance to that face. This + ! should be the xyz that we computed the distance to in the last + ! iteration of the filter loop. + distance = d(j) + xyz0 = xyz0 + distance * uvw + + ! Increment the indices into the next mesh cell. + if (uvw(j) > ZERO) then + ijk0(j) = ijk0(j) + 1 + else + ijk0(j) = ijk0(j) - 1 + end if + + ! If the next indices are invalid, then the track has left the mesh and + ! we are done. + if (any(ijk0(:m % n_dimension) < 1) & + .or. any(ijk0(:m % n_dimension) > m % dimension)) then + next_bin = NO_BIN_FOUND + return + end if + end if + + ! Compute the length of the track segment in this mesh cell. + if (all(ijk0(:m % n_dimension) == ijk1(:m % n_dimension))) then + ! The track ends in this cell. Use the particle end location rather + ! than the mesh surface. + distance = sqrt(sum((xyz1 - xyz0)**2)) + else + ! The track exits this cell. Use the distance to the mesh surface. + do j = 1, m % n_dimension + if (abs(uvw(j)) < FP_PRECISION) then + d(j) = INFINITY + else if (uvw(j) > 0) then + xyz_cross = m % lower_left(j) + ijk0(j) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + else + xyz_cross = m % lower_left(j) + (ijk0(j) - 1) * m % width(j) + d(j) = (xyz_cross - xyz0(j)) / uvw(j) + end if + end do + distance = minval(d(:m % n_dimension)) + end if + + ! Assign the next tally bin and the score + next_bin = mesh_indices_to_bin(m, ijk0(:m % n_dimension)) + weight = distance / total_distance + endif + end subroutine get_next_bin_mesh + + subroutine to_statepoint_mesh(this, filter_group) + class(MeshFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "mesh") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % mesh ) + end subroutine to_statepoint_mesh + + function text_label_mesh(this, bin) result(label) + class(MeshFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + integer, allocatable :: ijk(:) + type(RegularMesh), pointer :: m + + m => meshes(this % mesh) + allocate(ijk(m % n_dimension)) + call bin_to_mesh_indices(m, bin, ijk) + if (m % n_dimension == 2) then + label = "Mesh Index (" // trim(to_str(ijk(1))) // ", " // & + trim(to_str(ijk(2))) // ")" + elseif (m % n_dimension == 3) then + label = "Mesh Index (" // trim(to_str(ijk(1))) // ", " // & + trim(to_str(ijk(2))) // ", " // trim(to_str(ijk(3))) // ")" + end if + end function text_label_mesh + +!=============================================================================== +! UniverseFilter methods +!=============================================================================== + subroutine get_next_bin_universe(this, p, estimator, current_bin, next_bin, & + weight) + class(UniverseFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer :: i, start + + ! Find the coordinate level of the last bin we found. + if (current_bin == NO_BIN_FOUND) then + start = 1 + else + do i = 1, p % n_coord + if (p % coord(i) % universe == this % universes(current_bin)) then + start = i + 1 + exit + end if + end do + end if + + ! Starting one coordinate level deeper, find the next bin. + next_bin = NO_BIN_FOUND + do i = start, p % n_coord + if (this % map % has_key(p % coord(i) % universe)) then + next_bin = this % map % get_key(p % coord(i) % universe) + exit + end if + end do + weight = ONE + end subroutine get_next_bin_universe + + subroutine to_statepoint_universe(this, filter_group) + class(UniverseFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "universe") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % universes ) + end subroutine to_statepoint_universe + + subroutine initialize_universe(this) + class(UniverseFilter), intent(inout) :: this + + integer :: i, id + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % universes(i) + if (universe_dict % has_key(id)) then + this % universes(i) = universe_dict % get_key(id) + else + call fatal_error("Could not find universe " // trim(to_str(id)) & + &// " specified on a tally filter.") + end if + end do + + ! Generate mapping from universe indices to filter bins. + do i = 1, this % n_bins + call this % map % add_key(this % universes(i), i) + end do + end subroutine initialize_universe + + function text_label_universe(this, bin) result(label) + class(UniverseFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Universe " // to_str(universes(this % universes(bin)) % id) + end function text_label_universe + +!=============================================================================== +! MaterialFilter methods +!=============================================================================== + subroutine get_next_bin_material(this, p, estimator, current_bin, next_bin, & + weight) + class(MaterialFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + next_bin = NO_BIN_FOUND + if (current_bin == NO_BIN_FOUND) then + if (this % map % has_key(p % material)) then + next_bin = this % map % get_key(p % material) + end if + end if + weight = ONE + end subroutine get_next_bin_material + + subroutine to_statepoint_material(this, filter_group) + class(MaterialFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "material") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % materials ) + end subroutine to_statepoint_material + + subroutine initialize_material(this) + class(MaterialFilter), intent(inout) :: this + + integer :: i, id + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % materials(i) + if (material_dict % has_key(id)) then + this % materials(i) = material_dict % get_key(id) + else + call fatal_error("Could not find material " // trim(to_str(id)) & + &// " specified on a tally filter.") + end if + end do + + ! Generate mapping from material indices to filter bins. + do i = 1, this % n_bins + call this % map % add_key(this % materials(i), i) + end do + end subroutine initialize_material + + function text_label_material(this, bin) result(label) + class(MaterialFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Material " // to_str(materials(this % materials(bin)) % id) + end function text_label_material + +!=============================================================================== +! CellFilter methods +!=============================================================================== + subroutine get_next_bin_cell(this, p, estimator, current_bin, next_bin, & + weight) + class(CellFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer :: i, start + + ! Find the coordinate level of the last bin we found. + if (current_bin == NO_BIN_FOUND) then + start = 1 + else + do i = 1, p % n_coord + if (p % coord(i) % cell == this % cells(current_bin)) then + start = i + 1 + exit + end if + end do + end if + + ! Starting one coordinate level deeper, find the next bin. + next_bin = NO_BIN_FOUND + do i = start, p % n_coord + if (this % map % has_key(p % coord(i) % cell)) then + next_bin = this % map % get_key(p % coord(i) % cell) + exit + end if + end do + weight = ONE + end subroutine get_next_bin_cell + + subroutine to_statepoint_cell(this, filter_group) + class(CellFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "cell") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % cells ) + end subroutine to_statepoint_cell + + subroutine initialize_cell(this) + class(CellFilter), intent(inout) :: this + + integer :: i, id + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % cells(i) + if (cell_dict % has_key(id)) then + this % cells(i) = cell_dict % get_key(id) + else + call fatal_error("Could not find cell " // trim(to_str(id)) & + &// " specified on tally filter.") + end if + end do + + ! Generate mapping from cell indices to filter bins. + do i = 1, this % n_bins + call this % map % add_key(this % cells(i), i) + end do + end subroutine initialize_cell + + function text_label_cell(this, bin) result(label) + class(CellFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Cell " // to_str(cells(this % cells(bin)) % id) + end function text_label_cell + +!=============================================================================== +! DistribcellFilter methods +!=============================================================================== + subroutine get_next_bin_distribcell(this, p, estimator, current_bin, & + next_bin, weight) + class(DistribcellFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + logical :: cell_found + integer :: distribcell_index, offset, i + + cell_found = .false. + if (current_bin == NO_BIN_FOUND) then + distribcell_index = cells(this % cell) % distribcell_index + offset = 0 + do i = 1, p % n_coord + if (cells(p % coord(i) % cell) % type == CELL_FILL) then + offset = offset + cells(p % coord(i) % cell) % & + offset(distribcell_index) + elseif (cells(p % coord(i) % cell) % type == CELL_LATTICE) then + if (lattices(p % coord(i + 1) % lattice) % obj & + % are_valid_indices([& + p % coord(i + 1) % lattice_x, & + p % coord(i + 1) % lattice_y, & + p % coord(i + 1) % lattice_z])) then + offset = offset + lattices(p % coord(i + 1) % lattice) % obj % & + offset(distribcell_index, & + p % coord(i + 1) % lattice_x, & + p % coord(i + 1) % lattice_y, & + p % coord(i + 1) % lattice_z) + end if + end if + if (this % cell == p % coord(i) % cell) then + next_bin = offset + 1 + cell_found = .true. + exit + end if + end do + else + next_bin = NO_BIN_FOUND + end if + if (.not. cell_found) next_bin = NO_BIN_FOUND + weight = ONE + end subroutine get_next_bin_distribcell + + subroutine to_statepoint_distribcell(this, filter_group) + class(DistribcellFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "distribcell") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % cell ) + end subroutine to_statepoint_distribcell + + subroutine to_summary_distribcell(this, filter_group) + class(DistribcellFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + integer :: offset, k + character(MAX_LINE_LEN), allocatable :: paths(:) + character(MAX_LINE_LEN) :: path + + call write_dataset(filter_group, "type", "distribcell") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % cell ) + + ! Write paths to reach each distribcell instance + + ! Allocate array of strings for each distribcell path + allocate(paths(this % n_bins)) + + ! Store path for each distribcell instance + do k = 1, this % n_bins + path = '' + offset = 1 + call find_offset(this % cell, universes(BASE_UNIVERSE), k, offset, path) + paths(k) = path + end do + + ! Write array of distribcell paths to summary file + call write_dataset(filter_group, "paths", paths) + deallocate(paths) + end subroutine to_summary_distribcell + + subroutine initialize_distribcell(this) + class(DistribcellFilter), intent(inout) :: this + + integer :: id + + ! Convert id to index. + id = this % cell + if (cell_dict % has_key(id)) then + this % cell = cell_dict % get_key(id) + else + call fatal_error("Could not find cell " // trim(to_str(id)) & + &// " specified on tally filter.") + end if + end subroutine initialize_distribcell + + function text_label_distribcell(this, bin) result(label) + class(DistribcellFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + integer :: offset + type(Universe), pointer :: univ + + univ => universes(BASE_UNIVERSE) + offset = 0 + label = '' + call find_offset(this % cell, univ, bin-1, offset, label) + label = "Distributed Cell " // label + end function text_label_distribcell + +!=============================================================================== +! CellbornFilter methods +!=============================================================================== + subroutine get_next_bin_cellborn(this, p, estimator, current_bin, next_bin, & + weight) + class(CellbornFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + next_bin = NO_BIN_FOUND + if (current_bin == NO_BIN_FOUND) then + if (this % map % has_key(p % cell_born)) then + next_bin = this % map % get_key(p % cell_born) + end if + end if + weight = ONE + end subroutine get_next_bin_cellborn + + subroutine to_statepoint_cellborn(this, filter_group) + class(CellbornFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "cellborn") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % cells ) + end subroutine to_statepoint_cellborn + + subroutine initialize_cellborn(this) + class(CellbornFilter), intent(inout) :: this + + integer :: i, id + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % cells(i) + if (cell_dict % has_key(id)) then + this % cells(i) = cell_dict % get_key(id) + else + call fatal_error("Could not find cell " // trim(to_str(id)) & + &// " specified on tally filter.") + end if + end do + + ! Generate mapping from cell indices to filter bins. + do i = 1, this % n_bins + call this % map % add_key(this % cells(i), i) + end do + end subroutine initialize_cellborn + + function text_label_cellborn(this, bin) result(label) + class(CellbornFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Birth Cell " // to_str(cells(this % cells(bin)) % id) + end function text_label_cellborn + +!=============================================================================== +! SurfaceFilter methods +!=============================================================================== + subroutine get_next_bin_surface(this, p, estimator, current_bin, next_bin, & + weight) + class(SurfaceFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer :: i + + next_bin = NO_BIN_FOUND + if (current_bin == NO_BIN_FOUND) then + do i = 1, this % n_bins + if (p % surface == this % surfaces(i)) then + next_bin = i + exit + end if + end do + end if + weight = ONE + end subroutine get_next_bin_surface + + subroutine to_statepoint_surface(this, filter_group) + class(SurfaceFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "surface") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % surfaces ) + end subroutine to_statepoint_surface + + subroutine initialize_surface(this) + class(SurfaceFilter), intent(inout) :: this + + integer :: i, id + + ! Convert ids to indices. + do i = 1, this % n_bins + id = this % surfaces(i) + if (surface_dict % has_key(id)) then + this % surfaces(i) = surface_dict % get_key(id) + else + call fatal_error("Could not find surface " // trim(to_str(id)) & + &// " specified on tally filter.") + end if + end do + end subroutine initialize_surface + + function text_label_surface(this, bin) result(label) + class(SurfaceFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Surface " // to_str(surfaces(this % surfaces(bin)) % obj % id) + end function text_label_surface + +!=============================================================================== +! EnergyFilter methods +!=============================================================================== + subroutine get_next_bin_energy(this, p, estimator, current_bin, next_bin, & + weight) + class(EnergyFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer :: n + real(8) :: E + + if (current_bin == NO_BIN_FOUND) then + n = this % n_bins + + if ((.not. run_CE) .and. this % matches_transport_groups) then + if (estimator == ESTIMATOR_TRACKLENGTH) then + next_bin = p % g + else + next_bin = p % last_g + end if + + ! Tallies are ordered in increasing groups, group indices + ! however are the opposite, so switch + next_bin = energy_groups - next_bin + 1 + + else + ! Make sure the correct energy is used. + if (estimator == ESTIMATOR_TRACKLENGTH) then + E = p % E + else + E = p % last_E + end if + + ! Check if energy of the particle is within energy bins. + if (E < this % bins(1) .or. E > this % bins(n + 1)) then + next_bin = NO_BIN_FOUND + else + ! Search to find incoming energy bin. + next_bin = binary_search(this % bins, n + 1, E) + end if + end if + + else + next_bin = NO_BIN_FOUND + end if + weight = ONE + end subroutine get_next_bin_energy + + subroutine to_statepoint_energy(this, filter_group) + class(EnergyFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "energy") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins ) + end subroutine to_statepoint_energy + + function text_label_energy(this, bin) result(label) + class(EnergyFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Incoming Energy [" // trim(to_str(E0)) // ", " & + // trim(to_str(E1)) // ")" + end function text_label_energy + +!=============================================================================== +! EnergyoutFilter methods +!=============================================================================== + subroutine get_next_bin_energyout(this, p, estimator, current_bin, next_bin, & + weight) + class(EnergyoutFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer :: n + + if (current_bin == NO_BIN_FOUND) then + n = this % n_bins + + if ((.not. run_CE) .and. this % matches_transport_groups) then + next_bin = p % g + + ! Tallies are ordered in increasing groups, group indices + ! however are the opposite, so switch + next_bin = energy_groups - next_bin + 1 + + else + ! Check if energy of the particle is within energy bins. + if (p % E < this % bins(1) .or. p % E > this % bins(n + 1)) then + next_bin = NO_BIN_FOUND + else + ! Search to find incoming energy bin. + next_bin = binary_search(this % bins, n + 1, p % E) + end if + end if + + else + next_bin = NO_BIN_FOUND + end if + weight = ONE + end subroutine get_next_bin_energyout + + subroutine to_statepoint_energyout(this, filter_group) + class(EnergyoutFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "energyout") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins ) + end subroutine to_statepoint_energyout + + function text_label_energyout(this, bin) result(label) + class(EnergyoutFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Outgoing Energy [" // trim(to_str(E0)) // ", " & + // trim(to_str(E1)) // ")" + end function text_label_energyout + +!=============================================================================== +! DelayedGroupFilter methods +!=============================================================================== + subroutine get_next_bin_dg(this, p, estimator, current_bin, next_bin, weight) + class(DelayedGroupFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + if (current_bin == NO_BIN_FOUND) then + next_bin = 1 + else + next_bin = NO_BIN_FOUND + end if + weight = ONE + end subroutine get_next_bin_dg + + subroutine to_statepoint_dg(this, filter_group) + class(DelayedGroupFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "delayedgroup") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % groups ) + end subroutine to_statepoint_dg + + function text_label_dg(this, bin) result(label) + class(DelayedGroupFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Delayed Group " // to_str(this % groups(bin)) + end function text_label_dg + +!=============================================================================== +! MuFilter methods +!=============================================================================== + subroutine get_next_bin_mu(this, p, estimator, current_bin, next_bin, weight) + class(MuFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer :: n + + if (current_bin == NO_BIN_FOUND) then + n = this % n_bins + + ! Check if energy of the particle is within energy bins. + if (p % mu < this % bins(1) .or. p % mu > this % bins(n + 1)) then + next_bin = NO_BIN_FOUND + else + ! Search to find incoming energy bin. + next_bin = binary_search(this % bins, n + 1, p % mu) + end if + + else + next_bin = NO_BIN_FOUND + end if + weight = ONE + end subroutine get_next_bin_mu + + subroutine to_statepoint_mu(this, filter_group) + class(MuFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "mu") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins ) + end subroutine to_statepoint_mu + + function text_label_mu(this, bin) result(label) + class(MuFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Change-in-Angle [" // trim(to_str(E0)) // ", " & + // trim(to_str(E1)) // ")" + end function text_label_mu + +!=============================================================================== +! PolarFilter methods +!=============================================================================== + subroutine get_next_bin_polar(this, p, estimator, current_bin, next_bin, & + weight) + class(PolarFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer :: n + real(8) :: theta + + if (current_bin == NO_BIN_FOUND) then + n = this % n_bins + + ! Make sure the correct direction vector is used. + if (estimator == ESTIMATOR_TRACKLENGTH) then + theta = acos(p % coord(1) % uvw(3)) + else + theta = acos(p % last_uvw(3)) + end if + + ! Check if particle is within polar angle bins. + if (theta < this % bins(1) .or. theta > this % bins(n + 1)) then + next_bin = NO_BIN_FOUND + else + ! Search to find polar angle bin. + next_bin = binary_search(this % bins, n + 1, theta) + end if + + else + next_bin = NO_BIN_FOUND + end if + weight = ONE + end subroutine get_next_bin_polar + + subroutine to_statepoint_polar(this, filter_group) + class(PolarFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "polar") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins ) + end subroutine to_statepoint_polar + + function text_label_polar(this, bin) result(label) + class(PolarFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Polar Angle [" // trim(to_str(E0)) // ", " // trim(to_str(E1)) & + // ")" + end function text_label_polar + +!=============================================================================== +! AzimuthalFilter methods +!=============================================================================== + subroutine get_next_bin_azimuthal(this, p, estimator, current_bin, next_bin, & + weight) + class(AzimuthalFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + + integer :: n + real(8) :: phi + + if (current_bin == NO_BIN_FOUND) then + n = this % n_bins + + ! Make sure the correct direction vector is used. + if (estimator == ESTIMATOR_TRACKLENGTH) then + phi = atan2(p % coord(1) % uvw(2), p % coord(1) % uvw(1)) + else + phi = atan2(p % last_uvw(2), p % last_uvw(1)) + end if + + ! Check if particle is within azimuthal angle bins. + if (phi < this % bins(1) .or. phi > this % bins(n + 1)) then + next_bin = NO_BIN_FOUND + else + ! Search to find azimuthal angle bin. + next_bin = binary_search(this % bins, n + 1, phi) + end if + + else + next_bin = NO_BIN_FOUND + end if + weight = ONE + end subroutine get_next_bin_azimuthal + + subroutine to_statepoint_azimuthal(this, filter_group) + class(AzimuthalFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "azimuthal") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", this % bins ) + end subroutine to_statepoint_azimuthal + + function text_label_azimuthal(this, bin) result(label) + class(AzimuthalFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + real(8) :: E0, E1 + + E0 = this % bins(bin) + E1 = this % bins(bin + 1) + label = "Azimuthal Angle [" // trim(to_str(E0)) // ", " & + // trim(to_str(E1)) // ")" + end function text_label_azimuthal + +!=============================================================================== +! FIND_OFFSET (for distribcell) uses a given map number, a target cell ID, and +! a target offset to build a string which is the path from the base universe to +! the target cell with the given offset +!=============================================================================== + + recursive subroutine find_offset(goal, univ, final, offset, path) + + integer, intent(in) :: goal ! The target cell index + type(Universe), intent(in) :: univ ! Universe to begin search + integer, intent(in) :: final ! Target offset + integer, intent(inout) :: offset ! Current offset + character(*), intent(inout) :: path ! Path to offset + + integer :: map ! Index in maps vector + integer :: i, j ! Index over cells + integer :: k, l, m ! Indices in lattice + integer :: old_k, old_l, old_m ! Previous indices in lattice + integer :: n_x, n_y, n_z ! Lattice cell array dimensions + integer :: n ! Number of cells to search + integer :: cell_index ! Index in cells array + integer :: lat_offset ! Offset from lattice + integer :: temp_offset ! Looped sum of offsets + logical :: this_cell = .false. ! Advance in this cell? + logical :: later_cell = .false. ! Fill cells after this one? + type(Cell), pointer :: c ! Pointer to current cell + type(Universe), pointer :: next_univ ! Next universe to loop through + class(Lattice), pointer :: lat ! Pointer to current lattice + + ! Get the distribcell index for this cell + map = cells(goal) % distribcell_index + + n = univ % n_cells + + ! Write to the geometry stack + if (univ%id == 0) then + path = trim(path) // to_str(univ%id) + else + path = trim(path) // "->" // to_str(univ%id) + end if + + ! Look through all cells in this universe + do i = 1, n + ! If the cell matches the goal and the offset matches final, write to the + ! geometry stack + if (univ % cells(i) == goal .and. offset == final) then + c => cells(univ % cells(i)) + path = trim(path) // "->" // to_str(c % id) + return + end if + end do + + ! Find the fill cell or lattice cell that we need to enter + do i = 1, n + + later_cell = .false. + + cell_index = univ % cells(i) + c => cells(cell_index) + + this_cell = .false. + + ! If we got here, we still think the target is in this universe + ! or further down, but it's not this exact cell. + ! Compare offset to next cell to see if we should enter this cell + if (i /= n) then + + do j = i+1, n + + cell_index = univ % cells(j) + c => cells(cell_index) + + ! Skip normal cells which do not have offsets + if (c % type == CELL_NORMAL) then + cycle + end if + + ! Break loop once we've found the next cell with an offset + exit + end do + + ! Ensure we didn't just end the loop by iteration + if (c % type /= CELL_NORMAL) then + + ! There are more cells in this universe that it could be in + later_cell = .true. + + ! Two cases, lattice or fill cell + if (c % type == CELL_FILL) then + temp_offset = c % offset(map) + + ! Get the offset of the first lattice location + else + lat => lattices(c % fill) % obj + temp_offset = lat % offset(map, 1, 1, 1) + end if + + ! If the final offset is in the range of offset - temp_offset+offset + ! then the goal is in this cell + if (final < temp_offset + offset) then + this_cell = .true. + end if + end if + end if + + if (n == 1 .and. c % type /= CELL_NORMAL) then + this_cell = .true. + end if + + if (.not. later_cell) then + this_cell = .true. + end if + + ! Get pointer to THIS cell because target must be in this cell + if (this_cell) then + + cell_index = univ % cells(i) + c => cells(cell_index) + + path = trim(path) // "->" // to_str(c%id) + + ! ==================================================================== + ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL + if (c % type == CELL_FILL) then + + ! Enter this cell to update the current offset + offset = c % offset(map) + offset + + next_univ => universes(c % fill) + call find_offset(goal, next_univ, final, offset, path) + return + + ! ==================================================================== + ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL + elseif (c % type == CELL_LATTICE) then + + ! Set current lattice + lat => lattices(c % fill) % obj + + select type (lat) + + ! ================================================================== + ! RECTANGULAR LATTICES + type is (RectLattice) + + ! Write to the geometry stack + path = trim(path) // "->" // to_str(lat%id) + + n_x = lat % n_cells(1) + n_y = lat % n_cells(2) + n_z = lat % n_cells(3) + old_m = 1 + old_l = 1 + old_k = 1 + + ! Loop over lattice coordinates + do k = 1, n_x + do l = 1, n_y + do m = 1, n_z + + if (final >= lat % offset(map, k, l, m) + offset) then + if (k == n_x .and. l == n_y .and. m == n_z) then + ! This is last lattice cell, so target must be here + lat_offset = lat % offset(map, k, l, m) + offset = offset + lat_offset + next_univ => universes(lat % universes(k, l, m)) + path = trim(path) // "(" // trim(to_str(k)) // & + "," // trim(to_str(l)) // "," // & + trim(to_str(m)) // ")" + call find_offset(goal, next_univ, final, offset, path) + return + else + old_m = m + old_l = l + old_k = k + cycle + end if + else + ! Target is at this lattice position + lat_offset = lat % offset(map, old_k, old_l, old_m) + offset = offset + lat_offset + next_univ => universes(lat % universes(old_k, old_l, old_m)) + path = trim(path) // "(" // trim(to_str(old_k)) // & + "," // trim(to_str(old_l)) // "," // & + trim(to_str(old_m)) // ")" + call find_offset(goal, next_univ, final, offset, path) + return + end if + + end do + end do + end do + + ! ================================================================== + ! HEXAGONAL LATTICES + type is (HexLattice) + + ! Write to the geometry stack + path = trim(path) // "->" // to_str(lat%id) + + n_z = lat % n_axial + n_y = 2 * lat % n_rings - 1 + n_x = 2 * lat % n_rings - 1 + old_m = 1 + old_l = 1 + old_k = 1 + + ! Loop over lattice coordinates + do m = 1, n_z + do l = 1, n_y + do k = 1, n_x + + ! This array position is never used + if (k + l < lat % n_rings + 1) then + cycle + ! This array position is never used + else if (k + l > 3*lat % n_rings - 1) then + cycle + end if + + if (final >= lat % offset(map, k, l, m) + offset) then + if (k == lat % n_rings .and. l == n_y .and. m == n_z) then + ! This is last lattice cell, so target must be here + lat_offset = lat % offset(map, k, l, m) + offset = offset + lat_offset + next_univ => universes(lat % universes(k, l, m)) + path = trim(path) // "(" // & + trim(to_str(k - lat % n_rings)) // "," // & + trim(to_str(l - lat % n_rings)) // "," // & + trim(to_str(m)) // ")" + call find_offset(goal, next_univ, final, offset, path) + return + else + old_m = m + old_l = l + old_k = k + cycle + end if + else + ! Target is at this lattice position + lat_offset = lat % offset(map, old_k, old_l, old_m) + offset = offset + lat_offset + next_univ => universes(lat % universes(old_k, old_l, old_m)) + path = trim(path) // "(" // & + trim(to_str(old_k - lat % n_rings)) // "," // & + trim(to_str(old_l - lat % n_rings)) // "," // & + trim(to_str(old_m)) // ")" + call find_offset(goal, next_univ, final, offset, path) + return + end if + + end do + end do + end do + + end select + + end if + end if + end do + end subroutine find_offset + +end module tally_filter diff --git a/src/tally_filter_header.F90 b/src/tally_filter_header.F90 new file mode 100644 index 000000000..43ef846e3 --- /dev/null +++ b/src/tally_filter_header.F90 @@ -0,0 +1,105 @@ +module tally_filter_header + + use constants, only: MAX_LINE_LEN + use particle_header, only: Particle + + use hdf5 + + implicit none + +!=============================================================================== +! TALLYFILTER describes a filter that limits what events score to a tally. For +! example, a cell filter indicates that only particles in a specified cell +! should score to the tally. +!=============================================================================== + + type, abstract :: TallyFilter + integer :: n_bins = 0 + contains + procedure(get_next_bin_), deferred :: get_next_bin + procedure(to_statepoint_), deferred :: to_statepoint + procedure :: to_summary => filter_to_summary + procedure(text_label_), deferred :: text_label + procedure :: initialize => filter_initialize + end type TallyFilter + + abstract interface + +!=============================================================================== +! GET_NEXT_BIN gives the index for the next valid filter bin and a weight that +! will be applied to the flux. +! +! In principle, a filter can have multiple valid bins. If current_bin = +! NO_BIN_FOUND, then this method should give the first valid bin. Providing the +! first valid bin should then give the second valid bin, and so on. When there +! are no valid bins left, the next_bin should be NO_VALID_BIN. + + subroutine get_next_bin_(this, p, estimator, current_bin, next_bin, weight) + import TallyFilter + import Particle + class(TallyFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + integer, value, intent(in) :: current_bin + integer, intent(out) :: next_bin + real(8), intent(out) :: weight + end subroutine get_next_bin_ + +!=============================================================================== +! TO_STATEPOINT writes all the information needed to reconstruct the filter to +! the given filter_group. + + subroutine to_statepoint_(this, filter_group) + import TallyFilter + import HID_T + class(TallyFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + end subroutine to_statepoint_ + +!=============================================================================== +! TEXT_LABEL returns a string describing the given filter bin. For example, an +! energy filter might return the string "Incoming Energy [0.625E-6, 20.0)". +! This is used to write the tallies.out file. + + function text_label_(this, bin) result(label) + import TallyFilter + import MAX_LINE_LEN + class(TallyFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + end function text_label_ + + end interface + +!=============================================================================== +! TALLYFILTERCONTAINER contains an allocatable TallyFilter object for arrays of +! TallyFilters +!=============================================================================== + + type TallyFilterContainer + class(TallyFilter), allocatable :: obj + end type TallyFilterContainer + +contains + +!=============================================================================== +! TO_SUMMARY writes all the information needed to reconstruct the filter to the +! given filter_group. If this procedure is not overridden by the derived class, +! then it will call to_statepoint by default. + + subroutine filter_to_summary(this, filter_group) + class(TallyFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call this % to_statepoint(filter_group) + end subroutine filter_to_summary + +!=============================================================================== +! INITIALIZE sets up any internal data, as necessary. If this procedure is not +! overriden by the derived class, then it will do nothing by default. + + subroutine filter_initialize(this) + class(TallyFilter), intent(inout) :: this + end subroutine filter_initialize + +end module tally_filter_header diff --git a/src/tally_header.F90 b/src/tally_header.F90 index 8db9ab0e1..fe385458b 100644 --- a/src/tally_header.F90 +++ b/src/tally_header.F90 @@ -1,41 +1,13 @@ module tally_header - use constants, only: NONE, N_FILTER_TYPES - use trigger_header, only: TriggerObject + use constants, only: NONE, N_FILTER_TYPES + use tally_filter_header, only: TallyFilterContainer + use trigger_header, only: TriggerObject + use, intrinsic :: ISO_C_BINDING implicit none -!=============================================================================== -! TALLYMAPELEMENT gives an index to a tally which is to be scored and the -! corresponding bin for the filter variable -!=============================================================================== - - type TallyMapElement - integer :: index_tally - integer :: index_bin - end type TallyMapElement - -!=============================================================================== -! TALLYMAPITEM contains a list of tally/bin combinations for each mappable -! filter bin specified. -!=============================================================================== - - type TallyMapItem - type(TallyMapElement), allocatable :: elements(:) - end type TallyMapItem - -!=============================================================================== -! TALLYMAP contains a list of pairs of indices to tallies and the corresponding -! bin for a given filter. There is one TallyMap for each mappable filter -! type. The items array is as long as the corresponding array for that filter, -! e.g. for tally_maps(FILTER_CELL), items is n_cells long. -!=============================================================================== - - type TallyMap - type(TallyMapItem), allocatable :: items(:) - end type TallyMap - !=============================================================================== ! TALLYRESULT provides accumulation of results in a particular tally bin !=============================================================================== @@ -46,19 +18,6 @@ module tally_header real(C_DOUBLE) :: sum_sq = 0. end type TallyResult -!=============================================================================== -! TALLYFILTER describes a filter that limits what events score to a tally. For -! example, a cell filter indicates that only particles in a specified cell -! should score to the tally. -!=============================================================================== - - type TallyFilter - integer :: type = NONE - integer :: n_bins = 0 - integer, allocatable :: int_bins(:) - real(8), allocatable :: real_bins(:) ! Only used for energy filters - end type TallyFilter - !=============================================================================== ! TALLYOBJECT describes a user-specified tally. The region of phase space to ! tally in is given by the TallyFilters and the results are stored in a @@ -73,11 +32,7 @@ module tally_header integer :: type ! volume, surface current integer :: estimator ! collision, track-length real(8) :: volume ! volume of region - - ! Information about what filters should be used - - integer :: n_filters ! Number of filters - type(TallyFilter), allocatable :: filters(:) ! Filter data (type/bins) + type(TallyFilterContainer), allocatable :: filters(:) ! The stride attribute is used for determining the index in the results ! array for a matching_bin combination. Since multiple dimensions are @@ -124,10 +79,6 @@ module tally_header ! Tally precision triggers integer :: n_triggers = 0 ! # of triggers type(TriggerObject), allocatable :: triggers(:) ! Array of triggers - - ! Multi-Group Specific Information To Enable Rapid Tallying - logical :: energy_matches_groups = .false. - logical :: energyout_matches_groups = .false. end type TallyObject end module tally_header diff --git a/src/tally_initialize.F90 b/src/tally_initialize.F90 index 73aa18c60..1ae403bd6 100644 --- a/src/tally_initialize.F90 +++ b/src/tally_initialize.F90 @@ -2,7 +2,7 @@ module tally_initialize use constants use global - use tally_header, only: TallyObject, TallyMapElement, TallyMapItem + use tally_header, only: TallyObject implicit none private @@ -23,7 +23,6 @@ contains allocate(global_tallies(N_GLOBAL_TALLIES)) call setup_tally_arrays() - call setup_tally_maps() end subroutine configure_tallies @@ -45,17 +44,17 @@ contains t => tallies(i) ! Allocate stride and matching_bins arrays - allocate(t % stride(t % n_filters)) - max_n_filters = max(max_n_filters, t % n_filters) + allocate(t % stride(size(t % filters))) + max_n_filters = max(max_n_filters, size(t % filters)) ! The filters are traversed in opposite order so that the last filter has ! the shortest stride in memory and the first filter has the largest ! stride n = 1 - STRIDE: do j = t % n_filters, 1, -1 + STRIDE: do j = size(t % filters), 1, -1 t % stride(j) = n - n = n * t % filters(j) % n_bins + n = n * t % filters(j) % obj % n_bins end do STRIDE ! Set total number of filter and scoring bins @@ -70,101 +69,11 @@ contains ! Allocate array for matching filter bins !$omp parallel allocate(matching_bins(max_n_filters)) + allocate(filter_weights(max_n_filters)) !$omp end parallel end subroutine setup_tally_arrays -!=============================================================================== -! SETUP_TALLY_MAPS creates a map that allows a quick determination of which -! tallies and bins need to be scored to when a particle makes a collision. This -! subroutine also sets the stride attribute for each tally as well as allocating -! storage for the results array. -!=============================================================================== - - subroutine setup_tally_maps() - - integer :: i ! loop index for tallies - integer :: j ! loop index for filters - integer :: k ! loop index for bins - integer :: bin ! filter bin entries - integer :: type ! type of tally filter - type(TallyObject), pointer :: t - - ! allocate tally map array -- note that we don't need a tally map for the - ! energy_in and energy_out filters - allocate(tally_maps(N_FILTER_TYPES - 3)) - - ! allocate list of items for each different filter type - allocate(tally_maps(FILTER_UNIVERSE) % items(n_universes)) - allocate(tally_maps(FILTER_MATERIAL) % items(n_materials)) - allocate(tally_maps(FILTER_CELL) % items(n_cells)) - allocate(tally_maps(FILTER_CELLBORN) % items(n_cells)) - allocate(tally_maps(FILTER_SURFACE) % items(n_surfaces)) - - TALLY_LOOP: do i = 1, n_tallies - ! Get pointer to tally - t => tallies(i) - - ! No need to set up tally maps for surface current tallies - if (t % type == TALLY_SURFACE_CURRENT) cycle - - FILTER_LOOP: do j = 1, t % n_filters - ! Determine type of filter - type = t % filters(j) % type - - if (type == FILTER_CELL .or. type == FILTER_SURFACE .or. & - type == FILTER_MATERIAL .or. type == FILTER_UNIVERSE .or. & - type == FILTER_CELLBORN) then - - ! Add map elements - BIN_LOOP: do k = 1, t % filters(j) % n_bins - bin = t % filters(j) % int_bins(k) - call add_map_element(tally_maps(type) % items(bin), i, k) - end do BIN_LOOP - end if - - end do FILTER_LOOP - - end do TALLY_LOOP - - end subroutine setup_tally_maps - -!=============================================================================== -! ADD_MAP_ELEMENT adds a pair of tally and bin indices to the list for a given -! cell/surface/etc. -!=============================================================================== - - subroutine add_map_element(item, index_tally, index_bin) - - type(TallyMapItem), intent(inout) :: item - integer, intent(in) :: index_tally ! index in tallies array - integer, intent(in) :: index_bin ! index in bins array - - integer :: n ! size of elements array - type(TallyMapElement), allocatable :: temp(:) - - if (.not. allocated(item % elements)) then - allocate(item % elements(1)) - item % elements(1) % index_tally = index_tally - item % elements(1) % index_bin = index_bin - else - ! determine size of elements array - n = size(item % elements) - - ! allocate temporary storage and copy elements - allocate(temp(n+1)) - temp(1:n) = item % elements - - ! move allocation back to main array - call move_alloc(FROM=temp, TO=item%elements) - - ! set new element - item % elements(n+1) % index_tally = index_tally - item % elements(n+1) % index_bin = index_bin - end if - - end subroutine add_map_element - !=============================================================================== ! ADD_TALLIES extends the tallies array with a new group of tallies and assigns ! pointers to each group. This is called once for user tallies, once for CMFD diff --git a/src/tracking.F90 b/src/tracking.F90 index d52b09a75..69fb78c35 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -191,7 +191,7 @@ contains p % fission = .false. ! Save coordinates for tallying purposes - p % last_xyz = p % coord(1) % xyz + p % last_xyz_current = p % coord(1) % xyz ! Set last material to none since cross sections will need to be ! re-evaluated @@ -211,6 +211,9 @@ contains end do end if + ! Save coordinates for tallying purposes + p % last_xyz = p % coord(1) % xyz + ! If particle has too many events, display warning and kill it n_event = n_event + 1 if (n_event == MAX_EVENTS) then diff --git a/src/trigger.F90 b/src/trigger.F90 index ed362154b..2fd0e2a12 100644 --- a/src/trigger.F90 +++ b/src/trigger.F90 @@ -12,6 +12,7 @@ module trigger use mesh_header, only: RegularMesh use trigger_header, only: TriggerObject use tally, only: TallyObject + use tally_filter, only: MeshFilter implicit none @@ -165,7 +166,7 @@ contains else ! Initialize bins, filter level - matching_bins(1:t % n_filters) = 0 + matching_bins(1:size(t % filters)) = 0 FILTER_LOOP: do filter_index = 1, t % total_filter_bins @@ -265,7 +266,7 @@ contains end if end if end do NUCLIDE_LOOP - if (t % n_filters == 0) exit FILTER_LOOP + if (size(t % filters) == 0) exit FILTER_LOOP end do FILTER_LOOP end if end do TRIGGER_LOOP @@ -300,16 +301,19 @@ contains ! Get pointer to mesh i_filter_mesh = t % find_filter(FILTER_MESH) i_filter_surf = t % find_filter(FILTER_SURFACE) - m => meshes(t % filters(i_filter_mesh) % int_bins(1)) + select type(filt => t % filters(i_filter_mesh) % obj) + type is (MeshFilter) + m => meshes(filt % mesh) + end select ! initialize bins array - matching_bins(1:t % n_filters) = 1 + matching_bins(1:size(t % filters)) = 1 ! determine how many energyin bins there are i_filter_ein = t % find_filter(FILTER_ENERGYIN) if (i_filter_ein > 0) then print_ebin = .true. - n = t % filters(i_filter_ein) % n_bins + n = t % filters(i_filter_ein) % obj % n_bins else print_ebin = .false. n = 1 @@ -329,7 +333,7 @@ contains mesh_indices_to_bin(m, (/ i-1, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_RIGHT filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -341,7 +345,7 @@ contains matching_bins(i_filter_surf) = OUT_RIGHT filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -356,7 +360,7 @@ contains mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_RIGHT filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -368,7 +372,7 @@ contains matching_bins(i_filter_surf) = OUT_RIGHT filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -383,7 +387,7 @@ contains mesh_indices_to_bin(m, (/ i, j-1, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_FRONT filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -396,7 +400,7 @@ contains matching_bins(i_filter_surf) = OUT_FRONT filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -411,7 +415,7 @@ contains mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_FRONT filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -423,7 +427,7 @@ contains matching_bins(i_filter_surf) = OUT_FRONT filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -438,7 +442,7 @@ contains mesh_indices_to_bin(m, (/ i, j, k-1 /) + 1, .true.) matching_bins(i_filter_surf) = IN_TOP filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -450,7 +454,7 @@ contains matching_bins(i_filter_surf) = OUT_TOP filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -465,7 +469,7 @@ contains mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_TOP filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev @@ -477,7 +481,7 @@ contains matching_bins(i_filter_surf) = OUT_TOP filter_index = & - sum((matching_bins(1:t % n_filters) - 1) * t % stride) + 1 + sum((matching_bins(1:size(t % filters)) - 1) * t % stride) + 1 call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) if (trigger % std_dev < std_dev) then trigger % std_dev = std_dev diff --git a/tests/test_mgxs_library_condense/inputs_true.dat b/tests/test_mgxs_library_condense/inputs_true.dat index d92cc888a..0c648376e 100644 --- a/tests/test_mgxs_library_condense/inputs_true.dat +++ b/tests/test_mgxs_library_condense/inputs_true.dat @@ -1 +1 @@ -855919f7a333acff6423527b82656d6a472ea8416002fb475c2d21c676e2f75f5c040d209a3e9a3a6118cf944f2c1bfb8cf2318273b890861d790b1927791a35 \ No newline at end of file +e2cdca7ea5b3532050af5b12fac26d7ef212d2696bb1b73cdd00929b2243c40d100ad02438c7b090555b49815d0de6c48cf1b4ebf437a48bc80c2d2b4bad292e \ No newline at end of file diff --git a/tests/test_mgxs_library_condense/results_true.dat b/tests/test_mgxs_library_condense/results_true.dat index 13c277b15..ae768cbe6 100644 --- a/tests/test_mgxs_library_condense/results_true.dat +++ b/tests/test_mgxs_library_condense/results_true.dat @@ -34,6 +34,12 @@ 0 10000 1 1 total 0.085835 0.005592 material group out nuclide mean std. dev. 0 10000 1 total 1.0 0.046071 + material group out nuclide mean std. dev. +0 10000 1 total 1.0 0.051471 + material group in nuclide mean std. dev. +0 10000 1 total 4.996730e-07 3.650635e-08 + material group in nuclide mean std. dev. +0 10000 1 total 0.090004 0.006367 material group in nuclide mean std. dev. 0 10001 1 total 0.311594 0.013793 material group in nuclide mean std. dev. @@ -70,6 +76,12 @@ 0 10001 1 1 total 0.0 0.0 material group out nuclide mean std. dev. 0 10001 1 total 0.0 0.0 + material group out nuclide mean std. dev. +0 10001 1 total 0.0 0.0 + material group in nuclide mean std. dev. +0 10001 1 total 5.454760e-07 4.949800e-08 + material group in nuclide mean std. dev. +0 10001 1 total 0.0 0.0 material group in nuclide mean std. dev. 0 10002 1 total 0.904999 0.043964 material group in nuclide mean std. dev. @@ -106,3 +118,9 @@ 0 10002 1 1 total 0.0 0.0 material group out nuclide mean std. dev. 0 10002 1 total 0.0 0.0 + material group out nuclide mean std. dev. +0 10002 1 total 0.0 0.0 + material group in nuclide mean std. dev. +0 10002 1 total 5.773006e-07 5.322132e-08 + material group in nuclide mean std. dev. +0 10002 1 total 0.0 0.0 diff --git a/tests/test_mgxs_library_distribcell/inputs_true.dat b/tests/test_mgxs_library_distribcell/inputs_true.dat index 9c33eeab4..055ce35a5 100644 --- a/tests/test_mgxs_library_distribcell/inputs_true.dat +++ b/tests/test_mgxs_library_distribcell/inputs_true.dat @@ -1 +1 @@ -3a3b7f75b326c94a8e5c7efe3046b2fdb887e9f75ecf6eb27587f9450c77cf8fd6acc4198c15bffb4e7ceead6d7b4327c19536bbf9cc35dfaae3f4ce4c26cc1a \ No newline at end of file +2d948f3b12293294eaeca231a3df9d51195379e8bb38dd3e68d3bc512a7d08ed52a1109054ca381684ec127268710f6d6e9210ac8154c9b379608e996627624a \ No newline at end of file diff --git a/tests/test_mgxs_library_distribcell/results_true.dat b/tests/test_mgxs_library_distribcell/results_true.dat index 5000d60c3..c21ca09e9 100644 --- a/tests/test_mgxs_library_distribcell/results_true.dat +++ b/tests/test_mgxs_library_distribcell/results_true.dat @@ -34,3 +34,9 @@ 0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 1 total 0.0 0.0 avg(distribcell) group out nuclide mean std. dev. 0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 total 0.0 0.0 + avg(distribcell) group out nuclide mean std. dev. +0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 total 0.0 0.0 + avg(distribcell) group in nuclide mean std. dev. +0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 total 0.000001 6.946255e-07 + avg(distribcell) group in nuclide mean std. dev. +0 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,... 1 total 0.0 0.0 diff --git a/tests/test_mgxs_library_hdf5/inputs_true.dat b/tests/test_mgxs_library_hdf5/inputs_true.dat index d92cc888a..0c648376e 100644 --- a/tests/test_mgxs_library_hdf5/inputs_true.dat +++ b/tests/test_mgxs_library_hdf5/inputs_true.dat @@ -1 +1 @@ -855919f7a333acff6423527b82656d6a472ea8416002fb475c2d21c676e2f75f5c040d209a3e9a3a6118cf944f2c1bfb8cf2318273b890861d790b1927791a35 \ No newline at end of file +e2cdca7ea5b3532050af5b12fac26d7ef212d2696bb1b73cdd00929b2243c40d100ad02438c7b090555b49815d0de6c48cf1b4ebf437a48bc80c2d2b4bad292e \ No newline at end of file diff --git a/tests/test_mgxs_library_hdf5/results_true.dat b/tests/test_mgxs_library_hdf5/results_true.dat index 7391b2e42..b2bd28f27 100644 --- a/tests/test_mgxs_library_hdf5/results_true.dat +++ b/tests/test_mgxs_library_hdf5/results_true.dat @@ -63,6 +63,15 @@ domain=10000 type=nu-fission matrix domain=10000 type=chi [ 1. 0.] [ 0.04607052 0. ] +domain=10000 type=chi-prompt +[ 1. 0.] +[ 0.05147146 0. ] +domain=10000 type=inverse-velocity +[ 5.70932329e-08 2.85573950e-06] +[ 4.68792969e-09 2.44216503e-07] +domain=10000 type=prompt-nu-fission +[ 0.01923922 0.46671903] +[ 0.00130951 0.04141087] domain=10001 type=total [ 0.31373767 0.3008214 ] [ 0.0155819 0.02805245] @@ -128,6 +137,15 @@ domain=10001 type=nu-fission matrix domain=10001 type=chi [ 0. 0.] [ 0. 0.] +domain=10001 type=chi-prompt +[ 0. 0.] +[ 0. 0.] +domain=10001 type=inverse-velocity +[ 5.99598048e-08 2.98549021e-06] +[ 4.55309296e-09 3.41701554e-07] +domain=10001 type=prompt-nu-fission +[ 0. 0.] +[ 0. 0.] domain=10002 type=total [ 0.66457226 2.05238401] [ 0.03121475 0.22434291] @@ -193,3 +211,12 @@ domain=10002 type=nu-fission matrix domain=10002 type=chi [ 0. 0.] [ 0. 0.] +domain=10002 type=chi-prompt +[ 0. 0.] +[ 0. 0.] +domain=10002 type=inverse-velocity +[ 6.02207831e-08 3.04495537e-06] +[ 3.78043696e-09 3.60007673e-07] +domain=10002 type=prompt-nu-fission +[ 0. 0.] +[ 0. 0.] diff --git a/tests/test_mgxs_library_mesh/inputs_true.dat b/tests/test_mgxs_library_mesh/inputs_true.dat new file mode 100644 index 000000000..e036b49a2 --- /dev/null +++ b/tests/test_mgxs_library_mesh/inputs_true.dat @@ -0,0 +1 @@ +a4cd030bea212e45fdb159e75a7fb3d1947e9bf3d0384ac5d37a72298d67dcfdd1b9eb5c6af8ac6e5983bd5b47de9c17a2ea472b467b7222a4909ee070bf1ca3 \ No newline at end of file diff --git a/tests/test_mgxs_library_mesh/results_true.dat b/tests/test_mgxs_library_mesh/results_true.dat new file mode 100644 index 000000000..03019cfd3 --- /dev/null +++ b/tests/test_mgxs_library_mesh/results_true.dat @@ -0,0 +1,132 @@ + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.640786 0.044177 +1 1 2 1 1 total 0.660597 0.128423 +2 2 1 1 1 total 0.615276 0.104046 +3 2 2 1 1 total 0.646999 0.186709 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.36665 0.048814 +1 1 2 1 1 total 0.40784 0.096486 +2 2 1 1 1 total 0.36356 0.074111 +3 2 2 1 1 total 0.41456 0.160443 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.366650 0.048814 +1 1 2 1 1 total 0.407840 0.096486 +2 2 1 1 1 total 0.363560 0.074111 +3 2 2 1 1 total 0.414593 0.160436 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.025749 0.002863 +1 1 2 1 1 total 0.028400 0.005275 +2 2 1 1 1 total 0.022988 0.004099 +3 2 2 1 1 total 0.027589 0.010350 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.015861 0.002876 +1 1 2 1 1 total 0.017280 0.004371 +2 2 1 1 1 total 0.014403 0.003542 +3 2 2 1 1 total 0.018061 0.010110 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.009888 0.001077 +1 1 2 1 1 total 0.011121 0.002456 +2 2 1 1 1 total 0.008585 0.001552 +3 2 2 1 1 total 0.009527 0.003659 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.026065 0.002907 +1 1 2 1 1 total 0.029084 0.006430 +2 2 1 1 1 total 0.022596 0.004062 +3 2 2 1 1 total 0.025066 0.009687 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 1.938476 0.211550 +1 1 2 1 1 total 2.177360 0.480780 +2 2 1 1 1 total 1.682799 0.303764 +3 2 2 1 1 total 1.864890 0.715661 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.615037 0.041754 +1 1 2 1 1 total 0.632196 0.123878 +2 2 1 1 1 total 0.592288 0.100439 +3 2 2 1 1 total 0.619410 0.177190 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.584014 0.054315 +1 1 2 1 1 total 0.622514 0.111323 +2 2 1 1 1 total 0.587256 0.084833 +3 2 2 1 1 total 0.613792 0.168612 + mesh 1 group in group out nuclide moment mean std. dev. + x y z +0 1 1 1 1 1 total P0 0.584014 0.054315 +1 1 1 1 1 1 total P1 0.243427 0.025488 +2 1 1 1 1 1 total P2 0.089236 0.007357 +3 1 1 1 1 1 total P3 0.008994 0.005768 +4 1 2 1 1 1 total P0 0.622514 0.111323 +5 1 2 1 1 1 total P1 0.239376 0.042594 +6 1 2 1 1 1 total P2 0.088386 0.017200 +7 1 2 1 1 1 total P3 -0.001243 0.005639 +8 2 1 1 1 1 total P0 0.587256 0.084833 +9 2 1 1 1 1 total P1 0.245120 0.041033 +10 2 1 1 1 1 total P2 0.086784 0.016255 +11 2 1 1 1 1 total P3 0.008660 0.004755 +12 2 2 1 1 1 total P0 0.612950 0.167940 +13 2 2 1 1 1 total P1 0.226176 0.061882 +14 2 2 1 1 1 total P2 0.086593 0.026126 +15 2 2 1 1 1 total P3 0.009672 0.011995 + mesh 1 group in group out nuclide moment mean std. dev. + x y z +0 1 1 1 1 1 total P0 0.584014 0.054315 +1 1 1 1 1 1 total P1 0.243427 0.025488 +2 1 1 1 1 1 total P2 0.089236 0.007357 +3 1 1 1 1 1 total P3 0.008994 0.005768 +4 1 2 1 1 1 total P0 0.622514 0.111323 +5 1 2 1 1 1 total P1 0.239376 0.042594 +6 1 2 1 1 1 total P2 0.088386 0.017200 +7 1 2 1 1 1 total P3 -0.001243 0.005639 +8 2 1 1 1 1 total P0 0.587256 0.084833 +9 2 1 1 1 1 total P1 0.245120 0.041033 +10 2 1 1 1 1 total P2 0.086784 0.016255 +11 2 1 1 1 1 total P3 0.008660 0.004755 +12 2 2 1 1 1 total P0 0.613792 0.168612 +13 2 2 1 1 1 total P1 0.226142 0.061856 +14 2 2 1 1 1 total P2 0.086174 0.025979 +15 2 2 1 1 1 total P3 0.009721 0.012027 + mesh 1 group in group out nuclide mean std. dev. + x y z +0 1 1 1 1 1 total 1.000000 0.088094 +1 1 2 1 1 1 total 1.000000 0.160891 +2 2 1 1 1 1 total 1.000000 0.126864 +3 2 2 1 1 1 total 1.001374 0.305883 + mesh 1 group in group out nuclide mean std. dev. + x y z +0 1 1 1 1 1 total 0.027395 0.004680 +1 1 2 1 1 1 total 0.022914 0.006025 +2 2 1 1 1 1 total 0.019384 0.002846 +3 2 2 1 1 1 total 0.029629 0.006292 + mesh 1 group out nuclide mean std. dev. + x y z +0 1 1 1 1 total 1.0 0.220956 +1 1 2 1 1 total 1.0 0.316565 +2 2 1 1 1 total 1.0 0.132140 +3 2 2 1 1 total 1.0 0.181577 + mesh 1 group out nuclide mean std. dev. + x y z +0 1 1 1 1 total 1.0 0.222246 +1 1 2 1 1 total 1.0 0.316565 +2 2 1 1 1 total 1.0 0.132140 +3 2 2 1 1 total 1.0 0.181577 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 3.610522e-07 3.169931e-08 +1 1 2 1 1 total 3.942353e-07 8.459167e-08 +2 2 1 1 1 total 3.097784e-07 5.252025e-08 +3 2 2 1 1 total 3.799163e-07 1.806470e-07 + mesh 1 group in nuclide mean std. dev. + x y z +0 1 1 1 1 total 0.025920 0.002893 +1 1 2 1 1 total 0.028922 0.006394 +2 2 1 1 1 total 0.022467 0.004039 +3 2 2 1 1 total 0.024923 0.009632 diff --git a/tests/test_mgxs_library_mesh/test_mgxs_library_mesh.py b/tests/test_mgxs_library_mesh/test_mgxs_library_mesh.py new file mode 100644 index 000000000..df7a0a5ae --- /dev/null +++ b/tests/test_mgxs_library_mesh/test_mgxs_library_mesh.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +import os +import sys +import glob +import hashlib +sys.path.insert(0, os.pardir) +from testing_harness import PyAPITestHarness +import openmc +import openmc.mgxs + + +class MGXSTestHarness(PyAPITestHarness): + def _build_inputs(self): + # Generate inputs using parent class routine + super(MGXSTestHarness, self)._build_inputs() + + # Initialize a one-group structure + energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 20.]) + + # Initialize MGXS Library for a few cross section types + # for one material-filled cell in the geometry + self.mgxs_lib = openmc.mgxs.Library(self._input_set.geometry) + self.mgxs_lib.by_nuclide = False + + # Test all MGXS types + self.mgxs_lib.mgxs_types = openmc.mgxs.MGXS_TYPES + self.mgxs_lib.energy_groups = energy_groups + self.mgxs_lib.legendre_order = 3 + self.mgxs_lib.domain_type = 'mesh' + + # Instantiate a tally mesh + mesh = openmc.Mesh(mesh_id=1) + mesh.type = 'regular' + mesh.dimension = [2, 2] + mesh.lower_left = [-100., -100.] + mesh.width = [100., 100.] + + self.mgxs_lib.domains = [mesh] + self.mgxs_lib.build_library() + + # Initialize a tallies file + self._input_set.tallies = openmc.Tallies() + self.mgxs_lib.add_to_tallies_file(self._input_set.tallies, merge=False) + self._input_set.tallies.export_to_xml() + + def _get_results(self, hash_output=False): + """Digest info in the statepoint and return as a string.""" + + # Read the statepoint file. + statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] + sp = openmc.StatePoint(statepoint) + + # Load the MGXS library from the statepoint + self.mgxs_lib.load_from_statepoint(sp) + + # Build a string from Pandas Dataframe for each 1-group MGXS + outstr = '' + for domain in self.mgxs_lib.domains: + for mgxs_type in self.mgxs_lib.mgxs_types: + mgxs = self.mgxs_lib.get_mgxs(domain, mgxs_type) + df = mgxs.get_pandas_dataframe() + outstr += df.to_string() + '\n' + + # Hash the results if necessary + if hash_output: + sha512 = hashlib.sha512() + sha512.update(outstr.encode('utf-8')) + outstr = sha512.hexdigest() + + return outstr + + def _cleanup(self): + super(MGXSTestHarness, self)._cleanup() + f = os.path.join(os.getcwd(), 'tallies.xml') + if os.path.exists(f): os.remove(f) + + +if __name__ == '__main__': + harness = MGXSTestHarness('statepoint.10.*', True) + harness.main() diff --git a/tests/test_mgxs_library_no_nuclides/inputs_true.dat b/tests/test_mgxs_library_no_nuclides/inputs_true.dat index d92cc888a..0c648376e 100644 --- a/tests/test_mgxs_library_no_nuclides/inputs_true.dat +++ b/tests/test_mgxs_library_no_nuclides/inputs_true.dat @@ -1 +1 @@ -855919f7a333acff6423527b82656d6a472ea8416002fb475c2d21c676e2f75f5c040d209a3e9a3a6118cf944f2c1bfb8cf2318273b890861d790b1927791a35 \ No newline at end of file +e2cdca7ea5b3532050af5b12fac26d7ef212d2696bb1b73cdd00929b2243c40d100ad02438c7b090555b49815d0de6c48cf1b4ebf437a48bc80c2d2b4bad292e \ No newline at end of file diff --git a/tests/test_mgxs_library_no_nuclides/results_true.dat b/tests/test_mgxs_library_no_nuclides/results_true.dat index 599cee6c4..141143c8c 100644 --- a/tests/test_mgxs_library_no_nuclides/results_true.dat +++ b/tests/test_mgxs_library_no_nuclides/results_true.dat @@ -75,6 +75,15 @@ material group out nuclide mean std. dev. 1 10000 1 total 1.0 0.046071 0 10000 2 total 0.0 0.000000 + material group out nuclide mean std. dev. +1 10000 1 total 1.0 0.051471 +0 10000 2 total 0.0 0.000000 + material group in nuclide mean std. dev. +1 10000 1 total 5.709323e-08 4.687930e-09 +0 10000 2 total 2.855740e-06 2.442165e-07 + material group in nuclide mean std. dev. +1 10000 1 total 0.019239 0.001310 +0 10000 2 total 0.466719 0.041411 material group in nuclide mean std. dev. 1 10001 1 total 0.313738 0.015582 0 10001 2 total 0.300821 0.028052 @@ -152,6 +161,15 @@ material group out nuclide mean std. dev. 1 10001 1 total 0.0 0.0 0 10001 2 total 0.0 0.0 + material group out nuclide mean std. dev. +1 10001 1 total 0.0 0.0 +0 10001 2 total 0.0 0.0 + material group in nuclide mean std. dev. +1 10001 1 total 5.995980e-08 4.553093e-09 +0 10001 2 total 2.985490e-06 3.417016e-07 + material group in nuclide mean std. dev. +1 10001 1 total 0.0 0.0 +0 10001 2 total 0.0 0.0 material group in nuclide mean std. dev. 1 10002 1 total 0.664572 0.031215 0 10002 2 total 2.052384 0.224343 @@ -229,3 +247,12 @@ material group out nuclide mean std. dev. 1 10002 1 total 0.0 0.0 0 10002 2 total 0.0 0.0 + material group out nuclide mean std. dev. +1 10002 1 total 0.0 0.0 +0 10002 2 total 0.0 0.0 + material group in nuclide mean std. dev. +1 10002 1 total 6.022078e-08 3.780437e-09 +0 10002 2 total 3.044955e-06 3.600077e-07 + material group in nuclide mean std. dev. +1 10002 1 total 0.0 0.0 +0 10002 2 total 0.0 0.0 diff --git a/tests/test_mgxs_library_nuclides/inputs_true.dat b/tests/test_mgxs_library_nuclides/inputs_true.dat index ea7655c91..a15bbee4c 100644 --- a/tests/test_mgxs_library_nuclides/inputs_true.dat +++ b/tests/test_mgxs_library_nuclides/inputs_true.dat @@ -1 +1 @@ -739796983940a1bad601998cf9ea2f90453a994477c7f675c2fd404d1864fe04a7fbfb5a15c5fe7cf9bad016b78432ba0910baba6f9cc026143761e9f526b62a \ No newline at end of file +e4a5f03ab6167e96462c4ef537533fe33b98d7878ae00824c5619356bda8d548b3c71af01ba8c88d5a9b46dd1471d331e6f678a164af922200f2ee3642be6340 \ No newline at end of file diff --git a/tests/test_mgxs_library_nuclides/results_true.dat b/tests/test_mgxs_library_nuclides/results_true.dat index d8ebe19a1..3da814604 100644 --- a/tests/test_mgxs_library_nuclides/results_true.dat +++ b/tests/test_mgxs_library_nuclides/results_true.dat @@ -1 +1 @@ -d56c6bae6bf3cd8950d3f50f089458c1c6c807be780fc97570532c6af6eb7e3057968d3345bd3c363f01315271129ef7b2ca028b05767353e601dc37b035f8a7 \ No newline at end of file +e494320a213b5704a2ac915a2ba504857be91961ceb6735b6ad05d81eb31c44c9584d5bd9d40baececf1dcb5b030e6ecec63cfbd20639baf69bcb596c5c46591 \ No newline at end of file diff --git a/tests/test_score_current/results_true.dat b/tests/test_score_current/results_true.dat index d3ac03a70..5d26226ae 100644 --- a/tests/test_score_current/results_true.dat +++ b/tests/test_score_current/results_true.dat @@ -1 +1 @@ -a9310752363eb059ff40f16ac9716b41ccab6ec6607d29f498069318745e485d18d784264304cc2586865bd58cef7587203cc22a1d485c58ddd63c14c0defdb9 \ No newline at end of file +bafab1921a12146abb2bb29603b52b9cc28a5a950a7a6bb1e3f012c05891c310fad643760d4f148b04d0fef3d1f3e141d146e3a278d81cc6fc8187c37717c5e7 \ No newline at end of file diff --git a/tests/test_tallies/inputs_true.dat b/tests/test_tallies/inputs_true.dat index ddab630f2..9d67bc0d0 100644 --- a/tests/test_tallies/inputs_true.dat +++ b/tests/test_tallies/inputs_true.dat @@ -1 +1 @@ -a392a7a8f27fd2f959b06a6809df29b3482e215da175b19846c248af44dc2ee7e2b05269b802dd9d6ed434506b05092f349436a0048411adab6b296dba0bd683 \ No newline at end of file +930af242a043f2676a000dbc5a2db6b148edcb31ed8c87dbaa35a8efb37a3be8cff30cdf4dc03f9c5c7eb4021f7e4c3327e64681cdd8fd8722c95c69db850227 \ No newline at end of file diff --git a/tests/test_tallies/results_true.dat b/tests/test_tallies/results_true.dat index ff3a82845..7aa65e1c1 100644 --- a/tests/test_tallies/results_true.dat +++ b/tests/test_tallies/results_true.dat @@ -1 +1 @@ -a0c7d6ca246ecd7dd5fed06373af142390971401c4e97744f29e55810ab9c231c97c4d8947cdf0b3d2df0ae829a9ddf768e5b2d889bbea34f2b6db0e567db884 \ No newline at end of file +a51db2a4efc681805f85968e04411dc33beee0532c202f5179b9a82880ab60a75e53fa9141c81045ea1d2842372f2d8da900326f09382ea61dd80a3c9b43bba1 \ No newline at end of file diff --git a/tests/test_tallies/test_tallies.py b/tests/test_tallies/test_tallies.py index e51e4616f..ba0098513 100644 --- a/tests/test_tallies/test_tallies.py +++ b/tests/test_tallies/test_tallies.py @@ -113,21 +113,23 @@ class TalliesTestHarness(PyAPITestHarness): polar_tally4.estimator = 'tracklength' universe_tally = Tally() - universe_tally.filters = [Filter(type='universe', bins=(1, 2, 3, 4))] + universe_tally.filters = [ + Filter(type='universe', bins=(1, 2, 3, 4, 6, 8))] universe_tally.scores = ['total'] - cell_filter = Filter(type='cell', bins=(10, 21, 22, 23)) + cell_filter = Filter(type='cell', bins=(10, 21, 22, 23, 60)) score_tallies = [Tally(), Tally(), Tally()] for t in score_tallies: t.filters = [cell_filter] t.scores = ['absorption', 'delayed-nu-fission', 'events', 'fission', 'inverse-velocity', 'kappa-fission', '(n,2n)', '(n,n1)', - '(n,gamma)', 'nu-fission', 'scatter', 'elastic', 'total'] + '(n,gamma)', 'nu-fission', 'scatter', 'elastic', 'total', + 'prompt-nu-fission'] score_tallies[0].estimator = 'tracklength' score_tallies[1].estimator = 'analog' score_tallies[2].estimator = 'collision' - cell_filter2 = Filter(type='cell', bins=(21, 22, 23, 27, 28, 29)) + cell_filter2 = Filter(type='cell', bins=(21, 22, 23, 27, 28, 29, 60)) flux_tallies = [Tally() for i in range(4)] for t in flux_tallies: t.filters = [cell_filter2]