Merge remote-tracking branch 'upstream/develop' into pyapi_filters

This commit is contained in:
Sterling Harper 2016-09-07 22:01:11 -04:00
commit 57442f8ca6
268 changed files with 28528 additions and 18830 deletions

View file

@ -42,7 +42,7 @@ install: true
before_script:
- if [[ ! -e $HOME/nndc_hdf5/cross_sections.xml ]]; then
wget https://anl.box.com/shared/static/6pwyfjnufam0sb96kqwwrve6vdn8m7u4.xz -O - | tar -C $HOME -xvJ;
wget https://anl.box.com/shared/static/68b2yhu8e6mx1f6hnbzz9mxsgg42d9ls.xz -O - | tar -C $HOME -xvJ;
fi
- export OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml

Binary file not shown.

View file

@ -151,5 +151,6 @@ if not response or response.lower().startswith('y'):
env = os.environ.copy()
env['PYTHONPATH'] = os.path.join(cwd, '..')
subprocess.call(['../scripts/openmc-ace-to-hdf5', '-d', 'nndc_hdf5']
subprocess.call(['../scripts/openmc-ace-to-hdf5', '-d', 'nndc_hdf5',
'--fission_energy_release', 'fission_Q_data_endfb71.h5']
+ ace_files, env=env)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="257.157px" height="60px" viewBox="0 0 257.157 60" enable-background="new 0 0 257.157 60" xml:space="preserve">
<g>
<g>
<path fill="#656565" d="M81.676,48.809c-2.67,0-5.128-0.469-7.367-1.409c-2.243-0.937-4.179-2.206-5.81-3.805
c-1.632-1.601-2.901-3.477-3.807-5.638c-0.908-2.158-1.36-4.475-1.36-6.947v-0.099c0-2.472,0.46-4.788,1.385-6.945
c0.922-2.159,2.199-4.057,3.832-5.688c1.63-1.631,3.576-2.916,5.834-3.856s4.724-1.409,7.392-1.409
c2.67,0,5.125,0.469,7.367,1.409s4.179,2.21,5.812,3.808c1.63,1.6,2.899,3.479,3.805,5.637c0.907,2.161,1.361,4.476,1.361,6.947
v0.099c0,2.472-0.463,4.79-1.385,6.945c-0.922,2.161-2.2,4.059-3.832,5.688c-1.63,1.629-3.576,2.918-5.834,3.854
C86.81,48.34,84.344,48.809,81.676,48.809z M81.773,41.788c1.517,0,2.918-0.279,4.203-0.839c1.286-0.561,2.382-1.336,3.288-2.325
c0.907-0.986,1.614-2.131,2.126-3.437c0.512-1.302,0.768-2.694,0.768-4.178v-0.099c0-1.481-0.256-2.883-0.768-4.202
c-0.512-1.318-1.235-2.475-2.175-3.461c-0.94-0.991-2.053-1.771-3.338-2.35c-1.285-0.576-2.687-0.864-4.201-0.864
c-1.552,0-2.958,0.279-4.228,0.838c-1.27,0.562-2.358,1.336-3.264,2.327c-0.907,0.987-1.616,2.132-2.125,3.436
c-0.511,1.303-0.768,2.693-0.768,4.178v0.099c0,1.483,0.257,2.884,0.768,4.205c0.51,1.318,1.234,2.47,2.175,3.46
c0.939,0.988,2.042,1.772,3.313,2.347C78.815,41.499,80.224,41.788,81.773,41.788z"/>
<path fill="#656565" d="M102.145,21.715h7.516v3.808c0.924-1.253,2.035-2.283,3.338-3.09c1.301-0.809,2.942-1.211,4.92-1.211
c1.549,0,3.048,0.297,4.498,0.889c1.453,0.594,2.737,1.475,3.859,2.645c1.119,1.17,2.019,2.604,2.694,4.301
c0.675,1.7,1.013,3.652,1.013,5.859v0.1c0,2.209-0.338,4.162-1.013,5.858c-0.675,1.697-1.565,3.133-2.67,4.301
c-1.105,1.173-2.381,2.054-3.832,2.648c-1.452,0.594-2.966,0.889-4.548,0.889c-2.012,0-3.667-0.397-4.971-1.187
c-1.302-0.79-2.396-1.712-3.287-2.768v11.371h-7.516V21.715z M115.989,42.332c0.889,0,1.722-0.172,2.498-0.518
c0.773-0.348,1.458-0.843,2.051-1.484c0.592-0.641,1.064-1.408,1.409-2.298c0.347-0.89,0.52-1.896,0.52-3.018v-0.1
c0-1.087-0.173-2.082-0.52-2.99c-0.345-0.907-0.816-1.682-1.409-2.325c-0.593-0.642-1.278-1.137-2.051-1.482
c-0.776-0.345-1.609-0.52-2.498-0.52s-1.722,0.175-2.496,0.52c-0.775,0.346-1.451,0.841-2.028,1.482
c-0.577,0.644-1.037,1.418-1.385,2.325c-0.345,0.908-0.518,1.903-0.518,2.99v0.1c0,1.087,0.173,2.086,0.518,2.991
c0.348,0.906,0.808,1.684,1.385,2.324c0.577,0.642,1.253,1.137,2.028,1.484C114.267,42.16,115.101,42.332,115.989,42.332z"/>
<path fill="#656565" d="M145.62,48.809c-1.979,0-3.814-0.329-5.514-0.986c-1.696-0.661-3.162-1.6-4.399-2.816
c-1.237-1.223-2.199-2.666-2.891-4.331c-0.693-1.661-1.041-3.517-1.041-5.559v-0.102c0-1.877,0.321-3.658,0.964-5.34
c0.645-1.682,1.543-3.147,2.695-4.4c1.154-1.251,2.53-2.241,4.129-2.967c1.6-0.725,3.37-1.086,5.316-1.086
c2.206,0,4.118,0.394,5.733,1.187c1.617,0.791,2.958,1.854,4.03,3.188c1.072,1.335,1.864,2.867,2.374,4.598
c0.511,1.731,0.766,3.537,0.766,5.417c0,0.295-0.008,0.61-0.024,0.938c-0.016,0.331-0.04,0.676-0.074,1.038h-18.442
c0.364,1.716,1.112,3.009,2.25,3.881c1.138,0.873,2.547,1.313,4.228,1.313c1.251,0,2.373-0.216,3.363-0.646
c0.988-0.427,2.01-1.118,3.064-2.076l4.304,3.81c-1.254,1.547-2.771,2.76-4.552,3.633C150.12,48.372,148.026,48.809,145.62,48.809
z M150.464,32.89c-0.227-1.681-0.821-3.042-1.777-4.08c-0.956-1.037-2.226-1.557-3.807-1.557c-1.582,0-2.861,0.512-3.832,1.53
c-0.973,1.023-1.607,2.393-1.904,4.106H150.464z"/>
<path fill="#656565" d="M159.415,21.715h7.518v3.787c0.428-0.562,0.896-1.103,1.408-1.616c0.512-0.517,1.08-0.971,1.706-1.371
c0.625-0.397,1.318-0.712,2.078-0.946c0.757-0.233,1.613-0.347,2.569-0.347c2.867,0,5.084,0.873,6.65,2.619
c1.565,1.746,2.35,4.152,2.35,7.219v17.156h-7.518V33.471c0-1.776-0.395-3.118-1.186-4.021c-0.792-0.903-1.913-1.355-3.361-1.355
c-1.451,0-2.598,0.452-3.437,1.355c-0.841,0.903-1.261,2.245-1.261,4.021v14.745h-7.518V21.715z"/>
<path fill="#A31F34" d="M187.056,13.607h8.209l9.095,14.634l9.099-14.634h8.207v34.608h-7.513V25.619l-9.742,14.784h-0.196
l-9.645-14.634v22.446h-7.514V13.607z"/>
<path fill="#A31F34" d="M242.235,48.809c-2.537,0-4.893-0.459-7.071-1.385c-2.175-0.922-4.052-2.18-5.636-3.78
c-1.583-1.601-2.819-3.485-3.709-5.66c-0.888-2.179-1.333-4.501-1.333-6.974v-0.099c0-2.472,0.445-4.788,1.333-6.945
c0.89-2.159,2.126-4.057,3.709-5.688c1.584-1.631,3.477-2.916,5.687-3.856c2.207-0.94,4.647-1.409,7.317-1.409
c1.613,0,3.091,0.132,4.425,0.396c1.336,0.265,2.547,0.625,3.635,1.089c1.088,0.461,2.093,1.021,3.016,1.682
c0.923,0.657,1.781,1.385,2.57,2.175l-4.846,5.586c-1.349-1.218-2.728-2.175-4.128-2.866c-1.401-0.692-2.974-1.038-4.721-1.038
c-1.453,0-2.793,0.279-4.03,0.838c-1.235,0.562-2.299,1.336-3.188,2.327c-0.89,0.987-1.582,2.132-2.077,3.436
c-0.494,1.303-0.742,2.693-0.742,4.178v0.099c0,1.483,0.248,2.884,0.742,4.205c0.495,1.318,1.178,2.47,2.053,3.46
c0.872,0.988,1.929,1.772,3.164,2.347c1.235,0.576,2.594,0.865,4.079,0.865c1.979,0,3.65-0.362,5.02-1.086
c1.366-0.726,2.726-1.714,4.079-2.968l4.844,4.895c-0.89,0.958-1.814,1.814-2.768,2.571c-0.958,0.758-2.003,1.411-3.142,1.953
c-1.136,0.544-2.379,0.959-3.733,1.236C245.433,48.669,243.915,48.809,242.235,48.809z"/>
</g>
<path fill="#A31F34" d="M30.614,1.024c-10.866,0-20.325,5.991-25.284,14.845h20.495l13.167,22.804l-11.609,20.11
c1.062,0.119,2.139,0.192,3.231,0.192c16.003,0,28.975-12.972,28.975-28.976C59.589,13.997,46.617,1.024,30.614,1.024z"/>
<path fill="#656565" d="M16.416,55.243h5.807l9.567-16.57l-9.567-16.566H3.093l-0.607,1.052C1.954,25.355,1.639,27.638,1.639,30
C1.639,40.84,7.603,50.274,16.416,55.243z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -16,3 +16,7 @@
.wy-table, .rst-content table.docutils, .rst-content table.field-list {
margin-bottom: 0px;
}
.wy-side-nav-search {
background-color: #343131;
}

View file

@ -28,6 +28,9 @@ MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial',
'h5py', 'pandas', 'opencg']
sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES)
import numpy as np
np.polynomial.Polynomial = MagicMock
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@ -126,7 +129,7 @@ if not on_rtd:
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_logo = '_images/openmc200px.png'
html_logo = '_images/openmc_logo.png'
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".

View file

@ -0,0 +1,53 @@
.. _usersguide_fission_energy:
==================================
Fission Energy Release File Format
==================================
This file is a compact HDF5 representation of the ENDF MT=1, MF=458 data (see
ENDF-102_ for details). It gives the information needed to compute the energy
carried away from fission reactions by each reaction product (e.g. fragment
nuclei, neutrons) which depends on the incident neutron energy. OpenMC is
distributed with one of these files under
data/fission_Q_data_endfb71.h5. More files of this format can be created from
ENDF files with the
``openmc.data.write_compact_458_library`` function. They can be read with the
``openmc.data.FissionEnergyRelease.from_compact_hdf5`` class method.
:Attributes: - **comment** (*char[]*) -- An optional text comment
- **component order** (*char[][]*) -- An array of strings
specifying the order each reaction product occurs in the data
arrays. The components use the 2-3 letter abbreviations
specified in ENDF-102 e.g. EFR for fission fragments and ENP for
prompt neutrons.
**/<nuclide name>/**
Nuclides are named by concatenating their atomic symbol and mass number. For
example, 'U235' or 'Pu239'. Metastable nuclides are appended with an
'_m' and their metastable number. For example, 'Am242_m1'
:Datasets:
- **data** (*double[][][]*) -- The energy release coefficients. The
first axis indexes the component type. The second axis specifies
values or uncertainties. The third axis indexes the polynomial
order. If the data uses the Sher-Beck format, then the last axis
will have a length of one and ENDF-102 should be consulted for
energy dependence. Otherwise, the data uses the Madland format
which is a polynomial of incident energy.
For example, if 'EFR' is given first in the **component order**
attribute and the data uses the Madland format, then the energy
released in the form of fission fragments at an incident energy
:math:`E` is given by
.. math::
\text{data}[0, 0, 0] + \text{data}[0, 0, 1] \cdot E
+ \text{data}[0, 0, 2] \cdot E^2 + \ldots
And its uncertainty is
.. math::
\text{data}[0, 1, 0] + \text{data}[0, 1, 1] \cdot E
+ \text{data}[0, 1, 2] \cdot E^2 + \ldots
.. _ENDF-102: http://www.nndc.bnl.gov/endfdocs/ENDF-102-2012.pdf

View file

@ -15,6 +15,7 @@ Data Files
nuclear_data
mgxs_library
data_wmp
fission_energy
------------
Output Files
@ -30,3 +31,4 @@ Output Files
particle_restart
track
voxel
volume

View file

@ -1,4 +1,4 @@
.. _usersguide_nuclear_data:
.. _io_nuclear_data:
========================
Nuclear Data File Format
@ -16,29 +16,53 @@ Incident Neutron Data
- **metastable** (*int*) -- Metastable state (0=ground, 1=first
excited, etc.)
- **atomic_weight_ratio** (*double*) -- Mass in units of neutron masses
- **temperature** (*double*) -- Temperature in MeV
- **n_reaction** (*int*) -- Number of reactions
:Datasets: - **energy** (*double[]*) -- Energy points at which cross sections are tabulated
**/<nuclide name>/kTs/**
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the
temperature-dependent data set. For example, the data set corresponding to
300 Kelvin would be located at `300K`.
:Datasets:
- **<TTT>K** (*double*) -- kT values (in MeV) for each Temperature
TTT (in Kelvin)
**/<nuclide name>/reactions/reaction_<mt>/**
:Attributes: - **mt** (*int*) -- ENDF MT reaction number
- **label** (*char[]*) -- Name of the reaction
- **Q_value** (*double*) -- Q value in MeV
- **threshold_idx** (*int*) -- Index on the energy grid that the
reaction threshold corresponds to
- **center_of_mass** (*int*) -- Whether the reference frame for
scattering is center-of-mass (1) or laboratory (0)
- **n_product** (*int*) -- Number of reaction products
:Datasets: - **xs** (*double[]*) -- Cross section values tabulated against the nuclide energy grid
**/<nuclide name>/reactions/reaction_<mt>/<TTT>K/**
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the
temperature-dependent data set. For example, the data set corresponding to
300 Kelvin would be located at `300K`.
:Datasets:
- **xs** (*double[]*) -- Cross section values tabulated against the
nuclide energy grid for temperature TTT (in Kelvin)
:Attributes:
- **threshold_idx** (*int*) -- Index on the energy
grid that the reaction threshold corresponds to for
temperature TTT (in Kelvin)
**/<nuclide name>/reactions/reaction_<mt>/product_<j>/**
Reaction product data is described in :ref:`product`.
**/<nuclide name>/urr**
**/<nuclide name>/urr/<TTT>K/**
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the
temperature-dependent data set. For example, the data set corresponding to
300 Kelvin would be located at `300K`.
:Attributes: - **interpolation** (*int*) -- interpolation scheme
- **inelastic** (*int*) -- flag indicating inelastic scattering
@ -55,6 +79,36 @@ Incident Neutron Data
from fission. It is formatted as a reaction product, described in
:ref:`product`.
**/<nuclide name>/fission_energy_release/**
:Datasets: - **fragments** (:ref:`polynomial <1d_polynomial>`) -- Energy
released in the form of fragments as a function of incident
neutron energy.
- **prompt_neutrons** (:ref:`polynomial <1d_polynomial>` or
:ref:`tabulated <1d_tabulated>`) -- Energy released in the form of
prompt neutrons as a function of incident neutron energy.
- **delayed_neutrons** (:ref:`polynomial <1d_polynomial>`) -- Energy
released in the form of delayed neutrons as a function of incident
neutron energy.
- **prompt_photons** (:ref:`polynomial <1d_polynomial>`) -- Energy
released in the form of prompt photons as a function of incident
neutron energy.
- **delayed_photons** (:ref:`polynomial <1d_polynomial>`) -- Energy
released in the form of delayed photons as a function of incident
neutron energy.
- **betas** (:ref:`polynomial <1d_polynomial>`) -- Energy
released in the form of betas as a function of incident
neutron energy.
- **neutrinos** (:ref:`polynomial <1d_polynomial>`) -- Energy
released in the form of neutrinos as a function of incident
neutron energy.
- **q_prompt** (:ref:`polynomial <1d_polynomial>` or
:ref:`tabulated <1d_tabulated>`) -- The prompt fission Q-value
(fragments + prompt neutrons + prompt photons - incident energy)
- **q_recoverable** (:ref:`polynomial <1d_polynomial>` or
:ref:`tabulated <1d_tabulated>`) -- The recoverable fission Q-value
(Q_prompt + delayed neutrons + delayed photons + betas)
-------------------------------
Thermal Neutron Scattering Data
-------------------------------
@ -62,32 +116,48 @@ Thermal Neutron Scattering Data
**/<thermal name>/**
:Attributes: - **atomic_weight_ratio** (*double*) -- Mass in units of neutron masses
- **temperature** (*double*) -- Temperature in MeV
- **zaids** (*int[]*) -- ZAID identifiers for which the thermal
- **nuclides** (*char[][]*) -- Names of nuclides for which the thermal
scattering data applies to
**/<thermal name>/elastic/**
:Datasets: - **xs** (:ref:`tabulated <1d_tabulated>`) -- Thermal inelastic
scattering cross section
- **mu_out** (*double[][]*) -- Distribution of outgoing energies
and angles for coherent elastic scattering
**/<thermal name>/inelastic/**
:Attributes:
- **secondary_mode** (*char[]*) -- Indicates how the inelastic
outgoing angle-energy distributions are represented ('equal',
'skewed', or 'continuous').
**/<thermal name>/kTs/**
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the
temperature-dependent data set. For example, the data set corresponding to
300 Kelvin would be located at `300K`.
:Datasets:
- **<TTT>K** (*double*) -- kT values (in MeV) for each Temperature
TTT (in Kelvin)
**/<thermal name>/elastic/<TTT>K/**
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the
temperature-dependent data set. For example, the data set corresponding to
300 Kelvin would be located at `300K`.
:Datasets: - **xs** (:ref:`tabulated <1d_tabulated>`) -- Thermal inelastic
scattering cross section
scattering cross section for temperature TTT (in Kelvin)
- **mu_out** (*double[][]*) -- Distribution of outgoing energies
and angles for coherent elastic scattering for temperature TTT
(in Kelvin)
**/<thermal name>/inelastic/<TTT>K/**
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the
temperature-dependent data set. For example, the data set corresponding to
300 Kelvin would be located at `300K`.
:Datasets: - **xs** (:ref:`tabulated <1d_tabulated>`) -- Thermal inelastic
scattering cross section for temperature TTT (in Kelvin)
- **energy_out** (*double[][]*) -- Distribution of outgoing
energies for each incoming energy. Only present if secondary mode
is not continuous.
energies for each incoming energy for temperature TTT (in Kelvin).
Only present if secondary mode is not continuous.
- **mu_out** (*double[][][]*) -- Distribution of scattering cosines
for each pair of incoming and outgoing energies. Only present if
secondary mode is not continuous.
for each pair of incoming and outgoing energies. for temperature
TTT (in Kelvin). Only present if secondary mode is not continuous.
If the secondary mode is continuous, the outgoing energy-angle distribution is
given as a :ref:`correlated angle-energy distribution
@ -142,17 +212,19 @@ Tabulated
:Object type: Dataset
:Datatype: *double[2][]*
:Description: x-values are listed first followed by corresponding y-values
:Attributes: - **type** (*char[]*) -- 'tabulated'
:Attributes: - **type** (*char[]*) -- 'Tabulated1D'
- **breakpoints** (*int[]*) -- Region breakpoints
- **interpolation** (*int[]*) -- Region interpolation codes
.. _1d_polynomial:
Polynomial
----------
:Object type: Dataset
:Datatype: *double[]*
:Description: Polynomial coefficients listed in order of increasing power
:Attributes: - **type** (*char[]*) -- 'polynomial'
:Attributes: - **type** (*char[]*) -- 'Polynomial'
Coherent elastic scattering
---------------------------

View file

@ -0,0 +1,22 @@
.. _io_volume:
==================
Volume File Format
==================
**/**
:Attributes: - **samples** (*int*) -- Number of samples
- **lower_left** (*double[3]*) -- Lower-left coordinates of
bounding box
- **upper_right** (*double[3]*) -- Upper-right coordinates of
bounding box
**/cell_<id>/**
:Datasets: - **volume** (*double[2]*) -- Calculated volume and its uncertainty
in cubic centimeters
- **nuclides** (*char[][]*) -- Names of nuclides identified in the
cell
- **atoms** (*double[][2]*) -- Total number of atoms of each nuclide
and its uncertainty

View file

@ -53,12 +53,12 @@ speed up the calculation.
Logarithmic Mapping
+++++++++++++++++++
To speed up energy grid searches, OpenMC uses logarithmic mapping technique
[Brown]_ to limit the range of energies that must be searched for each
nuclide. The entire energy range is divided up into equal-lethargy segments, and
the bounding energies of each segment are mapped to bounding indices on each of
the nuclide energy grids. By default, OpenMC uses 8000 equal-lethargy segments
as recommended by Brown.
To speed up energy grid searches, OpenMC uses a `logarithmic mapping technique`_
to limit the range of energies that must be searched for each nuclide. The
entire energy range is divided up into equal-lethargy segments, and the bounding
energies of each segment are mapped to bounding indices on each of the nuclide
energy grids. By default, OpenMC uses 8000 equal-lethargy segments as
recommended by Brown.
Other Methods
+++++++++++++
@ -74,9 +74,9 @@ offers support for an experimental data format called windowed multipole (WMP).
This data format requires less memory than pointwise cross sections, and it
allows on-the-fly Doppler broadening to arbitrary temperature.
The multipole method was introduced by [Hwang]_ and the faster windowed
multipole method by [Josey]_. In the multipole format, cross section resonances
are represented by poles, :math:`p_j`, and residues, :math:`r_j`, in the complex
The multipole method was introduced by Hwang_ and the faster windowed multipole
method by Josey_. In the multipole format, cross section resonances are
represented by poles, :math:`p_j`, and residues, :math:`r_j`, in the complex
plane. The 0K cross sections in the resolved resonance region can be computed
by summing up a contribution from each pole:
@ -232,21 +232,10 @@ sections. This allows flexibility for the model to use highly anisotropic
scattering information in the water while the fuel can be simulated with linear
or even isotropic scattering.
.. only:: html
.. rubric:: References
.. [Brown] Forrest B. Brown, "New Hash-based Energy Lookup Algorithm for Monte
Carlo codes," LA-UR-14-24530, Los Alamos National Laboratory (2014).
.. [Hwang] R. N. Hwang, "A Rigorous Pole Representation of Multilevel Cross
Sections and Its Practical Application," *Nucl. Sci. Eng.*, **96**,
192-209 (1987).
.. [Josey] Colin Josey, Pablo Ducru, Benoit Forget, and Kord Smith, "Windowed
Multipole for Cross Section Doppler Broadening," *J. Comp. Phys*,
**307**, 715-727 (2016). http://dx.doi.org/10.1016/j.jcp.2015.08.013
.. _logarithmic mapping technique:
https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-14-24530.pdf
.. _Hwang: http://www.ans.org/pubs/journals/nse/a_16381
.. _Josey: http://dx.doi.org/10.1016/j.jcp.2015.08.013
.. _MCNP: http://mcnp.lanl.gov
.. _Serpent: http://montecarlo.vtt.fi
.. _NJOY: http://t2.lanl.gov/codes.shtml

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,13 @@
.. _notebook_mdgxs_part_i:
==========================
MDGXS Part I: Introduction
==========================
.. only:: html
.. notebook:: mdgxs-part-i.ipynb
.. only:: latex
IPython notebooks must be viewed in the online HTML documentation.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,13 @@
.. _notebook_mdgxs_part_ii:
================================
MDGXS Part II: Advanced Features
================================
.. only:: html
.. notebook:: mdgxs-part-ii.ipynb
.. only:: latex
IPython notebooks must be viewed in the online HTML documentation.

View file

@ -214,7 +214,6 @@
"source": [
"# Instantiate a Materials collection and export to XML\n",
"materials_file = openmc.Materials([inf_medium])\n",
"materials_file.default_xs = '71c'\n",
"materials_file.export_to_xml()"
]
},
@ -499,23 +498,37 @@
"output_type": "stream",
"text": [
"\n",
" .d88888b. 888b d888 .d8888b.\n",
" d88P\" \"Y88b 8888b d8888 d88P Y88b\n",
" 888 888 88888b.d88888 888 888\n",
" 888 888 88888b. .d88b. 88888b. 888Y88888P888 888 \n",
" 888 888 888 \"88b d8P Y8b 888 \"88b 888 Y888P 888 888 \n",
" 888 888 888 888 88888888 888 888 888 Y8P 888 888 888\n",
" Y88b. .d88P 888 d88P Y8b. 888 888 888 \" 888 Y88b d88P\n",
" \"Y88888P\" 88888P\" \"Y8888 888 888 888 888 \"Y8888P\"\n",
"__________________888______________________________________________________\n",
" 888\n",
" 888\n",
" %%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%\n",
" ############### %%%%%%%%%%%%%%%%%%%%%%%%\n",
" ################## %%%%%%%%%%%%%%%%%%%%%%%\n",
" ################### %%%%%%%%%%%%%%%%%%%%%%%\n",
" #################### %%%%%%%%%%%%%%%%%%%%%%\n",
" ##################### %%%%%%%%%%%%%%%%%%%%%\n",
" ###################### %%%%%%%%%%%%%%%%%%%%\n",
" ####################### %%%%%%%%%%%%%%%%%%\n",
" ####################### %%%%%%%%%%%%%%%%%\n",
" ###################### %%%%%%%%%%%%%%%%%\n",
" #################### %%%%%%%%%%%%%%%%%\n",
" ################# %%%%%%%%%%%%%%%%%\n",
" ############### %%%%%%%%%%%%%%%%\n",
" ############ %%%%%%%%%%%%%%%\n",
" ######## %%%%%%%%%%%%%%\n",
" %%%%%%%%%%%\n",
"\n",
" Copyright: 2011-2016 Massachusetts Institute of Technology\n",
" License: http://openmc.readthedocs.io/en/latest/license.html\n",
" Version: 0.7.1\n",
" Git SHA1: 3d68c07625e33cd64188df03ee03e9c31b3d4b74\n",
" Date/Time: 2016-07-22 21:03:18\n",
" | The OpenMC Monte Carlo Code\n",
" Copyright | 2011-2016 Massachusetts Institute of Technology\n",
" License | http://openmc.readthedocs.io/en/latest/license.html\n",
" Version | 0.8.0\n",
" Git SHA1 | fbebf7bf709fe2fe1813af95bff9b29c0d59312c\n",
" Date/Time | 2016-08-31 10:40:13\n",
" OpenMP Threads | 4\n",
"\n",
" ===========================================================================\n",
" ========================> INITIALIZATION <=========================\n",
@ -525,12 +538,12 @@
" Reading geometry XML file...\n",
" Reading cross sections XML file...\n",
" Reading materials XML file...\n",
" Reading H1.71c from /home/romano/openmc/data/nndc_hdf5/H1_71c.h5\n",
" Reading O16.71c from /home/romano/openmc/data/nndc_hdf5/O16_71c.h5\n",
" Reading U235.71c from /home/romano/openmc/data/nndc_hdf5/U235_71c.h5\n",
" Reading U238.71c from /home/romano/openmc/data/nndc_hdf5/U238_71c.h5\n",
" Reading Zr90.71c from /home/romano/openmc/data/nndc_hdf5/Zr90_71c.h5\n",
" Maximum neutron transport energy: 20.0000 MeV for H1.71c\n",
" Reading H1 from /home/romano/openmc/data/nndc_hdf5/H1.h5\n",
" Reading O16 from /home/romano/openmc/data/nndc_hdf5/O16.h5\n",
" Reading U235 from /home/romano/openmc/data/nndc_hdf5/U235.h5\n",
" Reading U238 from /home/romano/openmc/data/nndc_hdf5/U238.h5\n",
" Reading Zr90 from /home/romano/openmc/data/nndc_hdf5/Zr90.h5\n",
" Maximum neutron transport energy: 20.0000 MeV for H1\n",
" Reading tallies XML file...\n",
" Building neighboring cells lists for each surface...\n",
" Initializing source particles...\n",
@ -600,20 +613,20 @@
"\n",
" =======================> TIMING STATISTICS <=======================\n",
"\n",
" Total time for initialization = 3.2300E-01 seconds\n",
" Reading cross sections = 1.6900E-01 seconds\n",
" Total time in simulation = 1.9882E+01 seconds\n",
" Time in transport only = 1.9869E+01 seconds\n",
" Time in inactive batches = 2.6590E+00 seconds\n",
" Time in active batches = 1.7223E+01 seconds\n",
" Total time for initialization = 3.9900E-01 seconds\n",
" Reading cross sections = 2.6500E-01 seconds\n",
" Total time in simulation = 1.1488E+01 seconds\n",
" Time in transport only = 1.1152E+01 seconds\n",
" Time in inactive batches = 1.2180E+00 seconds\n",
" Time in active batches = 1.0270E+01 seconds\n",
" Time synchronizing fission bank = 4.0000E-03 seconds\n",
" Sampling source sites = 4.0000E-03 seconds\n",
" SEND/RECV source sites = 0.0000E+00 seconds\n",
" Sampling source sites = 3.0000E-03 seconds\n",
" SEND/RECV source sites = 1.0000E-03 seconds\n",
" Time accumulating tallies = 0.0000E+00 seconds\n",
" Total time for finalization = 0.0000E+00 seconds\n",
" Total time elapsed = 2.0217E+01 seconds\n",
" Calculation Rate (inactive) = 9402.03 neutrons/second\n",
" Calculation Rate (active) = 5806.19 neutrons/second\n",
" Total time for finalization = 1.0000E-03 seconds\n",
" Total time elapsed = 1.1901E+01 seconds\n",
" Calculation Rate (inactive) = 20525.5 neutrons/second\n",
" Calculation Rate (active) = 9737.10 neutrons/second\n",
"\n",
" ============================> RESULTS <============================\n",
"\n",
@ -894,7 +907,7 @@
" <td>6.250000e-07</td>\n",
" <td>total</td>\n",
" <td>(((total / flux) - (absorption / flux)) - (sca...</td>\n",
" <td>-3.774758e-15</td>\n",
" <td>-2.886580e-15</td>\n",
" <td>0.011292</td>\n",
" </tr>\n",
" <tr>\n",
@ -904,7 +917,7 @@
" <td>2.000000e+01</td>\n",
" <td>total</td>\n",
" <td>(((total / flux) - (absorption / flux)) - (sca...</td>\n",
" <td>1.443290e-15</td>\n",
" <td>-5.551115e-16</td>\n",
" <td>0.002570</td>\n",
" </tr>\n",
" </tbody>\n",
@ -917,8 +930,8 @@
"1 1 6.25e-07 2.00e+01 total \n",
"\n",
" score mean std. dev. \n",
"0 (((total / flux) - (absorption / flux)) - (sca... -3.77e-15 1.13e-02 \n",
"1 (((total / flux) - (absorption / flux)) - (sca... 1.44e-15 2.57e-03 "
"0 (((total / flux) - (absorption / flux)) - (sca... -2.89e-15 1.13e-02 \n",
"1 (((total / flux) - (absorption / flux)) - (sca... -5.55e-16 2.57e-03 "
]
},
"execution_count": 22,
@ -1167,21 +1180,21 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"display_name": "Python 3",
"language": "python",
"name": "python2"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.11"
"pygments_lexer": "ipython3",
"version": "3.5.2"
}
},
"nbformat": 4,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -105,7 +105,6 @@
"source": [
"# Instantiate a Materials collection\n",
"materials_file = openmc.Materials((fuel, water, zircaloy))\n",
"materials_file.default_xs = '71c'\n",
"\n",
"# Export to \"materials.xml\"\n",
"materials_file.export_to_xml()"
@ -136,8 +135,8 @@
"max_x = openmc.XPlane(x0=+0.63, boundary_type='reflective')\n",
"min_y = openmc.YPlane(y0=-0.63, boundary_type='reflective')\n",
"max_y = openmc.YPlane(y0=+0.63, boundary_type='reflective')\n",
"min_z = openmc.ZPlane(z0=-0.63, boundary_type='reflective')\n",
"max_z = openmc.ZPlane(z0=+0.63, boundary_type='reflective')"
"min_z = openmc.ZPlane(z0=-100., boundary_type='vacuum')\n",
"max_z = openmc.ZPlane(z0=+100., boundary_type='vacuum')"
]
},
{
@ -264,7 +263,7 @@
"settings_file.output = {'tallies': True}\n",
"\n",
"# Create an initial uniform spatial source distribution over fissionable zones\n",
"bounds = [-0.63, -0.63, -0.63, 0.63, 0.63, 0.63]\n",
"bounds = [-0.63, -0.63, -100., 0.63, 0.63, 100.]\n",
"uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)\n",
"settings_file.source = openmc.source.Source(space=uniform_dist)\n",
"\n",
@ -339,7 +338,7 @@
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8Tq\nQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+AHFwInLqDpadAAAALKSURBVGje7dpLcqQwDAbgHHE2\nYeEj+D4cwQucBUfo+3CEXoSp8OhuhF70T4qpKXmdr21LogK2Pj7A8QmNP+HDhw8fPnz48Kf6VH9G\n+66vy+je8k19jnf8C5dXIPv86ms56lPdjvaYbyodx3ze+XLE76cXFiD4zPji99z0/AJ4n1lfvJ6f\nnl0A6x+578efMSg1wPr172/jPO5yFXM+Ef78gdblM+WPHyguP//t1/g6pA0wfln+ho/fwgYYn19C\n/xwDvwHGc9OvC+hs37DTrwuwfWanXxdQTC9Mvyygs3wjTL8uwPJpn/tNDbSGz7T0SBEWw4vLXzbQ\n6b6RoveIoO6TvPxlA63qs7z8ZQPF9F+SH22vbX8OQKf5Rtv+EgDNJ3X58wZaxWd1+fMGiuFvir8b\nvjp8J/tGy/6jAmRvhW8fwL3vVT+o3grfPoB7r/IpALI3tz8FoJN84/NV873hB8UnM3xzANtf8nb4\ndwmg3grfFEDJO8JPE0i9Ff4pAYL3pI8mkHor/HMCeO9JH00g9SafEsh7T/ppARBvp48UwJnelT5S\nACd7O31TAlnvKx9SQCd7B58KgPO+8iMFuPWe9E8F8BveWX7bAjzX9y4//Jve+fhsH6Ctv7n8PTzj\nvY/v9gEOHz58+PBX+6v/f/wPvnd54f3j6venE/yl769Xv7+j3x/o98/V32/o9+fl389Xnx+g5x/o\n+Qt6/oOeP6HnX+j5G3z+h54/ouefV5/foufP6Pk3ev4On/+j9w/o/Qd6/4Le/6D3T/D9V67Y/ZsV\nQBq+s+8f0ftP+P41axXguP9NWgDuu/Cdfv+N3r/D9/9TAID+A7T/Ae2/gPs/0P4TtP8F7r9J3AIO\n9P+g/Udw/9Oygbf7r9D+L7j/DO1/Q/vv4P4/tP8Q7n9E+y/h/k+0/xTuf4X7b+H+X7T/+BPuf3aM\n8OHDhw8fPnz4w/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMDctMjJUMjE6Mzk6\nNDYtMDU6MDBOOEOsAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTA3LTIyVDIxOjM5OjQ2LTA1OjAw\nP2X7EAAAAABJRU5ErkJggg==\n",
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8Tq\nQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+AJAwQmKDRX/78AAALKSURBVGje7dpLcqQwDAbgHHE2\nYeEj+D4cwQucBUfo+3CEXoSp8OhuhF70T4qpKXmdr21LogK2Pj7A8QmNP+HDhw8fPnz48Kf6VH9G\n+66vy+je8k19jnf8C5dXIPv86ms56lPdjvaYbyodx3ze+XLE76cXFiD4zPji99z0/AJ4n1lfvJ6f\nnl0A6x+578efMSg1wPr172/jPO5yFXM+Ef78gdblM+WPHyguP//t1/g6pA0wfln+ho/fwgYYn19C\n/xwDvwHGc9OvC+hs37DTrwuwfWanXxdQTC9Mvyygs3wjTL8uwPJpn/tNDbSGz7T0SBEWw4vLXzbQ\n6b6RoveIoO6TvPxlA63qs7z8ZQPF9F+SH22vbX8OQKf5Rtv+EgDNJ3X58wZaxWd1+fMGiuFvir8b\nvjp8J/tGy/6jAmRvhW8fwL3vVT+o3grfPoB7r/IpALI3tz8FoJN84/NV873hB8UnM3xzANtf8nb4\ndwmg3grfFEDJO8JPE0i9Ff4pAYL3pI8mkHor/HMCeO9JH00g9SafEsh7T/ppARBvp48UwJnelT5S\nACd7O31TAlnvKx9SQCd7B58KgPO+8iMFuPWe9E8F8BveWX7bAjzX9y4//Jve+fhsH6Ctv7n8PTzj\nvY/v9gEOHz58+PBX+6v/f/wPvnd54f3j6venE/yl769Xv7+j3x/o98/V32/o9+fl389Xnx+g5x/o\n+Qt6/oOeP6HnX+j5G3z+h54/ouefV5/foufP6Pk3ev4On/+j9w/o/Qd6/4Le/6D3T/D9V67Y/ZsV\nQBq+s+8f0ftP+P41axXguP9NWgDuu/Cdfv+N3r/D9/9TAID+A7T/Ae2/gPs/0P4TtP8F7r9J3AIO\n9P+g/Udw/9Oygbf7r9D+L7j/DO1/Q/vv4P4/tP8Q7n9E+y/h/k+0/xTuf4X7b+H+X7T/+BPuf3aM\n8OHDhw8fPnz4w/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMDktMDNUMDQ6Mzg6\nNDAtMDQ6MDBo/hqzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTA5LTAzVDA0OjM4OjQwLTA0OjAw\nGaOiDwAAAABJRU5ErkJggg==\n",
"text/plain": [
"<IPython.core.display.Image object>"
]
@ -410,7 +409,32 @@
"tally.filters.append(energy_filter)\n",
"tally.scores = ['absorption', 'total']\n",
"tally.nuclides = [o16, h1]\n",
"tallies_file.append(tally)"
"tallies_file.append(tally)\n",
"\n",
"# Instantiate a tally mesh \n",
"mesh = openmc.Mesh(mesh_id=1)\n",
"mesh.type = 'regular'\n",
"mesh.dimension = [1, 1, 1]\n",
"mesh.lower_left = [-0.63, -0.63, -100.]\n",
"mesh.width = [1.26, 1.26, 200.]\n",
"mesh_filter = openmc.Filter(type='mesh', bins=[mesh.id])\n",
"mesh_filter.mesh = mesh\n",
"\n",
"# Instantiate thermal, fast, and total leakage tallies\n",
"leak = openmc.Tally(name='leakage')\n",
"leak.filters = [mesh_filter]\n",
"leak.scores = ['current']\n",
"tallies_file.append(leak)\n",
"\n",
"thermal_leak = openmc.Tally(name='thermal leakage')\n",
"thermal_leak.filters = [mesh_filter, openmc.Filter(type='energy', bins=[0., 0.625e-6])]\n",
"thermal_leak.scores = ['current']\n",
"tallies_file.append(thermal_leak)\n",
"\n",
"fast_leak = openmc.Tally(name='fast leakage')\n",
"fast_leak.filters = [mesh_filter, openmc.Filter(type='energy', bins=[0.625e-6, 20.])]\n",
"fast_leak.scores = ['current']\n",
"tallies_file.append(fast_leak)"
]
},
{
@ -484,12 +508,12 @@
"outputs": [],
"source": [
"# Instantiate energy filter to illustrate Tally slicing\n",
"energy_filter = openmc.Filter(type='energy', bins=np.logspace(np.log10(1e-8), np.log10(20), 10))\n",
"fine_energy_filter = openmc.Filter(type='energy', bins=np.logspace(np.log10(1e-8), np.log10(20), 10))\n",
"\n",
"# Instantiate flux Tally in moderator and fuel\n",
"tally = openmc.Tally(name='need-to-slice')\n",
"tally.filters = [openmc.Filter(type='cell', bins=[fuel_cell.id, moderator_cell.id])]\n",
"tally.filters.append(energy_filter)\n",
"tally.filters.append(fine_energy_filter)\n",
"tally.scores = ['nu-fission', 'scatter']\n",
"tally.nuclides = [h1, u238]\n",
"tallies_file.append(tally)"
@ -526,24 +550,39 @@
"name": "stdout",
"output_type": "stream",
"text": [
"rm: cannot remove 'statepoint.*': No such file or directory\n",
"\n",
" .d88888b. 888b d888 .d8888b.\n",
" d88P\" \"Y88b 8888b d8888 d88P Y88b\n",
" 888 888 88888b.d88888 888 888\n",
" 888 888 88888b. .d88b. 88888b. 888Y88888P888 888 \n",
" 888 888 888 \"88b d8P Y8b 888 \"88b 888 Y888P 888 888 \n",
" 888 888 888 888 88888888 888 888 888 Y8P 888 888 888\n",
" Y88b. .d88P 888 d88P Y8b. 888 888 888 \" 888 Y88b d88P\n",
" \"Y88888P\" 88888P\" \"Y8888 888 888 888 888 \"Y8888P\"\n",
"__________________888______________________________________________________\n",
" 888\n",
" 888\n",
" %%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%\n",
" %%%%%%%%%%%%%%%%%%%%%%%%\n",
" ############### %%%%%%%%%%%%%%%%%%%%%%%%\n",
" ################## %%%%%%%%%%%%%%%%%%%%%%%\n",
" ################### %%%%%%%%%%%%%%%%%%%%%%%\n",
" #################### %%%%%%%%%%%%%%%%%%%%%%\n",
" ##################### %%%%%%%%%%%%%%%%%%%%%\n",
" ###################### %%%%%%%%%%%%%%%%%%%%\n",
" ####################### %%%%%%%%%%%%%%%%%%\n",
" ####################### %%%%%%%%%%%%%%%%%\n",
" ###################### %%%%%%%%%%%%%%%%%\n",
" #################### %%%%%%%%%%%%%%%%%\n",
" ################# %%%%%%%%%%%%%%%%%\n",
" ############### %%%%%%%%%%%%%%%%\n",
" ############ %%%%%%%%%%%%%%%\n",
" ######## %%%%%%%%%%%%%%\n",
" %%%%%%%%%%%\n",
"\n",
" Copyright: 2011-2016 Massachusetts Institute of Technology\n",
" License: http://openmc.readthedocs.io/en/latest/license.html\n",
" Version: 0.7.1\n",
" Git SHA1: 3d68c07625e33cd64188df03ee03e9c31b3d4b74\n",
" Date/Time: 2016-07-22 21:39:46\n",
" | The OpenMC Monte Carlo Code\n",
" Copyright | 2011-2016 Massachusetts Institute of Technology\n",
" License | http://openmc.readthedocs.io/en/latest/license.html\n",
" Version | 0.8.0\n",
" Git SHA1 | 623b705a399f16c8e5063732bc6e6a357611542d\n",
" Date/Time | 2016-09-03 04:38:41\n",
" OpenMP Threads | 4\n",
"\n",
" ===========================================================================\n",
" ========================> INITIALIZATION <=========================\n",
@ -553,13 +592,13 @@
" Reading geometry XML file...\n",
" Reading cross sections XML file...\n",
" Reading materials XML file...\n",
" Reading U235.71c from /home/romano/openmc/data/nndc_hdf5/U235_71c.h5\n",
" Reading U238.71c from /home/romano/openmc/data/nndc_hdf5/U238_71c.h5\n",
" Reading O16.71c from /home/romano/openmc/data/nndc_hdf5/O16_71c.h5\n",
" Reading H1.71c from /home/romano/openmc/data/nndc_hdf5/H1_71c.h5\n",
" Reading B10.71c from /home/romano/openmc/data/nndc_hdf5/B10_71c.h5\n",
" Reading Zr90.71c from /home/romano/openmc/data/nndc_hdf5/Zr90_71c.h5\n",
" Maximum neutron transport energy: 20.0000 MeV for U235.71c\n",
" Reading U235 from /opt/xsdata/nndc_new/U235.h5\n",
" Reading U238 from /opt/xsdata/nndc_new/U238.h5\n",
" Reading O16 from /opt/xsdata/nndc_new/O16.h5\n",
" Reading H1 from /opt/xsdata/nndc_new/H1.h5\n",
" Reading B10 from /opt/xsdata/nndc_new/B10.h5\n",
" Reading Zr90 from /opt/xsdata/nndc_new/Zr90.h5\n",
" Maximum neutron transport energy: 20.0000 MeV for U235\n",
" Reading tallies XML file...\n",
" Building neighboring cells lists for each surface...\n",
" Initializing source particles...\n",
@ -570,26 +609,26 @@
"\n",
" Bat./Gen. k Average k \n",
" ========= ======== ==================== \n",
" 1/1 1.03471 \n",
" 2/1 1.03257 \n",
" 3/1 1.00600 \n",
" 4/1 1.04547 \n",
" 5/1 1.02287 \n",
" 6/1 1.05752 \n",
" 7/1 1.04283 1.05017 +/- 0.00734\n",
" 8/1 1.05189 1.05074 +/- 0.00428\n",
" 9/1 1.01645 1.04217 +/- 0.00909\n",
" 10/1 1.04978 1.04369 +/- 0.00721\n",
" 11/1 1.03459 1.04218 +/- 0.00608\n",
" 12/1 1.04019 1.04189 +/- 0.00514\n",
" 13/1 1.05985 1.04414 +/- 0.00499\n",
" 14/1 1.02111 1.04158 +/- 0.00509\n",
" 15/1 1.04774 1.04219 +/- 0.00459\n",
" 16/1 1.00733 1.03902 +/- 0.00523\n",
" 17/1 1.02224 1.03763 +/- 0.00497\n",
" 18/1 1.03263 1.03724 +/- 0.00459\n",
" 19/1 1.01611 1.03573 +/- 0.00451\n",
" 20/1 1.04692 1.03648 +/- 0.00426\n",
" 1/1 0.96168 \n",
" 2/1 0.96651 \n",
" 3/1 1.00678 \n",
" 4/1 0.98773 \n",
" 5/1 1.01883 \n",
" 6/1 1.02959 \n",
" 7/1 0.99859 1.01409 +/- 0.01550\n",
" 8/1 1.03441 1.02086 +/- 0.01123\n",
" 9/1 1.06097 1.03089 +/- 0.01279\n",
" 10/1 1.06094 1.03690 +/- 0.01159\n",
" 11/1 1.04687 1.03856 +/- 0.00961\n",
" 12/1 1.02982 1.03731 +/- 0.00821\n",
" 13/1 1.03520 1.03705 +/- 0.00712\n",
" 14/1 0.99508 1.03239 +/- 0.00782\n",
" 15/1 1.03973 1.03312 +/- 0.00703\n",
" 16/1 1.03807 1.03357 +/- 0.00638\n",
" 17/1 1.03091 1.03335 +/- 0.00583\n",
" 18/1 1.01421 1.03188 +/- 0.00556\n",
" 19/1 0.99339 1.02913 +/- 0.00583\n",
" 20/1 1.04827 1.03040 +/- 0.00558\n",
" Creating state point statepoint.20.h5...\n",
"\n",
" ===========================================================================\n",
@ -599,28 +638,28 @@
"\n",
" =======================> TIMING STATISTICS <=======================\n",
"\n",
" Total time for initialization = 3.5600E-01 seconds\n",
" Reading cross sections = 2.3400E-01 seconds\n",
" Total time in simulation = 1.8333E+01 seconds\n",
" Time in transport only = 1.8325E+01 seconds\n",
" Time in inactive batches = 2.6950E+00 seconds\n",
" Time in active batches = 1.5638E+01 seconds\n",
" Total time for initialization = 3.8900E-01 seconds\n",
" Reading cross sections = 2.7000E-01 seconds\n",
" Total time in simulation = 4.6960E+00 seconds\n",
" Time in transport only = 4.6760E+00 seconds\n",
" Time in inactive batches = 6.6400E-01 seconds\n",
" Time in active batches = 4.0320E+00 seconds\n",
" Time synchronizing fission bank = 1.0000E-03 seconds\n",
" Sampling source sites = 0.0000E+00 seconds\n",
" SEND/RECV source sites = 1.0000E-03 seconds\n",
" Sampling source sites = 1.0000E-03 seconds\n",
" SEND/RECV source sites = 0.0000E+00 seconds\n",
" Time accumulating tallies = 0.0000E+00 seconds\n",
" Total time for finalization = 1.0000E-03 seconds\n",
" Total time elapsed = 1.8711E+01 seconds\n",
" Calculation Rate (inactive) = 4638.22 neutrons/second\n",
" Calculation Rate (active) = 2398.00 neutrons/second\n",
" Total time elapsed = 5.0960E+00 seconds\n",
" Calculation Rate (inactive) = 18825.3 neutrons/second\n",
" Calculation Rate (active) = 9300.60 neutrons/second\n",
"\n",
" ============================> RESULTS <============================\n",
"\n",
" k-effective (Collision) = 1.03296 +/- 0.00669\n",
" k-effective (Track-length) = 1.03648 +/- 0.00426\n",
" k-effective (Absorption) = 1.03431 +/- 0.00702\n",
" Combined k-effective = 1.03621 +/- 0.00456\n",
" Leakage Fraction = 0.00000 +/- 0.00000\n",
" k-effective (Collision) = 1.02791 +/- 0.00553\n",
" k-effective (Track-length) = 1.03040 +/- 0.00558\n",
" k-effective (Absorption) = 1.02011 +/- 0.00491\n",
" Combined k-effective = 1.02461 +/- 0.00398\n",
" Leakage Fraction = 0.01677 +/- 0.00109\n",
"\n"
]
},
@ -674,8 +713,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"We have a tally of the total fission rate and the total absorption rate, so we can calculate k-infinity as:\n",
"$$k_\\infty = \\frac{\\langle \\nu \\Sigma_f \\phi \\rangle}{\\langle \\Sigma_a \\phi \\rangle}$$\n",
"We have a tally of the total fission rate and the total absorption rate, so we can calculate k-eff as:\n",
"$$k_{eff} = \\frac{\\langle \\nu \\Sigma_f \\phi \\rangle}{\\langle \\Sigma_a \\phi \\rangle + \\langle L \\rangle}$$\n",
"In this notation, $\\langle \\cdot \\rangle^a_b$ represents an OpenMC that is integrated over region $a$ and energy range $b$. If $a$ or $b$ is not reported, it means the value represents an integral over all space or all energy, respectively."
]
},
@ -704,17 +743,17 @@
" <tr>\n",
" <th>0</th>\n",
" <td>total</td>\n",
" <td>(nu-fission / absorption)</td>\n",
" <td>1.038387</td>\n",
" <td>0.006141</td>\n",
" <td>(nu-fission / (absorption + current))</td>\n",
" <td>1.02431</td>\n",
" <td>0.00704</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" nuclide score mean std. dev.\n",
"0 total (nu-fission / absorption) 1.04e+00 6.14e-03"
" nuclide score mean std. dev.\n",
"0 total (nu-fission / (absorption + current)) 1.02e+00 7.04e-03"
]
},
"execution_count": 24,
@ -723,10 +762,17 @@
}
],
"source": [
"# Compute k-infinity using tally arithmetic\n",
"# Get the fission and absorption rate tallies\n",
"fiss_rate = sp.get_tally(name='fiss. rate')\n",
"abs_rate = sp.get_tally(name='abs. rate')\n",
"keff = fiss_rate / abs_rate\n",
"\n",
"# Get the leakage tally\n",
"leak = sp.get_tally(name='leakage')\n",
"leak = leak.summation(filter_type='surface', remove_filter=True)\n",
"leak = leak.summation(filter_type='mesh', remove_filter=True)\n",
"\n",
"# Compute k-infinity using tally arithmetic\n",
"keff = fiss_rate / (abs_rate + leak)\n",
"keff.get_pandas_dataframe()"
]
},
@ -734,9 +780,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice that even though the neutron production rate and absorption rate are separate tallies, we still get a first-order estimate of the uncertainty on the quotient of them automatically!\n",
"Notice that even though the neutron production rate, absorption rate, and current are separate tallies, we still get a first-order estimate of the uncertainty on the quotient of them automatically!\n",
"\n",
"Often in textbooks you'll see k-infinity represented using the four-factor formula $$k_\\infty = p \\epsilon f \\eta.$$ Let's analyze each of these factors, starting with the resonance escape probability which is defined as $$p=\\frac{\\langle\\Sigma_a\\phi\\rangle_T}{\\langle\\Sigma_a\\phi\\rangle}$$ where the subscript $T$ means thermal energies."
"Often in textbooks you'll see k-eff represented using the six-factor formula $$k_{eff} = p \\epsilon f \\eta P_{FNL} P_{TNL}.$$ Let's analyze each of these factors, starting with the resonance escape probability which is defined as $$p=\\frac{\\langle\\Sigma_a\\phi\\rangle_T + \\langle L \\rangle_T}{\\langle\\Sigma_a\\phi\\rangle + \\langle L \\rangle_T}$$ where the subscript $T$ means thermal energies."
]
},
{
@ -768,17 +814,20 @@
" <td>0.0</td>\n",
" <td>6.250000e-07</td>\n",
" <td>total</td>\n",
" <td>absorption</td>\n",
" <td>0.693337</td>\n",
" <td>0.004109</td>\n",
" <td>(absorption + current)</td>\n",
" <td>0.695303</td>\n",
" <td>0.005091</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" energy low [MeV] energy high [MeV] nuclide score mean std. dev.\n",
"0 0.00e+00 6.25e-07 total absorption 6.93e-01 4.11e-03"
" energy low [MeV] energy high [MeV] nuclide score \\\n",
"0 0.00e+00 6.25e-07 total (absorption + current) \n",
"\n",
" mean std. dev. \n",
"0 6.95e-01 5.09e-03 "
]
},
"execution_count": 25,
@ -789,7 +838,10 @@
"source": [
"# Compute resonance escape probability using tally arithmetic\n",
"therm_abs_rate = sp.get_tally(name='therm. abs. rate')\n",
"res_esc = therm_abs_rate / abs_rate\n",
"thermal_leak = sp.get_tally(name='thermal leakage')\n",
"thermal_leak = thermal_leak.summation(filter_type='surface', remove_filter=True)\n",
"thermal_leak = thermal_leak.summation(filter_type='mesh', remove_filter=True)\n",
"res_esc = (therm_abs_rate + thermal_leak) / (abs_rate + thermal_leak)\n",
"res_esc.get_pandas_dataframe()"
]
},
@ -831,8 +883,8 @@
" <td>6.250000e-07</td>\n",
" <td>total</td>\n",
" <td>nu-fission</td>\n",
" <td>1.203042</td>\n",
" <td>0.0076</td>\n",
" <td>1.202639</td>\n",
" <td>0.010348</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
@ -840,7 +892,7 @@
],
"text/plain": [
" energy low [MeV] energy high [MeV] nuclide score mean std. dev.\n",
"0 0.00e+00 6.25e-07 total nu-fission 1.20e+00 7.60e-03"
"0 0.00e+00 6.25e-07 total nu-fission 1.20e+00 1.03e-02"
]
},
"execution_count": 26,
@ -896,8 +948,8 @@
" <td>10000</td>\n",
" <td>total</td>\n",
" <td>absorption</td>\n",
" <td>0.748413</td>\n",
" <td>0.004723</td>\n",
" <td>0.749349</td>\n",
" <td>0.006731</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
@ -905,10 +957,10 @@
],
"text/plain": [
" energy low [MeV] energy high [MeV] cell nuclide score mean \\\n",
"0 0.00e+00 6.25e-07 10000 total absorption 7.48e-01 \n",
"0 0.00e+00 6.25e-07 10000 total absorption 7.49e-01 \n",
"\n",
" std. dev. \n",
"0 4.72e-03 "
"0 6.73e-03 "
]
},
"execution_count": 27,
@ -927,7 +979,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The final factor is the number of fission neutrons produced per absorption in fuel, calculated as $$\\eta = \\frac{\\langle \\nu\\Sigma_f\\phi \\rangle_T}{\\langle \\Sigma_a \\phi \\rangle^F_T}$$"
"The next factor is the number of fission neutrons produced per absorption in fuel, calculated as $$\\eta = \\frac{\\langle \\nu\\Sigma_f\\phi \\rangle_T}{\\langle \\Sigma_a \\phi \\rangle^F_T}$$"
]
},
{
@ -962,8 +1014,8 @@
" <td>10000</td>\n",
" <td>total</td>\n",
" <td>(nu-fission / absorption)</td>\n",
" <td>1.663385</td>\n",
" <td>0.011253</td>\n",
" <td>1.663736</td>\n",
" <td>0.015707</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
@ -974,7 +1026,7 @@
"0 0.00e+00 6.25e-07 10000 total \n",
"\n",
" score mean std. dev. \n",
"0 (nu-fission / absorption) 1.66e+00 1.13e-02 "
"0 (nu-fission / absorption) 1.66e+00 1.57e-02 "
]
},
"execution_count": 28,
@ -992,7 +1044,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can calculate $k_\\infty$ using the product of the factors form the four-factor formula."
"There are two leakage factors to account for fast and thermal leakage. The fast non-leakage probability is computed as $$P_{FNL} = \\frac{\\langle \\Sigma_a\\phi \\rangle + \\langle L \\rangle_T}{\\langle \\Sigma_a \\phi \\rangle + \\langle L \\rangle}$$"
]
},
{
@ -1001,6 +1053,130 @@
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>energy low [MeV]</th>\n",
" <th>energy high [MeV]</th>\n",
" <th>nuclide</th>\n",
" <th>score</th>\n",
" <th>mean</th>\n",
" <th>std. dev.</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0.0</td>\n",
" <td>6.250000e-07</td>\n",
" <td>total</td>\n",
" <td>(absorption + current)</td>\n",
" <td>0.985102</td>\n",
" <td>0.005855</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" energy low [MeV] energy high [MeV] nuclide score \\\n",
"0 0.00e+00 6.25e-07 total (absorption + current) \n",
"\n",
" mean std. dev. \n",
"0 9.85e-01 5.86e-03 "
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p_fnl = (abs_rate + thermal_leak) / (abs_rate + leak)\n",
"p_fnl.get_pandas_dataframe()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The final factor is the thermal non-leakage probability and is computed as $$P_{TNL} = \\frac{\\langle \\Sigma_a\\phi \\rangle_T}{\\langle \\Sigma_a \\phi \\rangle_T + \\langle L \\rangle_T}$$"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>energy low [MeV]</th>\n",
" <th>energy high [MeV]</th>\n",
" <th>nuclide</th>\n",
" <th>score</th>\n",
" <th>mean</th>\n",
" <th>std. dev.</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0.0</td>\n",
" <td>6.250000e-07</td>\n",
" <td>total</td>\n",
" <td>(absorption / (absorption + current))</td>\n",
" <td>0.997407</td>\n",
" <td>0.008492</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" energy low [MeV] energy high [MeV] nuclide \\\n",
"0 0.00e+00 6.25e-07 total \n",
"\n",
" score mean std. dev. \n",
"0 (absorption / (absorption + current)) 9.97e-01 8.49e-03 "
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p_tnl = therm_abs_rate / (therm_abs_rate + thermal_leak)\n",
"p_tnl.get_pandas_dataframe()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can calculate $k_{eff}$ using the product of the factors form the four-factor formula."
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
@ -1026,9 +1202,9 @@
" <td>6.250000e-07</td>\n",
" <td>10000</td>\n",
" <td>total</td>\n",
" <td>(((absorption * nu-fission) * absorption) * (n...</td>\n",
" <td>1.038387</td>\n",
" <td>0.01316</td>\n",
" <td>((((((absorption + current) * nu-fission) * ab...</td>\n",
" <td>1.02431</td>\n",
" <td>0.02062</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
@ -1039,16 +1215,16 @@
"0 0.00e+00 6.25e-07 10000 total \n",
"\n",
" score mean std. dev. \n",
"0 (((absorption * nu-fission) * absorption) * (n... 1.04e+00 1.32e-02 "
"0 ((((((absorption + current) * nu-fission) * ab... 1.02e+00 2.06e-02 "
]
},
"execution_count": 29,
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"keff = res_esc * fast_fiss * therm_util * eta\n",
"keff = res_esc * fast_fiss * therm_util * eta * p_fnl * p_tnl\n",
"keff.get_pandas_dataframe()"
]
},
@ -1063,7 +1239,7 @@
},
{
"cell_type": "code",
"execution_count": 30,
"execution_count": 32,
"metadata": {
"collapsed": false,
"scrolled": true
@ -1079,7 +1255,7 @@
},
{
"cell_type": "code",
"execution_count": 31,
"execution_count": 33,
"metadata": {
"collapsed": false
},
@ -1109,8 +1285,8 @@
" <td>6.250000e-07</td>\n",
" <td>(U238 / total)</td>\n",
" <td>(nu-fission / flux)</td>\n",
" <td>6.636968e-07</td>\n",
" <td>4.132875e-09</td>\n",
" <td>6.662479e-07</td>\n",
" <td>6.039323e-09</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
@ -1119,8 +1295,8 @@
" <td>6.250000e-07</td>\n",
" <td>(U238 / total)</td>\n",
" <td>(scatter / flux)</td>\n",
" <td>2.099856e-01</td>\n",
" <td>1.232455e-03</td>\n",
" <td>2.099897e-01</td>\n",
" <td>1.843251e-03</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
@ -1129,8 +1305,8 @@
" <td>6.250000e-07</td>\n",
" <td>(U235 / total)</td>\n",
" <td>(nu-fission / flux)</td>\n",
" <td>3.552458e-01</td>\n",
" <td>2.252681e-03</td>\n",
" <td>3.568130e-01</td>\n",
" <td>3.255144e-03</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
@ -1139,8 +1315,8 @@
" <td>6.250000e-07</td>\n",
" <td>(U235 / total)</td>\n",
" <td>(scatter / flux)</td>\n",
" <td>5.554345e-03</td>\n",
" <td>3.265385e-05</td>\n",
" <td>5.555326e-03</td>\n",
" <td>4.893022e-05</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
@ -1149,8 +1325,8 @@
" <td>2.000000e+01</td>\n",
" <td>(U238 / total)</td>\n",
" <td>(nu-fission / flux)</td>\n",
" <td>7.126668e-03</td>\n",
" <td>5.296883e-05</td>\n",
" <td>7.215044e-03</td>\n",
" <td>4.968448e-05</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
@ -1159,8 +1335,8 @@
" <td>2.000000e+01</td>\n",
" <td>(U238 / total)</td>\n",
" <td>(scatter / flux)</td>\n",
" <td>2.277460e-01</td>\n",
" <td>1.003558e-03</td>\n",
" <td>2.273966e-01</td>\n",
" <td>8.969811e-04</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
@ -1169,8 +1345,8 @@
" <td>2.000000e+01</td>\n",
" <td>(U235 / total)</td>\n",
" <td>(nu-fission / flux)</td>\n",
" <td>8.010911e-03</td>\n",
" <td>6.802256e-05</td>\n",
" <td>7.969615e-03</td>\n",
" <td>5.374119e-05</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
@ -1179,8 +1355,8 @@
" <td>2.000000e+01</td>\n",
" <td>(U235 / total)</td>\n",
" <td>(scatter / flux)</td>\n",
" <td>3.367794e-03</td>\n",
" <td>1.443644e-05</td>\n",
" <td>3.362798e-03</td>\n",
" <td>1.286767e-05</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
@ -1198,17 +1374,17 @@
"7 10000 6.25e-07 2.00e+01 (U235 / total) \n",
"\n",
" score mean std. dev. \n",
"0 (nu-fission / flux) 6.64e-07 4.13e-09 \n",
"1 (scatter / flux) 2.10e-01 1.23e-03 \n",
"2 (nu-fission / flux) 3.55e-01 2.25e-03 \n",
"3 (scatter / flux) 5.55e-03 3.27e-05 \n",
"4 (nu-fission / flux) 7.13e-03 5.30e-05 \n",
"5 (scatter / flux) 2.28e-01 1.00e-03 \n",
"6 (nu-fission / flux) 8.01e-03 6.80e-05 \n",
"7 (scatter / flux) 3.37e-03 1.44e-05 "
"0 (nu-fission / flux) 6.66e-07 6.04e-09 \n",
"1 (scatter / flux) 2.10e-01 1.84e-03 \n",
"2 (nu-fission / flux) 3.57e-01 3.26e-03 \n",
"3 (scatter / flux) 5.56e-03 4.89e-05 \n",
"4 (nu-fission / flux) 7.22e-03 4.97e-05 \n",
"5 (scatter / flux) 2.27e-01 8.97e-04 \n",
"6 (nu-fission / flux) 7.97e-03 5.37e-05 \n",
"7 (scatter / flux) 3.36e-03 1.29e-05 "
]
},
"execution_count": 31,
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
@ -1227,7 +1403,7 @@
},
{
"cell_type": "code",
"execution_count": 32,
"execution_count": 34,
"metadata": {
"collapsed": false
},
@ -1236,11 +1412,11 @@
"name": "stdout",
"output_type": "stream",
"text": [
"[[[ 6.63696783e-07]\n",
" [ 3.55245846e-01]]\n",
"[[[ 6.66247898e-07]\n",
" [ 3.56812954e-01]]\n",
"\n",
" [[ 7.12666800e-03]\n",
" [ 8.01091088e-03]]]\n"
" [[ 7.21504433e-03]\n",
" [ 7.96961502e-03]]]\n"
]
}
],
@ -1259,7 +1435,7 @@
},
{
"cell_type": "code",
"execution_count": 33,
"execution_count": 35,
"metadata": {
"collapsed": false
},
@ -1268,9 +1444,9 @@
"name": "stdout",
"output_type": "stream",
"text": [
"[[[ 0.00555435]]\n",
"[[[ 0.00555533]]\n",
"\n",
" [[ 0.00336779]]]\n"
" [[ 0.0033628 ]]]\n"
]
}
],
@ -1283,7 +1459,7 @@
},
{
"cell_type": "code",
"execution_count": 34,
"execution_count": 36,
"metadata": {
"collapsed": false
},
@ -1292,8 +1468,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"[[[ 0.22774598]\n",
" [ 0.00336779]]]\n"
"[[[ 0.22739657]\n",
" [ 0.0033628 ]]]\n"
]
}
],
@ -1314,7 +1490,7 @@
},
{
"cell_type": "code",
"execution_count": 35,
"execution_count": 37,
"metadata": {
"collapsed": false
},
@ -1345,7 +1521,7 @@
" <td>U238</td>\n",
" <td>nu-fission</td>\n",
" <td>0.000002</td>\n",
" <td>7.473789e-09</td>\n",
" <td>1.057199e-08</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
@ -1354,8 +1530,8 @@
" <td>6.250000e-07</td>\n",
" <td>U235</td>\n",
" <td>nu-fission</td>\n",
" <td>0.861547</td>\n",
" <td>4.131310e-03</td>\n",
" <td>0.856784</td>\n",
" <td>5.730044e-03</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
@ -1364,8 +1540,8 @@
" <td>2.000000e+01</td>\n",
" <td>U238</td>\n",
" <td>nu-fission</td>\n",
" <td>0.082356</td>\n",
" <td>5.560461e-04</td>\n",
" <td>0.082495</td>\n",
" <td>5.176027e-04</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
@ -1374,8 +1550,8 @@
" <td>2.000000e+01</td>\n",
" <td>U235</td>\n",
" <td>nu-fission</td>\n",
" <td>0.092574</td>\n",
" <td>7.315442e-04</td>\n",
" <td>0.091123</td>\n",
" <td>5.574052e-04</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
@ -1383,19 +1559,19 @@
],
"text/plain": [
" cell energy low [MeV] energy high [MeV] nuclide score mean \\\n",
"0 10000 0.00e+00 6.25e-07 U238 nu-fission 1.61e-06 \n",
"1 10000 0.00e+00 6.25e-07 U235 nu-fission 8.62e-01 \n",
"2 10000 6.25e-07 2.00e+01 U238 nu-fission 8.24e-02 \n",
"3 10000 6.25e-07 2.00e+01 U235 nu-fission 9.26e-02 \n",
"0 10000 0.00e+00 6.25e-07 U238 nu-fission 1.60e-06 \n",
"1 10000 0.00e+00 6.25e-07 U235 nu-fission 8.57e-01 \n",
"2 10000 6.25e-07 2.00e+01 U238 nu-fission 8.25e-02 \n",
"3 10000 6.25e-07 2.00e+01 U235 nu-fission 9.11e-02 \n",
"\n",
" std. dev. \n",
"0 7.47e-09 \n",
"1 4.13e-03 \n",
"2 5.56e-04 \n",
"3 7.32e-04 "
"0 1.06e-08 \n",
"1 5.73e-03 \n",
"2 5.18e-04 \n",
"3 5.57e-04 "
]
},
"execution_count": 35,
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
@ -1408,7 +1584,7 @@
},
{
"cell_type": "code",
"execution_count": 36,
"execution_count": 38,
"metadata": {
"collapsed": false
},
@ -1438,8 +1614,8 @@
" <td>1.080060e-07</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>4.599225</td>\n",
" <td>0.015973</td>\n",
" <td>4.547947</td>\n",
" <td>0.028000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
@ -1448,8 +1624,8 @@
" <td>1.166529e-06</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>2.037260</td>\n",
" <td>0.011236</td>\n",
" <td>2.003068</td>\n",
" <td>0.008587</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
@ -1458,8 +1634,8 @@
" <td>1.259921e-05</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>1.662552</td>\n",
" <td>0.010280</td>\n",
" <td>1.647225</td>\n",
" <td>0.011136</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
@ -1468,8 +1644,8 @@
" <td>1.360790e-04</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>1.872201</td>\n",
" <td>0.012136</td>\n",
" <td>1.831367</td>\n",
" <td>0.010196</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
@ -1478,8 +1654,8 @@
" <td>1.469734e-03</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>2.080459</td>\n",
" <td>0.013155</td>\n",
" <td>2.039613</td>\n",
" <td>0.008059</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
@ -1488,8 +1664,8 @@
" <td>1.587401e-02</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>2.154996</td>\n",
" <td>0.011975</td>\n",
" <td>2.137523</td>\n",
" <td>0.012885</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
@ -1498,8 +1674,8 @@
" <td>1.714488e-01</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>2.218740</td>\n",
" <td>0.008528</td>\n",
" <td>2.170725</td>\n",
" <td>0.012669</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
@ -1508,8 +1684,8 @@
" <td>1.851749e+00</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>2.010517</td>\n",
" <td>0.009187</td>\n",
" <td>2.002724</td>\n",
" <td>0.010768</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
@ -1518,8 +1694,8 @@
" <td>2.000000e+01</td>\n",
" <td>H1</td>\n",
" <td>scatter</td>\n",
" <td>0.372022</td>\n",
" <td>0.003196</td>\n",
" <td>0.371624</td>\n",
" <td>0.002959</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
@ -1527,29 +1703,29 @@
],
"text/plain": [
" cell energy low [MeV] energy high [MeV] nuclide score mean \\\n",
"0 10002 1.00e-08 1.08e-07 H1 scatter 4.60e+00 \n",
"1 10002 1.08e-07 1.17e-06 H1 scatter 2.04e+00 \n",
"2 10002 1.17e-06 1.26e-05 H1 scatter 1.66e+00 \n",
"3 10002 1.26e-05 1.36e-04 H1 scatter 1.87e+00 \n",
"4 10002 1.36e-04 1.47e-03 H1 scatter 2.08e+00 \n",
"5 10002 1.47e-03 1.59e-02 H1 scatter 2.15e+00 \n",
"6 10002 1.59e-02 1.71e-01 H1 scatter 2.22e+00 \n",
"7 10002 1.71e-01 1.85e+00 H1 scatter 2.01e+00 \n",
"0 10002 1.00e-08 1.08e-07 H1 scatter 4.55e+00 \n",
"1 10002 1.08e-07 1.17e-06 H1 scatter 2.00e+00 \n",
"2 10002 1.17e-06 1.26e-05 H1 scatter 1.65e+00 \n",
"3 10002 1.26e-05 1.36e-04 H1 scatter 1.83e+00 \n",
"4 10002 1.36e-04 1.47e-03 H1 scatter 2.04e+00 \n",
"5 10002 1.47e-03 1.59e-02 H1 scatter 2.14e+00 \n",
"6 10002 1.59e-02 1.71e-01 H1 scatter 2.17e+00 \n",
"7 10002 1.71e-01 1.85e+00 H1 scatter 2.00e+00 \n",
"8 10002 1.85e+00 2.00e+01 H1 scatter 3.72e-01 \n",
"\n",
" std. dev. \n",
"0 1.60e-02 \n",
"1 1.12e-02 \n",
"2 1.03e-02 \n",
"3 1.21e-02 \n",
"4 1.32e-02 \n",
"5 1.20e-02 \n",
"6 8.53e-03 \n",
"7 9.19e-03 \n",
"8 3.20e-03 "
"0 2.80e-02 \n",
"1 8.59e-03 \n",
"2 1.11e-02 \n",
"3 1.02e-02 \n",
"4 8.06e-03 \n",
"5 1.29e-02 \n",
"6 1.27e-02 \n",
"7 1.08e-02 \n",
"8 2.96e-03 "
]
},
"execution_count": 36,
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}

View file

@ -27,6 +27,8 @@ Example Jupyter Notebooks
examples/mgxs-part-ii
examples/mgxs-part-iii
examples/mgxs-part-iv
examples/mdgxs-part-i
examples/mdgxs-part-ii
examples/nuclear-data
------------------------------------
@ -284,6 +286,19 @@ Multi-group Cross Sections
openmc.mgxs.TotalXS
openmc.mgxs.TransportXS
Multi-delayed-group Cross Sections
----------------------------------
.. autosummary::
:toctree: generated
:nosignatures:
:template: myclassinherit.rst
openmc.mgxs.MDGXS
openmc.mgxs.ChiDelayed
openmc.mgxs.DelayedNuFissionXS
openmc.mgxs.Beta
Multi-group Cross Section Libraries
-----------------------------------
@ -319,6 +334,7 @@ Functions
:nosignatures:
openmc.model.create_triso_lattice
openmc.model.pack_trisos
--------------------------------------------
:mod:`openmc.data` -- Nuclear Data Interface
@ -348,6 +364,7 @@ Core Classes
openmc.data.Tabulated1D
openmc.data.ThermalScattering
openmc.data.CoherentElastic
openmc.data.FissionEnergyRelease
Angle-Energy Distributions
--------------------------
@ -381,21 +398,22 @@ Classes
+++++++
.. autosummary::
:toctree: generated
:nosignatures:
:template: myclass.rst
:toctree: generated
:nosignatures:
:template: myclass.rst
openmc.data.ace.Library
openmc.data.ace.Table
openmc.data.ace.Library
openmc.data.ace.Table
Functions
+++++++++
.. autosummary::
:toctree: generated
:nosignatures:
:toctree: generated
:nosignatures:
openmc.data.ace.ascii_to_binary
openmc.data.ace.ascii_to_binary
openmc.data.write_compact_458_library
.. _Jupyter: https://jupyter.org/
.. _NumPy: http://www.numpy.org/

View file

@ -281,6 +281,8 @@ based on the recommended value in LA-UR-14-24530_.
.. note:: This element is not used in the multi-group :ref:`energy_mode`.
.. _multipole_library:
``<multipole_library>`` Element
-------------------------------
@ -290,8 +292,8 @@ OpenMC can use it for on-the-fly Doppler-broadening of resolved resonance range
cross sections. If this element is absent from the settings.xml file, the
:envvar:`OPENMC_MULTIPOLE_LIBRARY` environment variable will be used.
.. note:: The <use_windowed_multipole> element must also be set to "true"
for windowed multipole functionality.
.. note:: The :ref:`temperature_method` must also be set to "multipole" for
windowed multipole functionality.
``<max_order>`` Element
---------------------------
@ -395,19 +397,16 @@ attributes or sub-elements:
:scatterer:
An element with attributes/sub-elements called ``nuclide``, ``method``,
``xs_label``, ``xs_label_0K``, ``E_min``, and ``E_max``. The ``nuclide``
attribute is the name, as given by the ``name`` attribute within the
``nuclide`` sub-element of the ``material`` element in ``materials.xml``,
of the nuclide to which a resonance scattering treatment is to be applied.
``E_min``, and ``E_max``. The ``nuclide`` attribute is the name, as given
by the ``name`` attribute within the ``nuclide`` sub-element of the
``material`` element in ``materials.xml``, of the nuclide to which a
resonance scattering treatment is to be applied.
The ``method`` attribute gives the type of resonance scattering treatment
that is to be applied to the ``nuclide``. Acceptable inputs - none of
which are case-sensitive - for the ``method`` attribute are ``ARES``,
``CXS``, ``WCM``, and ``DBRC``. Descriptions of each of these methods
are documented here_. The ``xs_label`` attribute gives the label for the
cross section data of the ``nuclide`` at a given temperature. The
``xs_label_0K`` gives the label for the 0 K cross section data for the
``nuclide``. The ``E_min`` attribute gives the minimum energy above
which the ``method`` is applied. The ``E_max`` attribute gives the
are documented here_. The ``E_min`` attribute gives the minimum energy
above which the ``method`` is applied. The ``E_max`` attribute gives the
maximum energy below which the ``method`` is applied. One example would
be as follows:
@ -419,16 +418,12 @@ attributes or sub-elements:
<scatterer>
<nuclide>U-238</nuclide>
<method>ARES</method>
<xs_label>92238.72c</xs_label>
<xs_label_0K>92238.00c</xs_label_0K>
<E_min>5.0e-6</E_min>
<E_max>40.0e-6</E_max>
</scatterer>
<scatterer>
<nuclide>Pu-239</nuclide>
<method>dbrc</method>
<xs_label>94239.72c</xs_label>
<xs_label_0K>94239.00c</xs_label_0K>
<E_min>0.01e-6</E_min>
<E_max>210.0e-6</E_max>
</scatterer>
@ -714,6 +709,45 @@ survival biasing, otherwise known as implicit capture or absorption.
*Default*: false
.. _temperature_default:
``<temperature_default>`` Element
---------------------------------
The ``<temperature_default>`` element specifies a default temperature in Kelvin
that is to be applied to cells in the absence of an explicit cell temperature or
a material default temperature.
*Default*: 293.6 K
.. _temperature_method:
``<temperature_method>`` Element
--------------------------------
The ``<temperature_method>`` element has an accepted value of "nearest" or
"interpolation". A value of "nearest" indicates that for each cell, the nearest
temperature at which cross sections are given is to be applied, within a given
tolerance (see :ref:`temperature_tolerance`). A value of "multipole" indicates
that the windowed multipole method should be used to evaluate
temperature-dependent cross sections in the resolved resonance range (a
:ref:`windowed multipole library <multipole_library>` must also be available).
*Default*: "nearest"
.. _temperature_tolerance:
``<temperature_tolerance>`` Element
-----------------------------------
The ``<temperature_tolerance>`` element specifies a tolerance in Kelvin that is
to be applied when the "nearest" temperature method is used. For example, if a
cell temperature is 340 K and the tolerance is 15 K, then the closest
temperature in the range of 325 K to 355 K will be used to evaluate cross
sections.
*Default*: 10 K
``<threads>`` Element
---------------------
@ -836,6 +870,35 @@ displayed. This element takes the following attributes:
*Default*: 5
``<volume_calc>`` Element
-------------------------
The ``<volume_calc>`` element indicates that a stochastic volume calculation
should be run at the beginning of the simulation. This element has the following
sub-elements/attributes:
:cells:
The unique IDs of cells for which the volume should be estimated.
*Default*: None
:samples:
The number of samples used to estimate volumes.
*Default*: None
:lower_left:
The lower-left Cartesian coordinates of a bounding box that is used to
sample points within.
*Default*: None
:upper_right:
The upper-right Cartesian coordinates of a bounding box that is used to
sample points within.
*Default*: None
--------------------------------------
Geometry Specification -- geometry.xml
--------------------------------------
@ -1061,7 +1124,9 @@ Each ``<cell>`` element can have the following attributes or sub-elements:
specified for the "distributed temperature" feature. This will give each
unique instance of the cell its own temperature.
*Default*: The temperature of the coldest nuclide in the cell's material(s)
*Default*: If a material default temperature is supplied, it is used. In the
absence of a material default temperature, the :ref:`global default
temperature <temperature_default>` is used.
:rotation:
If the cell is filled with a universe, this element specifies the angles in
@ -1266,6 +1331,14 @@ Each ``material`` element can have the following attributes or sub-elements:
*Default*: ""
:temperature:
An element with no attributes which is used to set the default temperature
of the material in Kelvin.
*Default*: If a material default temperature is not given and a cell
temperature is not specified, the :ref:`global default temperature
<temperature_default>` is used.
:density:
An element with attributes/sub-elements called ``value`` and ``units``. The
``value`` attribute is the numeric value of the density while the ``units``
@ -1286,17 +1359,16 @@ Each ``material`` element can have the following attributes or sub-elements:
``nuclide``, ``element``, or ``sab`` quantity.
:nuclide:
An element with attributes/sub-elements called ``name``, ``xs``, and ``ao``
An element with attributes/sub-elements called ``name``, and ``ao``
or ``wo``. The ``name`` attribute is the name of the cross-section for a
desired nuclide while the ``xs`` attribute is the cross-section
identifier. Finally, the ``ao`` and ``wo`` attributes specify the atom or
desired nuclide. Finally, the ``ao`` and ``wo`` attributes specify the atom or
weight percent of that nuclide within the material, respectively. One
example would be as follows:
.. code-block:: xml
<nuclide name="H-1" xs="70c" ao="2.0" />
<nuclide name="O-16" xs="70c" ao="1.0" />
<nuclide name="H1" ao="2.0" />
<nuclide name="O16" ao="1.0" />
.. note:: If one nuclide is specified in atom percent, all others must also
be given in atom percent. The same applies for weight percentages.
@ -1320,11 +1392,10 @@ Each ``material`` element can have the following attributes or sub-elements:
Specifies that a natural element is present in the material. The natural
element is split up into individual isotopes based on `IUPAC Isotopic
Compositions of the Elements 2009`_. This element has
attributes/sub-elements called ``name``, ``xs``, and ``ao``. The ``name``
attribute is the atomic symbol of the element while the ``xs`` attribute is
the cross-section identifier. Finally, the ``ao`` attribute specifies the
atom percent of the element within the material, respectively. One example
would be as follows:
attributes/sub-elements called ``name``, and ``ao``. The ``name``
attribute is the atomic symbol of the element. Finally, the ``ao``
attribute specifies the atom percent of the element within the material,
respectively. One example would be as follows:
.. code-block:: xml
@ -1354,10 +1425,9 @@ Each ``material`` element can have the following attributes or sub-elements:
multi-group :ref:`energy_mode`.
:sab:
Associates an S(a,b) table with the material. This element has
attributes/sub-elements called ``name`` and ``xs``. The ``name`` attribute
is the name of the S(a,b) table that should be associated with the material,
and ``xs`` is the cross-section identifier for the table.
Associates an S(a,b) table with the material. This element has one
attribute/sub-element called ``name``. The ``name`` attribute
is the name of the S(a,b) table that should be associated with the material.
*Default*: None
@ -1368,14 +1438,13 @@ Each ``material`` element can have the following attributes or sub-elements:
recognizes that some multi-group libraries may be providing material
specific macroscopic cross sections instead of always providing nuclide
specific data like in the continuous-energy case. To that end, the
macroscopic element has attributes/sub-elements called ``name``, and ``xs``.
macroscopic element has one attribute/sub-element called ``name``.
The ``name`` attribute is the name of the cross-section for a
desired nuclide while the ``xs`` attribute is the cross-section
identifier. One example would be as follows:
desired nuclide. One example would be as follows:
.. code-block:: xml
<macroscopic name="UO2" xs="71c" />
<macroscopic name="UO2" />
.. note:: This element is only used in the multi-group :ref:`energy_mode`.
@ -1384,18 +1453,6 @@ Each ``material`` element can have the following attributes or sub-elements:
.. _IUPAC Isotopic Compositions of the Elements 2009:
http://pac.iupac.org/publications/pac/pdf/2011/pdf/8302x0397.pdf
``<default_xs>`` Element
------------------------
In some circumstances, the cross-section identifier may be the same for many or
all nuclides in a given problem. In this case, rather than specifying the
``xs=...`` attribute on every nuclide, a ``<default_xs>`` element can be used to
set the default cross-section identifier for any nuclide without an identifier
explicitly listed. This element has no attributes and accepts a 3-letter string
that indicates the default cross-section identifier, e.g. "70c".
*Default*: None
------------------------------------
Tallies Specification -- tallies.xml
------------------------------------
@ -1809,6 +1866,27 @@ The ``<tally>`` element accepts the following sub-elements:
| |:math:`\gamma`-rays are assumed to deposit their |
| |energy locally. Units are MeV per source particle. |
+----------------------+---------------------------------------------------+
|fission-q-prompt |The prompt fission energy production rate. This |
| |energy comes in the form of fission fragment |
| |nuclei, prompt neutrons, and prompt |
| |:math:`\gamma`-rays. This value depends on the |
| |incident energy and it requires that the nuclear |
| |data library contains the optional fission energy |
| |release data. Energy is assumed to be deposited |
| |locally. Units are MeV per source particle. |
+----------------------+---------------------------------------------------+
|fission-q-recoverable |The recoverable fission energy production rate. |
| |This energy comes in the form of fission fragment |
| |nuclei, prompt and delayed neutrons, prompt and |
| |delayed :math:`\gamma`-rays, and delayed |
| |:math:`\beta`-rays. This tally differs from the |
| |kappa-fission tally in that it is dependent on |
| |incident neutron energy and it requires that the |
| |nuclear data library contains the optional fission |
| |energy release data. Energy is assumed to be |
| |deposited locally. Units are MeV per source |
| |paticle. |
+----------------------+---------------------------------------------------+
.. note::
The ``analog`` estimator is actually identical to the ``collision``
@ -2055,8 +2133,8 @@ attributes or sub-elements. These are not used in "voxel" plots:
*Default*: None
:meshlines:
The ``meshlines`` sub-element allows for plotting the boundaries of
a tally mesh on top of a plot. Only one ``meshlines`` element is allowed per
The ``meshlines`` sub-element allows for plotting the boundaries of a
regular mesh on top of a plot. Only one ``meshlines`` element is allowed per
``plot`` element, and it must contain as attributes or sub-elements a mesh
type and a linewidth. Optionally, a color may be specified for the overlay:

View file

@ -204,20 +204,22 @@ should be used:
Compiling with MPI
++++++++++++++++++
To compile with MPI, set the :envvar:`FC` environment variable to the path to
the MPI Fortran wrapper. For example, in a bash shell:
To compile with MPI, set the :envvar:`FC` and :envvar:`CC` environment variables
to the path to the MPI Fortran and C wrappers, respectively. For example, in a
bash shell:
.. code-block:: sh
export FC=mpif90
export CC=mpicc
cmake /path/to/openmc
Note that in many shells, an environment variable can be set for a single
command, i.e.
Note that in many shells, environment variables can be set for a single command,
i.e.
.. code-block:: sh
FC=mpif90 cmake /path/to/openmc
FC=mpif90 CC=mpicc cmake /path/to/openmc
Selecting HDF5 Installation
+++++++++++++++++++++++++++
@ -343,7 +345,7 @@ compiler, it is necessary to specify that all objects be compiled with the
.. code-block:: sh
mkdir build && cd build
FC=ifort FFLAGS=-mmic cmake -Dopenmp=on ..
FC=ifort CC=icc FFLAGS=-mmic cmake -Dopenmp=on ..
make
Note that unless an HDF5 build for the Intel Xeon Phi is already on your target

View file

@ -25,7 +25,7 @@ moderator = openmc.Material(material_id=41, name='moderator')
moderator.set_density('g/cc', 1.0)
moderator.add_nuclide(h1, 2.)
moderator.add_nuclide(o16, 1.)
moderator.add_s_alpha_beta('c_H_in_H2O', '71t')
moderator.add_s_alpha_beta('c_H_in_H2O')
fuel = openmc.Material(material_id=40, name='fuel')
fuel.set_density('g/cc', 4.5)
@ -33,7 +33,6 @@ fuel.add_nuclide(u235, 1.)
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([moderator, fuel])
materials_file.default_xs = '71c'
materials_file.export_to_xml()

View file

@ -34,11 +34,10 @@ moderator = openmc.Material(material_id=3, name='moderator')
moderator.set_density('g/cc', 1.0)
moderator.add_nuclide(h1, 2.)
moderator.add_nuclide(o16, 1.)
moderator.add_s_alpha_beta('c_H_in_H2O', '71t')
moderator.add_s_alpha_beta('c_H_in_H2O')
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([fuel1, fuel2, moderator])
materials_file.default_xs = '71c'
materials_file.export_to_xml()

View file

@ -29,7 +29,7 @@ moderator = openmc.Material(material_id=2, name='moderator')
moderator.set_density('g/cc', 1.0)
moderator.add_nuclide(h1, 2.)
moderator.add_nuclide(o16, 1.)
moderator.add_s_alpha_beta('c_H_in_H2O', '71t')
moderator.add_s_alpha_beta('c_H_in_H2O')
iron = openmc.Material(material_id=3, name='iron')
iron.set_density('g/cc', 7.9)
@ -37,7 +37,6 @@ iron.add_nuclide(fe56, 1.)
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([moderator, fuel, iron])
materials_file.default_xs = '71c'
materials_file.export_to_xml()

View file

@ -28,11 +28,10 @@ moderator = openmc.Material(material_id=2, name='moderator')
moderator.set_density('g/cc', 1.0)
moderator.add_nuclide(h1, 2.)
moderator.add_nuclide(o16, 1.)
moderator.add_s_alpha_beta('c_H_in_H2O', '71t')
moderator.add_s_alpha_beta('c_H_in_H2O')
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials((moderator, fuel))
materials_file.default_xs = '71c'
materials_file.export_to_xml()

View file

@ -28,11 +28,10 @@ moderator = openmc.Material(material_id=2, name='moderator')
moderator.set_density('g/cc', 1.0)
moderator.add_nuclide(h1, 2.)
moderator.add_nuclide(o16, 1.)
moderator.add_s_alpha_beta('c_H_in_H2O', '71t')
moderator.add_s_alpha_beta('c_H_in_H2O')
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([moderator, fuel])
materials_file.default_xs = '71c'
materials_file.export_to_xml()

View file

@ -98,11 +98,10 @@ borated_water.add_nuclide(h1, 4.9457e-2)
borated_water.add_nuclide(h2, 7.4196e-6)
borated_water.add_nuclide(o16, 2.4672e-2)
borated_water.add_nuclide(o17, 6.0099e-5)
borated_water.add_s_alpha_beta('c_H_in_H2O', '71t')
borated_water.add_s_alpha_beta('c_H_in_H2O')
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([uo2, helium, zircaloy, borated_water])
materials_file.default_xs = '71c'
materials_file.export_to_xml()

View file

@ -19,7 +19,7 @@ groups = openmc.mgxs.EnergyGroups(group_edges=[1E-11, 0.0635E-6, 10.0E-6,
1.0E-4, 1.0E-3, 0.5, 1.0, 20.0])
# Instantiate the 7-group (C5G7) cross section data
uo2_xsdata = openmc.XSdata('UO2.300K', groups)
uo2_xsdata = openmc.XSdata('UO2', groups)
uo2_xsdata.order = 0
uo2_xsdata.total = [0.1779492, 0.3298048, 0.4803882, 0.5543674,
0.3118013, 0.3951678, 0.5644058]
@ -41,7 +41,7 @@ uo2_xsdata.nu_fission = [2.005998E-02, 2.027303E-03, 1.570599E-02,
uo2_xsdata.chi = [5.8791E-01, 4.1176E-01, 3.3906E-04, 1.1761E-07,
0.0000E+00, 0.0000E+00, 0.0000E+00]
h2o_xsdata = openmc.XSdata('LWTR.300K', groups)
h2o_xsdata = openmc.XSdata('LWTR', groups)
h2o_xsdata.order = 0
h2o_xsdata.total = [0.15920605, 0.412969593, 0.59030986, 0.58435,
0.718, 1.2544497, 2.650379]
@ -66,8 +66,8 @@ mg_cross_sections_file.export_to_xml()
###############################################################################
# Instantiate some Macroscopic Data
uo2_data = openmc.Macroscopic('UO2', '300K')
h2o_data = openmc.Macroscopic('LWTR', '300K')
uo2_data = openmc.Macroscopic('UO2')
h2o_data = openmc.Macroscopic('LWTR')
# Instantiate some Materials and register the appropriate Macroscopic objects
uo2 = openmc.Material(material_id=1, name='UO2 fuel')
@ -80,7 +80,6 @@ water.add_macroscopic(h2o_data)
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([uo2, water])
materials_file.default_xs = '300K'
materials_file.export_to_xml()

View file

@ -25,7 +25,6 @@ fuel.add_nuclide(u235, 1.)
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([fuel])
materials_file.default_xs = '71c'
materials_file.export_to_xml()

View file

@ -1,8 +1,6 @@
<?xml version="1.0"?>
<materials>
<default_xs>71c</default_xs>
<material id="40">
<density value="4.5" units="g/cc" />
<nuclide name="U235" ao="1.0" />
@ -12,7 +10,7 @@
<density value="1.0" units="g/cc" />
<nuclide name="H1" ao="2.0" />
<nuclide name="O16" ao="1.0" />
<sab name="c_H_in_H2O" xs="71t" />
<sab name="c_H_in_H2O"/>
</material>
</materials>

View file

@ -1,8 +1,6 @@
<?xml version="1.0"?>
<materials>
<default_xs>71c</default_xs>
<material id="1">
<density value="4.5" units="g/cc" />
<nuclide name="U235" ao="1.0" />
@ -17,7 +15,7 @@
<density value="1.0" units="g/cc" />
<nuclide name="O16" ao="1.0" />
<nuclide name="H1" ao="2.0" />
<sab name="c_H_in_H2O" xs="71t" />
<sab name="c_H_in_H2O" />
</material>
</materials>

View file

@ -1,8 +1,6 @@
<?xml version="1.0"?>
<materials>
<default_xs>71c</default_xs>
<!-- Definition of materials -->
<material id="1">
<density value="4.5" units="g/cc" />
@ -13,7 +11,7 @@
<density value="1.0" units="g/cc" />
<nuclide name="H1" ao="2.0" />
<nuclide name="O16" ao="1.0" />
<sab name="c_H_in_H2O" xs="71t" />
<sab name="c_H_in_H2O" />
</material>
</materials>

View file

@ -1,8 +1,6 @@
<?xml version="1.0"?>
<materials>
<default_xs>71c</default_xs>
<!-- Definition of materials -->
<material id="1">
<density value="4.5" units="g/cc" />
@ -13,7 +11,7 @@
<density value="1.0" units="g/cc" />
<nuclide name="H1" ao="2.0" />
<nuclide name="O16" ao="1.0" />
<sab name="c_H_in_H2O" xs="71t" />
<sab name="c_H_in_H2O" />
</material>
</materials>

View file

@ -1,9 +1,6 @@
<?xml version="1.0"?>
<materials>
<!-- By default, use 300K cross sections -->
<default_xs>71c</default_xs>
<!--
Since O-18 is not present in ENDF/B-VII, it was necessary to combine the
atom densities for O-17 and O-18 in any materials containing Oxygen.
@ -64,7 +61,7 @@
<nuclide name="H2" ao="7.4196e-06" />
<nuclide name="O16" ao="2.4672e-02" />
<nuclide name="O17" ao="6.0099e-05" />
<sab name="c_H_in_H2O" xs="71t" />
<sab name="c_H_in_H2O" />
</material>
</materials>

View file

@ -1,8 +1,5 @@
<?xml version="1.0"?>
<materials>
<!-- Set default xs set to use 300K data -->
<default_xs>300K</default_xs>
<!-- UO2 -->
<material id="1">
<density units="macro" value="1.0" />

View file

@ -11,8 +11,8 @@
-->
<xsdata>
<!-- Meta data for this data -->
<name>UO2.300K</name>
<alias>UO2.300K</alias>
<name>UO2</name>
<alias>UO2</alias>
<kT> 2.53E-8 </kT> <!-- in MeV -->
<order>0</order>
<fissionable>true</fissionable>
@ -67,8 +67,8 @@
<xsdata>
<!-- Meta data for this data -->
<name>MOX1.300K</name>
<alias>MOX1.300K</alias>
<name>MOX1</name>
<alias>MOX1</alias>
<kT> 2.53E-8 </kT> <!-- in MeV -->
<order>0</order>
<fissionable>true</fissionable>
@ -124,8 +124,8 @@
<xsdata>
<!-- Meta data for this data -->
<name>MOX2.300K</name>
<alias>MOX2.300K</alias>
<name>MOX2</name>
<alias>MOX2</alias>
<kT> 2.53E-8 </kT> <!-- in MeV -->
<order>0</order>
<fissionable>true</fissionable>
@ -180,8 +180,8 @@
<xsdata>
<!-- Meta data for this data -->
<name>MOX3.300K</name>
<alias>MOX3.300K</alias>
<name>MOX3</name>
<alias>MOX3</alias>
<kT> 2.53E-8 </kT> <!-- in MeV -->
<order>0</order>
<fissionable>true</fissionable>
@ -236,8 +236,8 @@
<xsdata>
<!-- Meta data for this data -->
<name>FC.300K</name>
<alias>FC.300K</alias>
<name>FC</name>
<alias>FC</alias>
<kT> 2.53E-8 </kT> <!-- in MeV -->
<order>0</order>
<fissionable>true</fissionable>
@ -286,8 +286,8 @@
<xsdata>
<!-- Meta data for this data -->
<name>GT.300K</name>
<alias>GT.300K</alias>
<name>GT</name>
<alias>GT</alias>
<kT> 2.53E-8 </kT> <!-- in MeV -->
<order>0</order>
<fissionable>false</fissionable>
@ -318,8 +318,8 @@
<xsdata>
<!-- Meta data for this data -->
<name>LWTR.300K</name>
<alias>LWTR.300K</alias>
<name>LWTR</name>
<alias>LWTR</alias>
<kT> 2.53E-8 </kT> <!-- in MeV -->
<order>0</order>
<fissionable>false</fissionable>
@ -351,8 +351,8 @@
<xsdata>
<!-- Meta data for this data -->
<name>CR.300K</name>
<alias>CR.300K</alias>
<name>CR</name>
<alias>CR</alias>
<kT> 2.53E-8 </kT> <!-- in MeV -->
<order>0</order>
<fissionable>false</fissionable>

View file

@ -1,8 +1,6 @@
<?xml version="1.0"?>
<materials>
<default_xs>71c</default_xs>
<material id="1">
<density value="4.5" units="g/cc" />
<nuclide name="U235" ao="1.0" />

View file

@ -3,7 +3,7 @@
openmc \- Executes the OpenMC Monte Carlo code
.SH DESCRIPTION
This command is used to execute the OpenMC Monte Carlo code. It is assumed that
a set of XML input files has already been created and that ACE format cross
a set of XML input files has already been created and that HDF5 format cross
sections are available.
.SH SYNOPSIS
\fBopenmc\fR [\fIoptions\fR] [\fIpath\fR]
@ -40,11 +40,21 @@ The behavior of
.B openmc
is affected by the following environment variables.
.TP
.B CROSS_SECTIONS
.B OPENMC_CROSS_SECTIONS
Indicates the default path to the cross_sections.xml summary file that is used
to locate ACE format cross section libraries if the user has not specified the
to locate HDF5 format cross section libraries if the user has not specified the
<cross_sections> tag in
.I settings.xml\fP.
.TP
.B OPENMC_MG_CROSS_SECTIONS
Indicates the default path to the mgxs.xml file that contains multi-group cross
section libraries if the user has not specified the <cross_sections> tag in
.I settings.xml\fP.
.TP
.B OPENMC_MULTIPOLE_LIBRARY
Indicates the default path to a directory containing windowed multipole data if
the user has not specified the <multipole_library> tag in
.I settings.xml\fP.
.SH LICENSE
Copyright \(co 2011-2016 Massachusetts Institute of Technology.
.PP

View file

@ -6,21 +6,23 @@ from openmc.nuclide import *
from openmc.macroscopic import *
from openmc.material import *
from openmc.plots import *
from openmc.region import *
from openmc.volume import *
from openmc.source import *
from openmc.settings import *
from openmc.surface import *
from openmc.universe import *
from openmc.mesh import *
from openmc.mgxs_library import *
from openmc.filter import *
from openmc.trigger import *
from openmc.tallies import *
from openmc.mgxs_library import *
from openmc.cmfd import *
from openmc.executor import *
from openmc.statepoint import *
from openmc.summary import *
from openmc.region import *
from openmc.source import *
from openmc.particle_restart import *
from openmc.mixin import *
try:
from openmc.opencg_compatible import *

View file

@ -85,6 +85,10 @@ class Cell(object):
Array of offsets used for distributed cell searches
distribcell_index : int
Index of this cell in distribcell arrays
volume_information : dict
Estimate of the volume and total number of atoms of each nuclide from a
stochastic volume calculation. This information is set with the
:meth:`Cell.add_volume_information` method.
"""
@ -100,6 +104,7 @@ class Cell(object):
self._translation = None
self._offsets = None
self._distribcell_index = None
self._volume_information = None
def __contains__(self, point):
if self.region is None:
@ -212,6 +217,10 @@ class Cell(object):
def distribcell_index(self):
return self._distribcell_index
@property
def volume_information(self):
return self._volume_information
@id.setter
def id(self, cell_id):
if cell_id is None:
@ -351,6 +360,25 @@ class Cell(object):
else:
self.region = Intersection(self.region, region)
def add_volume_information(self, volume_calc):
"""Add volume information to a cell.
Parameters
----------
volume_calc : openmc.VolumeCalculation
Results from a stochastic volume calculation
"""
if volume_calc.domain_type == 'cell':
for cell_id in volume_calc.results:
if cell_id == self.id:
self._volume_information = volume_calc.results[cell_id]
break
else:
raise ValueError('No volume information found for this cell.')
else:
raise ValueError('No volume information found for this cell.')
def get_cell_instance(self, path, distribcell_index):
# If the Cell is filled by a Material
@ -368,8 +396,19 @@ class Cell(object):
return offset
def get_all_nuclides(self):
"""Return all nuclides contained in the cell
def get_nuclides(self):
"""Returns all nuclides in the cell
Returns
-------
nuclides : list of str
List of nuclide names
"""
return self.fill.get_nuclides() if self.fill_type != 'void' else []
def get_nuclide_densities(self):
"""Return all nuclides contained in the cell and their densities
Returns
-------
@ -381,8 +420,24 @@ class Cell(object):
nuclides = OrderedDict()
if self.fill_type != 'void':
nuclides.update(self.fill.get_all_nuclides())
if self.fill_type == 'material':
nuclides.update(self.fill.get_nuclide_densities())
elif self.fill_type == 'void':
pass
else:
if self.volume_information is not None:
volume = self.volume_information['volume'][0]
for full_name, atoms in self.volume_information['atoms']:
name, xs = full_name.split('.')
nuclide = openmc.Nuclide(name, xs)
density = 1.0e-24 * atoms[0]/volume # density in atoms/b-cm
nuclides[name] = (nuclide, density)
else:
raise RuntimeError(
'Volume information is needed to calculate microscopic cross '
'sections for cell {}. This can be done by running a '
'stochastic volume calculation via the '
'openmc.VolumeCalculation object'.format(self.id))
return nuclides

View file

@ -14,3 +14,4 @@ from .nbody import *
from .thermal import *
from .urr import *
from .library import *
from .fission_energy import *

View file

@ -22,6 +22,8 @@ import sys
import numpy as np
from openmc.mixin import EqualityMixin
if sys.version_info[0] >= 3:
basestring = str
@ -131,7 +133,7 @@ def get_table(filename, name=None):
.format(name))
class Library(object):
class Library(EqualityMixin):
"""A Library objects represents an ACE-formatted file which may contain
multiple tables with data.
@ -353,7 +355,7 @@ class Library(object):
lines = [ace_file.readline() for i in range(13)]
class Table(object):
class Table(EqualityMixin):
"""ACE cross section table
Parameters

View file

@ -4,11 +4,12 @@ from numbers import Real
import numpy as np
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
from openmc.stats import Univariate, Tabular, Uniform
from .function import INTERPOLATION_SCHEME
class AngleDistribution(object):
class AngleDistribution(EqualityMixin):
"""Angle distribution as a function of incoming energy
Parameters

View file

@ -1,9 +1,10 @@
from abc import ABCMeta, abstractmethod
import openmc.data
from openmc.mixin import EqualityMixin
class AngleEnergy(object):
class AngleEnergy(EqualityMixin):
"""Distribution in angle and energy of a secondary particle."""
__metaclass = ABCMeta

View file

@ -56,6 +56,7 @@ class CorrelatedAngleEnergy(AngleEnergy):
@property
def interpolation(self):
return self._interpolation
@property
def energy(self):
return self._energy

View file

@ -218,3 +218,8 @@ def atomic_mass(isotope):
isotope = isotope[:isotope.find('_')]
return _ATOMIC_MASS.get(isotope.lower())
# The value of the Boltzman constant in units of MeV / K
# Values here are from the Committee on Data for Science and Technology
# (CODATA) 2010 recommendation (doi:10.1103/RevModPhys.84.1527).
K_BOLTZMANN = 8.6173324E-11

44
openmc/data/endf_utils.py Normal file
View file

@ -0,0 +1,44 @@
"""This module contains a few utility functions for reading ENDF_ data. It is by
no means enough to read an entire ENDF file. For a more complete ENDF reader,
see Pyne_.
.. _ENDF: http://www.nndc.bnl.gov/endf
.. _Pyne: http://www.pyne.io
"""
import re
def read_float(float_string):
"""Parse ENDF 6E11.0 formatted string into a float."""
assert len(float_string) == 11
pattern = r'([\s\-]\d+\.\d+)([\+\-]\d+)'
return float(re.sub(pattern, r'\1e\2', float_string))
def read_CONT_line(line):
"""Parse 80-column line from ENDF CONT record into floats and ints."""
return (read_float(line[0:11]), read_float(line[11:22]), int(line[22:33]),
int(line[33:44]), int(line[44:55]), int(line[55:66]),
int(line[66:70]), int(line[70:72]), int(line[72:75]),
int(line[75:80]))
def identify_nuclide(fname):
"""Read the header of an ENDF file and extract identifying information."""
with open(fname, 'r') as fh:
# Skip the tape id (TPID).
line = fh.readline()
# Read the first HEAD and CONT info.
line = fh.readline()
ZA, AW, LRP, LFI, NLIB, NMOD, MAT, MF, MT, NS = read_CONT_line(line)
line = fh.readline()
ELIS, STA, LIS, LISO, junk, NFOR, MAT, MF, MT, NS = read_CONT_line(line)
# Return dictionary of the most important identifying information.
return {'Z': int(ZA) // 1000,
'A': int(ZA) % 1000,
'LFI': bool(LFI),
'LIS': LIS,
'LISO': LISO}

View file

@ -8,9 +8,10 @@ import numpy as np
from .function import Tabulated1D, INTERPOLATION_SCHEME
from openmc.stats.univariate import Univariate, Tabular, Discrete, Mixture
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
class EnergyDistribution(object):
class EnergyDistribution(EqualityMixin):
"""Abstract superclass for all energy distributions."""
__metaclass__ = ABCMeta

View file

@ -0,0 +1,593 @@
from collections import Callable
from copy import deepcopy
import sys
import h5py
import numpy as np
from .data import ATOMIC_SYMBOL
from .endf_utils import read_float, read_CONT_line, identify_nuclide
from .function import Function1D, Tabulated1D, Polynomial, Sum
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
if sys.version_info[0] >= 3:
basestring = str
def _extract_458_data(filename):
"""Read an ENDF file and extract the MF=1, MT=458 values.
Parameters
----------
filename : str
Path to and ENDF file
Returns
-------
value : dict of str to list of float
Dictionary that gives lists of coefficients for each energy component.
The keys are the 2-3 letter strings used in ENDF-102, e.g. 'EFR' and
'ET'. The list will have a length of 1 for Sher-Beck data, more for
polynomial data.
uncertainty : dict of str to list of float
A dictionary with the same format as above. This is probably a
one-standard deviation value, but that is not specified explicitly in
ENDF-102. Also, some evaluations will give zero uncertainty. Use with
caution.
"""
ident = identify_nuclide(filename)
if not ident['LFI']:
# This nuclide isn't fissionable.
return None
# Extract the MF=1, MT=458 section.
lines = []
with open(filename, 'r') as fh:
line = fh.readline()
while line != '':
if line[70:75] == ' 1458':
lines.append(line)
line = fh.readline()
if len(lines) == 0:
# No 458 data here.
return None
# Read the number of coefficients in this LIST record.
NPL = read_CONT_line(lines[1])[4]
# Parse the ENDF LIST into an array.
data = []
for i in range(NPL):
row, column = divmod(i, 6)
data.append(read_float(lines[2 + row][11*column:11*(column+1)]))
# Declare the coefficient names and the order they are given in. The LIST
# contains a value followed immediately by an uncertainty for each of these
# components, times the polynomial order + 1.
labels = ('EFR', 'ENP', 'END', 'EGP', 'EGD', 'EB', 'ENU', 'ER', 'ET')
# Associate each set of values and uncertainties with its label.
value = {}
uncertainty = {}
for i, label in enumerate(labels):
value[label] = data[2*i::18]
uncertainty[label] = data[2*i + 1::18]
# In ENDF/B-7.1, data for 2nd-order coefficients were mistakenly not
# converted from MeV to eV. Check for this error and fix it if present.
n_coeffs = len(value['EFR'])
if n_coeffs == 3: # Only check 2nd-order data.
# Check each energy component for the error. If a 1 MeV neutron
# causes a change of more than 100 MeV, we know something is wrong.
error_present = False
for coeffs in value.values():
second_order = coeffs[2]
if abs(second_order) * 1e12 > 1e8:
error_present = True
break
# If we found the error, reduce all 2nd-order coeffs by 10**6.
if error_present:
for coeffs in value.values(): coeffs[2] *= 1e-6
for coeffs in uncertainty.values(): coeffs[2] *= 1e-6
# Convert eV to MeV.
for coeffs in value.values():
for i in range(len(coeffs)):
coeffs[i] *= 10**(-6 + 6*i)
for coeffs in uncertainty.values():
for i in range(len(coeffs)):
coeffs[i] *= 10**(-6 + 6*i)
return value, uncertainty
def write_compact_458_library(endf_files, output_name='fission_Q_data.h5',
comment=None, verbose=False):
"""Read ENDF files, strip the MF=1 MT=458 data and write to small HDF5.
Parameters
----------
endf_files : Collection of str
Strings giving the paths to the ENDF files that will be parsed for data.
output_name : str
Name of the output HDF5 file. Default is 'fission_Q_data.h5'.
comment : str
Comment to write in the output HDF5 file. Defaults to no comment.
verbose : bool
If True, print the name of each isomer as it is read. Defaults to
False.
"""
# Open the output file.
out = h5py.File(output_name, 'w', libver='latest')
# Write comments, if given. This commented out comment is the one used for
# the library distributed with OpenMC.
#comment = ('This data is extracted from ENDF/B-VII.1 library. Thanks '
# 'evaluators, for all your hard work :) Citation: '
# 'M. B. Chadwick, M. Herman, P. Oblozinsky, '
# 'M. E. Dunn, Y. Danon, A. C. Kahler, D. L. Smith, '
# 'B. Pritychenko, G. Arbanas, R. Arcilla, R. Brewer, '
# 'D. A. Brown, R. Capote, A. D. Carlson, Y. S. Cho, H. Derrien, '
# 'K. Guber, G. M. Hale, S. Hoblit, S. Holloway, T. D. Johnson, '
# 'T. Kawano, B. C. Kiedrowski, H. Kim, S. Kunieda, '
# 'N. M. Larson, L. Leal, J. P. Lestone, R. C. Little, '
# 'E. A. McCutchan, R. E. MacFarlane, M. MacInnes, '
# 'C. M. Mattoon, R. D. McKnight, S. F. Mughabghab, '
# 'G. P. A. Nobre, G. Palmiotti, A. Palumbo, M. T. Pigni, '
# 'V. G. Pronyaev, R. O. Sayer, A. A. Sonzogni, N. C. Summers, '
# 'P. Talou, I. J. Thompson, A. Trkov, R. L. Vogt, '
# 'S. C. van der Marck, A. Wallner, M. C. White, D. Wiarda, '
# 'and P. G. Young. ENDF/B-VII.1 nuclear data for science and '
# 'technology: Cross sections, covariances, fission product '
# 'yields and decay data", Nuclear Data Sheets, '
# '112(12):2887-2996 (2011).')
if comment is not None:
out.attrs['comment'] = np.string_(comment)
# Declare the order of the components. Use fixed-length numpy strings
# because they work well with h5py.
labels = np.array(('EFR', 'ENP', 'END', 'EGP', 'EGD', 'EB', 'ENU', 'ER',
'ET'), dtype='S3')
out.attrs['component order'] = labels
# Iterate over the given files.
if verbose: print('Reading ENDF files:')
for fname in endf_files:
if verbose: print(fname)
ident = identify_nuclide(fname)
# Skip non-fissionable nuclides.
if not ident['LFI']: continue
# Get the important bits.
data = _extract_458_data(fname)
if data is None: continue
value, uncertainty = data
# Make a group for this isomer.
name = ATOMIC_SYMBOL[ident['Z']] + str(ident['A'])
if ident['LISO'] != 0:
name += '_m' + str(ident['LISO'])
nuclide_group = out.create_group(name)
# Write all the coefficients into one array. The first dimension gives
# the component (e.g. fragments or prompt neutrons); the second switches
# between value and uncertainty; the third gives the polynomial order.
n_coeffs = len(value['EFR'])
data_out = np.zeros((len(labels), 2, n_coeffs))
for i, label in enumerate(labels):
data_out[i, 0, :] = value[label.decode()]
data_out[i, 1, :] = uncertainty[label.decode()]
nuclide_group.create_dataset('data', data=data_out)
out.close()
class FissionEnergyRelease(EqualityMixin):
"""Energy relased by fission reactions.
Energy is carried away from fission reactions by many different particles.
The attributes of this class specify how much energy is released in the form
of fission fragments, neutrons, photons, etc. Each component is also (in
general) a function of the incident neutron energy.
Following a fission reaction, most of the energy release is carried by the
daughter nuclei fragments. These fragments accelerate apart from the
Coulomb force on the time scale of ~10^-20 s [1]. Those fragments emit
prompt neutrons between ~10^-18 and ~10^-13 s after scission (although some
prompt neutrons may come directly from the scission point) [1]. Prompt
photons follow with a time scale of ~10^-14 to ~10^-7 s [1]. The fission
products then emit delayed neutrons with half lives between 0.1 and 100 s.
The remaining fission energy comes from beta decays of the fission products
which release beta particles, photons, and neutrinos (that escape the
reactor and do not produce usable heat).
Use the class methods to instantiate this class from an HDF5 or ENDF
dataset. The :meth:`FissionEnergyRelease.from_hdf5` method builds this
class from the usual OpenMC HDF5 data files.
:meth:`FissionEnergyRelease.from_endf` uses ENDF-formatted data.
:meth:`FissionEnergyRelease.from_compact_hdf5` uses a different HDF5 format
that is meant to be compact and store the exact same data as the ENDF
format. Files with this format can be generated with the
:func:`openmc.data.write_compact_458_library` function.
References
----------
[1] D. G. Madland, "Total prompt energy release in the neutron-induced
fission of ^235U, ^238U, and ^239Pu", Nuclear Physics A 772:113--137 (2006).
<http://dx.doi.org/10.1016/j.nuclphysa.2006.03.013>
Attributes
----------
fragments : Callable
Function that accepts incident neutron energy value(s) and returns the
kinetic energy of the fission daughter nuclides (after prompt neutron
emission).
prompt_neutrons : Callable
Function of energy that returns the kinetic energy of prompt fission
neutrons.
delayed_neutrons : Callable
Function of energy that returns the kinetic energy of delayed neutrons
emitted from fission products.
prompt_photons : Callable
Function of energy that returns the kinetic energy of prompt fission
photons.
delayed_photons : Callable
Function of energy that returns the kinetic energy of delayed photons.
betas : Callable
Function of energy that returns the kinetic energy of delayed beta
particles.
neutrinos : Callable
Function of energy that returns the kinetic energy of neutrinos.
recoverable : Callable
Function of energy that returns the kinetic energy of all products that
can be absorbed in the reactor (all of the energy except for the
neutrinos).
total : Callable
Function of energy that returns the kinetic energy of all products.
q_prompt : Callable
Function of energy that returns the prompt fission Q-value (fragments +
prompt neutrons + prompt photons - incident neutron energy).
q_recoverable : Callable
Function of energy that returns the recoverable fission Q-value
(total release - neutrinos - incident neutron energy). This value is
sometimes referred to as the pseudo-Q-value.
q_total : Callable
Function of energy that returns the total fission Q-value (total release
- incident neutron energy).
"""
def __init__(self):
self._fragments = None
self._prompt_neutrons = None
self._delayed_neutrons = None
self._prompt_photons = None
self._delayed_photons = None
self._betas = None
self._neutrinos = None
@property
def fragments(self):
return self._fragments
@property
def prompt_neutrons(self):
return self._prompt_neutrons
@property
def delayed_neutrons(self):
return self._delayed_neutrons
@property
def prompt_photons(self):
return self._prompt_photons
@property
def delayed_photons(self):
return self._delayed_photons
@property
def betas(self):
return self._betas
@property
def neutrinos(self):
return self._neutrinos
@property
def recoverable(self):
return Sum([self.fragments, self.prompt_neutrons, self.delayed_neutrons,
self.prompt_photons, self.delayed_photons, self.betas])
@property
def total(self):
return Sum([self.fragments, self.prompt_neutrons, self.delayed_neutrons,
self.prompt_photons, self.delayed_photons, self.betas,
self.neutrinos])
@property
def q_prompt(self):
return Sum([self.fragments, self.prompt_neutrons, self.prompt_photons,
lambda E: -E])
@property
def q_recoverable(self):
return Sum([self.recoverable, lambda E: -E])
@property
def q_total(self):
return Sum([self.total, lambda E: -E])
@fragments.setter
def fragments(self, energy_release):
cv.check_type('fragments', energy_release, Callable)
self._fragments = energy_release
@prompt_neutrons.setter
def prompt_neutrons(self, energy_release):
cv.check_type('prompt_neutrons', energy_release, Callable)
self._prompt_neutrons = energy_release
@delayed_neutrons.setter
def delayed_neutrons(self, energy_release):
cv.check_type('delayed_neutrons', energy_release, Callable)
self._delayed_neutrons = energy_release
@prompt_photons.setter
def prompt_photons(self, energy_release):
cv.check_type('prompt_photons', energy_release, Callable)
self._prompt_photons = energy_release
@delayed_photons.setter
def delayed_photons(self, energy_release):
cv.check_type('delayed_photons', energy_release, Callable)
self._delayed_photons = energy_release
@betas.setter
def betas(self, energy_release):
cv.check_type('betas', energy_release, Callable)
self._betas = energy_release
@neutrinos.setter
def neutrinos(self, energy_release):
cv.check_type('neutrinos', energy_release, Callable)
self._neutrinos = energy_release
@classmethod
def _from_dictionary(cls, energy_release, incident_neutron):
"""Generate fission energy release data from a dictionary.
Parameters
----------
energy_release : dict of str to list of float
Dictionary that gives lists of coefficients for each energy
component. The keys are the 2-3 letter strings used in ENDF-102,
e.g. 'EFR' and 'ET'. The list will have a length of 1 for Sher-Beck
data, more for polynomial data.
incident_neutron : openmc.data.IncidentNeutron
Corresponding incident neutron dataset
Returns
-------
openmc.data.FissionEnergyRelease
Fission energy release data
"""
out = cls()
# How many coefficients are given for each component? If we only find
# one value for each, then we need to use the Sher-Beck formula for
# energy dependence. Otherwise, it is a polynomial.
n_coeffs = len(energy_release['EFR'])
if n_coeffs > 1:
out.fragments = Polynomial(energy_release['EFR'])
out.prompt_neutrons = Polynomial(energy_release['ENP'])
out.delayed_neutrons = Polynomial(energy_release['END'])
out.prompt_photons = Polynomial(energy_release['EGP'])
out.delayed_photons = Polynomial(energy_release['EGD'])
out.betas = Polynomial(energy_release['EB'])
out.neutrinos = Polynomial(energy_release['ENU'])
else:
# EFR and ENP are energy independent. Use 0-order polynomials to
# make a constant function. The energy-dependence of END is
# unspecified in ENDF-102 so assume it is independent.
out.fragments = Polynomial((energy_release['EFR'][0]))
out.prompt_photons = Polynomial((energy_release['EGP'][0]))
out.delayed_neutrons = Polynomial((energy_release['END'][0]))
# EDP, EB, and ENU are linear.
out.delayed_photons = Polynomial((energy_release['EGD'][0], -0.075))
out.betas = Polynomial((energy_release['EB'][0], -0.075))
out.neutrinos = Polynomial((energy_release['ENU'][0], -0.105))
# Prompt neutrons require nu-data. It is not clear from ENDF-102
# whether prompt or total nu value should be used, but the delayed
# neutron fraction is so small that the difference is negligible.
# MT=18 (n, fission) might not be available so try MT=19 (n, f) as
# well.
if 18 in incident_neutron.reactions:
nu_prompt = [p for p in incident_neutron[18].products
if p.particle == 'neutron'
and p.emission_mode == 'prompt']
elif 19 in incident_neutron.reactions:
nu_prompt = [p for p in incident_neutron[19].products
if p.particle == 'neutron'
and p.emission_mode == 'prompt']
else:
raise ValueError('IncidentNeutron data has no fission '
'reaction.')
if len(nu_prompt) == 0:
raise ValueError('Nu data is needed to compute fission energy '
'release with the Sher-Beck format.')
if len(nu_prompt) > 1:
raise ValueError('Ambiguous prompt value.')
if not isinstance(nu_prompt[0].yield_, Tabulated1D):
raise TypeError('Sher-Beck fission energy release currently '
'only supports Tabulated1D nu data.')
ENP = deepcopy(nu_prompt[0].yield_)
ENP.y = (energy_release['ENP'] + 1.307 * ENP.x
- 8.07 * (ENP.y - ENP.y[0]))
out.prompt_neutrons = ENP
return out
@classmethod
def from_endf(cls, filename, incident_neutron):
"""Generate fission energy release data from an ENDF file.
Parameters
----------
filename : str
Name of the ENDF file containing fission energy release data
incident_neutron : openmc.data.IncidentNeutron
Corresponding incident neutron dataset
Returns
-------
openmc.data.FissionEnergyRelease
Fission energy release data
"""
# Check to make sure this ENDF file matches the expected isomer.
ident = identify_nuclide(filename)
if ident['Z'] != incident_neutron.atomic_number:
raise ValueError('The atomic number of the ENDF evaluation does '
'not match the given IncidentNeutron.')
if ident['A'] != incident_neutron.mass_number:
raise ValueError('The atomic mass of the ENDF evaluation does '
'not match the given IncidentNeutron.')
if ident['LISO'] != incident_neutron.metastable:
raise ValueError('The metastable state of the ENDF evaluation does '
'not match the given IncidentNeutron.')
if not ident['LFI']:
raise ValueError('The ENDF evaluation is not fissionable.')
# Read the 458 data from the ENDF file.
value, uncertainty = _extract_458_data(filename)
# Build the object.
return cls._from_dictionary(value, incident_neutron)
@classmethod
def from_hdf5(cls, group):
"""Generate fission energy release data from an HDF5 group.
Parameters
----------
group : h5py.Group
HDF5 group to read from
Returns
-------
openmc.data.FissionEnergyRelease
Fission energy release data
"""
obj = cls()
obj.fragments = Function1D.from_hdf5(group['fragments'])
obj.prompt_neutrons = Function1D.from_hdf5(group['prompt_neutrons'])
obj.delayed_neutrons = Function1D.from_hdf5(group['delayed_neutrons'])
obj.prompt_photons = Function1D.from_hdf5(group['prompt_photons'])
obj.delayed_photons = Function1D.from_hdf5(group['delayed_photons'])
obj.betas = Function1D.from_hdf5(group['betas'])
obj.neutrinos = Function1D.from_hdf5(group['neutrinos'])
return obj
@classmethod
def from_compact_hdf5(cls, fname, incident_neutron):
"""Generate fission energy release data from a small HDF5 library.
Parameters
----------
fname : str
Path to an HDF5 file containing fission energy release data. This
file should have been generated form the
:func:`openmc.data.write_compact_458_library` function.
incident_neutron : openmc.data.IncidentNeutron
Corresponding incident neutron dataset
Returns
-------
openmc.data.FissionEnergyRelease or None
Fission energy release data for the given nuclide if it is present
in the data file
"""
fin = h5py.File(fname, 'r')
components = [s.decode() for s in fin.attrs['component order']]
nuclide_name = ATOMIC_SYMBOL[incident_neutron.atomic_number]
nuclide_name += str(incident_neutron.mass_number)
if incident_neutron.metastable != 0:
nuclide_name += '_m' + str(incident_neutron.metastable)
if nuclide_name not in fin: return None
data = {c: fin[nuclide_name + '/data'][i, 0, :]
for i, c in enumerate(components)}
return cls._from_dictionary(data, incident_neutron)
def to_hdf5(self, group):
"""Write energy release data to an HDF5 group
Parameters
----------
group : h5py.Group
HDF5 group to write to
"""
self.fragments.to_hdf5(group, 'fragments')
self.prompt_neutrons.to_hdf5(group, 'prompt_neutrons')
self.delayed_neutrons.to_hdf5(group, 'delayed_neutrons')
self.prompt_photons.to_hdf5(group, 'prompt_photons')
self.delayed_photons.to_hdf5(group, 'delayed_photons')
self.betas.to_hdf5(group, 'betas')
self.neutrinos.to_hdf5(group, 'neutrinos')
if isinstance(self.prompt_neutrons, Polynomial):
# Add the polynomials for the relevant components together. Use a
# Polynomial((0.0, -1.0)) to subtract incident energy.
q_prompt = (self.fragments + self.prompt_neutrons +
self.prompt_photons + Polynomial((0.0, -1.0)))
q_prompt.to_hdf5(group, 'q_prompt')
q_recoverable = (self.fragments + self.prompt_neutrons +
self.delayed_neutrons + self.prompt_photons +
self.delayed_photons + self.betas +
Polynomial((0.0, -1.0)))
q_recoverable.to_hdf5(group, 'q_recoverable')
elif isinstance(self.prompt_neutrons, Tabulated1D):
# Make a Tabulated1D and evaluate the polynomial components at the
# table x points to get new y points. Subtract x from y to remove
# incident energy.
q_prompt = deepcopy(self.prompt_neutrons)
q_prompt.y += self.fragments(q_prompt.x)
q_prompt.y += self.prompt_photons(q_prompt.x)
q_prompt.y -= q_prompt.x
q_prompt.to_hdf5(group, 'q_prompt')
q_recoverable = q_prompt
q_recoverable.y += self.delayed_neutrons(q_recoverable.x)
q_recoverable.y += self.delayed_photons(q_recoverable.x)
q_recoverable.y += self.betas(q_recoverable.x)
q_recoverable.to_hdf5(group, 'q_recoverable')
else:
raise ValueError('Unrecognized energy release format')

View file

@ -1,15 +1,61 @@
from abc import ABCMeta, abstractmethod
from collections import Iterable, Callable
from numbers import Real, Integral
import numpy as np
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
INTERPOLATION_SCHEME = {1: 'histogram', 2: 'linear-linear', 3: 'linear-log',
4: 'log-linear', 5: 'log-log'}
class Tabulated1D(object):
class Function1D(EqualityMixin):
"""A function of one independent variable with HDF5 support."""
__metaclass__ = ABCMeta
@abstractmethod
def __call__(self): pass
@abstractmethod
def to_hdf5(self, group, name='xy'):
"""Write function to an HDF5 group
Parameters
----------
group : h5py.Group
HDF5 group to write to
name : str
Name of the dataset to create
"""
pass
@classmethod
def from_hdf5(cls, dataset):
"""Generate function from an HDF5 dataset
Parameters
----------
dataset : h5py.Dataset
Dataset to read from
Returns
-------
openmc.data.Function1D
Function read from dataset
"""
for subclass in cls.__subclasses__():
if dataset.attrs['type'].decode() == subclass.__name__:
return subclass.from_hdf5(dataset)
raise ValueError("Unrecognized Function1D class: '"
+ dataset.attrs['type'].decode() + "'")
class Tabulated1D(Function1D):
"""A one-dimensional tabulated function.
This class mirrors the TAB1 type from the ENDF-6 format. A tabulated
@ -239,7 +285,7 @@ class Tabulated1D(object):
"""
dataset = group.create_dataset(name, data=np.vstack(
[self.x, self.y]))
dataset.attrs['type'] = np.string_('tab1')
dataset.attrs['type'] = np.string_(type(self).__name__)
dataset.attrs['breakpoints'] = self.breakpoints
dataset.attrs['interpolation'] = self.interpolation
@ -258,6 +304,10 @@ class Tabulated1D(object):
Function read from dataset
"""
if dataset.attrs['type'].decode() != cls.__name__:
raise ValueError("Expected an HDF5 attribute 'type' equal to '"
+ cls.__name__ + "'")
x = dataset.value[0, :]
y = dataset.value[1, :]
breakpoints = dataset.attrs['breakpoints']
@ -304,7 +354,43 @@ class Tabulated1D(object):
return Tabulated1D(x, y, breakpoints, interpolation)
class Sum(object):
class Polynomial(np.polynomial.Polynomial, Function1D):
def to_hdf5(self, group, name='xy'):
"""Write polynomial function to an HDF5 group
Parameters
----------
group : h5py.Group
HDF5 group to write to
name : str
Name of the dataset to create
"""
dataset = group.create_dataset(name, data=self.coef)
dataset.attrs['type'] = np.string_(type(self).__name__)
@classmethod
def from_hdf5(cls, dataset):
"""Generate function from an HDF5 dataset
Parameters
----------
dataset : h5py.Dataset
Dataset to read from
Returns
-------
openmc.data.Function1D
Function read from dataset
"""
if dataset.attrs['type'].decode() != cls.__name__:
raise ValueError("Expected an HDF5 attribute 'type' equal to '"
+ cls.__name__ + "'")
return cls(dataset.value)
class Sum(EqualityMixin):
"""Sum of multiple functions.
This class allows you to create a callable object which represents the sum

View file

@ -3,9 +3,11 @@ import xml.etree.ElementTree as ET
import h5py
from openmc.mixin import EqualityMixin
from openmc.clean_xml import clean_xml_indentation
class DataLibrary(object):
class DataLibrary(EqualityMixin):
def __init__(self):
self.libraries = []

View file

@ -1,25 +1,95 @@
from __future__ import division, unicode_literals
import sys
from collections import OrderedDict, Iterable, Mapping
from collections import OrderedDict, Iterable, Mapping, MutableMapping
from itertools import chain
from numbers import Integral, Real
from warnings import warn
import numpy as np
import h5py
from .data import ATOMIC_SYMBOL, SUM_RULES
from .data import ATOMIC_SYMBOL, SUM_RULES, K_BOLTZMANN
from .ace import Table, get_table
from .fission_energy import FissionEnergyRelease
from .function import Tabulated1D, Sum
from .product import Product
from .reaction import Reaction, _get_photon_products
from .urr import ProbabilityTables
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
if sys.version_info[0] >= 3:
basestring = str
class IncidentNeutron(object):
def _get_metadata(zaid, metastable_scheme='nndc'):
"""Return basic identifying data for a nuclide with a given ZAID.
Parameters
----------
zaid : int
ZAID (1000*Z + A) obtained from a library
metastable_scheme : {'nndc', 'mcnp'}
Determine how ZAID identifiers are to be interpreted in the case of
a metastable nuclide. Because the normal ZAID (=1000*Z + A) does not
encode metastable information, different conventions are used among
different libraries. In MCNP libraries, the convention is to add 400
for a metastable nuclide except for Am242m, for which 95242 is
metastable and 95642 (or 1095242 in newer libraries) is the ground
state. For NNDC libraries, ZAID is given as 1000*Z + A + 100*m.
Returns
-------
name : str
Name of the table
element : str
The atomic symbol of the isotope in the table; e.g., Zr.
Z : int
Number of protons in the nucleus
mass_number : int
Number of nucleons in the nucleus
metastable : int
Metastable state of the nucleus. A value of zero indicates ground state.
"""
cv.check_type('zaid', zaid, int)
cv.check_value('metastable_scheme', metastable_scheme, ['nndc', 'mcnp'])
Z = zaid // 1000
mass_number = zaid % 1000
if metastable_scheme == 'mcnp':
if zaid > 1000000:
# New SZA format
Z = Z % 1000
if zaid == 1095242:
metastable = 0
else:
metastable = zaid // 1000000
else:
if zaid == 95242:
metastable = 1
elif zaid == 95642:
metastable = 0
else:
metastable = 1 if mass_number > 300 else 0
elif metastable_scheme == 'nndc':
metastable = 1 if mass_number > 300 else 0
while mass_number > 3 * Z:
mass_number -= 100
# Determine name
element = ATOMIC_SYMBOL[Z]
name = '{}{}'.format(element, mass_number)
if metastable > 0:
name += '_m{}'.format(metastable)
return (name, element, Z, mass_number, metastable)
class IncidentNeutron(EqualityMixin):
"""Continuous-energy neutron interaction data.
Instances of this class are not normally instantiated by the user but rather
@ -29,7 +99,7 @@ class IncidentNeutron(object):
Parameters
----------
name : str
Name of the table
Name of the nuclide using the GND naming convention
atomic_number : int
Number of protons in the nucleus
mass_number : int
@ -38,8 +108,9 @@ class IncidentNeutron(object):
Metastable state of the nucleus. A value of zero indicates ground state.
atomic_weight_ratio : float
Atomic mass ratio of the target nuclide.
temperature : float
Temperature of the target nuclide in MeV.
kTs : Iterable of float
List of temperatures of the target nuclide in the data set.
The temperatures have units of MeV.
Attributes
----------
@ -49,14 +120,19 @@ class IncidentNeutron(object):
Atomic symbol of the nuclide, e.g., 'Zr'
atomic_weight_ratio : float
Atomic weight ratio of the target nuclide.
energy : numpy.ndarray
energy : dict of numpy.ndarray
The energy values (MeV) at which reaction cross-sections are tabulated.
They keys of the dict are the temperature string ('294K') for each
set of energies
fission_energy : None or openmc.data.FissionEnergyRelease
The energy released by fission, tabulated by component (e.g. prompt
neutrons or beta particles) and dependent on incident neutron energy
mass_number : int
Number of nucleons in the nucleus
metastable : int
Metastable state of the nucleus. A value of zero indicates ground state.
name : str
ZAID identifier of the table, e.g. 92235.70c.
Name of the nuclide using the GND naming convention
reactions : collections.OrderedDict
Contains the cross sections, secondary angle and energy distributions,
and other associated data for each reaction. The keys are the MT values
@ -64,26 +140,32 @@ class IncidentNeutron(object):
summed_reactions : collections.OrderedDict
Contains summed cross sections, e.g., the total cross section. The keys
are the MT values and the values are Reaction objects.
temperature : float
Temperature of the target nuclide in MeV.
urr : None or openmc.data.ProbabilityTables
Unresolved resonance region probability tables
temperatures : list of str
List of string representations the temperatures of the target nuclide
in the data set. The temperatures are strings of the temperature,
rounded to the nearest integer; e.g., '294K'
kTs : Iterable of float
List of temperatures of the target nuclide in the data set.
The temperatures have units of MeV.
urr : dict
Dictionary whose keys are temperatures (e.g., '294K') and values are
unresolved resonance region probability tables.
"""
def __init__(self, name, atomic_number, mass_number, metastable,
atomic_weight_ratio, temperature):
atomic_weight_ratio, kTs):
self.name = name
self.atomic_number = atomic_number
self.mass_number = mass_number
self.metastable = metastable
self.atomic_weight_ratio = atomic_weight_ratio
self.temperature = temperature
self._energy = None
self.kTs = kTs
self.energy = {}
self._fission_energy = None
self.reactions = OrderedDict()
self.summed_reactions = OrderedDict()
self.urr = None
self._urr = {}
def __contains__(self, mt):
return mt in self.reactions or mt in self.summed_reactions
@ -123,12 +205,8 @@ class IncidentNeutron(object):
return self._atomic_weight_ratio
@property
def energy(self):
return self._energy
@property
def temperature(self):
return self._temperature
def fission_energy(self):
return self._fission_energy
@property
def reactions(self):
@ -142,6 +220,10 @@ class IncidentNeutron(object):
def urr(self):
return self._urr
@property
def temperatures(self):
return ["{}K".format(int(round(kT / K_BOLTZMANN))) for kT in self.kTs]
@name.setter
def name(self, name):
cv.check_type('name', name, basestring)
@ -149,7 +231,7 @@ class IncidentNeutron(object):
@property
def atomic_symbol(self):
return atomic_symbol[self.atomic_number]
return ATOMIC_SYMBOL[self.atomic_number]
@atomic_number.setter
def atomic_number(self, atomic_number):
@ -175,16 +257,11 @@ class IncidentNeutron(object):
cv.check_greater_than('atomic weight ratio', atomic_weight_ratio, 0.0)
self._atomic_weight_ratio = atomic_weight_ratio
@temperature.setter
def temperature(self, temperature):
cv.check_type('temperature', temperature, Real)
cv.check_greater_than('temperature', temperature, 0.0, True)
self._temperature = temperature
@energy.setter
def energy(self, energy):
cv.check_type('energy grid', energy, Iterable, Real)
self._energy = energy
@fission_energy.setter
def fission_energy(self, fission_energy):
cv.check_type('fission energy release', fission_energy,
FissionEnergyRelease)
self._fission_energy = fission_energy
@reactions.setter
def reactions(self, reactions):
@ -198,10 +275,61 @@ class IncidentNeutron(object):
@urr.setter
def urr(self, urr):
cv.check_type('probability tables', urr,
(ProbabilityTables, type(None)))
cv.check_type('probability table dictionary', urr, MutableMapping)
for key, value in urr:
cv.check_type('probability table temperature', key, basestring)
cv.check_type('probability tables', value, ProbabilityTables)
self._urr = urr
def add_temperature_from_ace(self, ace_or_filename, metastable_scheme='nndc'):
"""Append data from an ACE file at a different temperature.
Parameters
----------
ace_or_filename : openmc.data.ace.Table or str
ACE table to read from. If given as a string, it is assumed to be
the filename for the ACE file.
metastable_scheme : {'nndc', 'mcnp'}
Determine how ZAID identifiers are to be interpreted in the case of
a metastable nuclide. Because the normal ZAID (=1000*Z + A) does not
encode metastable information, different conventions are used among
different libraries. In MCNP libraries, the convention is to add 400
for a metastable nuclide except for Am242m, for which 95242 is
metastable and 95642 (or 1095242 in newer libraries) is the ground
state. For NNDC libraries, ZAID is given as 1000*Z + A + 100*m.
"""
data = IncidentNeutron.from_ace(ace_or_filename, metastable_scheme)
# Check if temprature already exists
strT = data.temperatures[0]
if strT in self.temperatures:
warn('Cross sections at T={} already exist.'.format(strT))
return
# Check that name matches
if data.name != self.name:
raise ValueError('Data provided for an incorrect nuclide.')
# Add temperature
self.kTs += data.kTs
# Add energy grid
self.energy[strT] = data.energy[strT]
# Add normal and summed reactions
for mt in chain(data.reactions, data.summed_reactions):
if mt not in self:
raise ValueError("Tried to add cross sections for MT={} at T={}"
" but this reaction doesn't exist.".format(
mt, strT))
self[mt].xs[strT] = data[mt].xs[strT]
# Add probability tables
if strT in data.urr:
self.urr[strT] = data.urr[strT]
def get_reaction_components(self, mt):
"""Determine what reactions make up summed reaction.
@ -255,10 +383,14 @@ class IncidentNeutron(object):
g.attrs['A'] = self.mass_number
g.attrs['metastable'] = self.metastable
g.attrs['atomic_weight_ratio'] = self.atomic_weight_ratio
g.attrs['temperature'] = self.temperature
ktg = g.create_group('kTs')
for i, temperature in enumerate(self.temperatures):
ktg.create_dataset(temperature, data=self.kTs[i])
# Write energy grid
g.create_dataset('energy', data=self.energy)
eg = g.create_group('energy')
for temperature in self.temperatures:
eg.create_dataset(temperature, data=self.energy[temperature])
# Write reaction data
rxs_group = g.create_group('reactions')
@ -272,9 +404,16 @@ class IncidentNeutron(object):
rx.derived_products[0].to_hdf5(tgroup)
# Write unresolved resonance probability tables
if self.urr is not None:
if self.urr:
urr_group = g.create_group('urr')
self.urr.to_hdf5(urr_group)
for temperature, urr in self.urr.items():
tgroup = urr_group.create_group(temperature)
urr.to_hdf5(tgroup)
# Write fission energy release data
if self.fission_energy is not None:
fer_group = g.create_group('fission_energy_release')
self.fission_energy.to_hdf5(fer_group)
f.close()
@ -306,13 +445,18 @@ class IncidentNeutron(object):
mass_number = group.attrs['A']
metastable = group.attrs['metastable']
atomic_weight_ratio = group.attrs['atomic_weight_ratio']
temperature = group.attrs['temperature']
kTg = group['kTs']
kTs = []
for temp in kTg:
kTs.append(kTg[temp].value)
data = cls(name, atomic_number, mass_number, metastable,
atomic_weight_ratio, temperature)
atomic_weight_ratio, kTs)
# Read energy grid
data.energy = group['energy'].value
e_group = group['energy']
for temperature, dset in e_group.items():
data.energy[temperature] = dset.value
# Read reaction data
rxs_group = group['reactions']
@ -326,21 +470,26 @@ 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.
# 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
rxs = [data[mt] for mt in SUM_RULES[mt_sum] if mt in data]
if len(rxs) > 0:
data.summed_reactions[mt_sum] = rx = Reaction(mt_sum)
for T in data.temperatures:
rx.xs[T] = Sum([rx.xs[T] for rx in rxs])
# Read unresolved resonance probability tables
if 'urr' in group:
urr_group = group['urr']
data.urr = ProbabilityTables.from_hdf5(urr_group)
for temperature, tgroup in urr_group.items():
data.urr[temperature] = ProbabilityTables.from_hdf5(tgroup)
# Read fission energy release data
if 'fission_energy_release' in group:
fer_group = group['fission_energy_release']
data.fission_energy = FissionEnergyRelease.from_hdf5(fer_group)
return data
@ -350,9 +499,9 @@ class IncidentNeutron(object):
Parameters
----------
ace : openmc.data.ace.Table or str
ACE table to read from. If given as a string, it is assumed to be
the filename for the ACE file.
ace_or_filename : openmc.data.ace.Table or str
ACE table to read from. If the value is a string, it is assumed to
be the filename for the ACE file.
metastable_scheme : {'nndc', 'mcnp'}
Determine how ZAID identifiers are to be interpreted in the case of
a metastable nuclide. Because the normal ZAID (=1000*Z + A) does not
@ -368,6 +517,8 @@ class IncidentNeutron(object):
Incident neutron continuous-energy data
"""
# First obtain the data for the first provided ACE table/file
if isinstance(ace_or_filename, Table):
ace = ace_or_filename
else:
@ -375,55 +526,35 @@ class IncidentNeutron(object):
# If mass number hasn't been specified, make an educated guess
zaid, xs = ace.name.split('.')
zaid = int(zaid)
Z = zaid // 1000
mass_number = zaid % 1000
name, element, Z, mass_number, metastable = \
_get_metadata(int(zaid), metastable_scheme)
if metastable_scheme == 'mcnp':
if zaid > 1000000:
# New SZA format
Z = Z % 1000
if zaid == 1095242:
metastable = 0
else:
metastable = zaid // 1000000
else:
if zaid == 95242:
metastable = 1
elif zaid == 95642:
metastable = 0
else:
metastable = 1 if mass_number > 300 else 0
elif metastable_scheme == 'nndc':
metastable = 1 if mass_number > 300 else 0
while mass_number > 3*Z:
mass_number -= 100
# Determine name for group
element = ATOMIC_SYMBOL[Z]
if metastable > 0:
name = '{}{}_m{}.{}'.format(element, mass_number, metastable, xs)
else:
name = '{}{}.{}'.format(element, mass_number, xs)
# Assign temperature to the running list
kTs = [ace.temperature]
data = cls(name, Z, mass_number, metastable,
ace.atomic_weight_ratio, ace.temperature)
ace.atomic_weight_ratio, kTs)
# Get string of temperature to use as a dictionary key
strT = data.temperatures[0]
# Read energy grid
n_energy = ace.nxs[3]
energy = ace.xss[ace.jxs[1]:ace.jxs[1] + n_energy]
data.energy = energy
total_xs = ace.xss[ace.jxs[1] + n_energy:ace.jxs[1] + 2*n_energy]
absorption_xs = ace.xss[ace.jxs[1] + 2*n_energy:ace.jxs[1] + 3*n_energy]
data.energy[strT] = energy
total_xs = ace.xss[ace.jxs[1] + n_energy:ace.jxs[1] + 2 * n_energy]
absorption_xs = ace.xss[ace.jxs[1] + 2 * n_energy:ace.jxs[1] +
3 * n_energy]
# Create summed reactions (total and absorption)
total = Reaction(1)
total.xs = Tabulated1D(energy, total_xs)
total.xs[strT] = Tabulated1D(energy, total_xs)
data.summed_reactions[1] = total
absorption = Reaction(27)
absorption.xs = Tabulated1D(energy, absorption_xs)
data.summed_reactions[27] = absorption
if np.count_nonzero(absorption_xs) > 0:
absorption = Reaction(27)
absorption.xs[strT] = Tabulated1D(energy, absorption_xs)
data.summed_reactions[27] = absorption
# Read each reaction
n_reaction = ace.nxs[4] + 1
@ -452,13 +583,16 @@ class IncidentNeutron(object):
warn('Photon production is present for MT={} but no '
'reaction components exist.'.format(mt))
continue
rx.xs = Sum([data.reactions[mt_i].xs for mt_i in mts])
rx.xs[strT] = Sum([data.reactions[mt_i].xs[strT]
for mt_i in mts])
# Determine summed cross section
rx.products += _get_photon_products(ace, rx)
data.summed_reactions[mt] = rx
# Read unresolved resonance probability tables
data.urr = ProbabilityTables.from_ace(ace)
urr = ProbabilityTables.from_ace(ace)
if urr is not None:
data.urr[strT] = urr
return data

View file

@ -3,17 +3,17 @@ from numbers import Real
import sys
import numpy as np
from numpy.polynomial.polynomial import Polynomial
import openmc.checkvalue as cv
from .function import Tabulated1D
from openmc.mixin import EqualityMixin
from .function import Tabulated1D, Polynomial, Function1D
from .angle_energy import AngleEnergy
if sys.version_info[0] >= 3:
basestring = str
class Product(object):
class Product(EqualityMixin):
"""Secondary particle emitted in a nuclear reaction
Parameters
@ -36,7 +36,7 @@ class Product(object):
yield represents particles from prompt and delayed sources.
particle : str
What particle the reaction product is.
yield_ : float or openmc.data.Tabulated1D or numpy.polynomial.Polynomial
yield_ : openmc.data.Function1D
Yield of secondary particle in the reaction.
"""
@ -47,7 +47,7 @@ class Product(object):
self.emission_mode = 'prompt'
self.distribution = []
self.applicability = []
self.yield_ = 1
self.yield_ = Polynomial((1,)) # 0-order polynomial i.e. a constant
def __repr__(self):
if isinstance(self.yield_, Real):
@ -119,8 +119,7 @@ class Product(object):
@yield_.setter
def yield_(self, yield_):
cv.check_type('product yield', yield_,
(Real, Tabulated1D, Polynomial))
cv.check_type('product yield', yield_, Function1D)
self._yield = yield_
def to_hdf5(self, group):
@ -138,16 +137,7 @@ class Product(object):
group.attrs['decay_rate'] = self.decay_rate
# Write yield
if isinstance(self.yield_, Tabulated1D):
self.yield_.to_hdf5(group, 'yield')
dset = group['yield']
dset.attrs['type'] = np.string_('tabulated')
elif isinstance(self.yield_, Polynomial):
dset = group.create_dataset('yield', data=self.yield_.coef)
dset.attrs['type'] = np.string_('polynomial')
else:
dset = group.create_dataset('yield', data=float(self.yield_))
dset.attrs['type'] = np.string_('constant')
self.yield_.to_hdf5(group, 'yield')
# Write applicability/distribution
group.attrs['n_distribution'] = len(self.distribution)
@ -180,13 +170,7 @@ class Product(object):
p.decay_rate = group.attrs['decay_rate']
# Read yield
yield_type = group['yield'].attrs['type'].decode()
if yield_type == 'constant':
p.yield_ = group['yield'].value
elif yield_type == 'polynomial':
p.yield_ = Polynomial(group['yield'].value)
elif yield_type == 'tabulated':
p.yield_ = Tabulated1D.from_hdf5(group['yield'])
p.yield_ = Function1D.from_hdf5(group['yield'])
# Read applicability/distribution
n_distribution = group.attrs['n_distribution']

View file

@ -1,18 +1,18 @@
from __future__ import division, unicode_literals
from collections import Iterable, Callable
from collections import Iterable, Callable, MutableMapping
from copy import deepcopy
from numbers import Real
from numbers import Real, Integral
from warnings import warn
import numpy as np
from numpy.polynomial import Polynomial
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
from openmc.stats import Uniform
from .angle_distribution import AngleDistribution
from .angle_energy import AngleEnergy
from .function import Tabulated1D
from .data import REACTION_NAME
from .function import Tabulated1D, Polynomial, Function1D
from .data import REACTION_NAME, K_BOLTZMANN
from .product import Product
from .uncorrelated import UncorrelatedAngleEnergy
@ -211,7 +211,7 @@ def _get_photon_products(ace, rx):
# Get photon production cross section
photon_prod_xs = ace.xss[idx + 2:idx + 2 + n_energy]
neutron_xs = rx.xs(energy)
neutron_xs = list(rx.xs.values())[0](energy)
idx = np.where(neutron_xs > 0.)
# Calculate photon yield
@ -251,7 +251,7 @@ def _get_photon_products(ace, rx):
return photons
class Reaction(object):
class Reaction(EqualityMixin):
"""A nuclear reaction
A Reaction object represents a single reaction channel for a nuclide with
@ -261,8 +261,7 @@ class Reaction(object):
Parameters
----------
mt : int
The ENDF MT number for this reaction. On occasion, MCNP uses MT numbers
that don't correspond exactly to the ENDF specification.
The ENDF MT number for this reaction.
Attributes
----------
@ -274,16 +273,12 @@ class Reaction(object):
The ENDF MT number for this reaction.
q_value : float
The Q-value of this reaction in MeV.
table : openmc.data.ace.Table
The ACE table which contains this reaction.
threshold : float
Threshold of the reaction in MeV
threshold_idx : int
The index on the energy grid corresponding to the threshold of this
reaction.
xs : callable
xs : dict of str to openmc.data.Function1D
Microscopic cross section for this reaction as a function of incident
energy
energy; these cross sections are provided in a dictionary where the key
is the temperature of the cross section set.
products : Iterable of openmc.data.Product
Reaction products
derived_products : Iterable of openmc.data.Product
@ -293,13 +288,13 @@ class Reaction(object):
"""
def __init__(self, mt):
self.center_of_mass = True
self._center_of_mass = True
self._q_value = 0.
self._xs = {}
self._products = []
self._derived_products = []
self.mt = mt
self.q_value = 0.
self.threshold_idx = 0
self._xs = None
self.products = []
self.derived_products = []
def __repr__(self):
if self.mt in REACTION_NAME:
@ -320,8 +315,8 @@ class Reaction(object):
return self._products
@property
def threshold(self):
return self.xs.x[0]
def derived_products(self):
return self._derived_products
@property
def xs(self):
@ -342,12 +337,18 @@ class Reaction(object):
cv.check_type('reaction products', products, Iterable, Product)
self._products = products
@derived_products.setter
def derived_products(self, derived_products):
cv.check_type('reaction derived products', derived_products,
Iterable, Product)
self._derived_products = derived_products
@xs.setter
def xs(self, xs):
cv.check_type('reaction cross section', xs, Callable)
if isinstance(xs, Tabulated1D):
for y in xs.y:
cv.check_greater_than('reaction cross section', y, 0.0, True)
cv.check_type('reaction cross section dictionary', xs, MutableMapping)
for key, value in xs.items():
cv.check_type('reaction cross section temperature', key, basestring)
cv.check_type('reaction cross section', value, Function1D)
self._xs = xs
def to_hdf5(self, group):
@ -366,10 +367,16 @@ class Reaction(object):
else:
group.attrs['label'] = np.string_(self.mt)
group.attrs['Q_value'] = self.q_value
group.attrs['threshold_idx'] = self.threshold_idx + 1
group.attrs['center_of_mass'] = 1 if self.center_of_mass else 0
if self.xs is not None:
group.create_dataset('xs', data=self.xs.y)
for T in self.xs:
Tgroup = group.create_group(T)
if self.xs[T] is not None:
dset = Tgroup.create_dataset('xs', data=self.xs[T].y)
if hasattr(self.xs[T], '_threshold_idx'):
threshold_idx = self.xs[T]._threshold_idx + 1
else:
threshold_idx = 1
dset.attrs['threshold_idx'] = threshold_idx
for i, p in enumerate(self.products):
pgroup = group.create_group('product_{}'.format(i))
p.to_hdf5(pgroup)
@ -382,8 +389,9 @@ class Reaction(object):
----------
group : h5py.Group
HDF5 group to write to
energy : Iterable of float
Array of energies at which cross sections are tabulated at
energy : dict
Dictionary whose keys are temperatures (e.g., '300K') and values are
arrays of energies at which cross sections are tabulated at.
Returns
-------
@ -391,16 +399,27 @@ class Reaction(object):
Reaction data
"""
mt = group.attrs['mt']
rx = cls(mt)
rx.q_value = group.attrs['Q_value']
rx.threshold_idx = group.attrs['threshold_idx'] - 1
rx.center_of_mass = bool(group.attrs['center_of_mass'])
# Read cross section
if 'xs' in group:
xs = group['xs'].value
rx.xs = Tabulated1D(energy[rx.threshold_idx:], xs)
# Read cross section at each temperature
for T, Tgroup in group.items():
if T.endswith('K'):
if 'xs' in Tgroup:
# Make sure temperature has associated energy grid
if T not in energy:
raise ValueError(
'Could not create reaction cross section for MT={} '
'at T={} because no corresponding energy grid '
'exists.'.format(mt, T))
xs = Tgroup['xs'].value
threshold_idx = Tgroup['xs'].attrs['threshold_idx'] - 1
tabulated_xs = Tabulated1D(energy[T][threshold_idx:], xs)
tabulated_xs._threshold_idx = threshold_idx
rx.xs[T] = tabulated_xs
# Determine number of products
n_product = 0
@ -421,6 +440,10 @@ class Reaction(object):
n_grid = ace.nxs[3]
grid = ace.xss[ace.jxs[1]:ace.jxs[1] + n_grid]
# Convert data temperature to a "300.0K" number for indexing
# temperature data
strT = str(int(round(ace.temperature / K_BOLTZMANN))) + "K"
if i_reaction > 0:
mt = int(ace.xss[ace.jxs[3] + i_reaction - 1])
rx = cls(mt)
@ -435,11 +458,11 @@ class Reaction(object):
loc = int(ace.xss[ace.jxs[6] + i_reaction - 1])
# Determine starting index on energy grid
rx.threshold_idx = int(ace.xss[ace.jxs[7] + loc - 1]) - 1
threshold_idx = int(ace.xss[ace.jxs[7] + loc - 1]) - 1
# Determine number of energies in reaction
n_energy = int(ace.xss[ace.jxs[7] + loc])
energy = grid[rx.threshold_idx:rx.threshold_idx + n_energy]
energy = grid[threshold_idx:threshold_idx + n_energy]
# Read reaction cross section
xs = ace.xss[ace.jxs[7] + loc + 1:ace.jxs[7] + loc + 1 + n_energy]
@ -450,7 +473,9 @@ class Reaction(object):
"to zero.".format(rx.mt, ace.name))
xs[xs < 0.0] = 0.0
rx.xs = Tabulated1D(energy, xs)
tabulated_xs = Tabulated1D(energy, xs)
tabulated_xs._threshold_idx = threshold_idx
rx.xs[strT] = tabulated_xs
# ==================================================================
# YIELD AND ANGLE-ENERGY DISTRIBUTION
@ -465,7 +490,8 @@ class Reaction(object):
idx = ace.jxs[11] + abs(ty) - 101
yield_ = Tabulated1D.from_ace(ace, idx)
else:
yield_ = abs(ty)
# 0-order polynomial i.e. a constant
yield_ = Polynomial((abs(ty),))
neutron = Product('neutron')
neutron.yield_ = yield_
@ -508,7 +534,9 @@ class Reaction(object):
"Setting to zero.".format(ace.name))
elastic_xs[elastic_xs < 0.0] = 0.0
rx.xs = Tabulated1D(grid, elastic_xs)
tabulated_xs = Tabulated1D(grid, elastic_xs)
tabulated_xs._threshold_idx = 0
rx.xs[strT] = tabulated_xs
# No energy distribution for elastic scattering
neutron = Product('neutron')

View file

@ -7,6 +7,8 @@ import numpy as np
import h5py
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
from .data import K_BOLTZMANN, ATOMIC_SYMBOL
from .ace import Table, get_table
from .angle_energy import AngleEnergy
from .function import Tabulated1D
@ -33,6 +35,7 @@ _THERMAL_NAMES = {'al': 'c_Al27', 'al27': 'c_Al27',
'orthod': 'c_ortho_D', 'dortho': 'c_ortho_D',
'orthoh': 'c_ortho_H', 'hortho': 'c_ortho_H',
'ouo2': 'c_O_in_UO2', 'o2-u': 'c_O_in_UO2', 'o2/u': 'c_O_in_UO2',
'sio2': 'c_SiO2',
'parad': 'c_para_D', 'dpara': 'c_para_D',
'parah': 'c_para_H', 'hpara': 'c_para_H',
'sch4': 'c_solid_CH4', 'smeth': 'c_solid_CH4',
@ -40,7 +43,27 @@ _THERMAL_NAMES = {'al': 'c_Al27', 'al27': 'c_Al27',
'zrzrh': 'c_Zr_in_ZrH', 'zr-h': 'c_Zr_in_ZrH', 'zr/h': 'c_Zr_in_ZrH'}
class CoherentElastic(object):
def get_thermal_name(name):
"""Get proper S(a,b) table name, e.g. 'HH2O' -> 'c_H_in_H2O'"""
if name in _THERMAL_NAMES.values():
return name
elif name.lower() in _THERMAL_NAMES:
return _THERMAL_NAMES[name.lower()]
else:
# Make an educated guess?? This actually works well for
# JEFF-3.2 which stupidly uses names like lw00.32t,
# lw01.32t, etc. for different temperatures
matches = get_close_matches(
name.lower(), _THERMAL_NAMES.keys(), cutoff=0.5)
if len(matches) > 0:
return _THERMAL_NAMES[matches[0]]
else:
# OK, we give up. Just use the ACE name.
return 'c_' + name
class CoherentElastic(EqualityMixin):
r"""Coherent elastic scattering data from a crystalline material
Parameters
@ -67,7 +90,7 @@ class CoherentElastic(object):
if isinstance(E, Iterable):
E = np.asarray(E)
idx = np.searchsorted(self.bragg_edges, E)
return self.factors[idx]/E
return self.factors[idx] / E
def __len__(self):
return len(self.bragg_edges)
@ -126,18 +149,19 @@ class CoherentElastic(object):
return cls(bragg_edges, factors)
class ThermalScattering(object):
class ThermalScattering(EqualityMixin):
"""A ThermalScattering object contains thermal scattering data as represented by
an S(alpha, beta) table.
Parameters
----------
name : str
ZAID identifier of the table, e.g. lwtr.10t.
Name of the material using GND convention, e.g. c_H_in_H2O
atomic_weight_ratio : float
Atomic mass ratio of the target nuclide.
temperature : float
Temperature of the target nuclide in eV.
kTs : Iterable of float
List of temperatures of the target nuclide in the data set.
The temperatures have units of MeV.
Attributes
----------
@ -150,25 +174,33 @@ class ThermalScattering(object):
Inelastic scattering cross section derived in the incoherent
approximation
name : str
Name of the table, e.g. lwtr.20t.
temperature : float
Temperature of the target nuclide in eV.
zaids : Iterable of int
ZAID identifiers that the thermal scattering data applies to
Name of the material using GND convention, e.g. c_H_in_H2O
temperatures : Iterable of str
List of string representations the temperatures of the target nuclide
in the data set. The temperatures are strings of the temperature,
rounded to the nearest integer; e.g., '294K'
kTs : Iterable of float
List of temperatures of the target nuclide in the data set.
The temperatures have units of MeV.
nuclides : Iterable of str
Nuclide names that the thermal scattering data applies to
"""
def __init__(self, name, atomic_weight_ratio, temperature):
def __init__(self, name, atomic_weight_ratio, kTs):
self.name = name
self.atomic_weight_ratio = atomic_weight_ratio
self.temperature = temperature
self.elastic_xs = None
self.elastic_mu_out = None
self.inelastic_xs = None
self.inelastic_e_out = None
self.inelastic_mu_out = None
self.kTs = kTs
self.temperatures = [str(int(round(kT / K_BOLTZMANN))) + "K"
for kT in kTs]
self.elastic_xs = {}
self.elastic_mu_out = {}
self.inelastic_xs = {}
self.inelastic_e_out = {}
self.inelastic_mu_out = {}
self.inelastic_dist = {}
self.secondary_mode = None
self.zaids = []
self.nuclides = []
def __repr__(self):
if hasattr(self, 'name'):
@ -194,83 +226,44 @@ class ThermalScattering(object):
# Write basic data
g = f.create_group(self.name)
g.attrs['atomic_weight_ratio'] = self.atomic_weight_ratio
g.attrs['temperature'] = self.temperature
g.attrs['zaids'] = self.zaids
g.attrs['nuclides'] = np.array(self.nuclides, dtype='S')
g.attrs['secondary_mode'] = np.string_(self.secondary_mode)
ktg = g.create_group('kTs')
for i, temperature in enumerate(self.temperatures):
ktg.create_dataset(temperature, data=self.kTs[i])
# Write thermal elastic scattering
if self.elastic_xs is not None:
elastic_group = g.create_group('elastic')
self.elastic_xs.to_hdf5(elastic_group, 'xs')
if self.elastic_mu_out is not None:
elastic_group.create_dataset('mu_out', data=self.elastic_mu_out)
for T in self.temperatures:
Tg = g.create_group(T)
# Write thermal elastic scattering
if self.elastic_xs:
elastic_group = Tg.create_group('elastic')
# Write thermal inelastic scattering
if self.inelastic_xs is not None:
inelastic_group = g.create_group('inelastic')
self.inelastic_xs.to_hdf5(inelastic_group, 'xs')
inelastic_group.attrs['secondary_mode'] = np.string_(self.secondary_mode)
if self.secondary_mode in ('equal', 'skewed'):
inelastic_group.create_dataset('energy_out', data=self.inelastic_e_out)
inelastic_group.create_dataset('mu_out', data=self.inelastic_mu_out)
elif self.secondary_mode == 'continuous':
self.inelastic_dist.to_hdf5(inelastic_group)
self.elastic_xs[T].to_hdf5(elastic_group, 'xs')
if self.elastic_mu_out:
elastic_group.create_dataset('mu_out',
data=self.elastic_mu_out[T])
@classmethod
def from_hdf5(cls, group):
"""Generate thermal scattering data from HDF5 group
# Write thermal inelastic scattering
if self.inelastic_xs:
inelastic_group = Tg.create_group('inelastic')
self.inelastic_xs[T].to_hdf5(inelastic_group, 'xs')
if self.secondary_mode in ('equal', 'skewed'):
inelastic_group.create_dataset('energy_out',
data=self.inelastic_e_out[T])
inelastic_group.create_dataset('mu_out',
data=self.inelastic_mu_out[T])
elif self.secondary_mode == 'continuous':
self.inelastic_dist[T].to_hdf5(inelastic_group)
f.close()
def add_temperature_from_ace(self, ace_or_filename, name=None):
"""Add data to the ThermalScattering object from an ACE file at a
different temperature.
Parameters
----------
group : h5py.Group
HDF5 group to read from
Returns
-------
openmc.data.ThermalScattering
Neutron thermal scattering data
"""
name = group.name[1:]
atomic_weight_ratio = group.attrs['atomic_weight_ratio']
temperature = group.attrs['temperature']
table = cls(name, atomic_weight_ratio, temperature)
table.zaids = group.attrs['zaids']
# Read thermal elastic scattering
if 'elastic' in group:
elastic_group = group['elastic']
# Cross section
elastic_xs_type = elastic_group['xs'].attrs['type'].decode()
if elastic_xs_type == 'tab1':
table.elastic_xs = Tabulated1D.from_hdf5(elastic_group['xs'])
elif elastic_xs_type == 'bragg':
table.elastic_xs = CoherentElastic.from_hdf5(elastic_group['xs'])
# Angular distribution
if 'mu_out' in elastic_group:
table.elastic_mu_out = elastic_group['mu_out'].value
# Read thermal inelastic scattering
if 'inelastic' in group:
inelastic_group = group['inelastic']
table.secondary_mode = inelastic_group.attrs['secondary_mode'].decode()
table.inelastic_xs = Tabulated1D.from_hdf5(inelastic_group['xs'])
if table.secondary_mode in ('equal', 'skewed'):
table.inelastic_e_out = inelastic_group['energy_out']
table.inelastic_mu_out = inelastic_group['mu_out']
elif table.secondary_mode == 'continuous':
table.inelastic_dist = AngleEnergy.from_hdf5(inelastic_group)
return table
@classmethod
def from_ace(cls, ace_or_filename, name=None):
"""Generate thermal scattering data from an ACE table
Parameters
----------
ace : openmc.data.ace.Table or str
ace_or_filename : openmc.data.ace.Table or str
ACE table to read from. If given as a string, it is assumed to be
the filename for the ACE file.
name : str
@ -293,7 +286,235 @@ class ThermalScattering(object):
ace_name, xs = ace.name.split('.')
if name is None:
if ace_name.lower() in _THERMAL_NAMES:
name = _THERMAL_NAMES[ace_name.lower()] + '.' + xs
name = _THERMAL_NAMES[ace_name.lower()]
else:
# Make an educated guess? This actually works well for JEFF-3.2
# which stupidly uses names like lw00.32t, lw01.32t, etc. for
# different temperatures
matches = get_close_matches(
ace_name.lower(), _THERMAL_NAMES.keys(), cutoff=0.5)
if len(matches) > 0:
name = _THERMAL_NAMES[matches[0]]
else:
# OK, we give up. Just use the ACE name.
name = 'c_' + ace.name
warn('Thermal scattering material "{}" is not recognized. '
'Assigning a name of {}.'.format(ace.name, name))
# If this ACE data matches the data within self then get the data
if ace.temperature not in self.kTs:
if name == self.name:
# Add temperature and kTs
strT = str(int(round(ace.temperature / K_BOLTZMANN))) + "K"
self.temperatures.append(strT)
self.kTs.append(ace.temperature)
# Incoherent inelastic scattering cross section
idx = ace.jxs[1]
n_energy = int(ace.xss[idx])
energy = ace.xss[idx + 1: idx + 1 + n_energy]
xs = ace.xss[idx + 1 + n_energy: idx + 1 + 2 * n_energy]
self.inelastic_xs[strT] = Tabulated1D(energy, xs)
# Make sure secondary_mode is always equal. This should always
# be the case, but to reduce future debugging should something
# change, this will alert the developers to the issue.
if ace.nxs[7] == 0:
secondary_mode = 'equal'
elif ace.nxs[7] == 1:
secondary_mode = 'skewed'
elif ace.nxs[7] == 2:
secondary_mode = 'continuous'
if secondary_mode != self.secondary_mode:
raise ValueError('Secondary Modes are inconsistent.')
n_energy_out = ace.nxs[4]
if self.secondary_mode in ('equal', 'skewed'):
n_mu = ace.nxs[3]
idx = ace.jxs[3]
self.inelastic_e_out[strT] = \
ace.xss[idx:idx + n_energy * n_energy_out * (n_mu + 2):
n_mu + 2]
self.inelastic_e_out[strT].shape = \
(n_energy, n_energy_out)
self.inelastic_mu_out[strT] = \
ace.xss[idx:idx + n_energy * n_energy_out * (n_mu + 2)]
self.inelastic_mu_out[strT].shape = \
(n_energy, n_energy_out, n_mu + 2)
self.inelastic_mu_out[strT] = \
self.inelastic_mu_out[strT][:, :, 1:]
else:
n_mu = ace.nxs[3] - 1
idx = ace.jxs[3]
locc = ace.xss[idx:idx + n_energy].astype(int)
n_energy_out = \
ace.xss[idx + n_energy:idx + 2 * n_energy].astype(int)
energy_out = []
mu_out = []
for i in range(n_energy):
idx = locc[i]
# Outgoing energy distribution for incoming energy i
e = ace.xss[idx + 1:idx + 1 + n_energy_out[i]*(n_mu + 3):
n_mu + 3]
p = ace.xss[idx + 2:idx + 2 + n_energy_out[i]*(n_mu + 3):
n_mu + 3]
c = ace.xss[idx + 3:idx + 3 + n_energy_out[i]*(n_mu + 3):
n_mu + 3]
eout_i = Tabular(e, p, 'linear-linear', ignore_negative=True)
eout_i.c = c
# Outgoing angle distribution for each
# (incoming, outgoing) energy pair
mu_i = []
for j in range(n_energy_out[i]):
mu = ace.xss[idx + 4:idx + 4 + n_mu]
p_mu = 1. / n_mu * np.ones(n_mu)
mu_ij = Discrete(mu, p_mu)
mu_ij.c = np.cumsum(p_mu)
mu_i.append(mu_ij)
idx += 3 + n_mu
energy_out.append(eout_i)
mu_out.append(mu_i)
# Create correlated angle-energy distribution
breakpoints = [n_energy]
interpolation = [2]
energy = self.inelastic_xs[strT].x
self.inelastic_dist[strT] = CorrelatedAngleEnergy(
breakpoints, interpolation, energy, energy_out, mu_out)
# Incoherent/coherent elastic scattering cross section
idx = ace.jxs[4]
if idx != 0:
n_energy = int(ace.xss[idx])
energy = ace.xss[idx + 1: idx + 1 + n_energy]
P = ace.xss[idx + 1 + n_energy: idx + 1 + 2 * n_energy]
if ace.nxs[5] == 4:
self.elastic_xs[strT] = CoherentElastic(energy, P)
else:
self.elastic_xs[strT] = Tabulated1D(energy, P)
# Angular distribution
n_mu = ace.nxs[6]
if n_mu != -1:
idx = ace.jxs[6]
self.elastic_mu_out[strT] = \
ace.xss[idx:idx + n_energy * n_mu]
self.elastic_mu_out[strT].shape = \
(n_energy, n_mu)
else:
raise ValueError('Data provided for an incorrect library')
else:
raise Warning('Temperature data set already within '
'IncidentNeutron object')
@classmethod
def from_hdf5(cls, group_or_filename):
"""Generate thermal scattering data from HDF5 group
Parameters
----------
group_or_filename : h5py.Group or str
HDF5 group containing interaction data. If given as a string, it is
assumed to be the filename for the HDF5 file, and the first group
is used to read from.
Returns
-------
openmc.data.ThermalScattering
Neutron thermal scattering data
"""
if isinstance(group_or_filename, h5py.Group):
group = group_or_filename
else:
h5file = h5py.File(group_or_filename, 'r')
group = list(h5file.values())[0]
name = group.name[1:]
atomic_weight_ratio = group.attrs['atomic_weight_ratio']
kTg = group['kTs']
kTs = []
for temp in kTg:
kTs.append(kTg[temp].value)
temperatures = [str(int(round(kT / K_BOLTZMANN))) + "K" for kT in kTs]
table = cls(name, atomic_weight_ratio, kTs)
table.nuclides = [nuc.decode() for nuc in group.attrs['nuclides']]
table.secondary_mode = group.attrs['secondary_mode'].decode()
# Read thermal elastic scattering
for T in temperatures:
Tgroup = group[T]
if 'elastic' in Tgroup:
elastic_group = Tgroup['elastic']
# Cross section
elastic_xs_type = elastic_group['xs'].attrs['type'].decode()
if elastic_xs_type == 'Tabulated1D':
table.elastic_xs[T] = \
Tabulated1D.from_hdf5(elastic_group['xs'])
elif elastic_xs_type == 'bragg':
table.elastic_xs[T] = \
CoherentElastic.from_hdf5(elastic_group['xs'])
# Angular distribution
if 'mu_out' in elastic_group:
table.elastic_mu_out[T] = \
elastic_group['mu_out'].value
# Read thermal inelastic scattering
if 'inelastic' in Tgroup:
inelastic_group = Tgroup['inelastic']
table.inelastic_xs[T] = \
Tabulated1D.from_hdf5(inelastic_group['xs'])
if table.secondary_mode in ('equal', 'skewed'):
table.inelastic_e_out[T] = \
inelastic_group['energy_out']
table.inelastic_mu_out[T] = \
inelastic_group['mu_out']
elif table.secondary_mode == 'continuous':
table.inelastic_dist[T] = \
AngleEnergy.from_hdf5(inelastic_group)
return table
@classmethod
def from_ace(cls, ace_or_filename, name=None):
"""Generate thermal scattering data from an ACE table
Parameters
----------
ace_or_filename : openmc.data.ace.Table or str
ACE table to read from. If given as a string, it is assumed to be
the filename for the ACE file.
name : str
GND-conforming name of the material, e.g. c_H_in_H2O. If none is
passed, the appropriate name is guessed based on the name of the ACE
table.
Returns
-------
openmc.data.ThermalScattering
Thermal scattering data
"""
if isinstance(ace_or_filename, Table):
ace = ace_or_filename
else:
ace = get_table(ace_or_filename)
# Get new name that is GND-consistent
ace_name, xs = ace.name.split('.')
if name is None:
if ace_name.lower() in _THERMAL_NAMES:
name = _THERMAL_NAMES[ace_name.lower()]
else:
# Make an educated guess?? This actually works well for JEFF-3.2
# which stupidly uses names like lw00.32t, lw01.32t, etc. for
@ -301,21 +522,25 @@ class ThermalScattering(object):
matches = get_close_matches(
ace_name.lower(), _THERMAL_NAMES.keys(), cutoff=0.5)
if len(matches) > 0:
name = _THERMAL_NAMES[matches[0]] + '.' + xs
name = _THERMAL_NAMES[matches[0]]
else:
# OK, we give up. Just use the ACE name.
name = 'c_' + ace.name
warn('Thermal scattering material "{}" is not recognized. '
'Assigning a name of {}.'.format(ace.name, name))
table = cls(name, ace.atomic_weight_ratio, ace.temperature)
# Assign temperature to the running list
kTs = [ace.temperature]
temperatures = [str(int(round(ace.temperature / K_BOLTZMANN))) + "K"]
table = cls(name, ace.atomic_weight_ratio, kTs)
# Incoherent inelastic scattering cross section
idx = ace.jxs[1]
n_energy = int(ace.xss[idx])
energy = ace.xss[idx+1 : idx+1+n_energy]
xs = ace.xss[idx+1+n_energy : idx+1+2*n_energy]
table.inelastic_xs = Tabulated1D(energy, xs)
table.inelastic_xs[temperatures[0]] = Tabulated1D(energy, xs)
if ace.nxs[7] == 0:
table.secondary_mode = 'equal'
@ -328,34 +553,45 @@ class ThermalScattering(object):
if table.secondary_mode in ('equal', 'skewed'):
n_mu = ace.nxs[3]
idx = ace.jxs[3]
table.inelastic_e_out = ace.xss[idx:idx+n_energy*n_energy_out*(n_mu+2):n_mu+2]
table.inelastic_e_out.shape = (n_energy, n_energy_out)
table.inelastic_e_out[temperatures[0]] = \
ace.xss[idx:idx + n_energy * n_energy_out * (n_mu + 2):
n_mu + 2]
table.inelastic_e_out[temperatures[0]].shape = \
(n_energy, n_energy_out)
table.inelastic_mu_out = ace.xss[idx:idx+n_energy*n_energy_out*(n_mu+2)]
table.inelastic_mu_out.shape = (n_energy, n_energy_out, n_mu+2)
table.inelastic_mu_out = table.inelastic_mu_out[:, :, 1:]
table.inelastic_mu_out[temperatures[0]] = \
ace.xss[idx:idx + n_energy * n_energy_out * (n_mu + 2)]
table.inelastic_mu_out[temperatures[0]].shape = \
(n_energy, n_energy_out, n_mu+2)
table.inelastic_mu_out[temperatures[0]] = \
table.inelastic_mu_out[temperatures[0]][:, :, 1:]
else:
n_mu = ace.nxs[3] - 1
idx = ace.jxs[3]
locc = ace.xss[idx:idx + n_energy].astype(int)
n_energy_out = ace.xss[idx + n_energy:idx + 2*n_energy].astype(int)
n_energy_out = \
ace.xss[idx + n_energy:idx + 2 * n_energy].astype(int)
energy_out = []
mu_out = []
for i in range(n_energy):
idx = locc[i]
# Outgoing energy distribution for incoming energy i
e = ace.xss[idx + 1:idx + 1 + n_energy_out[i]*(n_mu + 3):n_mu + 3]
p = ace.xss[idx + 2:idx + 2 + n_energy_out[i]*(n_mu + 3):n_mu + 3]
c = ace.xss[idx + 3:idx + 3 + n_energy_out[i]*(n_mu + 3):n_mu + 3]
e = ace.xss[idx + 1:idx + 1 + n_energy_out[i]*(n_mu + 3):
n_mu + 3]
p = ace.xss[idx + 2:idx + 2 + n_energy_out[i]*(n_mu + 3):
n_mu + 3]
c = ace.xss[idx + 3:idx + 3 + n_energy_out[i]*(n_mu + 3):
n_mu + 3]
eout_i = Tabular(e, p, 'linear-linear', ignore_negative=True)
eout_i.c = c
# Outgoing angle distribution for each (incoming, outgoing) energy pair
# Outgoing angle distribution for each
# (incoming, outgoing) energy pair
mu_i = []
for j in range(n_energy_out[i]):
mu = ace.xss[idx + 4:idx + 4 + n_mu]
p_mu = 1./n_mu*np.ones(n_mu)
p_mu = 1. / n_mu * np.ones(n_mu)
mu_ij = Discrete(mu, p_mu)
mu_ij.c = np.cumsum(p_mu)
mu_i.append(mu_ij)
@ -367,31 +603,35 @@ class ThermalScattering(object):
# Create correlated angle-energy distribution
breakpoints = [n_energy]
interpolation = [2]
energy = table.inelastic_xs.x
table.inelastic_dist = CorrelatedAngleEnergy(
energy = table.inelastic_xs[temperatures[0]].x
table.inelastic_dist[temperatures[0]] = CorrelatedAngleEnergy(
breakpoints, interpolation, energy, energy_out, mu_out)
# Incoherent/coherent elastic scattering cross section
idx = ace.jxs[4]
if idx != 0:
n_energy = int(ace.xss[idx])
energy = ace.xss[idx+1 : idx+1+n_energy]
P = ace.xss[idx+1+n_energy : idx+1+2*n_energy]
energy = ace.xss[idx + 1: idx + 1 + n_energy]
P = ace.xss[idx + 1 + n_energy: idx + 1 + 2 * n_energy]
if ace.nxs[5] == 4:
table.elastic_xs = CoherentElastic(energy, P)
table.elastic_xs[temperatures[0]] = CoherentElastic(energy, P)
else:
table.elastic_xs = Tabulated1D(energy, P)
table.elastic_xs[temperatures[0]] = Tabulated1D(energy, P)
# Angular distribution
n_mu = ace.nxs[6]
if n_mu != -1:
idx = ace.jxs[6]
table.elastic_mu_out = ace.xss[idx:idx + n_energy*n_mu]
table.elastic_mu_out.shape = (n_energy, n_mu)
table.elastic_mu_out[temperatures[0]] = \
ace.xss[idx:idx + n_energy * n_mu]
table.elastic_mu_out[temperatures[0]].shape = \
(n_energy, n_mu)
# Get relevant ZAIDs
pairs = np.fromiter(map(lambda p: p[0], ace.pairs), int)
table.zaids = pairs[np.nonzero(pairs)]
# Get relevant nuclides
for zaid, awr in ace.pairs:
if zaid > 0:
Z, A = divmod(zaid, 1000)
table.nuclides.append(ATOMIC_SYMBOL[Z] + str(A))
return table

View file

@ -4,9 +4,10 @@ from numbers import Integral, Real
import numpy as np
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
class ProbabilityTables(object):
class ProbabilityTables(EqualityMixin):
r"""Unresolved resonance region probability tables.
Parameters

View file

@ -19,38 +19,28 @@ class Element(object):
----------
name : str
Chemical symbol of the element, e.g. Pu
xs : str
Cross section identifier, e.g. 71c
Attributes
----------
name : str
Chemical symbol of the element, e.g. Pu
xs : str
Cross section identifier, e.g. 71c
scattering : {'data', 'iso-in-lab', None}
The type of angular scattering distribution to use
"""
def __init__(self, name='', xs=None):
def __init__(self, name=''):
# Initialize class attributes
self._name = ''
self._xs = None
self._scattering = None
# Set class attributes
self.name = name
if xs is not None:
self.xs = xs
def __eq__(self, other):
if isinstance(other, Element):
if self.name != other.name:
return False
elif self.xs != other.xs:
return False
else:
return True
elif isinstance(other, basestring) and other == self.name:
@ -72,17 +62,12 @@ class Element(object):
def __repr__(self):
string = 'Element - {0}\n'.format(self._name)
string += '{0: <16}{1}{2}\n'.format('\tXS', '=\t', self._xs)
if self.scattering is not None:
string += '{0: <16}{1}{2}\n'.format('\tscattering', '=\t',
self.scattering)
return string
@property
def xs(self):
return self._xs
@property
def name(self):
return self._name
@ -91,11 +76,6 @@ class Element(object):
def scattering(self):
return self._scattering
@xs.setter
def xs(self, xs):
check_type('cross section identifier', xs, basestring)
self._xs = xs
@name.setter
def name(self, name):
check_type('element name', name, basestring)
@ -127,6 +107,6 @@ class Element(object):
isotopes = []
for isotope, abundance in sorted(NATURAL_ABUNDANCE.items()):
if re.match(r'{}\d+'.format(self.name), isotope):
nuc = openmc.Nuclide(isotope, self.xs)
nuc = openmc.Nuclide(isotope)
isotopes.append((nuc, abundance))
return isotopes

View file

@ -20,6 +20,12 @@ _FILTER_TYPES = ['universe', 'material', 'cell', 'cellborn', 'surface',
'mesh', 'energy', 'energyout', 'mu', 'polar', 'azimuthal',
'distribcell', 'delayedgroup']
_CURRENT_NAMES = {1: 'x-min out', 2: 'x-max out',
3: 'y-min out', 4: 'y-max out',
5: 'z-min out', 6: 'z-max out',
7: 'x-min in', 8: 'x-max in',
9: 'y-min in', 10: 'y-max in',
11: 'z-min in', 12: 'z-max in'}
class FilterMeta(ABCMeta):
def __new__(cls, name, bases, namespace, **kwargs):
if not name.endswith('Filter'):
@ -260,7 +266,7 @@ class Filter(with_metaclass(FilterMeta, object)):
cell instance ID for 'distribcell' Filters. The bin is a 2-tuple of
floats for 'energy' and 'energyout' filters corresponding to the
energy boundaries of the bin of interest. The bin is an (x,y,z)
3-tuple for 'mesh' filters corresponding to the mesh cell
3-tuple for 'mesh' filters corresponding to the mesh cell of
interest.
Returns
@ -387,7 +393,6 @@ class Filter(with_metaclass(FilterMeta, object)):
filter_bins = np.repeat(self.bins, self.stride)
tile_factor = data_size / len(filter_bins)
filter_bins = np.tile(filter_bins, tile_factor)
filter_bins = filter_bins
df = pd.concat([df, pd.DataFrame(
{self.short_name.lower() : filter_bins})])
@ -416,7 +421,22 @@ class UniverseFilter(IntegralFilter): pass
class MaterialFilter(IntegralFilter): pass
class CellFilter(IntegralFilter): pass
class CellbornFilter(IntegralFilter): pass
class SurfaceFilter(IntegralFilter): pass
class SurfaceFilter(IntegralFilter):
def get_pandas_dataframe(self, data_size, distribcell_paths=True):
# Initialize Pandas DataFrame
import pandas as pd
df = pd.DataFrame()
filter_bins = np.repeat(self.bins, self.stride)
tile_factor = data_size / len(filter_bins)
filter_bins = np.tile(filter_bins, tile_factor)
filter_bins = [_CURRENT_NAMES[x] for x in filter_bins]
df = pd.concat([df, pd.DataFrame(
{self.short_name.lower() : filter_bins})])
return df
class MeshFilter(Filter):

View file

@ -50,6 +50,20 @@ class Geometry(object):
self._root_universe = root_universe
def add_volume_information(self, volume_calc):
"""Add volume information from a stochastic volume calculation.
Parameters
----------
volume_calc : openmc.VolumeCalculation
Results from a stochastic volume calculation
"""
if volume_calc.domain_type == 'cell':
for cell in self.get_all_cells():
if cell.id in volume_calc.results:
cell.add_volume_information(volume_calc)
def export_to_xml(self):
"""Create a geometry.xml file that can be used for a simulation.
@ -166,24 +180,6 @@ class Geometry(object):
universes.sort(key=lambda x: x.id)
return universes
def get_all_nuclides(self):
"""Return all nuclides assigned to a material in the geometry
Returns
-------
list of openmc.Nuclide
Nuclides in the geometry
"""
nuclides = OrderedDict()
materials = self.get_all_materials()
for material in materials:
nuclides.update(material.get_all_nuclides())
return nuclides
def get_all_materials(self):
"""Return all materials assigned to a cell

View file

@ -144,25 +144,26 @@ class Lattice(object):
return univs
def get_all_nuclides(self):
"""Return all nuclides contained in the lattice
def get_nuclides(self):
"""Returns all nuclides in the lattice
Returns
-------
nuclides : collections.OrderedDict
Dictionary whose keys are nuclide names and values are 2-tuples of
(nuclide, density)
nuclides : list of str
List of nuclide names
"""
nuclides = OrderedDict()
nuclides = []
# Get all unique Universes contained in each of the lattice cells
unique_universes = self.get_unique_universes()
# Append all Universes containing each cell to the dictionary
for universe_id, universe in unique_universes.items():
nuclides.update(universe.get_all_nuclides())
for universe in unique_universes.values():
for nuclide in universe.get_nuclides():
if nuclide not in nuclides:
nuclides.append(nuclide)
return nuclides

View file

@ -13,35 +13,25 @@ class Macroscopic(object):
----------
name : str
Name of the macroscopic data, e.g. UO2
xs : str
Cross section identifier, e.g. 71c
Attributes
----------
name : str
Name of the nuclide, e.g. UO2
xs : str
Cross section identifier, e.g. 71c
"""
def __init__(self, name='', xs=None):
def __init__(self, name=''):
# Initialize class attributes
self._name = ''
self._xs = None
# Set the Material class attributes
# Set the Macroscopic class attributes
self.name = name
if xs is not None:
self.xs = xs
def __eq__(self, other):
if isinstance(other, Macroscopic):
if self.name != other.name:
return False
elif self.xs != other.xs:
return False
else:
return True
elif isinstance(other, basestring) and other == self.name:
@ -53,27 +43,17 @@ class Macroscopic(object):
return not self == other
def __hash__(self):
return hash((self._name, self._xs))
return hash((self._name))
def __repr__(self):
string = 'Nuclide - {0}\n'.format(self._name)
string += '{0: <16}{1}{2}\n'.format('\tXS', '=\t', self._xs)
return string
@property
def name(self):
return self._name
@property
def xs(self):
return self._xs
@name.setter
def name(self, name):
check_type('name', name, basestring)
self._name = name
@xs.setter
def xs(self, xs):
check_type('cross-section identifier', xs, basestring)
self._xs = xs

View file

@ -6,6 +6,7 @@ from xml.etree import ElementTree as ET
import sys
import openmc
import openmc.data
import openmc.checkvalue as cv
from openmc.clean_xml import sort_xml_elements, clean_xml_indentation
@ -40,11 +41,19 @@ class Material(object):
name : str, optional
Name of the material. If not specified, the name will be the empty
string.
temperature : str, optional
The temperature identifier applied to this material. The units are
in Kelvin and the temperature rounded to the nearest integer.
For example, a tempreature of 293.6K would be provided as '294K'
Attributes
----------
id : int
Unique identifier for the material
temperature : str
The temperature identifier applied to this material. The units are
in Kelvin and the temperature rounded to the nearest integer.
For example, a tempreature of 293.6K would be provided as '294K'
density : float
Density of the material (units defined separately)
density_units : str
@ -62,10 +71,11 @@ class Material(object):
"""
def __init__(self, material_id=None, name=''):
def __init__(self, material_id=None, name='', temperature=None):
# Initialize class attributes
self.id = material_id
self.name = name
self.temperature = temperature
self._density = None
self._density_units = ''
@ -79,7 +89,7 @@ class Material(object):
# A list of tuples (element, percent, percent type)
self._elements = []
# If specified, a list of tuples of (table name, xs identifier)
# If specified, a list of table names
self._sab = []
# If true, the material will be initialized as distributed
@ -119,6 +129,8 @@ class Material(object):
string = 'Material\n'
string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id)
string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name)
string += '{0: <16}{1}{2}\n'.format('\Temperature', '=\t',
self._temperature)
string += '{0: <16}{1}{2}'.format('\tDensity', '=\t', self._density)
string += ' [{0}]\n'.format(self._density_units)
@ -126,13 +138,12 @@ class Material(object):
string += '{0: <16}\n'.format('\tS(a,b) Tables')
for sab in self._sab:
string += '{0: <16}{1}[{2}{3}]\n'.format('\tS(a,b)', '=\t',
sab[0], sab[1])
string += '{0: <16}{1}{2}\n'.format('\tS(a,b)', '=\t', sab)
string += '{0: <16}\n'.format('\tNuclides')
for nuclide, percent, percent_type in self._nuclides:
string += '{0: <16}'.format('\t{0.name}.{0.xs}'.format(nuclide))
string += '{0: <16}'.format('\t{0.name}'.format(nuclide))
string += '=\t{0: <12} [{1}]\n'.format(percent, percent_type)
if self._macroscopic is not None:
@ -142,7 +153,7 @@ class Material(object):
string += '{0: <16}\n'.format('\tElements')
for element, percent, percent_type in self._elements:
string += '{0: <16}'.format('\t{0.name}.{0.xs}'.format(element))
string += '{0: <16}'.format('\t{0.name}'.format(element))
string += '=\t{0: <12} [{1}]\n'.format(percent, percent_type)
return string
@ -155,6 +166,10 @@ class Material(object):
def name(self):
return self._name
@property
def temperature(self):
return self._temperature
@property
def density(self):
return self._density
@ -200,6 +215,15 @@ class Material(object):
else:
self._name = ''
@temperature.setter
def temperature(self, temperature):
if temperature is not None:
cv.check_type('Temperature for Material ID="{0}"'.format(self._id),
temperature, basestring)
self._temperature = temperature
else:
self._temperature = ''
def set_density(self, units, density=None):
"""Set the density of the material
@ -457,15 +481,13 @@ class Material(object):
if element == elm:
self._nuclides.remove(elm)
def add_s_alpha_beta(self, name, xs):
def add_s_alpha_beta(self, name):
r"""Add an :math:`S(\alpha,\beta)` table to the material
Parameters
----------
name : str
Name of the :math:`S(\alpha,\beta)` table
xs : str
Cross section identifier, e.g. '71t'
"""
@ -479,12 +501,14 @@ class Material(object):
'non-string table name "{1}"'.format(self._id, name)
raise ValueError(msg)
if not isinstance(xs, basestring):
msg = 'Unable to add an S(a,b) table to Material ID="{0}" with a ' \
'non-string cross-section identifier "{1}"'.format(self._id, xs)
raise ValueError(msg)
new_name = openmc.data.get_thermal_name(name)
if new_name != name:
msg = 'OpenMC S(a,b) tables follow the GND naming convention. ' \
'Table "{}" is being renamed as "{}".'.format(name, new_name)
warnings.warn(msg)
self._sab.append(new_name)
self._sab.append((name, xs))
def make_isotropic_in_lab(self):
for nuclide, percent, percent_type in self._nuclides:
@ -492,9 +516,31 @@ class Material(object):
for element, percent, percent_type in self._elements:
element.scattering = 'iso-in-lab'
def get_all_nuclides(self):
def get_nuclides(self):
"""Returns all nuclides in the material
Returns
-------
nuclides : list of str
List of nuclide names
"""
nuclides = []
for nuclide, density, density_type in self._nuclides:
nuclides.append(nuclide.name)
for element, density, density_type in self._elements:
# Expand natural element into isotopes
for isotope, abundance in element.expand():
nuclides.append(isotope.name)
return nuclides
def get_nuclide_densities(self):
"""Returns all nuclides in the material and their densities
Returns
-------
nuclides : dict
@ -525,9 +571,6 @@ class Material(object):
else:
xml_element.set("wo", str(nuclide[1]))
if nuclide[0].xs is not None:
xml_element.set("xs", nuclide[0].xs)
if not nuclide[0].scattering is None:
xml_element.set("scattering", nuclide[0].scattering)
@ -537,9 +580,6 @@ class Material(object):
xml_element = ET.Element("macroscopic")
xml_element.set("name", macroscopic.name)
if macroscopic.xs is not None:
xml_element.set("xs", macroscopic.xs)
return xml_element
def _get_element_xml(self, element, distrib=False):
@ -552,9 +592,6 @@ class Material(object):
else:
xml_element.set("wo", str(element[1]))
if element[0].xs is not None:
xml_element.set("xs", element[0].xs)
if not element[0].scattering is None:
xml_element.set("scattering", element[0].scattering)
@ -593,6 +630,11 @@ class Material(object):
if len(self._name) > 0:
element.set("name", str(self._name))
# Create temperature XML subelement
if len(self.temperature) > 0:
subelement = ET.SubElement(element, "temperature")
subelement.text = self.temperature
# Create density XML subelement
subelement = ET.SubElement(element, "density")
if self._density_units is not 'sum':
@ -657,8 +699,7 @@ class Material(object):
if len(self._sab) > 0:
for sab in self._sab:
subelement = ET.SubElement(element, "sab")
subelement.set("name", sab[0])
subelement.set("xs", sab[1])
subelement.set("name", sab)
return element
@ -683,30 +724,14 @@ class Materials(cv.CheckedList):
materials : Iterable of openmc.Material
Materials to add to the collection
Attributes
----------
default_xs : str
The default cross section identifier applied to a nuclide when none is
specified
"""
def __init__(self, materials=None):
super(Materials, self).__init__(Material, 'materials collection')
self._default_xs = None
self._materials_file = ET.Element("materials")
if materials is not None:
self += materials
@property
def default_xs(self):
return self._default_xs
@default_xs.setter
def default_xs(self, xs):
cv.check_type('default xs', xs, basestring)
self._default_xs = xs
def add_material(self, material):
"""Append material to collection
@ -788,10 +813,6 @@ class Materials(cv.CheckedList):
material.make_isotropic_in_lab()
def _create_material_subelements(self):
if self._default_xs is not None:
subelement = ET.SubElement(self._materials_file, "default_xs")
subelement.text = self._default_xs
for material in self:
xml_element = material.get_material_xml()
self._materials_file.append(xml_element)

View file

@ -6,6 +6,7 @@ import sys
import numpy as np
import openmc.checkvalue as cv
import openmc
if sys.version_info[0] >= 3:
@ -181,6 +182,38 @@ class Mesh(object):
string += '{0: <16}{1}{2}\n'.format('\tPixels', '=\t', self._width)
return string
def cell_generator(self):
"""Generator function to traverse through every [i,j,k] index
of the mesh.
For example the following code:
.. code-block:: python
for mesh_index in mymesh.cell_generator():
print mesh_index
will produce the following output for a 3-D 2x2x2 mesh in mymesh::
[1, 1, 1]
[1, 1, 2]
[1, 2, 1]
[1, 2, 2]
...
"""
if len(self.dimension) == 2:
for x in range(self.dimension[0]):
for y in range(self.dimension[1]):
yield [x + 1, y + 1, 1]
else:
for x in range(self.dimension[0]):
for y in range(self.dimension[1]):
for z in range(self.dimension[2]):
yield [x + 1, y + 1, z + 1]
def get_mesh_xml(self):
"""Return XML representation of the mesh
@ -210,3 +243,99 @@ class Mesh(object):
subelement.text = ' '.join(map(str, self._width))
return element
def build_cells(self, bc=['reflective'] * 6):
"""Generates a lattice of universes with the same dimensionality
as the mesh object. The individual cells/universes produced
will not have material definitions applied and so downstream code
will have to apply that information.
Parameters
----------
bc : iterable of {'reflective', 'periodic', 'transmission', or 'vacuum'}
Boundary conditions for each of the four faces of a rectangle
(if aplying to a 2D mesh) or six faces of a parallelepiped
(if applying to a 3D mesh) provided in the following order:
[x min, x max, y min, y max, z min, z max]. 2-D cells do not
contain the z min and z max entries.
Returns
-------
root_cell : openmc.Cell
The cell containing the lattice representing the mesh geometry;
this cell is a single parallelepiped with boundaries matching
the outermost mesh boundary with the boundary conditions from bc
applied.
cells : iterable of openmc.Cell
The list of cells within each lattice position mimicking the mesh
geometry.
"""
twod = len(self.dimension) == 2
cv.check_length('bc', bc, length_min=4, length_max=6)
for entry in bc:
cv.check_value('bc', entry, ['transmission', 'vacuum',
'reflective', 'periodic'])
# Build the cell which will contain the lattice
xplanes = [openmc.XPlane(x0=self.lower_left[0],
boundary_type=bc[0]),
openmc.XPlane(x0=self.upper_right[0],
boundary_type=bc[1])]
yplanes = [openmc.YPlane(y0=self.lower_left[1],
boundary_type=bc[2]),
openmc.YPlane(y0=self.upper_right[1],
boundary_type=bc[3])]
if twod:
zplanes = [openmc.ZPlane(z0=np.finfo(np.float).min,
boundary_type='reflective'),
openmc.ZPlane(z0=np.finfo(np.float).max,
boundary_type='reflective')]
else:
zplanes = [openmc.ZPlane(z0=self.lower_left[2],
boundary_type=bc[4]),
openmc.ZPlane(z0=self.upper_right[2],
boundary_type=bc[5])]
root_cell = openmc.Cell()
root_cell.region = ((+xplanes[0] & -xplanes[1]) &
(+yplanes[0] & -yplanes[1]) &
(+zplanes[0] & -zplanes[1]))
# Build the universes which will be used for each of the [i,j,k]
# locations within the mesh.
# We will also have to build cells to assign to these universes
universes = np.ndarray(self.dimension[::-1], dtype=np.object)
cells = []
for [i, j, k] in self.cell_generator():
if twod:
universes[j - 1, i - 1] = openmc.Universe()
cells.append(openmc.Cell())
universes[j - 1, i - 1].add_cells([cells[-1]])
else:
universes[k - 1, j - 1, i - 1] = openmc.Universe()
cells.append(openmc.Cell())
universes[k - 1, j - 1, i - 1].add_cells([cells[-1]])
lattice = openmc.RectLattice()
lattice.lower_left = self.lower_left
if self.width is not None:
lattice.pitch = self.width
else:
dx = ((self.upper_right[0] - self.lower_left[0]) /
self.dimension[0])
dy = ((self.upper_right[1] - self.lower_left[1]) /
self.dimension[1])
if twod:
lattice.pitch = [dx, dy]
else:
dz = ((self.upper_right[2] - self.lower_left[2]) /
self.dimension[2])
lattice.pitch = [dx, dy, dz]
lattice.universes = universes
# Fill Cell with the Lattice
root_cell.fill = lattice
return root_cell, cells

View file

@ -1,3 +1,4 @@
from openmc.mgxs.groups import EnergyGroups
from openmc.mgxs.library import Library
from openmc.mgxs.mgxs import *
from openmc.mgxs.mdgxs import *

View file

@ -11,6 +11,7 @@ import numpy as np
import openmc
import openmc.mgxs
import openmc.checkvalue as cv
from openmc.tallies import ESTIMATOR_TYPES
if sys.version_info[0] >= 3:
@ -18,17 +19,18 @@ if sys.version_info[0] >= 3:
class Library(object):
"""A multi-group cross section library for some energy group structure.
"""A multi-energy-group and multi-delayed-group cross section library for
some energy group structure.
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 deterministic neutronics calculations.
This class helps automate the generation of MGXS objects for some energy
group structure and domain type. The Library serves as a collection for
MGXS objects with routines to automate the initialization of tallies for
input files, the loading of tally data from statepoint files, data storage,
energy group condensation and more.
This class helps automate the generation of MGXS and MDGXS objects for some
energy group structure and domain type. The Library serves as a collection
for MGXS and MDGXS objects with routines to automate the initialization of
tallies for input files, the loading of tally data from statepoint files,
data storage, energy group condensation and more.
Parameters
----------
@ -39,7 +41,7 @@ class Library(object):
mgxs_types : Iterable of str
The types of cross sections in the library (e.g., ['total', 'scatter'])
name : str, optional
Name of the multi-group cross section. library Used as a label to
Name of the multi-group cross section library. Used as a label to
identify tallies in OpenMC 'tallies.xml' file.
Attributes
@ -64,6 +66,11 @@ class Library(object):
The highest legendre moment in the scattering matrices (default is 0)
energy_groups : openmc.mgxs.EnergyGroups
Energy group structure for energy condensation
delayed_groups : list of int
Delayed groups to filter out the xs
estimator : str or None
The tally estimator used to compute multi-group cross sections. If None,
the default for each MGXS type is used.
tally_trigger : openmc.Trigger
An (optional) tally precision trigger given to each tally used to
compute the cross section
@ -95,6 +102,7 @@ class Library(object):
self._domain_type = None
self._domains = 'all'
self._energy_groups = None
self._delayed_groups = None
self._correction = 'P0'
self._legendre_order = 0
self._tally_trigger = None
@ -102,6 +110,7 @@ class Library(object):
self._sp_filename = None
self._keff = None
self._sparse = False
self._estimator = None
self.name = name
self.openmc_geometry = openmc_geometry
@ -126,6 +135,7 @@ class Library(object):
clone._correction = self.correction
clone._legendre_order = self.legendre_order
clone._energy_groups = copy.deepcopy(self.energy_groups, memo)
clone._delayed_groups = copy.deepcopy(self.delayed_groups, memo)
clone._tally_trigger = copy.deepcopy(self.tally_trigger, memo)
clone._all_mgxs = copy.deepcopy(self.all_mgxs)
clone._sp_filename = self._sp_filename
@ -194,6 +204,10 @@ class Library(object):
def energy_groups(self):
return self._energy_groups
@property
def delayed_groups(self):
return self._delayed_groups
@property
def correction(self):
return self._correction
@ -206,10 +220,21 @@ class Library(object):
def tally_trigger(self):
return self._tally_trigger
@property
def estimator(self):
return self._estimator
@property
def num_groups(self):
return self.energy_groups.num_groups
@property
def num_delayed_groups(self):
if self.delayed_groups == None:
return 0
else:
return len(self.delayed_groups)
@property
def all_mgxs(self):
return self._all_mgxs
@ -239,22 +264,33 @@ class Library(object):
@mgxs_types.setter
def mgxs_types(self, mgxs_types):
all_mgxs_types = openmc.mgxs.MGXS_TYPES + openmc.mgxs.MDGXS_TYPES
if mgxs_types == 'all':
self._mgxs_types = openmc.mgxs.MGXS_TYPES
self._mgxs_types = all_mgxs_types
else:
cv.check_iterable_type('mgxs_types', mgxs_types, basestring)
for mgxs_type in mgxs_types:
cv.check_value('mgxs_type', mgxs_type, openmc.mgxs.MGXS_TYPES)
cv.check_value('mgxs_type', mgxs_type, all_mgxs_types)
self._mgxs_types = mgxs_types
@by_nuclide.setter
def by_nuclide(self, by_nuclide):
cv.check_type('by_nuclide', by_nuclide, bool)
if by_nuclide == True and self.domain_type == 'mesh':
raise ValueError('Unable to create MGXS library by nuclide with '
'mesh domain')
self._by_nuclide = by_nuclide
@domain_type.setter
def domain_type(self, domain_type):
cv.check_value('domain type', domain_type, openmc.mgxs.DOMAIN_TYPES)
if self.by_nuclide == True and domain_type == 'mesh':
raise ValueError('Unable to create MGXS library by nuclide with '
'mesh domain')
self._domain_type = domain_type
@domains.setter
@ -298,6 +334,23 @@ class Library(object):
cv.check_type('energy groups', energy_groups, openmc.mgxs.EnergyGroups)
self._energy_groups = energy_groups
@delayed_groups.setter
def delayed_groups(self, delayed_groups):
if delayed_groups != None:
cv.check_type('delayed groups', delayed_groups, list, int)
cv.check_greater_than('num delayed groups', len(delayed_groups), 0)
# Check that the groups are within [1, MAX_DELAYED_GROUPS]
for group in delayed_groups:
cv.check_greater_than('delayed group', group, 0)
cv.check_less_than('delayed group', group,
openmc.mgxs.MAX_DELAYED_GROUPS,
equality=True)
self._delayed_groups = delayed_groups
@correction.setter
def correction(self, correction):
cv.check_value('correction', correction, ('P0', None))
@ -327,6 +380,11 @@ class Library(object):
cv.check_type('tally trigger', tally_trigger, openmc.Trigger)
self._tally_trigger = tally_trigger
@estimator.setter
def estimator(self, estimator):
cv.check_value('estimator', estimator, ESTIMATOR_TYPES)
self._estimator = estimator
@sparse.setter
def sparse(self, sparse):
"""Convert tally data from NumPy arrays to SciPy list of lists (LIL)
@ -363,14 +421,23 @@ class Library(object):
for domain in self.domains:
self.all_mgxs[domain.id] = OrderedDict()
for mgxs_type in self.mgxs_types:
mgxs = openmc.mgxs.MGXS.get_mgxs(mgxs_type, name=self.name)
if mgxs_type in openmc.mgxs.MDGXS_TYPES:
mgxs = openmc.mgxs.MDGXS.get_mgxs(mgxs_type, name=self.name)
else:
mgxs = openmc.mgxs.MGXS.get_mgxs(mgxs_type, name=self.name)
mgxs.domain = domain
mgxs.domain_type = self.domain_type
mgxs.energy_groups = self.energy_groups
mgxs.by_nuclide = self.by_nuclide
if self.estimator is not None:
mgxs.estimator = self.estimator
if mgxs_type in openmc.mgxs.MDGXS_TYPES:
mgxs.delayed_groups = self.delayed_groups
# If a tally trigger was specified, add it to the MGXS
if self.tally_trigger:
if self.tally_trigger is not None:
mgxs.tally_trigger = self.tally_trigger
# Specify whether to use a transport ('P0') correction
@ -460,7 +527,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', 'chi-prompt', 'inverse-velocity', 'prompt-nu-fission'}
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', 'delayed-nu-fission', 'chi-delayed', 'beta'}
The type of multi-group cross section object to return
Returns
@ -755,15 +822,15 @@ class Library(object):
return pickle.load(open(full_filename, 'rb'))
def get_xsdata(self, domain, xsdata_name, nuclide='total', xs_type='macro',
xs_id='1m', order=None, tabular_legendre=None,
tabular_points=33):
order=None, tabular_legendre=None, tabular_points=33,
subdomain=None):
"""Generates an openmc.XSdata object describing a multi-group cross section
data set for eventual combination in to an openmc.MGXSLibrary object
(i.e., the library).
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
xsdata_name : str
Name to apply to the "xsdata" entry produced by this method
@ -774,8 +841,6 @@ class Library(object):
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'. If the Library object is not tallied by
nuclide this will be set to 'macro' regardless.
xs_ids : str
Cross section set identifier. Defaults to '1m'.
order : int
Scattering order for this data entry. Default is None,
which will set the XSdata object to use the order of the
@ -792,6 +857,12 @@ class Library(object):
parameter is set to `True`. In this case, this parameter sets the
number of equally-spaced points in the domain of [-1,1] to be used
in building the tabular distribution. Default is `33`.
subdomain : iterable of int
This parameter is not used unless using a mesh domain. In that
case, the subdomain is an [i,j,k] index (1-based indexing) of the
mesh cell of interest in the openmc.Mesh object. Note:
this parameter currently only supports subdomains within a mesh,
and not the subdomains of a distribcell.
Returns
-------
@ -811,11 +882,10 @@ class Library(object):
"""
cv.check_type('domain', domain, (openmc.Material, openmc.Cell,
openmc.Cell))
openmc.Universe, openmc.Mesh))
cv.check_type('xsdata_name', xsdata_name, basestring)
cv.check_type('nuclide', nuclide, basestring)
cv.check_value('xs_type', xs_type, ['macro', 'micro'])
cv.check_type('xs_id', xs_id, basestring)
cv.check_type('order', order, (type(None), Integral))
if order is not None:
cv.check_greater_than('order', order, 0, equality=True)
@ -824,6 +894,9 @@ class Library(object):
(type(None), bool))
if tabular_points is not None:
cv.check_greater_than('tabular_points', tabular_points, 1)
if subdomain is not None:
cv.check_iterable_type('subdomain', subdomain, Integral,
max_depth=3)
# Make sure statepoint has been loaded
if self._sp_filename is None:
@ -839,7 +912,6 @@ class Library(object):
name = xsdata_name
if nuclide is not 'total':
name += '_' + nuclide
name += '.' + xs_id
xsdata = openmc.XSdata(name, self.energy_groups)
if order is None:
@ -859,52 +931,66 @@ class Library(object):
xsdata.zaid = self._nuclides[nuclide][0]
xsdata.awr = self._nuclides[nuclide][1]
if subdomain is None:
subdomain = 'all'
else:
subdomain = [subdomain]
# Now get xs data itself
if 'nu-transport' in self.mgxs_types and self.correction == 'P0':
mymgxs = self.get_mgxs(domain, 'nu-transport')
xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide])
xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide],
subdomains=subdomain)
elif 'total' in self.mgxs_types:
mymgxs = self.get_mgxs(domain, 'total')
xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide])
xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide],
subdomain=subdomain)
if 'absorption' in self.mgxs_types:
mymgxs = self.get_mgxs(domain, 'absorption')
xsdata.set_absorption_mgxs(mymgxs, xs_type=xs_type,
nuclide=[nuclide])
nuclide=[nuclide],
subdomain=subdomain)
if 'fission' in self.mgxs_types:
mymgxs = self.get_mgxs(domain, 'fission')
xsdata.set_fission_mgxs(mymgxs, xs_type=xs_type,
nuclide=[nuclide])
nuclide=[nuclide], subdomain=subdomain)
if 'kappa-fission' in self.mgxs_types:
mymgxs = self.get_mgxs(domain, 'kappa-fission')
xsdata.set_kappa_fission_mgxs(mymgxs, xs_type=xs_type,
nuclide=[nuclide])
nuclide=[nuclide],
subdomain=subdomain)
# For chi and nu-fission we can either have only a nu-fission matrix
# provided, or vectors of chi and nu-fission provided
if 'nu-fission matrix' in self.mgxs_types:
mymgxs = self.get_mgxs(domain, 'nu-fission matrix')
xsdata.set_nu_fission_mgxs(mymgxs, xs_type=xs_type,
nuclide=[nuclide])
nuclide=[nuclide],
subdomain=subdomain)
else:
if 'chi' in self.mgxs_types:
mymgxs = self.get_mgxs(domain, 'chi')
xsdata.set_chi_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide])
xsdata.set_chi_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide],
subdomain=subdomain)
if 'nu-fission' in self.mgxs_types:
mymgxs = self.get_mgxs(domain, 'nu-fission')
xsdata.set_nu_fission_mgxs(mymgxs, xs_type=xs_type,
nuclide=[nuclide])
nuclide=[nuclide],
subdomain=subdomain)
# If multiplicity matrix is available, prefer that
if 'multiplicity matrix' in self.mgxs_types:
mymgxs = self.get_mgxs(domain, 'multiplicity matrix')
xsdata.set_multiplicity_mgxs(mymgxs, xs_type=xs_type,
nuclide=[nuclide])
nuclide=[nuclide],
subdomain=subdomain)
using_multiplicity = True
# multiplicity wil fall back to using scatter and nu-scatter
# multiplicity will fall back to using scatter and nu-scatter
elif ((('scatter matrix' in self.mgxs_types) and
('nu-scatter matrix' in self.mgxs_types))):
scatt_mgxs = self.get_mgxs(domain, 'scatter matrix')
nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix')
xsdata.set_multiplicity_mgxs(nuscatt_mgxs, scatt_mgxs,
xs_type=xs_type, nuclide=[nuclide])
xs_type=xs_type, nuclide=[nuclide],
subdomain=subdomain)
using_multiplicity = True
else:
using_multiplicity = False
@ -912,12 +998,13 @@ class Library(object):
if using_multiplicity:
nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix')
xsdata.set_scatter_mgxs(nuscatt_mgxs, xs_type=xs_type,
nuclide=[nuclide])
nuclide=[nuclide], subdomain=subdomain)
else:
if 'nu-scatter matrix' in self.mgxs_types:
nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix')
xsdata.set_scatter_mgxs(nuscatt_mgxs, xs_type=xs_type,
nuclide=[nuclide])
nuclide=[nuclide],
subdomain=subdomain)
# Since we are not using multiplicity, then
# scattering multiplication (nu-scatter) must be
@ -931,8 +1018,7 @@ class Library(object):
return xsdata
def create_mg_library(self, xs_type='macro', xsdata_names=None,
xs_ids=None, tabular_legendre=None,
tabular_points=33):
tabular_legendre=None, tabular_points=33):
"""Creates an openmc.MGXSLibrary object to contain the MGXS data for the
Multi-Group mode of OpenMC.
@ -945,10 +1031,6 @@ class Library(object):
xsdata_names : Iterable of str
List of names to apply to the "xsdata" entries in the
resultant mgxs data file. Defaults to 'set1', 'set2', ...
xs_ids : str or Iterable of str
Cross section set identifier (i.e., '71c') for all
data sets (if only str) or for each individual one
(if iterable of str). Defaults to '1m'.
tabular_legendre : None or bool
Flag to denote whether or not the Legendre expansion of the
scattering angular distribution is to be converted to a tabular
@ -988,15 +1070,6 @@ class Library(object):
cv.check_value('xs_type', xs_type, ['macro', 'micro'])
if xsdata_names is not None:
cv.check_iterable_type('xsdata_names', xsdata_names, basestring)
if xs_ids is not None:
if isinstance(xs_ids, basestring):
# If we only have a string lets convert it now to a list
# of strings.
xs_ids = [xs_ids for i in range(len(self.domains))]
else:
cv.check_iterable_type('xs_ids', xs_ids, basestring)
else:
xs_ids = ['1m' for i in range(len(self.domains))]
# If gathering material-specific data, set the xs_type to macro
if not self.by_nuclide:
@ -1005,32 +1078,55 @@ class Library(object):
# Initialize file
mgxs_file = openmc.MGXSLibrary(self.energy_groups)
# Create the xsdata object and add it to the mgxs_file
for i, domain in enumerate(self.domains):
if self.by_nuclide:
nuclides = list(domain.get_all_nuclides().keys())
else:
nuclides = ['total']
for nuclide in nuclides:
# Build & add metadata to XSdata object
if xsdata_names is None:
xsdata_name = 'set' + str(i + 1)
if self.domain_type == 'mesh':
# Create the xsdata objects and add to the mgxs_file
i = 0
for domain in self.domains:
if self.by_nuclide:
raise NotImplementedError("Mesh domains do not currently "
"support nuclidic tallies")
for subdomain in domain.cell_generator():
# Build & add metadata to XSdata object
if xsdata_names is None:
xsdata_name = 'set' + str(i + 1)
else:
xsdata_name = xsdata_names[i]
# Create XSdata and Macroscopic for this domain
xsdata = self.get_xsdata(domain, xsdata_name,
tabular_legendre=tabular_legendre,
tabular_points=tabular_points,
subdomain=subdomain)
mgxs_file.add_xsdata(xsdata)
i += 1
else:
# Create the xsdata object and add it to the mgxs_file
for i, domain in enumerate(self.domains):
if self.by_nuclide:
nuclides = domain.get_nuclides()
else:
xsdata_name = xsdata_names[i]
if nuclide is not 'total':
xsdata_name += '_' + nuclide
nuclides = ['total']
for nuclide in nuclides:
# Build & add metadata to XSdata object
if xsdata_names is None:
xsdata_name = 'set' + str(i + 1)
else:
xsdata_name = xsdata_names[i]
if nuclide is not 'total':
xsdata_name += '_' + nuclide
xsdata = self.get_xsdata(domain, xsdata_name, nuclide=nuclide,
xs_type=xs_type, xs_id=xs_ids[i],
tabular_legendre=tabular_legendre,
tabular_points=tabular_points)
xsdata = self.get_xsdata(domain, xsdata_name,
nuclide=nuclide, xs_type=xs_type,
tabular_legendre=tabular_legendre,
tabular_points=tabular_points)
mgxs_file.add_xsdata(xsdata)
mgxs_file.add_xsdata(xsdata)
return mgxs_file
def create_mg_mode(self, xsdata_names=None, xs_ids=None,
tabular_legendre=None, tabular_points=33):
def create_mg_mode(self, xsdata_names=None, tabular_legendre=None,
tabular_points=33, bc=['reflective'] * 6):
"""Creates an openmc.MGXSLibrary object to contain the MGXS data for the
Multi-Group mode of OpenMC as well as the associated openmc.Materials
and openmc.Geometry objects. The created Geometry is the same as that
@ -1044,10 +1140,6 @@ class Library(object):
xsdata_names : Iterable of str
List of names to apply to the "xsdata" entries in the
resultant mgxs data file. Defaults to 'set1', 'set2', ...
xs_ids : str or Iterable of str
Cross section set identifier (i.e., '71c') for all
data sets (if only str) or for each individual one
(if iterable of str). Defaults to '1m'.
tabular_legendre : None or bool
Flag to denote whether or not the Legendre expansion of the
scattering angular distribution is to be converted to a tabular
@ -1060,6 +1152,12 @@ class Library(object):
parameter is set to `True`. In this case, this parameter sets the
number of equally-spaced points in the domain of [-1,1] to be used
in building the tabular distribution. Default is `33`.
bc : iterable of {'reflective', 'periodic', 'transmission', or 'vacuum'}
Boundary conditions for each of the four faces of a rectangle
(if applying to a 2D mesh) or six faces of a parallelepiped
(if applying to a 3D mesh) provided in the following order:
[x min, x max, y min, y max, z min, z max]. 2-D cells do not
contain the z min and z max entries.
Returns
-------
@ -1090,62 +1188,76 @@ class Library(object):
# multi-group cross section types
self.check_library_for_openmc_mgxs()
if xsdata_names is not None:
cv.check_iterable_type('xsdata_names', xsdata_names, basestring)
if xs_ids is not None:
if isinstance(xs_ids, basestring):
# If we only have a string lets convert it now to a list
# of strings.
xs_ids = [xs_ids for i in range(len(self.domains))]
else:
cv.check_iterable_type('xs_ids', xs_ids, basestring)
# If the domain type is a mesh, then there can only be one domain for
# this method. This is because we can build a model automatically if
# the user provided multiple mesh domains for library generation since
# the multiple meshes could be overlapping or in disparate regions
# of the continuous energy model. The next step makes sure there is
# only one before continuing.
if self.domain_type == 'mesh':
cv.check_length("domains", self.domains, 1, 1)
# Get the MGXS File Data
mgxs_file = self.create_mg_library('macro', xsdata_names,
tabular_legendre, tabular_points)
# Now move on the creating the geometry and assigning materials
if self.domain_type == 'mesh':
root = openmc.Universe(name='root', universe_id=0)
# Add cells representative of the mesh with reflective BC
root_cell, cells = \
self.domains[0].build_cells(bc)
root.add_cell(root_cell)
geometry = openmc.Geometry()
geometry.root_universe = root
materials = openmc.Materials()
for i, subdomain in enumerate(self.domains[0].cell_generator()):
xsdata = mgxs_file.xsdatas[i]
# Build the macroscopic and assign it to the cell of
# interest
macroscopic = openmc.Macroscopic(name=xsdata.name)
# Create Material and add to collection
material = openmc.Material(name=xsdata.name)
material.add_macroscopic(macroscopic)
materials.append(material)
# Set the materials for each of the universes
cells[i].fill = materials[i]
else:
xs_ids = ['1m' for i in range(len(self.domains))]
xs_type = 'macro'
# Create a copy of the Geometry for these Macroscopics
geometry = copy.deepcopy(self.openmc_geometry)
materials = openmc.Materials()
# Initialize MGXS File
mgxs_file = openmc.MGXSLibrary(self.energy_groups)
# Get all Cells from the Geometry for differentiation
all_cells = geometry.get_all_material_cells()
# Create a copy of the Geometry to differentiate for these Macroscopics
geometry = copy.deepcopy(self.openmc_geometry)
materials = openmc.Materials()
# Create the xsdata object and add it to the mgxs_file
for i, domain in enumerate(self.domains):
xsdata = mgxs_file.xsdatas[i]
# Get all Cells from the Geometry for differentiation
all_cells = geometry.get_all_material_cells()
macroscopic = openmc.Macroscopic(name=xsdata.name)
# Create the xsdata object and add it to the mgxs_file
for i, domain in enumerate(self.domains):
# Create Material and add to collection
material = openmc.Material(name=xsdata.name)
material.add_macroscopic(macroscopic)
materials.append(material)
# Build & add metadata to XSdata object
if xsdata_names is None:
xsdata_name = 'set' + str(i + 1)
else:
xsdata_name = xsdata_names[i]
# Differentiate Geometry with new Material
if self.domain_type == 'material':
# Fill all appropriate Cells with new Material
for cell in all_cells:
if cell.fill.id == domain.id:
cell.fill = material
# Create XSdata and Macroscopic for this domain
xsdata = self.get_xsdata(domain, xsdata_name, nuclide='total',
xs_type=xs_type, xs_id=xs_ids[i],
tabular_legendre=tabular_legendre,
tabular_points=tabular_points)
mgxs_file.add_xsdata(xsdata)
macroscopic = openmc.Macroscopic(name=xsdata_name, xs=xs_ids[i])
# Create Material and add to collection
material = openmc.Material(name=xsdata_name + '.' + xs_ids[i])
material.add_macroscopic(macroscopic)
materials.append(material)
# Differentiate Geometry with new Material
if self.domain_type == 'material':
# Fill all appropriate Cells with new Material
for cell in all_cells:
if cell.fill.id == domain.id:
cell.fill = material
elif self.domain_type == 'cell':
for cell in all_cells:
if cell.id == domain.id:
cell.fill = material
elif self.domain_type == 'cell':
for cell in all_cells:
if cell.id == domain.id:
cell.fill = material
return mgxs_file, materials, geometry

1576
openmc/mgxs/mdgxs.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,7 @@ import numpy as np
import openmc
import openmc.checkvalue as cv
from openmc.tallies import ESTIMATOR_TYPES
from openmc.mgxs import EnergyGroups
if sys.version_info[0] >= 3:
@ -39,7 +40,6 @@ MGXS_TYPES = ['total',
'inverse-velocity',
'prompt-nu-fission']
# Supported domain types
DOMAIN_TYPES = ['cell',
'distribcell',
@ -102,7 +102,7 @@ class MGXS(object):
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'}
estimator : {'tracklength', 'collision', '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
@ -144,11 +144,11 @@ class MGXS(object):
def __init__(self, domain=None, domain_type=None,
energy_groups=None, by_nuclide=False, name=''):
self._name = ''
self._rxn_type = None
self._by_nuclide = None
self._nuclides = None
self._estimator = 'tracklength'
self._domain = None
self._domain_type = None
self._energy_groups = None
@ -160,6 +160,7 @@ class MGXS(object):
self._loaded_sp = False
self._derived = False
self._hdf5_key = None
self._valid_estimators = ESTIMATOR_TYPES
self.name = name
self.by_nuclide = by_nuclide
@ -250,7 +251,7 @@ class MGXS(object):
@property
def estimator(self):
return 'tracklength'
return self._estimator
@property
def tallies(self):
@ -288,9 +289,7 @@ class MGXS(object):
# If this is a by-nuclide cross-section, add nuclides to Tally
if self.by_nuclide and score != 'flux':
all_nuclides = self.get_all_nuclides()
for nuclide in all_nuclides:
self._tallies[key].nuclides.append(nuclide)
self._tallies[key].nuclides += self.get_nuclides()
else:
self._tallies[key].nuclides.append('total')
@ -329,16 +328,16 @@ class MGXS(object):
@property
def num_nuclides(self):
if self.by_nuclide:
return len(self.get_all_nuclides())
return len(self.get_nuclides())
else:
return 1
@property
def nuclides(self):
if self.by_nuclide:
return self.get_all_nuclides()
return self.get_nuclides()
else:
return 'sum'
return ['sum']
@property
def loaded_sp(self):
@ -370,6 +369,11 @@ class MGXS(object):
cv.check_iterable_type('nuclides', nuclides, basestring)
self._nuclides = nuclides
@estimator.setter
def estimator(self, estimator):
cv.check_value('estimator', estimator, self._valid_estimators)
self._estimator = estimator
@domain.setter
def domain(self, domain):
cv.check_type('domain', domain, _DOMAINS)
@ -503,7 +507,7 @@ class MGXS(object):
mgxs.name = name
return mgxs
def get_all_nuclides(self):
def get_nuclides(self):
"""Get all nuclides in the cross section's spatial domain.
Returns
@ -528,8 +532,7 @@ class MGXS(object):
# Otherwise, return all nuclides in the spatial domain
else:
nuclides = self.domain.get_all_nuclides()
return list(nuclides.keys())
return self.domain.get_nuclides()
def get_nuclide_density(self, nuclide):
"""Get the atomic number density in units of atoms/b-cm for a nuclide
@ -545,26 +548,14 @@ class MGXS(object):
float
The atomic number density (atom/b-cm) for the nuclide of interest
Raises
-------
ValueError
When the density is requested for a nuclide which is not found in
the spatial domain.
"""
cv.check_type('nuclide', nuclide, basestring)
# Get list of all nuclides in the spatial domain
nuclides = self.domain.get_all_nuclides()
nuclides = self.domain.get_nuclide_densities()
if nuclide not in nuclides:
msg = 'Unable to get density for nuclide "{0}" which is not in ' \
'{1} "{2}"'.format(nuclide, self.domain_type, self.domain.id)
ValueError(msg)
density = nuclides[nuclide][1]
return density
return nuclides[nuclide][1] if nuclide in nuclides else 0.0
def get_nuclide_densities(self, nuclides='all'):
"""Get an array of atomic number densities in units of atom/b-cm for all
@ -597,14 +588,14 @@ class MGXS(object):
# Sum the atomic number densities for all nuclides
if nuclides == 'sum':
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
densities = np.zeros(1, dtype=np.float)
for nuclide in nuclides:
densities[0] += self.get_nuclide_density(nuclide)
# Tabulate the atomic number densities for all nuclides
elif nuclides == 'all':
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
densities = np.zeros(self.num_nuclides, dtype=np.float)
for i, nuclide in enumerate(nuclides):
densities[i] += self.get_nuclide_density(nuclide)
@ -635,7 +626,7 @@ class MGXS(object):
# If computing xs for each nuclide, replace CrossNuclides with originals
if self.by_nuclide:
self.xs_tally._nuclides = []
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
for nuclide in nuclides:
self.xs_tally.nuclides.append(openmc.Nuclide(nuclide))
@ -725,11 +716,13 @@ class MGXS(object):
def get_xs(self, groups='all', subdomains='all', nuclides='all',
xs_type='macro', order_groups='increasing',
value='mean', **kwargs):
value='mean', squeeze=True, **kwargs):
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.
This method constructs a 3D NumPy array for the requested
multi-group cross section data for one or more subdomains
(1st dimension), energy groups (2nd dimension), and nuclides
(3rd dimension).
Parameters
----------
@ -751,6 +744,9 @@ class MGXS(object):
Defaults to 'increasing'.
value : {'mean', 'std_dev', 'rel_err'}
A string for the type of value to return. Defaults to 'mean'.
squeeze : bool
A boolean representing whether to eliminate the extra dimensions
of the multi-dimensional array to be returned. Defaults to True.
Returns
-------
@ -781,7 +777,8 @@ class MGXS(object):
# Construct a collection of the domain filter bins
if not isinstance(subdomains, basestring):
cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3)
cv.check_iterable_type('subdomains', subdomains, Integral,
max_depth=3)
for subdomain in subdomains:
filters.append(self.domain_type)
filter_bins.append((subdomain,))
@ -796,7 +793,7 @@ class MGXS(object):
# Construct a collection of the nuclides to retrieve from the xs tally
if self.by_nuclide:
if nuclides == 'all' or nuclides == 'sum' or nuclides == ['sum']:
query_nuclides = self.get_all_nuclides()
query_nuclides = self.get_nuclides()
else:
query_nuclides = nuclides
else:
@ -820,25 +817,29 @@ class MGXS(object):
if value == 'mean' or value == 'std_dev':
xs /= densities[np.newaxis, :, np.newaxis]
# Eliminate the trivial score dimension
xs = np.squeeze(xs, axis=len(xs.shape) - 1)
xs = np.nan_to_num(xs)
if groups == 'all':
num_groups = self.num_groups
else:
num_groups = len(groups)
# Reshape tally data array with separate axes for domain and energy
num_subdomains = int(xs.shape[0] / num_groups)
new_shape = (num_subdomains, num_groups) + xs.shape[1:]
xs = np.reshape(xs, new_shape)
# Reverse data if user requested increasing energy groups since
# tally data is stored in order of increasing energies
if order_groups == 'increasing':
if groups == 'all':
num_groups = self.num_groups
else:
num_groups = len(groups)
# Reshape tally data array with separate axes for domain and energy
num_subdomains = int(xs.shape[0] / num_groups)
new_shape = (num_subdomains, num_groups) + xs.shape[1:]
xs = np.reshape(xs, new_shape)
# Reverse energies to align with increasing energy groups
xs = xs[:, ::-1, :]
# Eliminate trivial dimensions
xs = np.squeeze(xs)
xs = np.atleast_1d(xs)
if squeeze:
xs = np.squeeze(xs)
xs = np.atleast_1d(xs)
return xs
def get_condensed_xs(self, coarse_groups):
@ -1164,7 +1165,7 @@ class MGXS(object):
# Construct a collection of the nuclides to report
if self.by_nuclide:
if nuclides == 'all':
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
elif nuclides == 'sum':
nuclides = ['sum']
else:
@ -1302,7 +1303,7 @@ class MGXS(object):
# Construct a collection of the nuclides to report
if self.by_nuclide:
if nuclides == 'all':
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
densities = np.zeros(len(nuclides), dtype=np.float)
elif nuclides == 'sum':
nuclides = ['sum']
@ -1351,8 +1352,6 @@ class MGXS(object):
std_dev = self.get_xs(subdomains=[subdomain], nuclides=[nuclide],
xs_type=xs_type, value='std_dev',
row_column=row_column)
average = average.squeeze()
std_dev = std_dev.squeeze()
# Add MGXS results data to the HDF5 group
nuclide_group.require_dataset('average', dtype=np.float64,
@ -1484,25 +1483,27 @@ class MGXS(object):
if self.by_nuclide and nuclides == 'sum':
# Use tally summation to sum across all nuclides
query_nuclides = self.get_all_nuclides()
xs_tally = self.xs_tally.summation(nuclides=query_nuclides)
query_nuclides = [nuclides]
xs_tally = self.xs_tally.summation(nuclides=self.get_nuclides())
df = xs_tally.get_pandas_dataframe(
distribcell_paths=distribcell_paths)
# Remove nuclide column since it is homogeneous and redundant
if self.domain_type == 'mesh':
df.drop('nuclide', axis=1, level=0, inplace=True)
df.drop('sum(nuclide)', axis=1, level=0, inplace=True)
else:
df.drop('nuclide', axis=1, inplace=True)
df.drop('sum(nuclide)', axis=1, inplace=True)
# If the user requested a specific set of nuclides
elif self.by_nuclide and nuclides != 'all':
query_nuclides = nuclides
xs_tally = self.xs_tally.get_slice(nuclides=nuclides)
df = xs_tally.get_pandas_dataframe(
distribcell_paths=distribcell_paths)
# If the user requested all nuclides, keep nuclide column in dataframe
else:
query_nuclides = self.nuclides
df = self.xs_tally.get_pandas_dataframe(
distribcell_paths=distribcell_paths)
@ -1514,19 +1515,19 @@ class MGXS(object):
# Override energy groups bounds with indices
all_groups = np.arange(self.num_groups, 0, -1, dtype=np.int)
all_groups = np.repeat(all_groups, self.num_nuclides)
all_groups = np.repeat(all_groups, len(query_nuclides))
if 'energy low [MeV]' in df and 'energyout low [MeV]' in df:
df.rename(columns={'energy low [MeV]': 'group in'},
inplace=True)
in_groups = np.tile(all_groups, self.num_subdomains)
in_groups = np.repeat(in_groups, df.shape[0] / in_groups.size)
in_groups = np.tile(all_groups, int(self.num_subdomains))
in_groups = np.repeat(in_groups, int(df.shape[0] / in_groups.size))
df['group in'] = in_groups
del df['energy high [MeV]']
df.rename(columns={'energyout low [MeV]': 'group out'},
inplace=True)
out_groups = np.repeat(all_groups, self.xs_tally.num_scores)
out_groups = np.tile(out_groups, df.shape[0] / out_groups.size)
out_groups = np.tile(out_groups, int(df.shape[0] / out_groups.size))
df['group out'] = out_groups
del df['energyout high [MeV]']
columns = ['group in', 'group out']
@ -1534,14 +1535,14 @@ class MGXS(object):
elif 'energyout low [MeV]' in df:
df.rename(columns={'energyout low [MeV]': 'group out'},
inplace=True)
in_groups = np.tile(all_groups, self.num_subdomains)
in_groups = np.tile(all_groups, int(df.shape[0] / all_groups.size))
df['group out'] = in_groups
del df['energyout high [MeV]']
columns = ['group out']
elif 'energy low [MeV]' in df:
df.rename(columns={'energy low [MeV]': 'group in'}, inplace=True)
in_groups = np.tile(all_groups, self.num_subdomains)
in_groups = np.tile(all_groups, int(df.shape[0] / all_groups.size))
df['group in'] = in_groups
del df['energy high [MeV]']
columns = ['group in']
@ -1564,6 +1565,10 @@ class MGXS(object):
df['mean'] /= np.tile(densities, tile_factor)
df['std. dev.'] /= np.tile(densities, tile_factor)
# Replace NaNs by zeros (happens if nuclide density is zero)
df['mean'].replace(np.nan, 0.0, inplace=True)
df['std. dev.'].replace(np.nan, 0.0, inplace=True)
# Sort the dataframe by domain type id (e.g., distribcell id) and
# energy groups such that data is from fast to thermal
if self.domain_type == 'mesh':
@ -1572,6 +1577,7 @@ class MGXS(object):
(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'):
@ -1646,7 +1652,7 @@ class MatrixMGXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -1695,18 +1701,16 @@ class MatrixMGXS(MGXS):
return [[energy], [energy, energyout]]
@property
def estimator(self):
return 'analog'
def get_xs(self, in_groups='all', out_groups='all',
subdomains='all', nuclides='all',
xs_type='macro', order_groups='increasing',
row_column='inout', value='mean', **kwargs):
row_column='inout', value='mean', squeeze=True, **kwargs):
"""Returns an array of multi-group cross sections.
This method constructs a 2D NumPy array for the requested multi-group
matrix data for one or more energy groups and subdomains.
This method constructs a 4D NumPy array for the requested
multi-group cross section data for one or more subdomains
(1st dimension), energy groups in (2nd dimension), energy groups out
(3rd dimension), and nuclides (4th dimension).
Parameters
----------
@ -1735,6 +1739,9 @@ class MatrixMGXS(MGXS):
Defaults to 'inout'.
value : {'mean', 'std_dev', 'rel_err'}
A string for the type of value to return. Defaults to 'mean'.
squeeze : bool
A boolean representing whether to eliminate the extra dimensions
of the multi-dimensional array to be returned. Defaults to True.
Returns
-------
@ -1790,7 +1797,7 @@ class MatrixMGXS(MGXS):
# Construct a collection of the nuclides to retrieve from the xs tally
if self.by_nuclide:
if nuclides == 'all' or nuclides == 'sum' or nuclides == ['sum']:
query_nuclides = self.get_all_nuclides()
query_nuclides = self.get_nuclides()
else:
query_nuclides = nuclides
else:
@ -1806,8 +1813,6 @@ class MatrixMGXS(MGXS):
filter_bins=filter_bins,
nuclides=query_nuclides, value=value)
xs = np.nan_to_num(xs)
# Divide by atom number densities for microscopic cross sections
if xs_type == 'micro':
if self.by_nuclide:
@ -1817,33 +1822,36 @@ class MatrixMGXS(MGXS):
if value == 'mean' or value == 'std_dev':
xs /= densities[np.newaxis, :, np.newaxis]
# Eliminate the trivial score dimension
xs = np.squeeze(xs, axis=len(xs.shape) - 1)
xs = np.nan_to_num(xs)
if in_groups == 'all':
num_in_groups = self.num_groups
else:
num_in_groups = len(in_groups)
if out_groups == 'all':
num_out_groups = self.num_groups
else:
num_out_groups = len(out_groups)
# Reshape tally data array with separate axes for domain and energy
num_subdomains = int(xs.shape[0] / (num_in_groups * num_out_groups))
new_shape = (num_subdomains, num_in_groups, num_out_groups)
new_shape += xs.shape[1:]
xs = np.reshape(xs, new_shape)
# Transpose the matrix if requested by user
if row_column == 'outin':
xs = np.swapaxes(xs, 1, 2)
# Reverse data if user requested increasing energy groups since
# tally data is stored in order of increasing energies
if order_groups == 'increasing':
if in_groups == 'all':
num_in_groups = self.num_groups
else:
num_in_groups = len(in_groups)
if out_groups == 'all':
num_out_groups = self.num_groups
else:
num_out_groups = len(out_groups)
# Reshape tally data array with separate axes for domain and energy
num_subdomains = int(xs.shape[0] /
(num_in_groups * num_out_groups))
new_shape = (num_subdomains, num_in_groups, num_out_groups)
new_shape += xs.shape[1:]
xs = np.reshape(xs, new_shape)
# Transpose the matrix if requested by user
if row_column == 'outin':
xs = np.swapaxes(xs, 1, 2)
# Reverse energies to align with increasing energy groups
xs = xs[:, ::-1, ::-1, :]
# Eliminate trivial dimensions
if squeeze:
xs = np.squeeze(xs)
xs = np.atleast_2d(xs)
@ -1937,7 +1945,7 @@ class MatrixMGXS(MGXS):
# Construct a collection of the nuclides to report
if self.by_nuclide:
if nuclides == 'all':
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
if nuclides == 'sum':
nuclides = ['sum']
else:
@ -2075,7 +2083,7 @@ class TotalXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -2193,7 +2201,7 @@ class TransportXS(MGXS):
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'}
estimator : '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
@ -2236,6 +2244,8 @@ class TransportXS(MGXS):
super(TransportXS, self).__init__(domain, domain_type,
groups, by_nuclide, name)
self._rxn_type = 'transport'
self._estimator = 'analog'
self._valid_estimators = ['analog']
@property
def scores(self):
@ -2248,10 +2258,6 @@ class TransportXS(MGXS):
energyout_filter = openmc.Filter('energyout', group_edges)
return [[energy_filter], [energy_filter], [energyout_filter]]
@property
def estimator(self):
return 'analog'
@property
def rxn_rate_tally(self):
if self._rxn_rate_tally is None:
@ -2323,7 +2329,7 @@ class NuTransportXS(TransportXS):
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'}
estimator : '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
@ -2444,7 +2450,7 @@ class AbsorptionXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -2461,7 +2467,8 @@ class AbsorptionXS(MGXS):
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).
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.
@ -2560,7 +2567,7 @@ class CaptureXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -2682,7 +2689,7 @@ class FissionXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -2793,7 +2800,7 @@ class NuFissionXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -2837,7 +2844,6 @@ class NuFissionXS(MGXS):
groups, by_nuclide, name)
self._rxn_type = 'nu-fission'
class KappaFissionXS(MGXS):
r"""A recoverable fission energy production rate multi-group cross section.
@ -2909,7 +2915,7 @@ class KappaFissionXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -3022,7 +3028,7 @@ class ScatterXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -3137,7 +3143,7 @@ class NuScatterXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -3180,10 +3186,8 @@ class NuScatterXS(MGXS):
super(NuScatterXS, self).__init__(domain, domain_type,
groups, by_nuclide, name)
self._rxn_type = 'nu-scatter'
@property
def estimator(self):
return 'analog'
self._estimator = 'analog'
self._valid_estimators = ['analog']
class ScatterMatrixXS(MatrixMGXS):
@ -3271,7 +3275,7 @@ class ScatterMatrixXS(MatrixMGXS):
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'}
estimator : '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
@ -3317,6 +3321,8 @@ class ScatterMatrixXS(MatrixMGXS):
self._correction = 'P0'
self._legendre_order = 0
self._hdf5_key = 'scatter matrix'
self._estimator = 'analog'
self._valid_estimators = ['analog']
def __deepcopy__(self, memo):
clone = super(ScatterMatrixXS, self).__deepcopy__(memo)
@ -3520,11 +3526,13 @@ class ScatterMatrixXS(MatrixMGXS):
def get_xs(self, in_groups='all', out_groups='all',
subdomains='all', nuclides='all', moment='all',
xs_type='macro', order_groups='increasing',
row_column='inout', value='mean'):
row_column='inout', value='mean', squeeze=True):
r"""Returns an array of multi-group cross sections.
This method constructs a 2D NumPy array for the requested scattering
matrix data data for one or more energy groups and subdomains.
This method constructs a 5D NumPy array for the requested
multi-group cross section data for one or more subdomains
(1st dimension), energy groups in (2nd dimension), energy groups out
(3rd dimension), nuclides (4th dimension), and moments (5th dimension).
NOTE: The scattering moments are not multiplied by the :math:`(2l+1)/2`
prefactor in the expansion of the scattering source into Legendre
@ -3560,6 +3568,9 @@ class ScatterMatrixXS(MatrixMGXS):
Defaults to 'inout'.
value : {'mean', 'std_dev', 'rel_err'}
A string for the type of value to return. Defaults to 'mean'.
squeeze : bool
A boolean representing whether to eliminate the extra dimensions
of the multi-dimensional array to be returned. Defaults to True.
Returns
-------
@ -3622,7 +3633,7 @@ class ScatterMatrixXS(MatrixMGXS):
# Construct a collection of the nuclides to retrieve from the xs tally
if self.by_nuclide:
if nuclides == 'all' or nuclides == 'sum' or nuclides == ['sum']:
query_nuclides = self.get_all_nuclides()
query_nuclides = self.get_nuclides()
else:
query_nuclides = nuclides
else:
@ -3638,8 +3649,6 @@ class ScatterMatrixXS(MatrixMGXS):
filter_bins=filter_bins,
nuclides=query_nuclides, value=value)
xs = np.nan_to_num(xs)
# Divide by atom number densities for microscopic cross sections
if xs_type == 'micro':
if self.by_nuclide:
@ -3649,32 +3658,35 @@ class ScatterMatrixXS(MatrixMGXS):
if value == 'mean' or value == 'std_dev':
xs /= densities[np.newaxis, :, np.newaxis]
# Convert and nans to zero
xs = np.nan_to_num(xs)
if in_groups == 'all':
num_in_groups = self.num_groups
else:
num_in_groups = len(in_groups)
if out_groups == 'all':
num_out_groups = self.num_groups
else:
num_out_groups = len(out_groups)
# Reshape tally data array with separate axes for domain and energy
num_subdomains = int(xs.shape[0] / (num_in_groups * num_out_groups))
new_shape = (num_subdomains, num_in_groups, num_out_groups)
new_shape += xs.shape[1:]
xs = np.reshape(xs, new_shape)
# Transpose the scattering matrix if requested by user
if row_column == 'outin':
xs = np.swapaxes(xs, 1, 2)
# Reverse data if user requested increasing energy groups since
# tally data is stored in order of increasing energies
if order_groups == 'increasing':
if in_groups == 'all':
num_in_groups = self.num_groups
else:
num_in_groups = len(in_groups)
if out_groups == 'all':
num_out_groups = self.num_groups
else:
num_out_groups = len(out_groups)
# Reshape tally data array with separate axes for domain and energy
num_subdomains = int(xs.shape[0] / (num_in_groups * num_out_groups))
new_shape = (num_subdomains, num_in_groups, num_out_groups)
new_shape += xs.shape[1:]
xs = np.reshape(xs, new_shape)
# Transpose the scattering matrix if requested by user
if row_column == 'outin':
xs = np.swapaxes(xs, 1, 2)
# Reverse energies to align with increasing energy groups
xs = xs[:, ::-1, ::-1, :]
# Eliminate trivial dimensions
if squeeze:
xs = np.squeeze(xs)
xs = np.atleast_2d(xs)
@ -3731,7 +3743,7 @@ class ScatterMatrixXS(MatrixMGXS):
if self.legendre_order > 0:
# Insert a column corresponding to the Legendre moments
moments = ['P{}'.format(i) for i in range(self.legendre_order+1)]
moments = np.tile(moments, df.shape[0] / len(moments))
moments = np.tile(moments, int(df.shape[0] / len(moments)))
df['moment'] = moments
# Place the moment column before the mean column
@ -3789,7 +3801,7 @@ class ScatterMatrixXS(MatrixMGXS):
# Construct a collection of the nuclides to report
if self.by_nuclide:
if nuclides == 'all':
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
if nuclides == 'sum':
nuclides = ['sum']
else:
@ -3932,7 +3944,7 @@ class NuScatterMatrixXS(ScatterMatrixXS):
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'}
estimator : '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
@ -4054,7 +4066,7 @@ class MultiplicityMatrixXS(MatrixMGXS):
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'}
estimator : '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
@ -4097,6 +4109,8 @@ class MultiplicityMatrixXS(MatrixMGXS):
super(MultiplicityMatrixXS, self).__init__(domain, domain_type, groups,
by_nuclide, name)
self._rxn_type = 'multiplicity matrix'
self._estimator = 'analog'
self._valid_estimators = ['analog']
@property
def scores(self):
@ -4201,7 +4215,7 @@ class NuFissionMatrixXS(MatrixMGXS):
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'}
estimator : '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
@ -4245,6 +4259,8 @@ class NuFissionMatrixXS(MatrixMGXS):
groups, by_nuclide, name)
self._rxn_type = 'nu-fission'
self._hdf5_key = 'nu-fission matrix'
self._estimator = 'analog'
self._valid_estimators = ['analog']
class Chi(MGXS):
@ -4316,7 +4332,7 @@ class Chi(MGXS):
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'}
estimator : '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
@ -4358,6 +4374,8 @@ class Chi(MGXS):
groups=None, by_nuclide=False, name=''):
super(Chi, self).__init__(domain, domain_type, groups, by_nuclide, name)
self._rxn_type = 'chi'
self._estimator = 'analog'
self._valid_estimators = ['analog']
@property
def scores(self):
@ -4375,10 +4393,6 @@ class Chi(MGXS):
def tally_keys(self):
return ['nu-fission-in', 'nu-fission-out']
@property
def estimator(self):
return 'analog'
@property
def rxn_rate_tally(self):
if self._rxn_rate_tally is None:
@ -4515,11 +4529,13 @@ class Chi(MGXS):
def get_xs(self, groups='all', subdomains='all', nuclides='all',
xs_type='macro', order_groups='increasing',
value='mean', **kwargs):
value='mean', squeeze=True, **kwargs):
"""Returns an array of the fission spectrum.
This method constructs a 2D NumPy array for the requested multi-group
cross section data data for one or more energy groups and subdomains.
This method constructs a 3D NumPy array for the requested
multi-group cross section data for one or more subdomains
(1st dimension), energy groups (2nd dimension), and nuclides
(3rd dimension).
Parameters
----------
@ -4541,6 +4557,9 @@ class Chi(MGXS):
Defaults to 'increasing'.
value : {'mean', 'std_dev', 'rel_err'}
A string for the type of value to return. Defaults to 'mean'.
squeeze : bool
A boolean representing whether to eliminate the extra dimensions
of the multi-dimensional array to be returned. Defaults to True.
Returns
-------
@ -4595,7 +4614,7 @@ class Chi(MGXS):
nu_fission_out = self.tallies['nu-fission-out']
# Sum out all nuclides
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
nu_fission_in = nu_fission_in.summation(nuclides=nuclides)
nu_fission_out = nu_fission_out.summation(nuclides=nuclides)
@ -4615,7 +4634,7 @@ class Chi(MGXS):
# Get chi for all nuclides in the domain
elif nuclides == 'all':
nuclides = self.get_all_nuclides()
nuclides = self.get_nuclides()
xs = self.xs_tally.get_values(filters=filters,
filter_bins=filter_bins,
nuclides=nuclides, value=value)
@ -4632,27 +4651,29 @@ class Chi(MGXS):
xs = self.xs_tally.get_values(filters=filters,
filter_bins=filter_bins, value=value)
# Eliminate the trivial score dimension
xs = np.squeeze(xs, axis=len(xs.shape) - 1)
xs = np.nan_to_num(xs)
# Reshape tally data array with separate axes for domain and energy
if groups == 'all':
num_groups = self.num_groups
else:
num_groups = len(groups)
num_subdomains = int(xs.shape[0] / num_groups)
new_shape = (num_subdomains, num_groups) + xs.shape[1:]
xs = np.reshape(xs, new_shape)
# Reverse data if user requested increasing energy groups since
# tally data is stored in order of increasing energies
if order_groups == 'increasing':
# Reshape tally data array with separate axes for domain and energy
if groups == 'all':
num_groups = self.num_groups
else:
num_groups = len(groups)
num_subdomains = int(xs.shape[0] / num_groups)
new_shape = (num_subdomains, num_groups) + xs.shape[1:]
xs = np.reshape(xs, new_shape)
# Reverse energies to align with increasing energy groups
xs = xs[:, ::-1, :]
# Eliminate trivial dimensions
if squeeze:
xs = np.squeeze(xs)
xs = np.atleast_1d(xs)
xs = np.nan_to_num(xs)
return xs
def get_pandas_dataframe(self, groups='all', nuclides='all',
@ -4761,7 +4782,7 @@ class ChiPrompt(Chi):
\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')\\
\chi(E)^p \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') \\
@ -4806,7 +4827,7 @@ class ChiPrompt(Chi):
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'}
estimator : '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
@ -4921,7 +4942,7 @@ class InverseVelocity(MGXS):
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'}
estimator : {'tracklength', 'collision', '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
@ -4938,7 +4959,8 @@ class InverseVelocity(MGXS):
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).
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.
@ -5055,7 +5077,7 @@ class PromptNuFissionXS(MGXS):
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'}
estimator : {'tracklength', 'collision', '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

View file

@ -99,9 +99,6 @@ class XSdata(object):
Unique identifier for the xsdata object
alias : str
Separate unique identifier for the xsdata object
zaid : int
1000*(atomic number) + mass number. As an example, the zaid of U235
would be 92235.
awr : float
Atomic weight ratio of an isotope. That is, the ratio of the mass
of the isotope to the mass of a single neutron.
@ -227,7 +224,6 @@ class XSdata(object):
self._energy_groups = energy_groups
self._representation = representation
self._alias = None
self._zaid = None
self._awr = None
self._kT = None
self._fissionable = False
@ -262,10 +258,6 @@ class XSdata(object):
def alias(self):
return self._alias
@property
def zaid(self):
return self._zaid
@property
def awr(self):
return self._awr
@ -396,13 +388,6 @@ class XSdata(object):
else:
self._alias = self._name
@zaid.setter
def zaid(self, zaid):
# Check type and value
check_type('zaid', zaid, Integral)
check_greater_than('zaid', zaid, 0)
self._zaid = zaid
@awr.setter
def awr(self, awr):
# Check validity of type and that the awr value is > 0
@ -603,7 +588,8 @@ class XSdata(object):
if np.sum(self._nu_fission) > 0.0:
self._fissionable = True
def set_total_mgxs(self, total, nuclide='total', xs_type='macro'):
def set_total_mgxs(self, total, nuclide='total', xs_type='macro',
subdomain='all'):
"""This method allows for an openmc.mgxs.TotalXS or
openmc.mgxs.TransportXS to be used to set the total cross section
for this XSdata object.
@ -619,6 +605,9 @@ class XSdata(object):
xs_type: {'macro', 'micro'}
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'.
subdomain : iterable of int
If the MGXS contains a mesh domain type, the subdomain parameter
specifies which mesh cell (i.e., [i, j, k] index) to use.
See also
--------
@ -631,16 +620,17 @@ class XSdata(object):
openmc.mgxs.TransportXS))
check_value('energy_groups', total.energy_groups, [self.energy_groups])
check_value('domain_type', total.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if self.representation is 'isotropic':
self._total = total.get_xs(nuclides=nuclide, xs_type=xs_type)
self._total = total.get_xs(nuclides=nuclide, xs_type=xs_type,
subdomains=subdomain)
elif self.representation is 'angle':
msg = 'Angular-Dependent MGXS have not yet been implemented'
raise ValueError(msg)
def set_absorption_mgxs(self, absorption, nuclide='total',
xs_type='macro'):
xs_type='macro', subdomain=None):
"""This method allows for an openmc.mgxs.AbsorptionXS
to be used to set the absorption cross section for this XSdata object.
@ -655,6 +645,9 @@ class XSdata(object):
xs_type: {'macro', 'micro'}
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'.
subdomain : iterable of int
If the MGXS contains a mesh domain type, the subdomain parameter
specifies which mesh cell (i.e., [i, j, k] index) to use.
See also
--------
@ -667,16 +660,18 @@ class XSdata(object):
check_value('energy_groups', absorption.energy_groups,
[self.energy_groups])
check_value('domain_type', absorption.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if self.representation is 'isotropic':
self._absorption = absorption.get_xs(nuclides=nuclide,
xs_type=xs_type)
xs_type=xs_type,
subdomains=subdomain)
elif self.representation is 'angle':
msg = 'Angular-Dependent MGXS have not yet been implemented'
raise ValueError(msg)
def set_fission_mgxs(self, fission, nuclide='total', xs_type='macro'):
def set_fission_mgxs(self, fission, nuclide='total', xs_type='macro',
subdomain=None):
"""This method allows for an openmc.mgxs.FissionXS
to be used to set the fission cross section for this XSdata object.
@ -691,6 +686,9 @@ class XSdata(object):
xs_type: {'macro', 'micro'}
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'.
subdomain : iterable of int
If the MGXS contains a mesh domain type, the subdomain parameter
specifies which mesh cell (i.e., [i, j, k] index) to use.
See also
--------
@ -703,17 +701,18 @@ class XSdata(object):
check_value('energy_groups', fission.energy_groups,
[self.energy_groups])
check_value('domain_type', fission.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if self.representation is 'isotropic':
self._fission = fission.get_xs(nuclides=nuclide,
xs_type=xs_type)
xs_type=xs_type,
subdomains=subdomain)
elif self.representation is 'angle':
msg = 'Angular-Dependent MGXS have not yet been implemented'
raise ValueError(msg)
def set_nu_fission_mgxs(self, nu_fission, nuclide='total',
xs_type='macro'):
xs_type='macro', subdomain=None):
"""This method allows for an openmc.mgxs.NuFissionXS
to be used to set the nu-fission cross section for this XSdata object.
@ -728,6 +727,9 @@ class XSdata(object):
xs_type: {'macro', 'micro'}
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'.
subdomain : iterable of int
If the MGXS contains a mesh domain type, the subdomain parameter
specifies which mesh cell (i.e., [i, j, k] index) to use.
See also
--------
@ -741,11 +743,12 @@ class XSdata(object):
check_value('energy_groups', nu_fission.energy_groups,
[self.energy_groups])
check_value('domain_type', nu_fission.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if self.representation is 'isotropic':
self._nu_fission = nu_fission.get_xs(nuclides=nuclide,
xs_type=xs_type)
xs_type=xs_type,
subdomains=subdomain)
elif self.representation is 'angle':
msg = 'Angular-Dependent MGXS have not yet been implemented'
raise ValueError(msg)
@ -759,7 +762,7 @@ class XSdata(object):
self._fissionable = True
def set_kappa_fission_mgxs(self, k_fission, nuclide='total',
xs_type='macro'):
xs_type='macro', subdomain=None):
"""This method allows for an openmc.mgxs.KappaFissionXS
to be used to set the kappa-fission cross section for this XSdata
object.
@ -775,6 +778,9 @@ class XSdata(object):
xs_type: {'macro', 'micro'}
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'.
subdomain : iterable of int
If the MGXS contains a mesh domain type, the subdomain parameter
specifies which mesh cell (i.e., [i, j, k] index) to use.
See also
--------
@ -787,16 +793,18 @@ class XSdata(object):
check_value('energy_groups', k_fission.energy_groups,
[self.energy_groups])
check_value('domain_type', k_fission.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if self.representation is 'isotropic':
self._kappa_fission = k_fission.get_xs(nuclides=nuclide,
xs_type=xs_type)
xs_type=xs_type,
subdomains=subdomain)
elif self.representation is 'angle':
msg = 'Angular-Dependent MGXS have not yet been implemented'
raise ValueError(msg)
def set_chi_mgxs(self, chi, nuclide='total', xs_type='macro'):
def set_chi_mgxs(self, chi, nuclide='total', xs_type='macro',
subdomain=None):
"""This method allows for an openmc.mgxs.Chi
to be used to set chi for this XSdata object.
@ -810,6 +818,9 @@ class XSdata(object):
xs_type: {'macro', 'micro'}
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'.
subdomain : iterable of int
If the MGXS contains a mesh domain type, the subdomain parameter
specifies which mesh cell (i.e., [i, j, k] index) to use.
See also
--------
@ -827,11 +838,11 @@ class XSdata(object):
check_type('chi', chi, openmc.mgxs.Chi)
check_value('energy_groups', chi.energy_groups, [self.energy_groups])
check_value('domain_type', chi.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if self.representation is 'isotropic':
self._chi = chi.get_xs(nuclides=nuclide,
xs_type=xs_type)
xs_type=xs_type, subdomains=subdomain)
elif self.representation is 'angle':
msg = 'Angular-Dependent MGXS have not yet been implemented'
raise ValueError(msg)
@ -839,7 +850,8 @@ class XSdata(object):
if self.use_chi is not None:
self.use_chi = True
def set_scatter_mgxs(self, scatter, nuclide='total', xs_type='macro'):
def set_scatter_mgxs(self, scatter, nuclide='total', xs_type='macro',
subdomain=None):
"""This method allows for an openmc.mgxs.ScatterMatrixXS
to be used to set the scatter matrix cross section for this XSdata
object. If the XSdata.order attribute has not yet been set, then
@ -856,6 +868,9 @@ class XSdata(object):
xs_type: {'macro', 'micro'}
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'.
subdomain : iterable of int
If the MGXS contains a mesh domain type, the subdomain parameter
specifies which mesh cell (i.e., [i, j, k] index) to use.
See also
--------
@ -868,7 +883,7 @@ class XSdata(object):
check_value('energy_groups', scatter.energy_groups,
[self.energy_groups])
check_value('domain_type', scatter.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if self.scatt_type != 'legendre':
msg = 'Anisotropic scattering representations other than ' \
@ -892,16 +907,16 @@ class XSdata(object):
self.energy_groups.num_groups,
self.energy_groups.num_groups))
for moment in range(self.num_orders):
self._scatter[moment, :, :] = scatter.get_xs(nuclides=nuclide,
xs_type=xs_type,
moment=moment)
self._scatter[moment, :, :] = \
scatter.get_xs(nuclides=nuclide, xs_type=xs_type,
moment=moment, subdomains=subdomain)
elif self.representation is 'angle':
msg = 'Angular-Dependent MGXS have not yet been implemented'
raise ValueError(msg)
def set_multiplicity_mgxs(self, nuscatter, scatter=None, nuclide='total',
xs_type='macro'):
xs_type='macro', subdomain=None):
"""This method allows for either the direct use of only an
openmc.mgxs.MultiplicityMatrixXS OR an openmc.mgxs.NuScatterMatrixXS and
openmc.mgxs.ScatterMatrixXS to be used to set the scattering
@ -926,6 +941,9 @@ class XSdata(object):
xs_type: {'macro', 'micro'}
Provide the macro or micro cross section in units of cm^-1 or
barns. Defaults to 'macro'.
subdomain : iterable of int
If the MGXS contains a mesh domain type, the subdomain parameter
specifies which mesh cell (i.e., [i, j, k] index) to use.
See also
--------
@ -939,7 +957,7 @@ class XSdata(object):
check_value('energy_groups', nuscatter.energy_groups,
[self.energy_groups])
check_value('domain_type', nuscatter.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if scatter is not None:
check_type('scatter', scatter, openmc.mgxs.ScatterMatrixXS)
if isinstance(nuscatter, openmc.mgxs.MultiplicityMatrixXS):
@ -950,16 +968,18 @@ class XSdata(object):
check_value('energy_groups', scatter.energy_groups,
[self.energy_groups])
check_value('domain_type', scatter.domain_type,
['universe', 'cell', 'material'])
['universe', 'cell', 'material', 'mesh'])
if self.representation is 'isotropic':
nuscatt = nuscatter.get_xs(nuclides=nuclide,
xs_type=xs_type, moment=0)
xs_type=xs_type, moment=0,
subdomains=subdomain)
if isinstance(nuscatter, openmc.mgxs.MultiplicityMatrixXS):
self._multiplicity = nuscatt
else:
scatt = scatter.get_xs(nuclides=nuclide,
xs_type=xs_type, moment=0)
xs_type=xs_type, moment=0,
subdomains=subdomain)
self._multiplicity = np.divide(nuscatt, scatt)
elif self.representation is 'angle':
msg = 'Angular-Dependent MGXS have not yet been implemented'
@ -978,18 +998,10 @@ class XSdata(object):
subelement = ET.SubElement(element, 'kT')
subelement.text = str(self._kT)
if self._zaid is not None:
subelement = ET.SubElement(element, 'zaid')
subelement.text = str(self._zaid)
if self._awr is not None:
subelement = ET.SubElement(element, 'awr')
subelement.text = str(self._awr)
if self._kT is not None:
subelement = ET.SubElement(element, 'kT')
subelement.text = str(self._kT)
if self._fissionable is not None:
subelement = ET.SubElement(element, 'fissionable')
subelement.text = str(self._fissionable)
@ -1085,6 +1097,10 @@ class MGXSLibrary(object):
def energy_groups(self):
return self._energy_groups
@property
def xsdatas(self):
return self._xsdatas
@inverse_velocities.setter
def inverse_velocities(self, inverse_velocities):
check_type('inverse_velocities', inverse_velocities, Iterable, Real)

20
openmc/mixin.py Normal file
View file

@ -0,0 +1,20 @@
import numpy as np
class EqualityMixin(object):
"""A Class which provides generic __eq__ and __ne__ functionality which
can easily be inherited by downstream classes.
"""
def __eq__(self, other):
if isinstance(other, type(self)):
for key, value in self.__dict__.items():
if not np.array_equal(value, other.__dict__.get(key)):
return False
else:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)

View file

@ -1,13 +1,26 @@
from __future__ import division
import copy
from collections import Iterable
from numbers import Real
import warnings
import itertools
import random
from collections import Iterable, defaultdict
from numbers import Real
from random import uniform, gauss
from heapq import heappush, heappop
from math import pi, sin, cos, floor, log10, sqrt
from abc import ABCMeta, abstractproperty, abstractmethod
import numpy as np
try:
import scipy.spatial
_SCIPY_AVAILABLE = True
except ImportError:
_SCIPY_AVAILABLE = False
import openmc
import openmc.checkvalue as cv
class TRISO(openmc.Cell):
"""Tristructural-isotopic (TRISO) micro fuel particle
@ -82,6 +95,377 @@ class TRISO(openmc.Cell):
k_min:k_max+1, j_min:j_max+1, i_min:i_max+1]))
class _Domain(object):
"""Container in which to pack particles.
Parameters
----------
particle_radius : float
Radius of particles to be packed in container.
center : Iterable of float
Cartesian coordinates of the center of the container. Default is
[0., 0., 0.]
Attributes
----------
particle_radius : float
Radius of particles to be packed in container.
center : list of float
Cartesian coordinates of the center of the container. Default is
[0., 0., 0.]
cell_length : list of float
Length in x-, y-, and z- directions of each cell in mesh overlaid on
domain.
limits : list of float
Minimum and maximum position in x-, y-, and z-directions where particle
center can be placed.
volume : float
Volume of the container.
"""
__metaclass__ = ABCMeta
def __init__(self, particle_radius, center=[0., 0., 0.]):
self._cell_length = None
self._limits = None
self.particle_radius = particle_radius
self.center = center
@property
def particle_radius(self):
return self._particle_radius
@property
def center(self):
return self._center
@abstractproperty
def limits(self):
pass
@abstractproperty
def cell_length(self):
pass
@abstractproperty
def volume(self):
pass
@particle_radius.setter
def particle_radius(self, particle_radius):
self._particle_radius = float(particle_radius)
self._limits = None
self._cell_length = None
@center.setter
def center(self, center):
if np.asarray(center).size != 3:
raise ValueError('Unable to set domain center to {} since it must '
'be of length 3'.format(center))
self._center = [float(x) for x in center]
self._limits = None
self._cell_length = None
def mesh_cell(self, p):
"""Calculate the index of the cell in a mesh overlaid on the domain in
which the given particle center falls.
Parameters
----------
p : Iterable of float
Cartesian coordinates of particle center.
Returns
-------
tuple of int
Indices of mesh cell.
"""
return tuple(int(p[i]/self.cell_length[i]) for i in range(3))
def nearby_mesh_cells(self, p):
"""Calculates the indices of all cells in a mesh overlaid on the domain
within one diameter of the given particle.
Parameters
----------
p : Iterable of float
Cartesian coordinates of particle center.
Returns
-------
list of tuple of int
Indices of mesh cells.
"""
d = 2*self.particle_radius
r = [[a/self.cell_length[i] for a in [p[i]-d, p[i], p[i]+d]]
for i in range(3)]
return list(itertools.product(*({int(x) for x in y} for y in r)))
@abstractmethod
def random_point(self):
"""Generate Cartesian coordinates of center of a particle that is
contained entirely within the domain with uniform probability.
Returns
-------
list of float
Cartesian coordinates of particle center.
"""
pass
class _CubicDomain(_Domain):
"""Cubic container in which to pack particles.
Parameters
----------
length : float
Length of each side of the cubic container.
particle_radius : float
Radius of particles to be packed in container.
center : Iterable of float
Cartesian coordinates of the center of the container. Default is
[0., 0., 0.]
Attributes
----------
length : float
Length of each side of the cubic container.
particle_radius : float
Radius of particles to be packed in container.
center : list of float
Cartesian coordinates of the center of the container. Default is
[0., 0., 0.]
cell_length : list of float
Length in x-, y-, and z- directions of each cell in mesh overlaid on
domain.
limits : list of float
Minimum and maximum position in x-, y-, and z-directions where particle
center can be placed.
volume : float
Volume of the container.
"""
def __init__(self, length, particle_radius, center=[0., 0., 0.]):
super(_CubicDomain, self).__init__(particle_radius, center)
self.length = length
@property
def length(self):
return self._length
@property
def limits(self):
if self._limits is None:
xlim = self.length/2 - self.particle_radius
self._limits = [[x - xlim for x in self.center],
[x + xlim for x in self.center]]
return self._limits
@property
def cell_length(self):
if self._cell_length is None:
mesh_length = [self.length, self.length, self.length]
self._cell_length = [x/int(x/(4*self.particle_radius))
for x in mesh_length]
return self._cell_length
@property
def volume(self):
return self.length**3
@length.setter
def length(self, length):
self._length = float(length)
self._limits = None
self._cell_length = None
@limits.setter
def limits(self, limits):
self._limits = limits
def random_point(self):
return [uniform(self.limits[0][0], self.limits[1][0]),
uniform(self.limits[0][1], self.limits[1][1]),
uniform(self.limits[0][2], self.limits[1][2])]
class _CylindricalDomain(_Domain):
"""Cylindrical container in which to pack particles.
Parameters
----------
length : float
Length along z-axis of the cylindrical container.
radius : float
Radius of the cylindrical container.
center : Iterable of float
Cartesian coordinates of the center of the container. Default is
[0., 0., 0.]
Attributes
----------
length : float
Length along z-axis of the cylindrical container.
radius : float
Radius of the cylindrical container.
particle_radius : float
Radius of particles to be packed in container.
center : list of float
Cartesian coordinates of the center of the container. Default is
[0., 0., 0.]
cell_length : list of float
Length in x-, y-, and z- directions of each cell in mesh overlaid on
domain.
limits : list of float
Minimum and maximum position in x-, y-, and z-directions where particle
center can be placed.
volume : float
Volume of the container.
"""
def __init__(self, length, radius, particle_radius, center=[0., 0., 0.]):
super(_CylindricalDomain, self).__init__(particle_radius, center)
self.length = length
self.radius = radius
@property
def length(self):
return self._length
@property
def radius(self):
return self._radius
@property
def limits(self):
if self._limits is None:
xlim = self.length/2 - self.particle_radius
rlim = self.radius - self.particle_radius
self._limits = [[self.center[0] - rlim, self.center[1] - rlim,
self.center[2] - xlim],
[self.center[0] + rlim, self.center[1] + rlim,
self.center[2] + xlim]]
return self._limits
@property
def cell_length(self):
if self._cell_length is None:
mesh_length = [2*self.radius, 2*self.radius, self.length]
self._cell_length = [x/int(x/(4*self.particle_radius))
for x in mesh_length]
return self._cell_length
@property
def volume(self):
return self.length * pi * self.radius**2
@length.setter
def length(self, length):
self._length = float(length)
self._limits = None
self._cell_length = None
@radius.setter
def radius(self, radius):
self._radius = float(radius)
self._limits = None
self._cell_length = None
@limits.setter
def limits(self, limits):
self._limits = limits
def random_point(self):
r = sqrt(uniform(0, (self.radius - self.particle_radius)**2))
t = uniform(0, 2*pi)
return [r*cos(t) + self.center[0], r*sin(t) + self.center[1],
uniform(self.limits[0][2], self.limits[1][2])]
class _SphericalDomain(_Domain):
"""Spherical container in which to pack particles.
Parameters
----------
radius : float
Radius of the spherical container.
center : Iterable of float
Cartesian coordinates of the center of the container. Default is
[0., 0., 0.]
Attributes
----------
radius : float
Radius of the spherical container.
particle_radius : float
Radius of particles to be packed in container.
center : list of float
Cartesian coordinates of the center of the container. Default is
[0., 0., 0.]
cell_length : list of float
Length in x-, y-, and z- directions of each cell in mesh overlaid on
domain.
limits : list of float
Minimum and maximum position in x-, y-, and z-directions where particle
center can be placed.
volume : float
Volume of the container.
"""
def __init__(self, radius, particle_radius, center=[0., 0., 0.]):
super(_SphericalDomain, self).__init__(particle_radius, center)
self.radius = radius
@property
def radius(self):
return self._radius
@property
def limits(self):
if self._limits is None:
rlim = self.radius - self.particle_radius
self._limits = [[x - rlim for x in self.center],
[x + rlim for x in self.center]]
return self._limits
@property
def cell_length(self):
if self._cell_length is None:
mesh_length = [2*self.radius, 2*self.radius, 2*self.radius]
self._cell_length = [x/int(x/(4*self.particle_radius))
for x in mesh_length]
return self._cell_length
@property
def volume(self):
return 4/3 * pi * self.radius**3
@radius.setter
def radius(self, radius):
self._radius = float(radius)
self._limits = None
self._cell_length = None
@limits.setter
def limits(self, limits):
self._limits = limits
def random_point(self):
x = (gauss(0, 1), gauss(0, 1), gauss(0, 1))
r = (uniform(0, (self.radius - self.particle_radius)**3)**(1/3) /
sqrt(x[0]**2 + x[1]**2 + x[2]**2))
return [r*x[i] + self.center[i] for i in range(3)]
def create_triso_lattice(trisos, lower_left, pitch, shape, background):
"""Create a lattice containing TRISO particles for optimized tracking.
@ -153,3 +537,503 @@ def create_triso_lattice(trisos, lower_left, pitch, shape, background):
lattice.outer = openmc.Universe(cells=[background_cell])
return lattice
def _random_sequential_pack(domain, n_particles):
"""Random sequential packing of particles within a container.
Parameters
----------
domain : openmc.model._Domain
Container in which to pack particles.
n_particles : int
Number of particles to pack.
Returns
------
numpy.ndarray
Cartesian coordinates of centers of particles.
"""
sqd = (2*domain.particle_radius)**2
particles = []
mesh = defaultdict(list)
for i in range(n_particles):
# Randomly sample new center coordinates while there are any overlaps
while True:
p = domain.random_point()
idx = domain.mesh_cell(p)
if any((p[0]-q[0])**2 + (p[1]-q[1])**2 + (p[2]-q[2])**2 < sqd
for q in mesh[idx]):
continue
else:
break
particles.append(p)
for idx in domain.nearby_mesh_cells(p):
mesh[idx].append(p)
return np.array(particles)
def _close_random_pack(domain, particles, contraction_rate):
"""Close random packing of particles using the Jodrey-Tory algorithm.
Parameters
----------
domain : openmc.model._Domain
Container in which to pack particles.
particles : numpy.ndarray
Initial Cartesian coordinates of centers of particles.
contraction_rate : float
Contraction rate of outer diameter.
"""
def add_rod(d, i, j):
"""Add a new rod to the priority queue.
Parameters
----------
d : float
distance between centers of particles i and j.
i, j : int
Index of particles in particles array.
"""
rod = [d, i, j]
rods_map[i] = (j, rod)
rods_map[j] = (i, rod)
heappush(rods, rod)
def remove_rod(i):
"""Mark the rod containing particle i as removed.
Parameters
----------
i : int
Index of particle in particles array.
"""
if i in rods_map:
j, rod = rods_map.pop(i)
del rods_map[j]
rod[1] = removed
rod[2] = removed
def pop_rod():
"""Remove and return the shortest rod.
Returns
-------
d : float
distance between centers of particles i and j.
i, j : int
Index of particles in particles array.
"""
while rods:
d, i, j = heappop(rods)
if i != removed and j != removed:
del rods_map[i]
del rods_map[j]
return d, i, j
def create_rod_list():
"""Generate sorted list of rods (distances between particle centers).
Rods are arranged in a heap where each element contains the rod length
and the particle indices. A rod between particles p and q is only
included if the distance between p and q could not be changed by the
elimination of a greater overlap, i.e. q has no nearer neighbors than p.
A mapping of particle ids to rods is maintained in 'rods_map'. Each key
in the dict is the id of a particle that is in the rod list, and the
value is the id of its nearest neighbor and the rod that contains them.
The dict is used to find rods in the priority queue and to mark removed
rods so rods can be "removed" without breaking the heap structure
invariant.
"""
# Create KD tree for quick nearest neighbor search
tree = scipy.spatial.cKDTree(particles)
# Find distance to nearest neighbor and index of nearest neighbor for
# all particles
d, n = tree.query(particles, k=2)
d = d[:,1]
n = n[:,1]
# Array of particle indices, indices of nearest neighbors, and
# distances to nearest neighbors
a = np.vstack((list(range(n.size)), n, d)).T
# Sort along second column and swap first and second columns to create
# array of nearest neighbor indices, indices of particles they are
# nearest neighbors of, and distances between them
b = a[a[:,1].argsort()]
b[:,[0, 1]] = b[:,[1, 0]]
# Find the intersection between 'a' and 'b': a list of particles who
# are each other's nearest neighbors and the distance between them
r = list({tuple(x) for x in a} & {tuple(x) for x in b})
# Remove duplicate rods and sort by distance
r = map(list, set([(x[2], int(min(x[0:2])), int(max(x[0:2])))
for x in r]))
# Clear priority queue and add rods
del rods[:]
rods_map.clear()
for d, i, j in r:
add_rod(d, i, j)
# Inner diameter is set initially to the shortest center-to-center
# distance between any two particles
if rods:
inner_diameter[0] = rods[0][0]
def update_mesh(i):
"""Update which mesh cells the particle is in based on new particle
center coordinates.
'mesh'/'mesh_map' is a two way dictionary used to look up which
particles are located within one diameter of a given mesh cell and
which mesh cells a given particle center is within one diameter of.
This is used to speed up the nearest neighbor search.
Parameters
----------
i : int
Index of particle in particles array.
"""
# Determine which mesh cells the particle is in and remove the
# particle id from those cells
for idx in mesh_map[i]:
mesh[idx].remove(i)
del mesh_map[i]
# Determine which mesh cells are within one diameter of particle's
# center and add this particle to the list of particles in those cells
for idx in domain.nearby_mesh_cells(particles[i]):
mesh[idx].add(i)
mesh_map[i].add(idx)
def reduce_outer_diameter():
"""Reduce the outer diameter so that at the (i+1)-st iteration it is:
d_out^(i+1) = d_out^(i) - (1/2)^(j) * d_out0 * k / n,
where k is the contraction rate, n is the number of particles, and
j = floor(-log10(pf_out - pf_in)).
"""
inner_pf = (4/3 * pi * (inner_diameter[0]/2)**3 * n_particles /
domain.volume)
outer_pf = (4/3 * pi * (outer_diameter[0]/2)**3 * n_particles /
domain.volume)
j = floor(-log10(outer_pf - inner_pf))
outer_diameter[0] = (outer_diameter[0] - 0.5**j * contraction_rate *
initial_outer_diameter / n_particles)
def repel_particles(i, j, d):
"""Move particles p and q apart according to the following
transformation (accounting for reflective boundary conditions on
domain):
r_i^(n+1) = r_i^(n) + 1/2(d_out^(n+1) - d^(n))
r_j^(n+1) = r_j^(n) - 1/2(d_out^(n+1) - d^(n))
Parameters
----------
i, j : int
Index of particles in particles array.
d : float
distance between centers of particles i and j.
"""
# Moving each particle distance 'r' away from the other along the line
# joining the particle centers will ensure their final distance is equal
# to the outer diameter
r = (outer_diameter[0] - d)/2
v = (particles[i] - particles[j])/d
particles[i] += r*v
particles[j] -= r*v
# Apply reflective boundary conditions
particles[i] = particles[i].clip(domain.limits[0], domain.limits[1])
particles[j] = particles[j].clip(domain.limits[0], domain.limits[1])
update_mesh(i)
update_mesh(j)
def nearest(i):
"""Find index of nearest neighbor of particle i.
Parameters
----------
i : int
Index in particles array of particle for which to find nearest
neighbor.
Returns
-------
int
Index in particles array of nearest neighbor of i
float
distance between i and nearest neighbor.
"""
# Need the second nearest neighbor of i since the nearest neighbor
# will be itself. Using argpartition, the k-th nearest neighbor is
# placed at index k.
idx = list(mesh[domain.mesh_cell(particles[i])])
dists = scipy.spatial.distance.cdist([particles[i]], particles[idx])[0]
if dists.size > 1:
j = dists.argpartition(1)[1]
return idx[j], dists[j]
else:
return None, None
def update_rod_list(i, j):
"""Update the rod list with the new nearest neighbors of particles i
and j since their overlap was eliminated.
Parameters
----------
i, j : int
Index of particles in particles array.
"""
# If the nearest neighbor k of particle i has no nearer neighbors,
# remove the rod currently containing k from the rod list and add rod
# k-i, keeping the rod list sorted
k, d_ik = nearest(i)
if k and nearest(k)[0] == i:
remove_rod(k)
add_rod(d_ik, i, k)
l, d_jl = nearest(j)
if l and nearest(l)[0] == j:
remove_rod(l)
add_rod(d_jl, j, l)
# Set inner diameter to the shortest distance between two particle
# centers
if rods:
inner_diameter[0] = rods[0][0]
if not _SCIPY_AVAILABLE:
raise ImportError('SciPy must be installed to perform '
'close random packing.')
n_particles = len(particles)
diameter = 2*domain.particle_radius
# Flag for marking rods that have been removed from priority queue
removed = -1
# Outer diameter initially set to arbitrary value that yields pf of 1
initial_outer_diameter = 2*(domain.volume/(n_particles*4/3*pi))**(1/3)
# Inner and outer diameter of particles will change during packing
outer_diameter = [initial_outer_diameter]
inner_diameter = [0]
rods = []
rods_map = {}
mesh = defaultdict(set)
mesh_map = defaultdict(set)
for i in range(n_particles):
for idx in domain.nearby_mesh_cells(particles[i]):
mesh[idx].add(i)
mesh_map[i].add(idx)
while True:
create_rod_list()
if inner_diameter[0] >= diameter:
break
while True:
d, i, j = pop_rod()
reduce_outer_diameter()
repel_particles(i, j, d)
update_rod_list(i, j)
if inner_diameter[0] >= diameter or not rods:
break
def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None,
domain_radius=None, domain_center=[0., 0., 0.],
n_particles=None, packing_fraction=None,
initial_packing_fraction=0.3, contraction_rate=1/400, seed=1):
"""Generate a random, non-overlapping configuration of TRISO particles
within a container.
Parameters
----------
radius : float
Outer radius of TRISO particles.
fill : openmc.Universe
Universe which contains all layers of the TRISO particle.
domain_shape : {'cube', 'cylinder', or 'sphere'}
Geometry of the container in which the TRISO particles are packed.
domain_length : float
Length of the container (if cube or cylinder).
domain_radius : float
Radius of the container (if cylinder or sphere).
domain_center : Iterable of float
Cartesian coordinates of the center of the container.
n_particles : int
Number of TRISO particles to pack in the domain. Exactly one of
'n_particles' and 'packing_fraction' should be specified -- the other
will be calculated.
packing_fraction : float
Packing fraction of particles. Exactly one of 'n_particles' and
'packing_fraction' should be specified -- the other will be calculated.
initial_packing_fraction : float, optional
Packing fraction used to initialize the configuration of particles in
the domain. Default value is 0.3. It is not recommended to set the
initial packing fraction much higher than 0.3 as the random sequential
packing algorithm becomes prohibitively slow as it approaches its limit
(~0.38).
contraction_rate : float, optional
Contraction rate of outer diameter. This can affect the speed of the
close random packing algorithm. Default value is 1/400.
seed : int, optional
RNG seed.
Returns
-------
trisos : list of openmc.model.TRISO
List of TRISO particles in the domain.
Notes
-----
The particle configuration is generated using a combination of random
sequential packing (RSP) and close random packing (CRP). RSP performs
better than CRP for lower packing fractions (pf), but it becomes
prohibitively slow as it approaches its packing limit (~0.38). CRP can
achieve higher pf of up to ~0.64 and scales better with increasing pf.
If the desired pf is below some threshold for which RSP will be faster than
CRP ('initial_packing_fraction'), only RSP is used. If a higher pf is
required, particles with a radius smaller than the desired final radius
(and therefore with a smaller pf) are initialized within the domain using
RSP. This initial configuration of particles is then used as a starting
point for CRP using Jodrey and Tory's algorithm [1]_.
In RSP, particle centers are placed one by one at random, and placement
attempts for a particle are made until the particle is not overlapping any
others. This implementation of the algorithm uses a mesh over the domain
to speed up the nearest neighbor search by only searching for a particle's
neighbors within that mesh cell.
In CRP, each particle is assigned two diameters, and inner and an outer,
which approach each other during the simulation. The inner diameter,
defined as the minimum center-to-center distance, is the true diameter of
the particles and defines the pf. At each iteration the worst overlap
between particles based on outer diameter is eliminated by moving the
particles apart along the line joining their centers. Iterations continue
until the two diameters converge or until the desired pf is reached.
References
----------
.. [1] W. S. Jodrey and E. M. Tory, "Computer simulation of close random
packing of equal spheres", Phys. Rev. A 32 (1985) 2347-2351.
"""
# Check for valid container geometry and dimensions
if domain_shape not in ['cube', 'cylinder', 'sphere']:
raise ValueError('Unable to set domain_shape to "{}". Only "cube", '
'"cylinder", and "sphere" are '
'supported."'.format(domain_shape))
if not domain_length and domain_shape in ['cube', 'cylinder']:
raise ValueError('"domain_length" must be specified for {} domain '
'geometry '.format(domain_shape))
if not domain_radius and domain_shape in ['cylinder', 'sphere']:
raise ValueError('"domain_radius" must be specified for {} domain '
'geometry '.format(domain_shape))
if domain_shape is 'cube':
domain = _CubicDomain(length=domain_length, particle_radius=radius,
center=domain_center)
elif domain_shape is 'cylinder':
domain = _CylindricalDomain(length=domain_length, radius=domain_radius,
particle_radius=radius, center=domain_center)
elif domain_shape is 'sphere':
domain = _SphericalDomain(radius=domain_radius, particle_radius=radius,
center=domain_center)
# Calculate the packing fraction if the number of particles is specified;
# otherwise, calculate the number of particles from the packing fraction.
if ((n_particles is None and packing_fraction is None) or
(n_particles is not None and packing_fraction is not None)):
raise ValueError('Exactly one of "n_particles" and "packing_fraction" '
'must be specified.')
elif packing_fraction is None:
n_particles = int(n_particles)
packing_fraction = 4/3*pi*radius**3*n_particles / domain.volume
elif n_particles is None:
packing_fraction = float(packing_fraction)
n_particles = int(packing_fraction*domain.volume // (4/3*pi*radius**3))
# Check for valid packing fractions for each algorithm
if packing_fraction >= 0.64:
raise ValueError('Packing fraction of {} is greater than the '
'packing fraction limit for close random '
'packing (0.64)'.format(packing_fraction))
if initial_packing_fraction >= 0.38:
raise ValueError('Initial packing fraction of {} is greater than the '
'packing fraction limit for random sequential'
'packing (0.38)'.format(initial_packing_fraction))
if initial_packing_fraction > packing_fraction:
initial_packing_fraction = packing_fraction
if packing_fraction > 0.3:
initial_packing_fraction = 0.3
random.seed(seed)
# Calculate the particle radius used in the initial random sequential
# packing from the initial packing fraction
initial_radius = (3/4 * initial_packing_fraction * domain.volume /
(pi * n_particles))**(1/3)
domain.particle_radius = initial_radius
# Recalculate the limits for the initial random sequential packing using
# the desired final particle radius to ensure particles are fully contained
# within the domain during the close random pack
domain.limits = [[x - initial_radius + radius for x in domain.limits[0]],
[x + initial_radius - radius for x in domain.limits[1]]]
# Generate non-overlapping particles for an initial inner radius using
# random sequential packing algorithm
particles = _random_sequential_pack(domain, n_particles)
# Use the particle configuration produced in random sequential packing as a
# starting point for close random pack with the desired final particle
# radius
if initial_packing_fraction != packing_fraction:
domain.particle_radius = radius
_close_random_pack(domain, particles, contraction_rate)
trisos = []
for p in particles:
trisos.append(TRISO(radius, fill, p))
return trisos

View file

@ -1,5 +1,6 @@
from numbers import Integral
import sys
import warnings
from openmc.checkvalue import check_type
@ -14,42 +15,28 @@ class Nuclide(object):
----------
name : str
Name of the nuclide, e.g. U235
xs : str
Cross section identifier, e.g. 71c
Attributes
----------
name : str
Name of the nuclide, e.g. U235
xs : str
Cross section identifier, e.g. 71c
zaid : int
1000*(atomic number) + mass number. As an example, the zaid of U235
would be 92235.
scattering : 'data' or 'iso-in-lab' or None
The type of angular scattering distribution to use
"""
def __init__(self, name='', xs=None):
def __init__(self, name=''):
# Initialize class attributes
self._name = ''
self._xs = None
self._zaid = None
self._scattering = None
# Set the Material class attributes
self.name = name
if xs is not None:
self.xs = xs
def __eq__(self, other):
if isinstance(other, Nuclide):
if self.name != other.name:
return False
elif self.xs != other.xs:
return False
else:
return True
elif isinstance(other, basestring) and other == self.name:
@ -71,9 +58,6 @@ class Nuclide(object):
def __repr__(self):
string = 'Nuclide - {0}\n'.format(self._name)
string += '{0: <16}{1}{2}\n'.format('\tXS', '=\t', self.xs)
if self.zaid is not None:
string += '{0: <16}{1}{2}\n'.format('\tZAID', '=\t', self.zaid)
if self.scattering is not None:
string += '{0: <16}{1}{2}\n'.format('\tscattering', '=\t',
self.scattering)
@ -83,14 +67,6 @@ class Nuclide(object):
def name(self):
return self._name
@property
def xs(self):
return self._xs
@property
def zaid(self):
return self._zaid
@property
def scattering(self):
return self._scattering
@ -100,19 +76,18 @@ class Nuclide(object):
check_type('name', name, basestring)
self._name = name
@xs.setter
def xs(self, xs):
check_type('cross-section identifier', xs, basestring)
self._xs = xs
if '-' in name:
self._name = name.replace('-', '')
self._name = self._name.replace('Nat', '0')
if self._name.endswith('m'):
self._name = self._name[:-1] + '_m1'
@zaid.setter
def zaid(self, zaid):
check_type('zaid', zaid, Integral)
self._zaid = zaid
msg = 'OpenMC nuclides follow the GND naming convention. Nuclide ' \
'"{}" is being renamed as "{}".'.format(name, self._name)
warnings.warn(msg)
@scattering.setter
def scattering(self, scattering):
if not scattering in ['data', 'iso-in-lab']:
msg = 'Unable to set scattering for Nuclide to {0} ' \
'which is not "data" or "iso-in-lab"'.format(scattering)

View file

@ -689,13 +689,14 @@ def get_openmc_cell(opencg_cell):
translation = np.asarray(opencg_cell.translation, dtype=np.float64)
openmc_cell.translation = translation
surfaces = []
operators = []
for surface, halfspace in opencg_cell.surfaces.values():
surfaces.append(get_openmc_surface(surface))
operators.append(operator.neg if halfspace == -1 else operator.pos)
openmc_cell.region = openmc.Intersection(
*[op(s) for op, s in zip(operators, surfaces)])
if opencg_cell.surfaces:
surfaces = []
operators = []
for surface, halfspace in opencg_cell.surfaces.values():
surfaces.append(get_openmc_surface(surface))
operators.append(operator.neg if halfspace == -1 else operator.pos)
openmc_cell.region = openmc.Intersection(
*[op(s) for op, s in zip(operators, surfaces)])
# Add the OpenMC Cell to the global collection of all OpenMC Cells
OPENMC_CELLS[cell_id] = openmc_cell
@ -911,6 +912,7 @@ def get_openmc_lattice(opencg_lattice):
if lattice_id in OPENMC_LATTICES:
return OPENMC_LATTICES[lattice_id]
name = opencg_lattice.name
dimension = opencg_lattice.dimension
width = opencg_lattice.width
offset = opencg_lattice.offset
@ -941,7 +943,7 @@ def get_openmc_lattice(opencg_lattice):
((np.array(width, dtype=np.float64) *
np.array(dimension, dtype=np.float64))) / -2.0
openmc_lattice = openmc.RectLattice(lattice_id=lattice_id)
openmc_lattice = openmc.RectLattice(lattice_id=lattice_id, name=name)
openmc_lattice.pitch = width
openmc_lattice.universes = universe_array
openmc_lattice.lower_left = lower_left
@ -995,13 +997,17 @@ def get_opencg_geometry(openmc_geometry):
return opencg_geometry
def get_openmc_geometry(opencg_geometry):
def get_openmc_geometry(opencg_geometry, compatible=False):
"""Return an OpenMC geometry corresponding to an OpenCG geometry.
Parameters
----------
opencg_geometry : opencg.Geometry
OpenCG geometry
compatible : bool
Whether the OpenCG geometry is compatible with OpenMC's geometric
primitives. This should be set to False if the OpenCG geometry
uses SquarePrism surfaces. False by default.
Returns
-------
@ -1017,9 +1023,6 @@ def get_openmc_geometry(opencg_geometry):
opencg_geometry.assign_auto_ids()
opencg_geometry = copy.deepcopy(opencg_geometry)
# Update Cell bounding boxes in Geometry
opencg_geometry.update_bounding_boxes()
# Clear dictionaries and auto-generated ID
OPENMC_SURFACES.clear()
OPENCG_SURFACES.clear()
@ -1031,12 +1034,13 @@ def get_openmc_geometry(opencg_geometry):
OPENCG_LATTICES.clear()
# Make the entire geometry "compatible" before assigning auto IDs
universes = opencg_geometry.get_all_universes()
for universe in universes.values():
if not isinstance(universe, opencg.Lattice):
make_opencg_cells_compatible(universe)
if not compatible:
universes = opencg_geometry.get_all_universes()
for universe in universes.values():
if not isinstance(universe, opencg.Lattice):
make_opencg_cells_compatible(universe)
opencg_geometry.assign_auto_ids()
opencg_geometry.assign_auto_ids()
opencg_root_universe = opencg_geometry.root_universe
openmc_root_universe = get_openmc_universe(opencg_root_universe)

View file

@ -67,6 +67,11 @@ class Plot(object):
col_spec : dict
Dictionary indicating that certain cells/materials (keys) should be
colored with a specific RGB (values)
level : int
Universe depth to plot at
meshlines : dict
Dictionary defining type, id, linewidth and color of a regular mesh
to be plotted on top of a plot
"""
@ -81,10 +86,12 @@ class Plot(object):
self._color = 'cell'
self._type = 'slice'
self._basis = 'xy'
self._background = [0, 0, 0]
self._background = None
self._mask_components = None
self._mask_background = None
self._col_spec = None
self._level = None
self._meshlines = None
@property
def id(self):
@ -138,6 +145,14 @@ class Plot(object):
def col_spec(self):
return self._col_spec
@property
def level(self):
return self._level
@property
def meshlines(self):
return self._meshlines
@id.setter
def id(self, plot_id):
if plot_id is None:
@ -231,9 +246,9 @@ class Plot(object):
@mask_components.setter
def mask_components(self, mask_components):
cv.check_type('plot mask_components', mask_components, Iterable, Integral)
cv.check_type('plot mask components', mask_components, Iterable, Integral)
for component in mask_components:
cv.check_greater_than('plot mask_components', component, 0, True)
cv.check_greater_than('plot mask components', component, 0, True)
self._mask_components = mask_components
@mask_background.setter
@ -245,6 +260,45 @@ class Plot(object):
cv.check_less_than('plot mask background', rgb, 256)
self._mask_background = mask_background
@level.setter
def level(self, plot_level):
cv.check_type('plot level', plot_level, Integral)
cv.check_greater_than('plot level', plot_level, 0, equality=True)
self._level = plot_level
@meshlines.setter
def meshlines(self, meshlines):
cv.check_type('plot meshlines', meshlines, dict)
if 'type' not in meshlines:
msg = 'Unable to set on plot the meshlines "{0}" which ' \
'does not have a "type" key'.format(meshlines)
raise ValueError(msg)
elif meshlines['type'] not in ['tally', 'entropy', 'ufs', 'cmfd']:
msg = 'Unable to set the meshlines with ' \
'type "{0}"'.format(meshlines['type'])
raise ValueError(msg)
if 'id' in meshlines:
cv.check_type('plot meshlines id', meshlines['id'], Integral)
cv.check_greater_than('plot meshlines id', meshlines['id'], 0,
equality=True)
if 'linewidth' in meshlines:
cv.check_type('plot mesh linewidth', meshlines['linewidth'], Integral)
cv.check_greater_than('plot mesh linewidth', meshlines['linewidth'],
0, equality=True)
if 'color' in meshlines:
cv.check_type('plot meshlines color', meshlines['color'], Iterable,
Integral)
cv.check_length('plot meshlines color', meshlines['color'], 3)
for rgb in meshlines['color']:
cv.check_greater_than('plot meshlines color', rgb, 0, True)
cv.check_less_than('plot meshlines color', rgb, 256)
self._meshlines = meshlines
def __repr__(self):
string = 'Plot\n'
string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id)
@ -256,11 +310,16 @@ class Plot(object):
string += '{0: <16}{1}{2}\n'.format('\tOrigin', '=\t', self._origin)
string += '{0: <16}{1}{2}\n'.format('\tPixels', '=\t', self._origin)
string += '{0: <16}{1}{2}\n'.format('\tColor', '=\t', self._color)
string += '{0: <16}{1}{2}\n'.format('\tMask', '=\t',
string += '{0: <16}{1}{2}\n'.format('\tBackground', '=\t',
self._background)
string += '{0: <16}{1}{2}\n'.format('\tMask components', '=\t',
self._mask_components)
string += '{0: <16}{1}{2}\n'.format('\tMask', '=\t',
string += '{0: <16}{1}{2}\n'.format('\tMask background', '=\t',
self._mask_background)
string += '{0: <16}{1}{2}\n'.format('\tCol Spec', '=\t', self._col_spec)
string += '{0: <16}{1}{2}\n'.format('\tLevel', '=\t', self._level)
string += '{0: <16}{1}{2}\n'.format('\tMeshlines', '=\t',
self._meshlines)
return string
def colorize(self, geometry, seed=1):
@ -382,7 +441,7 @@ class Plot(object):
subelement = ET.SubElement(element, "pixels")
subelement.text = ' '.join(map(str, self._pixels))
if self._mask_background is not None:
if self._background is not None:
subelement = ET.SubElement(element, "background")
subelement.text = ' '.join(map(str, self._background))
@ -400,6 +459,21 @@ class Plot(object):
subelement.set("background", ' '.join(map(
str, self._mask_background)))
if self._level is not None:
subelement = ET.SubElement(element, "level")
subelement.text = str(self._level)
if self._meshlines is not None:
subelement = ET.SubElement(element, "meshlines")
subelement.set("meshtype", self._meshlines['type'])
if self._meshlines['id'] is not None:
subelement.set("id", str(self._meshlines['id']))
if self._meshlines['linewidth'] is not None:
subelement.set("linewidth", str(self._meshlines['linewidth']))
if self._meshlines['color'] is not None:
subelement.set("color", ' '.join(map(
str, self._meshlines['color'])))
return element

View file

@ -1,4 +1,4 @@
from collections import Iterable
from collections import Iterable, MutableSequence, Mapping
from numbers import Real, Integral
import warnings
from xml.etree import ElementTree as ET
@ -7,10 +7,8 @@ import sys
import numpy as np
from openmc.clean_xml import clean_xml_indentation
from openmc.checkvalue import (check_type, check_length, check_value,
check_greater_than, check_less_than)
from openmc import Nuclide
from openmc.source import Source
import openmc.checkvalue as cv
from openmc import Nuclide, VolumeCalculation, Source
if sys.version_info[0] >= 3:
basestring = str
@ -80,8 +78,6 @@ class Settings(object):
cross section library. If it is not set, the
:envvar:`OPENMC_MULTIPOLE_LIBRARY` environment variable will be used. A
multipole library is optional.
energy_grid : {'nuclide', 'logarithm', 'material-union'}
Set the method used to search energy grids.
energy_mode : {'continuous-energy', 'multi-group'}
Set whether the calculation should be continuous-energy or multi-group.
max_order : int
@ -105,6 +101,14 @@ class Settings(object):
Coordinates of the lower-left point of the Shannon entropy mesh
entropy_upper_right : tuple or list
Coordinates of the upper-right point of the Shannon entropy mesh
temperature : dict
Defines a default temperature and method for treating intermediate
temperatures at which nuclear data doesn't exist. Accepted keys are
'default', 'method', and 'tolerance'. The value for 'default' should be
a float representing the default temperature in Kelvin. The value for
'method' should be 'nearest' or 'multipole'. If the method is
'nearest', 'tolerance' indicates a range of temperature within which
cross sections may be used.
trigger_active : bool
Indicate whether tally triggers are used
trigger_max_batches : int
@ -132,11 +136,10 @@ class Settings(object):
Coordinates of the lower-left point of the UFS mesh
ufs_upper_right : tuple or list
Coordinates of the upper-right point of the UFS mesh
use_windowed_multipole : bool
Whether or not windowed multipole can be used to evaluate resolved
resonance cross sections.
resonance_scattering : ResonanceScattering or iterable of ResonanceScattering
The elastic scattering model to use for resonant isotopes
volume_calculations : VolumeCalculation or iterable of VolumeCalculation
Stochastic volume calculation specifications
"""
@ -155,12 +158,11 @@ class Settings(object):
self._max_order = None
# Source subelement
self._source = None
self._source = cv.CheckedList(Source, 'source distributions')
self._confidence_intervals = None
self._cross_sections = None
self._multipole_library = None
self._energy_grid = None
self._ptables = None
self._run_cmfd = None
self._seed = None
@ -197,6 +199,8 @@ class Settings(object):
self._trace = None
self._track = None
self._temperature = {}
# Cutoff subelement
self._weight = None
self._weight_avg = None
@ -216,10 +220,11 @@ class Settings(object):
self._settings_file = ET.Element("settings")
self._run_mode_subelement = None
self._source_element = None
self._multipole_active = None
self._resonance_scattering = None
self._resonance_scattering = cv.CheckedList(
ResonanceScattering, 'resonance scattering models')
self._volume_calculations = cv.CheckedList(
VolumeCalculation, 'volume calculations')
@property
def run_mode(self):
@ -269,10 +274,6 @@ class Settings(object):
def multipole_library(self):
return self._multipole_library
@property
def energy_grid(self):
return self._energy_grid
@property
def ptables(self):
return self._ptables
@ -361,6 +362,10 @@ class Settings(object):
def verbosity(self):
return self._verbosity
@property
def temperature(self):
return self._temperature
@property
def trace(self):
return self._trace
@ -413,14 +418,14 @@ class Settings(object):
def dd_count_interactions(self):
return self._dd_count_interactions
@property
def use_windowed_multipole(self):
return self._multipole_active
@property
def resonance_scattering(self):
return self._resonance_scattering
@property
def volume_calculations(self):
return self._volume_calculations
@run_mode.setter
def run_mode(self, run_mode):
if run_mode not in ['eigenvalue', 'fixed source']:
@ -431,26 +436,26 @@ class Settings(object):
@batches.setter
def batches(self, batches):
check_type('batches', batches, Integral)
check_greater_than('batches', batches, 0)
cv.check_type('batches', batches, Integral)
cv.check_greater_than('batches', batches, 0)
self._batches = batches
@generations_per_batch.setter
def generations_per_batch(self, generations_per_batch):
check_type('generations per patch', generations_per_batch, Integral)
check_greater_than('generations per batch', generations_per_batch, 0)
cv.check_type('generations per patch', generations_per_batch, Integral)
cv.check_greater_than('generations per batch', generations_per_batch, 0)
self._generations_per_batch = generations_per_batch
@inactive.setter
def inactive(self, inactive):
check_type('inactive batches', inactive, Integral)
check_greater_than('inactive batches', inactive, 0, True)
cv.check_type('inactive batches', inactive, Integral)
cv.check_greater_than('inactive batches', inactive, 0, True)
self._inactive = inactive
@particles.setter
def particles(self, particles):
check_type('particles', particles, Integral)
check_greater_than('particles', particles, 0)
cv.check_type('particles', particles, Integral)
cv.check_greater_than('particles', particles, 0)
self._particles = particles
@keff_trigger.setter
@ -484,23 +489,21 @@ class Settings(object):
@energy_mode.setter
def energy_mode(self, energy_mode):
check_value('energy mode', energy_mode,
cv.check_value('energy mode', energy_mode,
['continuous-energy', 'multi-group'])
self._energy_mode = energy_mode
@max_order.setter
def max_order(self, max_order):
check_type('maximum scattering order', max_order, Integral)
check_greater_than('maximum scattering order', max_order, 0, True)
cv.check_type('maximum scattering order', max_order, Integral)
cv.check_greater_than('maximum scattering order', max_order, 0, True)
self._max_order = max_order
@source.setter
def source(self, source):
if isinstance(source, Source):
self._source = [source,]
else:
check_type('source distribution', source, Iterable, Source)
self._source = source
if not isinstance(source, MutableSequence):
source = [source]
self._source = cv.CheckedList(Source, 'source distributions', source)
@output.setter
def output(self, output):
@ -525,197 +528,206 @@ class Settings(object):
@output_path.setter
def output_path(self, output_path):
check_type('output path', output_path, basestring)
cv.check_type('output path', output_path, basestring)
self._output_path = output_path
@verbosity.setter
def verbosity(self, verbosity):
check_type('verbosity', verbosity, Integral)
check_greater_than('verbosity', verbosity, 1, True)
check_less_than('verbosity', verbosity, 10, True)
cv.check_type('verbosity', verbosity, Integral)
cv.check_greater_than('verbosity', verbosity, 1, True)
cv.check_less_than('verbosity', verbosity, 10, True)
self._verbosity = verbosity
@statepoint_batches.setter
def statepoint_batches(self, batches):
check_type('statepoint batches', batches, Iterable, Integral)
cv.check_type('statepoint batches', batches, Iterable, Integral)
for batch in batches:
check_greater_than('statepoint batch', batch, 0)
cv.check_greater_than('statepoint batch', batch, 0)
self._statepoint_batches = batches
@statepoint_interval.setter
def statepoint_interval(self, interval):
check_type('statepoint interval', interval, Integral)
cv.check_type('statepoint interval', interval, Integral)
self._statepoint_interval = interval
@sourcepoint_batches.setter
def sourcepoint_batches(self, batches):
check_type('sourcepoint batches', batches, Iterable, Integral)
cv.check_type('sourcepoint batches', batches, Iterable, Integral)
for batch in batches:
check_greater_than('sourcepoint batch', batch, 0)
cv.check_greater_than('sourcepoint batch', batch, 0)
self._sourcepoint_batches = batches
@sourcepoint_interval.setter
def sourcepoint_interval(self, interval):
check_type('sourcepoint interval', interval, Integral)
cv.check_type('sourcepoint interval', interval, Integral)
self._sourcepoint_interval = interval
@sourcepoint_separate.setter
def sourcepoint_separate(self, source_separate):
check_type('sourcepoint separate', source_separate, bool)
cv.check_type('sourcepoint separate', source_separate, bool)
self._sourcepoint_separate = source_separate
@sourcepoint_write.setter
def sourcepoint_write(self, source_write):
check_type('sourcepoint write', source_write, bool)
cv.check_type('sourcepoint write', source_write, bool)
self._sourcepoint_write = source_write
@sourcepoint_overwrite.setter
def sourcepoint_overwrite(self, source_overwrite):
check_type('sourcepoint overwrite', source_overwrite, bool)
cv.check_type('sourcepoint overwrite', source_overwrite, bool)
self._sourcepoint_overwrite = source_overwrite
@confidence_intervals.setter
def confidence_intervals(self, confidence_intervals):
check_type('confidence interval', confidence_intervals, bool)
cv.check_type('confidence interval', confidence_intervals, bool)
self._confidence_intervals = confidence_intervals
@cross_sections.setter
def cross_sections(self, cross_sections):
check_type('cross sections', cross_sections, basestring)
cv.check_type('cross sections', cross_sections, basestring)
self._cross_sections = cross_sections
@multipole_library.setter
def multipole_library(self, multipole_library):
check_type('cross sections', multipole_library, basestring)
cv.check_type('cross sections', multipole_library, basestring)
self._multipole_library = multipole_library
@energy_grid.setter
def energy_grid(self, energy_grid):
check_value('energy grid', energy_grid,
['nuclide', 'logarithm', 'material-union'])
self._energy_grid = energy_grid
@ptables.setter
def ptables(self, ptables):
check_type('probability tables', ptables, bool)
cv.check_type('probability tables', ptables, bool)
self._ptables = ptables
@run_cmfd.setter
def run_cmfd(self, run_cmfd):
check_type('run_cmfd', run_cmfd, bool)
cv.check_type('run_cmfd', run_cmfd, bool)
self._run_cmfd = run_cmfd
@seed.setter
def seed(self, seed):
check_type('random number generator seed', seed, Integral)
check_greater_than('random number generator seed', seed, 0)
cv.check_type('random number generator seed', seed, Integral)
cv.check_greater_than('random number generator seed', seed, 0)
self._seed = seed
@survival_biasing.setter
def survival_biasing(self, survival_biasing):
check_type('survival biasing', survival_biasing, bool)
cv.check_type('survival biasing', survival_biasing, bool)
self._survival_biasing = survival_biasing
@weight.setter
def weight(self, weight):
check_type('weight cutoff', weight, Real)
check_greater_than('weight cutoff', weight, 0.0)
cv.check_type('weight cutoff', weight, Real)
cv.check_greater_than('weight cutoff', weight, 0.0)
self._weight = weight
@weight_avg.setter
def weight_avg(self, weight_avg):
check_type('average survival weight', weight_avg, Real)
check_greater_than('average survival weight', weight_avg, 0.0)
cv.check_type('average survival weight', weight_avg, Real)
cv.check_greater_than('average survival weight', weight_avg, 0.0)
self._weight_avg = weight_avg
@entropy_dimension.setter
def entropy_dimension(self, dimension):
check_type('entropy mesh dimension', dimension, Iterable, Integral)
check_length('entropy mesh dimension', dimension, 3)
cv.check_type('entropy mesh dimension', dimension, Iterable, Integral)
cv.check_length('entropy mesh dimension', dimension, 3)
self._entropy_dimension = dimension
@entropy_lower_left.setter
def entropy_lower_left(self, lower_left):
check_type('entropy mesh lower left corner', lower_left,
cv.check_type('entropy mesh lower left corner', lower_left,
Iterable, Real)
check_length('entropy mesh lower left corner', lower_left, 3)
cv.check_length('entropy mesh lower left corner', lower_left, 3)
self._entropy_lower_left = lower_left
@entropy_upper_right.setter
def entropy_upper_right(self, upper_right):
check_type('entropy mesh upper right corner', upper_right,
cv.check_type('entropy mesh upper right corner', upper_right,
Iterable, Real)
check_length('entropy mesh upper right corner', upper_right, 3)
cv.check_length('entropy mesh upper right corner', upper_right, 3)
self._entropy_upper_right = upper_right
@trigger_active.setter
def trigger_active(self, trigger_active):
check_type('trigger active', trigger_active, bool)
cv.check_type('trigger active', trigger_active, bool)
self._trigger_active = trigger_active
@trigger_max_batches.setter
def trigger_max_batches(self, trigger_max_batches):
check_type('trigger maximum batches', trigger_max_batches, Integral)
check_greater_than('trigger maximum batches', trigger_max_batches, 0)
cv.check_type('trigger maximum batches', trigger_max_batches, Integral)
cv.check_greater_than('trigger maximum batches', trigger_max_batches, 0)
self._trigger_max_batches = trigger_max_batches
@trigger_batch_interval.setter
def trigger_batch_interval(self, trigger_batch_interval):
check_type('trigger batch interval', trigger_batch_interval, Integral)
check_greater_than('trigger batch interval', trigger_batch_interval, 0)
cv.check_type('trigger batch interval', trigger_batch_interval, Integral)
cv.check_greater_than('trigger batch interval', trigger_batch_interval, 0)
self._trigger_batch_interval = trigger_batch_interval
@no_reduce.setter
def no_reduce(self, no_reduce):
check_type('no reduction option', no_reduce, bool)
cv.check_type('no reduction option', no_reduce, bool)
self._no_reduce = no_reduce
@temperature.setter
def temperature(self, temperature):
cv.check_type('temperature settings', temperature, Mapping)
for key, value in temperature.items():
cv.check_value('temperature key', key,
['default', 'method', 'tolerance'])
if key == 'default':
cv.check_type('default temperature', value, Real)
elif key == 'method':
cv.check_value('temperature method', value,
['nearest', 'interpolation', 'multipole'])
elif key == 'tolerance':
cv.check_type('temperature tolerance', value, Real)
self._temperature = temperature
@threads.setter
def threads(self, threads):
check_type('number of threads', threads, Integral)
check_greater_than('number of threads', threads, 0)
cv.check_type('number of threads', threads, Integral)
cv.check_greater_than('number of threads', threads, 0)
self._threads = threads
@trace.setter
def trace(self, trace):
check_type('trace', trace, Iterable, Integral)
check_length('trace', trace, 3)
check_greater_than('trace batch', trace[0], 0)
check_greater_than('trace generation', trace[1], 0)
check_greater_than('trace particle', trace[2], 0)
cv.check_type('trace', trace, Iterable, Integral)
cv.check_length('trace', trace, 3)
cv.check_greater_than('trace batch', trace[0], 0)
cv.check_greater_than('trace generation', trace[1], 0)
cv.check_greater_than('trace particle', trace[2], 0)
self._trace = trace
@track.setter
def track(self, track):
check_type('track', track, Iterable, Integral)
cv.check_type('track', track, Iterable, Integral)
if len(track) % 3 != 0:
msg = 'Unable to set the track to "{0}" since its length is ' \
'not a multiple of 3'.format(track)
raise ValueError(msg)
for t in zip(track[::3], track[1::3], track[2::3]):
check_greater_than('track batch', t[0], 0)
check_greater_than('track generation', t[0], 0)
check_greater_than('track particle', t[0], 0)
cv.check_greater_than('track batch', t[0], 0)
cv.check_greater_than('track generation', t[0], 0)
cv.check_greater_than('track particle', t[0], 0)
self._track = track
@ufs_dimension.setter
def ufs_dimension(self, dimension):
check_type('UFS mesh dimension', dimension, Iterable, Integral)
check_length('UFS mesh dimension', dimension, 3)
cv.check_type('UFS mesh dimension', dimension, Iterable, Integral)
cv.check_length('UFS mesh dimension', dimension, 3)
for dim in dimension:
check_greater_than('UFS mesh dimension', dim, 1, True)
cv.check_greater_than('UFS mesh dimension', dim, 1, True)
self._ufs_dimension = dimension
@ufs_lower_left.setter
def ufs_lower_left(self, lower_left):
check_type('UFS mesh lower left corner', lower_left, Iterable, Real)
check_length('UFS mesh lower left corner', lower_left, 3)
cv.check_type('UFS mesh lower left corner', lower_left, Iterable, Real)
cv.check_length('UFS mesh lower left corner', lower_left, 3)
self._ufs_lower_left = lower_left
@ufs_upper_right.setter
def ufs_upper_right(self, upper_right):
check_type('UFS mesh upper right corner', upper_right, Iterable, Real)
check_length('UFS mesh upper right corner', upper_right, 3)
cv.check_type('UFS mesh upper right corner', upper_right, Iterable, Real)
cv.check_length('UFS mesh upper right corner', upper_right, 3)
self._ufs_upper_right = upper_right
@dd_mesh_dimension.setter
@ -724,8 +736,8 @@ class Settings(object):
warnings.warn('This feature is not yet implemented in a release '
'version of openmc')
check_type('DD mesh dimension', dimension, Iterable, Integral)
check_length('DD mesh dimension', dimension, 3)
cv.check_type('DD mesh dimension', dimension, Iterable, Integral)
cv.check_length('DD mesh dimension', dimension, 3)
self._dd_mesh_dimension = dimension
@ -735,8 +747,8 @@ class Settings(object):
warnings.warn('This feature is not yet implemented in a release '
'version of openmc')
check_type('DD mesh lower left corner', lower_left, Iterable, Real)
check_length('DD mesh lower left corner', lower_left, 3)
cv.check_type('DD mesh lower left corner', lower_left, Iterable, Real)
cv.check_length('DD mesh lower left corner', lower_left, 3)
self._dd_mesh_lower_left = lower_left
@ -746,8 +758,8 @@ class Settings(object):
warnings.warn('This feature is not yet implemented in a release '
'version of openmc')
check_type('DD mesh upper right corner', upper_right, Iterable, Real)
check_length('DD mesh upper right corner', upper_right, 3)
cv.check_type('DD mesh upper right corner', upper_right, Iterable, Real)
cv.check_length('DD mesh upper right corner', upper_right, 3)
self._dd_mesh_upper_right = upper_right
@ -757,7 +769,7 @@ class Settings(object):
warnings.warn('This feature is not yet implemented in a release '
'version of openmc')
check_type('DD nodemap', nodemap, Iterable)
cv.check_type('DD nodemap', nodemap, Iterable)
nodemap = np.array(nodemap).flatten()
@ -782,7 +794,7 @@ class Settings(object):
warnings.warn('This feature is not yet implemented in a release '
'version of openmc')
check_type('DD allow leakage', allow, bool)
cv.check_type('DD allow leakage', allow, bool)
self._dd_allow_leakage = allow
@ -793,24 +805,23 @@ class Settings(object):
warnings.warn('This feature is not yet implemented in a release '
'version of openmc')
check_type('DD count interactions', interactions, bool)
cv.check_type('DD count interactions', interactions, bool)
self._dd_count_interactions = interactions
@use_windowed_multipole.setter
def use_windowed_multipole(self, active):
check_type('use_windowed_multipole', active, bool)
self._multipole_active = active
@resonance_scattering.setter
def resonance_scattering(self, res):
if isinstance(res, Iterable):
check_type('resonance_scattering', res, Iterable,
ResonanceScattering)
self._resonance_scattering = res
else:
check_type('resonance_scattering', res, ResonanceScattering)
self._resonance_scattering = [res]
if not isinstance(res, MutableSequence):
res = [res]
self._resonance_scattering = cv.CheckedList(
ResonanceScattering, 'resonance scattering models', res)
@volume_calculations.setter
def volume_calculations(self, vol_calcs):
if not isinstance(vol_calcs, MutableSequence):
vol_calcs = [vol_calcs]
self._volume_calculations = cv.CheckedList(
VolumeCalculation, 'stochastic volume calculations', vol_calcs)
def _create_run_mode_subelement(self):
@ -869,9 +880,12 @@ class Settings(object):
element.text = str(self._max_order)
def _create_source_subelement(self):
if self.source is not None:
for source in self.source:
self._settings_file.append(source.to_xml())
for source in self.source:
self._settings_file.append(source.to_xml_element())
def _create_volume_calcs_subelement(self):
for calc in self.volume_calculations:
self._settings_file.append(calc.to_xml_element())
def _create_output_subelement(self):
if self._output is not None:
@ -952,11 +966,6 @@ class Settings(object):
element = ET.SubElement(self._settings_file, "multipole_library")
element.text = str(self._multipole_library)
def _create_energy_grid_subelement(self):
if self._energy_grid is not None:
element = ET.SubElement(self._settings_file, "energy_grid")
element.text = str(self._energy_grid)
def _create_ptables_subelement(self):
if self._ptables is not None:
element = ET.SubElement(self._settings_file, "ptables")
@ -1039,6 +1048,13 @@ class Settings(object):
element = ET.SubElement(self._settings_file, "no_reduce")
element.text = str(self._no_reduce).lower()
def _create_temperature_subelements(self):
if self.temperature:
for key, value in self.temperature.items():
element = ET.SubElement(self._settings_file,
"temperature_{}".format(key))
element.text = str(value)
def _create_threads_subelement(self):
if self._threads is not None:
element = ET.SubElement(self._settings_file, "threads")
@ -1096,24 +1112,11 @@ class Settings(object):
subelement = ET.SubElement(element, "count_interactions")
subelement.text = str(self._dd_count_interactions).lower()
def _create_use_multipole_subelement(self):
if self._multipole_active is not None:
element = ET.SubElement(self._settings_file,
"use_windowed_multipole")
element.text = str(self._multipole_active)
def _create_resonance_scattering_element(self):
if self.resonance_scattering is None:
return
element = ET.SubElement(self._settings_file, "resonance_scattering")
for r in self.resonance_scattering:
if r.nuclide.name != r.nuclide_0K.name:
raise ValueError("The nuclide and nuclide_0K attributes of "
"a ResonantScattering object must have "
"identical names.")
r.create_xml_subelement(element)
def _create_resonance_scattering_subelement(self):
if len(self.resonance_scattering) > 0:
elem = ET.SubElement(self._settings_file, 'resonance_scattering')
for r in self.resonance_scattering:
elem.append(r.to_xml_element())
def export_to_xml(self):
"""Create a settings.xml file that can be used for a simulation.
@ -1125,7 +1128,6 @@ class Settings(object):
self._source_subelement = None
self._trigger_subelement = None
self._run_mode_subelement = None
self._source_element = None
self._create_run_mode_subelement()
self._create_source_subelement()
@ -1135,7 +1137,6 @@ class Settings(object):
self._create_confidence_intervals()
self._create_cross_sections_subelement()
self._create_multipole_library_subelement()
self._create_energy_grid_subelement()
self._create_energy_mode_subelement()
self._create_max_order_subelement()
self._create_ptables_subelement()
@ -1148,12 +1149,13 @@ class Settings(object):
self._create_no_reduce_subelement()
self._create_threads_subelement()
self._create_verbosity_subelement()
self._create_temperature_subelements()
self._create_trace_subelement()
self._create_track_subelement()
self._create_ufs_subelement()
self._create_dd_subelement()
self._create_use_multipole_subelement()
self._create_resonance_scattering_element()
self._create_resonance_scattering_subelement()
self._create_volume_calcs_subelement()
# Clean the indentation in the file to be user-readable
clean_xml_indentation(self._settings_file)
@ -1167,14 +1169,26 @@ class Settings(object):
class ResonanceScattering(object):
"""Specification of the elastic scattering model for resonant isotopes
Parameters
----------
nuclide : openmc.Nuclide
The nuclide affected by this resonance scattering treatment.
method : {'ARES', 'CXS', 'DBRC', 'WCM'}
The method used to sample outgoing scattering energies. Valid options
are 'ARES', 'CXS' (constant cross section), 'DBRC' (Doppler broadening
rejection correction), and 'WCM' (weight correction method).
E_min : float
The minimum energy above which the specified method is applied. By
default, CXS will be used below E_min.
E_max : float
The maximum energy below which the specified method is applied. By
default, the asymptotic target-at-rest model is applied above E_max.
Attributes
----------
nuclide : openmc.Nuclide
The nuclide affected by this resonance scattering treatment.
nuclide_0K : openmc.Nuclide
This should be the same isotope as the nuclide attribute above, but it
should have an xs attribute that identifies 0 Kelvin data.
method : str
method : {'ARES', 'CXS', 'DBRC', 'WCM'}
The method used to sample outgoing scattering energies. Valid options
are 'ARES', 'CXS' (constant cross section), 'DBRC' (Doppler broadening
rejection correction), and 'WCM' (weight correction method).
@ -1187,21 +1201,20 @@ class ResonanceScattering(object):
"""
def __init__(self):
self._nuclide = None
self._nuclide_0K = None
self._method = None
def __init__(self, nuclide, method='CXS', E_min=None, E_max=None):
self._E_min = None
self._E_max = None
self.nuclide = nuclide
self.method = method
if E_min is not None:
self.E_min = E_min
if E_max is not None:
self.E_max = E_max
@property
def nuclide(self):
return self._nuclide
@property
def nuclide_0K(self):
return self._nuclide_0K
@property
def method(self):
return self._method
@ -1216,45 +1229,45 @@ class ResonanceScattering(object):
@nuclide.setter
def nuclide(self, nuc):
check_type('nuclide', nuc, Nuclide)
cv.check_type('nuclide', nuc, Nuclide)
self._nuclide = nuc
@nuclide_0K.setter
def nuclide_0K(self, nuc):
check_type('nuclide_0K', nuc, Nuclide)
self._nuclide_0K = nuc
@method.setter
def method(self, m):
check_value('method', m, ('ARES', 'CXS', 'DBRC', 'WCM'))
cv.check_value('method', m, ('ARES', 'CXS', 'DBRC', 'WCM'))
self._method = m
@E_min.setter
def E_min(self, E):
check_type('E_min', E, Real)
check_greater_than('E_min', E, 0, True)
cv.check_type('E_min', E, Real)
cv.check_greater_than('E_min', E, 0, True)
self._E_min = E
@E_max.setter
def E_max(self, E):
check_type('E_max', E, Real)
check_greater_than('E_max', E, 0, True)
cv.check_type('E_max', E, Real)
cv.check_greater_than('E_max', E, 0, True)
self._E_max = E
def create_xml_subelement(self, xml_element):
scatterer = ET.SubElement(xml_element, "scatterer")
def to_xml_element(self):
"""Return XML representation of the resonance scattering model
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing resonance scattering model
"""
scatterer = ET.Element("scatterer")
subelement = ET.SubElement(scatterer, 'nuclide')
subelement.text = self.nuclide.name
if self.method is not None:
subelement = ET.SubElement(scatterer, 'method')
subelement.text = self.method
subelement = ET.SubElement(scatterer, 'xs_label')
subelement.text = '{0.name}.{0.xs}'.format(self.nuclide)
subelement = ET.SubElement(scatterer, 'xs_label_0K')
subelement.text = '{0.name}.{0.xs}'.format(self.nuclide_0K)
if self.E_min is not None:
subelement = ET.SubElement(scatterer, 'E_min')
subelement.text = str(self.E_min)
if self.E_max is not None:
subelement = ET.SubElement(scatterer, 'E_max')
subelement.text = str(self.E_max)
return scatterer

View file

@ -103,7 +103,7 @@ class Source(object):
cv.check_greater_than('source strength', strength, 0.0, True)
self._strength = strength
def to_xml(self):
def to_xml_element(self):
"""Return XML representation of the source
Returns
@ -117,9 +117,9 @@ class Source(object):
if self.file is not None:
element.set("file", self.file)
if self.space is not None:
element.append(self.space.to_xml())
element.append(self.space.to_xml_element())
if self.angle is not None:
element.append(self.angle.to_xml())
element.append(self.angle.to_xml_element())
if self.energy is not None:
element.append(self.energy.to_xml('energy'))
element.append(self.energy.to_xml_element('energy'))
return element

View file

@ -2,6 +2,7 @@ import sys
import re
import os
import warnings
import glob
import numpy as np
@ -22,8 +23,9 @@ class StatePoint(object):
filename : str
Path to file to load
autolink : bool, optional
Whether to automatically link in metadata from a summary.h5
file. Defaults to True.
Whether to automatically link in metadata from a summary.h5 file and
stochastic volume calculation results from volume_*.h5 files. Defaults
to True.
Attributes
----------
@ -143,6 +145,12 @@ class StatePoint(object):
su = openmc.Summary(path_summary)
self.link_with_summary(su)
path_volume = os.path.join(os.path.dirname(filename), 'volume_*.h5')
for path_i in glob.glob(path_volume):
if re.search(r'volume_\d+\.h5', path_i):
vol = openmc.VolumeCalculation.from_hdf5(path_i)
self.add_volume_information(vol)
def close(self):
self._f.close()
@ -481,6 +489,18 @@ class StatePoint(object):
for tally_id in self.tallies:
self.tallies[tally_id].sparse = self.sparse
def add_volume_information(self, volume_calc):
"""Add volume information to the geometry within the file
Parameters
----------
volume_calc : openmc.VolumeCalculation
Results from a stochastic volume calculation
"""
if self.summary is not None:
self.summary.add_volume_information(volume_calc)
def get_tally(self, scores=[], filters=[], nuclides=[],
name=None, id=None, estimator=None, exact_filters=False,
exact_nuclides=False, exact_scores=False):
@ -662,7 +682,7 @@ class StatePoint(object):
if isinstance(tally_filter, openmc.SurfaceFilter):
surface_ids = []
for bin in tally_filter.bins:
surface_ids.append(summary.surfaces[bin].id)
surface_ids.append(bin)
tally_filter.bins = surface_ids
if isinstance(tally_filter, (openmc.CellFilter,

View file

@ -50,7 +50,7 @@ class UnitSphere(object):
self._reference_uvw = uvw/np.linalg.norm(uvw)
@abstractmethod
def to_xml(self):
def to_xml_element(self):
return ''
@ -109,13 +109,21 @@ class PolarAzimuthal(UnitSphere):
cv.check_type('azimuthal angle', phi, Univariate)
self._phi = phi
def to_xml(self):
def to_xml_element(self):
"""Return XML representation of the angular distribution
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing angular distribution data
"""
element = ET.Element('angle')
element.set("type", "mu-phi")
if self.reference_uvw is not None:
element.set("reference_uvw", ' '.join(map(str, self.reference_uvw)))
element.append(self.mu.to_xml('mu'))
element.append(self.phi.to_xml('phi'))
element.append(self.mu.to_xml_element('mu'))
element.append(self.phi.to_xml_element('phi'))
return element
@ -127,7 +135,15 @@ class Isotropic(UnitSphere):
def __init__(self):
super(Isotropic, self).__init__()
def to_xml(self):
def to_xml_element(self):
"""Return XML representation of the isotropic distribution
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing isotropic distribution data
"""
element = ET.Element('angle')
element.set("type", "isotropic")
return element
@ -152,7 +168,15 @@ class Monodirectional(UnitSphere):
def __init__(self, reference_uvw=[1., 0., 0.]):
super(Monodirectional, self).__init__(reference_uvw)
def to_xml(self):
def to_xml_element(self):
"""Return XML representation of the monodirectional distribution
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing monodirectional distribution data
"""
element = ET.Element('angle')
element.set("type", "monodirectional")
if self.reference_uvw is not None:
@ -174,7 +198,7 @@ class Spatial(object):
pass
@abstractmethod
def to_xml(self):
def to_xml_element(self):
return ''
@ -238,12 +262,20 @@ class CartesianIndependent(Spatial):
cv.check_type('z coordinate', z, Univariate)
self._z = z
def to_xml(self):
def to_xml_element(self):
"""Return XML representation of the spatial distribution
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing spatial distribution data
"""
element = ET.Element('space')
element.set('type', 'cartesian')
element.append(self.x.to_xml('x'))
element.append(self.y.to_xml('y'))
element.append(self.z.to_xml('z'))
element.append(self.x.to_xml_element('x'))
element.append(self.y.to_xml_element('y'))
element.append(self.z.to_xml_element('z'))
return element
@ -308,7 +340,15 @@ class Box(Spatial):
cv.check_type('only fissionable', only_fissionable, bool)
self._only_fissionable = only_fissionable
def to_xml(self):
def to_xml_element(self):
"""Return XML representation of the box distribution
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing box distribution data
"""
element = ET.Element('space')
if self.only_fissionable:
element.set("type", "fission")
@ -352,7 +392,15 @@ class Point(Spatial):
cv.check_length('coordinate', xyz, 3)
self._xyz = xyz
def to_xml(self):
def to_xml_element(self):
"""Return XML representation of the point distribution
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing point distribution location
"""
element = ET.Element('space')
element.set("type", "point")
params = ET.SubElement(element, "parameters")

View file

@ -7,6 +7,7 @@ from xml.etree import ElementTree as ET
import numpy as np
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
if sys.version_info[0] >= 3:
basestring = str
@ -15,7 +16,7 @@ _INTERPOLATION_SCHEMES = ['histogram', 'linear-linear', 'linear-log',
'log-linear', 'log-log']
class Univariate(object):
class Univariate(EqualityMixin):
"""Probability distribution of a single random variable.
The Univariate class is an abstract class that can be derived to implement a
@ -29,7 +30,7 @@ class Univariate(object):
pass
@abstractmethod
def to_xml(self, element_name):
def to_xml_element(self, element_name):
return ''
@abstractmethod
@ -92,7 +93,20 @@ class Discrete(Univariate):
cv.check_greater_than('discrete probability', pk, 0.0, True)
self._p = p
def to_xml(self, element_name):
def to_xml_element(self, element_name):
"""Return XML representation of the discrete distribution
Parameters
----------
element_name : str
XML element name
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing discrete distribution data
"""
element = ET.Element(element_name)
element.set("type", "discrete")
@ -153,7 +167,20 @@ class Uniform(Univariate):
t.c = [0., 1.]
return t
def to_xml(self, element_name):
def to_xml_element(self, element_name):
"""Return XML representation of the uniform distribution
Parameters
----------
element_name : str
XML element name
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing uniform distribution data
"""
element = ET.Element(element_name)
element.set("type", "uniform")
element.set("parameters", '{} {}'.format(self.a, self.b))
@ -196,7 +223,20 @@ class Maxwell(Univariate):
cv.check_greater_than('Maxwell temperature', theta, 0.0)
self._theta = theta
def to_xml(self, element_name):
def to_xml_element(self, element_name):
"""Return XML representation of the Maxwellian distribution
Parameters
----------
element_name : str
XML element name
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing Maxwellian distribution data
"""
element = ET.Element(element_name)
element.set("type", "maxwell")
element.set("parameters", str(self.theta))
@ -254,7 +294,20 @@ class Watt(Univariate):
cv.check_greater_than('Watt b', b, 0.0)
self._b = b
def to_xml(self, element_name):
def to_xml_element(self, element_name):
"""Return XML representation of the Watt distribution
Parameters
----------
element_name : str
XML element name
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing Watt distribution data
"""
element = ET.Element(element_name)
element.set("type", "watt")
element.set("parameters", '{} {}'.format(self.a, self.b))
@ -333,7 +386,20 @@ class Tabular(Univariate):
cv.check_value('interpolation', interpolation, _INTERPOLATION_SCHEMES)
self._interpolation = interpolation
def to_xml(self, element_name):
def to_xml_element(self, element_name):
"""Return XML representation of the tabular distribution
Parameters
----------
element_name : str
XML element name
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing tabular distribution data
"""
element = ET.Element(element_name)
element.set("type", "tabular")
element.set("interpolation", self.interpolation)
@ -386,7 +452,7 @@ class Legendre(Univariate):
self._legendre_polynomial = np.polynomial.legendre.Legendre(
coefficients)
def to_xml(self, element_name):
def to_xml_element(self, element_name):
raise NotImplementedError
@ -440,5 +506,5 @@ class Mixture(Univariate):
Iterable, Univariate)
self._distribution = distribution
def to_xml(self, element_name):
def to_xml_element(self, element_name):
raise NotImplementedError

View file

@ -83,11 +83,10 @@ class Summary(object):
n_nuclides = self._f['nuclides/n_nuclides_total'].value
names = self._f['nuclides/names'].value
awrs = self._f['nuclides/awrs'].value
zaids = self._f['nuclides/zaids'].value
for n in range(n_nuclides):
name = names[n].decode()
name = name[:name.find('.')]
self.nuclides[name] = (zaids[n], awrs[n])
self.nuclides[name] = awrs[n]
def _read_geometry(self):
# Read in and initialize the Materials and Geometry
@ -124,22 +123,21 @@ class Summary(object):
if 'sab_names' in self._f['materials'][key]:
sab_tables = self._f['materials'][key]['sab_names'].value
for sab_table in sab_tables:
name, xs = sab_table.decode().split('.')
material.add_s_alpha_beta(name, xs)
name = sab_table.decode()
material.add_s_alpha_beta(name)
# Set the Material's density to atom/b-cm as used by OpenMC
material.set_density(density=density, units='atom/b-cm')
# Add all nuclides to the Material
for fullname, density in zip(nuclides, nuc_densities):
fullname = fullname.decode().strip()
name, xs = fullname.split('.')
name = fullname.decode().strip()
if 'nat' in name:
material.add_element(openmc.Element(name=name, xs=xs),
material.add_element(openmc.Element(name=name),
percent=density, percent_type='ao')
else:
material.add_nuclide(openmc.Nuclide(name=name, xs=xs),
material.add_nuclide(openmc.Nuclide(name=name),
percent=density, percent_type='ao')
# Add the Material to the global dictionary of all Materials
@ -576,6 +574,17 @@ class Summary(object):
# Add Tally to the global dictionary of all Tallies
self.tallies[tally_id] = tally
def add_volume_information(self, volume_calc):
"""Add volume information to the geometry within the summary file
Parameters
----------
volume_calc : openmc.VolumeCalculation
Results from a stochastic volume calculation
"""
self.openmc_geometry.add_volume_information(volume_calc)
def get_material_by_id(self, material_id):
"""Return a Material object given the material id

View file

@ -40,6 +40,9 @@ _SCORE_CLASSES = (basestring, CrossScore, AggregateScore)
_NUCLIDE_CLASSES = (basestring, Nuclide, CrossNuclide, AggregateNuclide)
_FILTER_CLASSES = (Filter, CrossFilter, AggregateFilter)
# Valid types of estimators
ESTIMATOR_TYPES = ['tracklength', 'collision', 'analog']
def reset_auto_tally_id():
"""Reset counter for auto-generated tally IDs."""
@ -387,8 +390,7 @@ class Tally(object):
@estimator.setter
def estimator(self, estimator):
cv.check_value('estimator', estimator,
['analog', 'tracklength', 'collision'])
cv.check_value('estimator', estimator, ESTIMATOR_TYPES)
self._estimator = estimator
@triggers.setter
@ -795,6 +797,9 @@ class Tally(object):
else:
no_scores_match = False
if score == 'current' and score not in self.scores:
return False
# Nuclides cannot be specified on 'flux' scores
if 'flux' in self.scores or 'flux' in other.scores:
if self.nuclides != other.nuclides:
@ -2189,8 +2194,8 @@ class Tally(object):
"""
cv.check_type('filter1', filter1, (Filter, CrossFilter, AggregateFilter))
cv.check_type('filter2', filter2, (Filter, CrossFilter, AggregateFilter))
cv.check_type('filter1', filter1, _FILTER_CLASSES)
cv.check_type('filter2', filter2, _FILTER_CLASSES)
# Check that the filters exist in the tally and are not the same
if filter1 == filter2:
@ -2272,8 +2277,8 @@ class Tally(object):
'since it does not contain any results.'.format(self.id)
raise ValueError(msg)
cv.check_type('nuclide1', nuclide1, Nuclide)
cv.check_type('nuclide2', nuclide2, Nuclide)
cv.check_type('nuclide1', nuclide1, _NUCLIDE_CLASSES)
cv.check_type('nuclide2', nuclide2, _NUCLIDE_CLASSES)
# Check that the nuclides exist in the tally and are not the same
if nuclide1 == nuclide2:
@ -3310,7 +3315,7 @@ class Tally(object):
"""
cv.check_type('new_filter', new_filter, Filter)
cv.check_type('new_filter', new_filter, _FILTER_CLASSES)
if new_filter in self.filters:
msg = 'Unable to diagonalize Tally ID="{0}" which already ' \

View file

@ -349,7 +349,27 @@ class Universe(object):
# Return the offset computed at all nested Universe levels
return offset
def get_all_nuclides(self):
def get_nuclides(self):
"""Returns all nuclides in the universe
Returns
-------
nuclides : list of str
List of nuclide names
"""
nuclides = []
# Append all Nuclides in each Cell in the Universe to the dictionary
for cell in self.cells.values():
for nuclide in cell.get_nuclides():
if nuclide not in nuclides:
nuclides.append(nuclide)
return nuclides
def get_nuclide_densities(self):
"""Return all nuclides contained in the universe
Returns
@ -360,13 +380,8 @@ class Universe(object):
"""
nuclides = OrderedDict()
# Append all Nuclides in each Cell in the Universe to the dictionary
for cell in self._cells.values():
nuclides.update(cell.get_all_nuclides())
return nuclides
raise NotImplementedError('Determining average nuclide densities over '
'an entire universe not yet supported.')
def get_all_cells(self):
"""Return all cells that are contained within the universe

248
openmc/volume.py Normal file
View file

@ -0,0 +1,248 @@
from collections import Iterable, Mapping
from numbers import Real, Integral
from xml.etree import ElementTree as ET
from warnings import warn
import numpy as np
import pandas as pd
import openmc
import openmc.checkvalue as cv
class VolumeCalculation(object):
"""Stochastic volume calculation specifications and results.
Parameters
----------
domains : Iterable of openmc.Cell, openmc.Material, or openmc.Universe
Domains to find volumes of
samples : int
Number of samples used to generate volume estimates
lower_left : Iterable of float
Lower-left coordinates of bounding box used to sample points. If this
argument is not supplied, an attempt is made to automatically determine
a bounding box.
upper_right : Iterable of float
Upper-right coordinates of bounding box used to sample points. If this
argument is not supplied, an attempt is made to automatically determine
a bounding box.
Attributes
----------
ids : Iterable of int
IDs of domains to find volumes of
domain_type : {'cell', 'material', 'universe'}
Type of each domain
samples : int
Number of samples used to generate volume estimates
lower_left : Iterable of float
Lower-left coordinates of bounding box used to sample points
upper_right : Iterable of float
Upper-right coordinates of bounding box used to sample points
results : dict
Dictionary whose keys are unique IDs of domains and values are
dictionaries with calculated volumes and total number of atoms for each
nuclide present in the domain.
volumes : dict
Dictionary whose keys are unique IDs of domains and values are the
estimated volumes
atoms_dataframe : pandas.DataFrame
DataFrame showing the estimated number of atoms for each nuclide present
in each domain specified.
"""
def __init__(self, domains, samples, lower_left=None,
upper_right=None):
self._results = None
cv.check_type('domains', domains, Iterable,
(openmc.Cell, openmc.Material, openmc.Universe))
if isinstance(domains[0], openmc.Cell):
self._domain_type = 'cell'
elif isinstance(domains[0], openmc.Material):
self._domain_type = 'material'
elif isinstance(domains[0], openmc.Universe):
self._domain_type = 'universe'
self.ids = [d.id for d in domains]
self.samples = samples
if lower_left is not None:
if upper_right is None:
raise ValueError('Both lower-left and upper-right coordinates '
'should be specified')
# For cell domains, try to compute bounding box and make sure
# user-specified one is valid
if self.domain_type == 'cell':
for c in domains:
if c.region is None:
continue
ll, ur = c.region.bounding_box
if np.any(np.isinf(ll)) or np.any(np.isinf(ur)):
continue
if (np.any(np.asarray(lower_left) > ll) or
np.any(np.asarray(upper_right) < ur)):
warn("Specified bounding box is smaller than computed "
"bounding box for cell {}. Volume calculation may "
"be incorrect!".format(c.id))
self.lower_left = lower_left
self.upper_right = upper_right
else:
if self.domain_type == 'cell':
ll, ur = openmc.Union(*[c.region for c in domains]).bounding_box
if np.any(np.isinf(ll)) or np.any(np.isinf(ur)):
raise ValueError('Could not automatically determine bounding '
'box for stochastic volume calculation.')
else:
self.lower_left = ll
self.upper_right = ur
else:
raise ValueError('Could not automatically determine bounding box '
'for stochastic volume calculation.')
@property
def ids(self):
return self._ids
@property
def samples(self):
return self._samples
@property
def lower_left(self):
return self._lower_left
@property
def upper_right(self):
return self._upper_right
@property
def results(self):
return self._results
@property
def domain_type(self):
return self._domain_type
@property
def volumes(self):
return {uid: results['volume'] for uid, results in self.results.items()}
@property
def atoms_dataframe(self):
items = []
columns = [self.domain_type.capitalize(), 'Nuclide', 'Atoms',
'Uncertainty']
for uid, results in self.results.items():
for name, atoms in results['atoms']:
items.append((uid, name, atoms[0], atoms[1]))
return pd.DataFrame.from_records(items, columns=columns)
@ids.setter
def ids(self, ids):
cv.check_type('domain IDs', ids, Iterable, Real)
self._ids = ids
@samples.setter
def samples(self, samples):
cv.check_type('number of samples', samples, Integral)
cv.check_greater_than('number of samples', samples, 0)
self._samples = samples
@lower_left.setter
def lower_left(self, lower_left):
name = 'lower-left bounding box coordinates',
cv.check_type(name, lower_left, Iterable, Real)
cv.check_length(name, lower_left, 3)
self._lower_left = lower_left
@upper_right.setter
def upper_right(self, upper_right):
name = 'upper-right bounding box coordinates'
cv.check_type(name, upper_right, Iterable, Real)
cv.check_length(name, upper_right, 3)
self._upper_right = upper_right
@results.setter
def results(self, results):
cv.check_type('results', results, Mapping)
self._results = results
@classmethod
def from_hdf5(cls, filename):
"""Load stochastic volume calculation results from HDF5 file.
Parameters
----------
filename : str
Path to volume.h5 file
Returns
-------
openmc.VolumeCalculation
Results of the stochastic volume calculation
"""
import h5py
with h5py.File(filename, 'r') as f:
domain_type = f.attrs['domain_type'].decode()
samples = f.attrs['samples']
lower_left = f.attrs['lower_left']
upper_right = f.attrs['upper_right']
results = {}
ids = []
for obj_name in f:
if obj_name.startswith('domain_'):
domain_id = int(obj_name[7:])
ids.append(domain_id)
group = f[obj_name]
volume = tuple(group['volume'].value)
nucnames = group['nuclides'].value
atoms = group['atoms'].value
atom_list = []
for name_i, atoms_i in zip(nucnames, atoms):
atom_list.append((name_i.decode(), tuple(atoms_i)))
results[domain_id] = {'volume': volume, 'atoms': atom_list}
# Instantiate some throw-away domains that are used by the constructor
# to assign IDs
if domain_type == 'cell':
domains = [openmc.Cell(uid) for uid in ids]
elif domain_type == 'material':
domains = [openmc.Material(uid) for uid in ids]
elif domain_type == 'universe':
domains = [openmc.Universe(uid) for uid in ids]
# Instantiate the class and assign results
vol = cls(domains, samples, lower_left, upper_right)
vol.results = results
return vol
def to_xml_element(self):
"""Return XML representation of the volume calculation
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing volume calculation data
"""
element = ET.Element("volume_calc")
dt_elem = ET.SubElement(element, "domain_type")
dt_elem.text = self.domain_type
id_elem = ET.SubElement(element, "domain_ids")
id_elem.text = ' '.join(str(uid) for uid in self.ids)
samples_elem = ET.SubElement(element, "samples")
samples_elem.text = str(self.samples)
ll_elem = ET.SubElement(element, "lower_left")
ll_elem.text = ' '.join(str(x) for x in self.lower_left)
ur_elem = ET.SubElement(element, "upper_right")
ur_elem.text = ' '.join(str(x) for x in self.upper_right)
return element

View file

@ -7,7 +7,7 @@ OpenMC Monte Carlo Particle Transport Code
The OpenMC project aims to provide a fully-featured Monte Carlo particle
transport code based on modern methods. It is a constructive solid geometry,
continuous-energy transport code that uses ACE format cross sections. The
continuous-energy transport code that uses HDF5 format cross sections. The
project started under the Computational Reactor Physics Group at MIT.
Complete documentation on the usage of OpenMC is hosted on Read the Docs at

View file

@ -25,6 +25,13 @@ follows the NNDC data convention (1000*Z + A + 300 + 100*m), or the MCNP data
convention (essentially the same as NNDC, except that the first metastable state
of Am242 is 95242 and the ground state is 95642).
The optional --fission_energy_release argument will accept an HDF5 file
containing a library of fission energy release (ENDF MF=1 MT=458) data. A
library built from ENDF/B-VII.1 data is released with OpenMC and can be found at
openmc/data/fission_Q_data_endb71.h5. This data is necessary for
'fission-q-prompt' and 'fission-q-recoverable' tallies, but is not needed
otherwise.
"""
class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter,
@ -47,6 +54,8 @@ parser.add_argument('--xsdir', help='MCNP xsdir file that lists '
'ACE libraries')
parser.add_argument('--xsdata', help='Serpent xsdata file that lists '
'ACE libraries')
parser.add_argument('--fission_energy_release', help='HDF5 file containing '
'fission energy release data')
args = parser.parse_args()
if not os.path.isdir(args.destination):
@ -106,6 +115,7 @@ elif args.xsdata is not None:
else:
ace_libraries = args.libraries
nuclides = {}
library = openmc.data.DataLibrary()
for filename in ace_libraries:
@ -116,36 +126,87 @@ for filename in ace_libraries:
lib = openmc.data.ace.Library(filename)
for table in lib.tables:
if table.name.endswith('c'):
name, xs = table.name.split('.')
if xs.endswith('c'):
# Continuous-energy neutron data
try:
neutron = openmc.data.IncidentNeutron.from_ace(
table, args.metastable)
except Exception as e:
print('Failed to convert {}: {}'.format(table.name, e))
continue
print('Converting {} (ACE) to {} (HDF5)'.format(table.name, neutron.name))
if name not in nuclides:
try:
neutron = openmc.data.IncidentNeutron.from_ace(
table, args.metastable)
except Exception as e:
print('Failed to convert {}: {}'.format(table.name, e))
continue
# Determine filename
outfile = os.path.join(args.destination,
neutron.name.replace('.', '_') + '.h5')
neutron.export_to_hdf5(outfile, 'w')
# Fission energy release data, if available
if args.fission_energy_release is not None:
fer = openmc.data.FissionEnergyRelease.from_compact_hdf5(
args.fission_energy_release, neutron)
if fer is not None:
neutron.fission_energy = fer
# Register with library
library.register_file(outfile)
print('Converting {} (ACE) to {} (HDF5)'.format(table.name,
neutron.name))
elif table.name.endswith('t'):
# Determine filename
outfile = os.path.join(args.destination,
neutron.name.replace('.', '_') + '.h5')
neutron.export_to_hdf5(outfile, 'w')
# Register with library
library.register_file(outfile)
# Add nuclide to list
nuclides[name] = outfile
else:
# Then we only need to append the data
try:
neutron = \
openmc.data.IncidentNeutron.from_hdf5(nuclides[name])
print('Converting {} (ACE) to {} (HDF5)'.format(table.name,
neutron.name))
neutron.add_temperature_from_ace(table, args.metastable)
neutron.export_to_hdf5(nuclides[name] + '_1', 'w')
os.rename(nuclides[name] + '_1', nuclides[name])
except Exception as e:
print('Failed to convert {}: {}'.format(table.name, e))
continue
elif xs.endswith('t'):
# Adjust name to be the new thermal scattering name
name = openmc.data.get_thermal_name(name)
# Thermal scattering data
thermal = openmc.data.ThermalScattering.from_ace(table)
print('Converting {} (ACE) to {} (HDF5)'.format(table.name, thermal.name))
if name not in nuclides:
try:
thermal = openmc.data.ThermalScattering.from_ace(table)
except Exception as e:
print('Failed to convert {}: {}'.format(table.name, e))
continue
print('Converting {} (ACE) to {} (HDF5)'.format(table.name,
thermal.name))
# Determine filename
outfile = os.path.join(args.destination,
thermal.name.replace('.', '_') + '.h5')
thermal.export_to_hdf5(outfile, 'w')
# Determine filename
outfile = os.path.join(args.destination,
thermal.name.replace('.', '_') + '.h5')
thermal.export_to_hdf5(outfile, 'w')
# Register with library
library.register_file(outfile, 'thermal')
# Register with library
library.register_file(outfile, 'thermal')
# Add data to list
nuclides[name] = outfile
else:
# Then we only need to append the data
try:
thermal = openmc.data.ThermalScattering.from_hdf5(nuclides[name])
print('Converting {} (ACE) to {} (HDF5)'.format(table.name,
thermal.name))
thermal.add_temperature_from_ace(table)
thermal.export_to_hdf5(nuclides[name] + '_1', 'w')
os.rename(nuclides[name] + '_1', nuclides[name])
except Exception as e:
print('Failed to convert {}: {}'.format(table.name, e))
continue
# Write cross_sections.xml
libpath = os.path.join(args.destination, 'cross_sections.xml')

View file

@ -253,23 +253,6 @@ def update_geometry(geometry_root):
return was_updated
def get_thermal_name(name):
"""Get proper S(a,b) table name, e.g. 'HH2O' -> 'c_H_in_H2O'"""
if name.lower() in _THERMAL_NAMES:
return _THERMAL_NAMES[name.lower()]
else:
# Make an educated guess?? This actually works well for
# JEFF-3.2 which stupidly uses names like lw00.32t,
# lw01.32t, etc. for different temperatures
matches = get_close_matches(
name.lower(), _THERMAL_NAMES.keys(), cutoff=0.5)
if len(matches) > 0:
return _THERMAL_NAMES[matches[0]] + '.' + xs
else:
# OK, we give up. Just use the ACE name.
return 'c_' + name
return name
def update_materials(root):
"""Update the given XML materials tree. Return True if changes were made."""
was_updated = False
@ -298,13 +281,13 @@ def update_materials(root):
for sab in material.findall('sab'):
if 'name' in sab.attrib:
sabname = sab.attrib['name']
sab.set('name', get_thermal_name(sabname))
sab.set('name', openmc.data.get_thermal_name(sabname))
was_updated = True
elif sab.find('name') is not None:
name_elem = sab.find('name')
sabname = name_elem.text
name_elem.text = get_thermal(sabname)
name_elem.text = openmc.data.get_thermal_name(sabname)
was_updated = True
return was_updated

275
src/algorithm.F90 Normal file
View file

@ -0,0 +1,275 @@
module algorithm
use constants
use stl_vector, only: VectorInt, VectorReal
implicit none
integer, parameter :: MAX_ITERATION = 64
interface binary_search
module procedure binary_search_real, binary_search_int4, binary_search_int8
end interface binary_search
interface sort
module procedure sort_int, sort_real, sort_vector_int, sort_vector_real
end interface sort
interface find
module procedure find_int, find_real, find_vector_int, find_vector_real
end interface find
contains
!===============================================================================
! BINARY_SEARCH performs a binary search of an array to find where a specific
! value lies in the array. This is used extensively for energy grid searching
!===============================================================================
pure function binary_search_real(array, n, val) result(array_index)
integer, intent(in) :: n
real(8), intent(in) :: array(n)
real(8), intent(in) :: val
integer :: array_index
integer :: L
integer :: R
integer :: n_iteration
L = 1
R = n
if (val < array(L) .or. val > array(R)) then
array_index = -1
return
end if
n_iteration = 0
do while (R - L > 1)
! Find values at midpoint
array_index = L + (R - L)/2
if (val >= array(array_index)) then
L = array_index
else
R = array_index
end if
! check for large number of iterations
n_iteration = n_iteration + 1
if (n_iteration == MAX_ITERATION) then
array_index = -2
return
end if
end do
array_index = L
end function binary_search_real
pure function binary_search_int4(array, n, val) result(array_index)
integer, intent(in) :: n
integer, intent(in) :: array(n)
integer, intent(in) :: val
integer :: array_index
integer :: L
integer :: R
integer :: n_iteration
L = 1
R = n
if (val < array(L) .or. val > array(R)) then
array_index = -1
return
end if
n_iteration = 0
do while (R - L > 1)
! Find values at midpoint
array_index = L + (R - L)/2
if (val >= array(array_index)) then
L = array_index
else
R = array_index
end if
! check for large number of iterations
n_iteration = n_iteration + 1
if (n_iteration == MAX_ITERATION) then
array_index = -2
return
end if
end do
array_index = L
end function binary_search_int4
pure function binary_search_int8(array, n, val) result(array_index)
integer, intent(in) :: n
integer(8), intent(in) :: array(n)
integer(8), intent(in) :: val
integer :: array_index
integer :: L
integer :: R
integer :: n_iteration
L = 1
R = n
if (val < array(L) .or. val > array(R)) then
array_index = -1
return
end if
n_iteration = 0
do while (R - L > 1)
! Find values at midpoint
array_index = L + (R - L)/2
if (val >= array(array_index)) then
L = array_index
else
R = array_index
end if
! check for large number of iterations
n_iteration = n_iteration + 1
if (n_iteration == MAX_ITERATION) then
array_index = -2
return
end if
end do
array_index = L
end function binary_search_int8
!===============================================================================
! SORT sorts an array in place using an insertion sort.
!===============================================================================
pure subroutine sort_int(array)
integer, intent(inout) :: array(:)
integer :: k, m
integer :: temp
if (size(array) > 1) then
SORT: do k = 2, size(array)
! Save value to move
m = k
temp = array(k)
MOVE_OVER: do while (m > 1)
! Check if insertion value is greater than (m-1)th value
if (temp >= array(m - 1)) exit
! Move values over until hitting one that's not larger
array(m) = array(m - 1)
m = m - 1
end do MOVE_OVER
! Put the original value into its new position
array(m) = temp
end do SORT
end if
end subroutine sort_int
pure subroutine sort_real(array)
real(8), intent(inout) :: array(:)
integer :: k, m
real(8) :: temp
if (size(array) > 1) then
SORT: do k = 2, size(array)
! Save value to move
m = k
temp = array(k)
MOVE_OVER: do while (m > 1)
! Check if insertion value is greater than (m-1)th value
if (temp >= array(m - 1)) exit
! Move values over until hitting one that's not larger
array(m) = array(m - 1)
m = m - 1
end do MOVE_OVER
! Put the original value into its new position
array(m) = temp
end do SORT
end if
end subroutine sort_real
pure subroutine sort_vector_int(vec)
type(VectorInt), intent(inout) :: vec
call sort_int(vec % data(1:vec%size()))
end subroutine sort_vector_int
pure subroutine sort_vector_real(vec)
type(VectorReal), intent(inout) :: vec
call sort_real(vec % data(1:vec%size()))
end subroutine sort_vector_real
!===============================================================================
! FIND determines the index of the first occurrence of a value in an array. If
! the value does not appear in the array, -1 is returned.
!===============================================================================
pure function find_int(array, val) result(index)
integer, intent(in) :: array(:)
integer, intent(in) :: val
integer :: index
integer :: i
index = -1
do i = 1, size(array)
if (array(i) == val) then
index = i
exit
end if
end do
end function find_int
pure function find_real(array, val) result(index)
real(8), intent(in) :: array(:)
real(8), intent(in) :: val
integer :: index
integer :: i
index = -1
do i = 1, size(array)
if (array(i) == val) then
index = i
exit
end if
end do
end function find_real
pure function find_vector_int(vec, val) result(index)
type(VectorInt), intent(in) :: vec
integer, intent(in) :: val
integer :: index
index = find_int(vec % data(1:vec % size()), val)
end function find_vector_int
pure function find_vector_real(vec, val) result(index)
type(VectorReal), intent(in) :: vec
real(8), intent(in) :: val
integer :: index
index = find_real(vec % data(1:vec % size()), val)
end function find_vector_real
end module algorithm

View file

@ -2,12 +2,12 @@ module angle_distribution
use hdf5, only: HID_T, HSIZE_T
use algorithm, only: binary_search
use constants, only: ZERO, ONE, HISTOGRAM, LINEAR_LINEAR
use distribution_univariate, only: DistributionContainer, Tabular
use hdf5_interface, only: read_attribute, get_shape, read_dataset, &
open_dataset, close_dataset
use random_lcg, only: prn
use search, only: binary_search
implicit none
private

View file

@ -51,9 +51,10 @@ contains
subroutine compute_xs()
use constants, only: FILTER_MESH, FILTER_ENERGYIN, FILTER_ENERGYOUT, &
FILTER_SURFACE, IN_RIGHT, OUT_RIGHT, IN_FRONT, &
OUT_FRONT, IN_TOP, OUT_TOP, CMFD_NOACCEL, ZERO, &
ONE, TINY_BIT
FILTER_SURFACE, OUT_LEFT, OUT_RIGHT, OUT_BACK, &
OUT_FRONT, OUT_BOTTOM, OUT_TOP, IN_LEFT, IN_RIGHT, &
IN_BACK, IN_FRONT, IN_BOTTOM, IN_TOP, CMFD_NOACCEL, &
ZERO, ONE, TINY_BIT
use error, only: fatal_error
use global, only: cmfd, n_cmfd_tallies, cmfd_tallies, meshes,&
matching_bins
@ -234,76 +235,74 @@ contains
matching_bins(i_filter_ein) = ng - h + 1
end if
! Left surface
! Get the bin for this mesh cell
matching_bins(i_filter_mesh) = mesh_indices_to_bin(m, &
(/ i-1, j, k /) + 1, .true.)
matching_bins(i_filter_surf) = IN_RIGHT
(/ i, j, k /))
! Left surface
matching_bins(i_filter_surf) = OUT_LEFT
score_index = sum((matching_bins(1:size(t % filters)) - 1) &
* t%stride) + 1 ! outgoing
* t % stride) + 1
cmfd % current(1,h,i,j,k) = t % results(1,score_index) % sum
matching_bins(i_filter_surf) = OUT_RIGHT
matching_bins(i_filter_surf) = IN_LEFT
score_index = sum((matching_bins(1:size(t % filters)) - 1) &
* t % stride) + 1 ! incoming
* t % stride) + 1
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:size(t % filters)) - 1) &
* t % stride) + 1 ! incoming
* t % stride) + 1
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:size(t % filters)) - 1) &
* t % stride) + 1 ! outgoing
* t % stride) + 1
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
matching_bins(i_filter_surf) = OUT_BACK
score_index = sum((matching_bins(1:size(t % filters)) - 1) &
* t % stride) + 1 ! outgoing
* t % stride) + 1
cmfd % current(5,h,i,j,k) = t % results(1,score_index) % sum
matching_bins(i_filter_surf) = OUT_FRONT
matching_bins(i_filter_surf) = IN_BACK
score_index = sum((matching_bins(1:size(t % filters)) - 1) &
* t % stride) + 1 ! incoming
* t % stride) + 1
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:size(t % filters)) - 1) &
* t % stride) + 1 ! incoming
* t % stride) + 1
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:size(t % filters)) - 1) &
* t % stride) + 1 ! outgoing
* t % stride) + 1
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
matching_bins(i_filter_surf) = OUT_BOTTOM
score_index = sum((matching_bins(1:size(t % filters)) - 1) &
* t % stride) + 1 ! outgoing
* t % stride) + 1
cmfd % current(9,h,i,j,k) = t % results(1,score_index) % sum
matching_bins(i_filter_surf) = OUT_TOP
matching_bins(i_filter_surf) = IN_BOTTOM
score_index = sum((matching_bins(1:size(t % filters)) - 1) &
* t % stride) + 1 ! incoming
* t % stride) + 1
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:size(t % filters)) - 1) &
* t % stride) + 1 ! incoming
* t % stride) + 1
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:size(t % filters)) - 1) &
* t % stride) + 1 ! outgoing
* t % stride) + 1
cmfd % current(12,h,i,j,k) = t % results(1,score_index) % sum
end if TALLY
@ -448,7 +447,6 @@ contains
! Get leakage
leakage = ZERO
LEAK: do l = 1, 3
leakage = leakage + ((cmfd % current(4*l,g,i,j,k) - &
cmfd % current(4*l-1,g,i,j,k))) - &
((cmfd % current(4*l-2,g,i,j,k) - &

View file

@ -213,13 +213,13 @@ contains
subroutine cmfd_reweight(new_weights)
use algorithm, only: binary_search
use constants, only: ZERO, ONE
use error, only: warning, fatal_error
use global, only: meshes, source_bank, work, n_user_meshes, cmfd, &
master
use mesh_header, only: RegularMesh
use mesh, only: count_bank_sites, get_mesh_indices
use search, only: binary_search
use string, only: to_str
#ifdef MPI

View file

@ -531,13 +531,15 @@ contains
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))
filt % n_bins = 4 * m % n_dimension
allocate(filt % surfaces(4 * m % n_dimension))
if (m % n_dimension == 2) then
filt % surfaces = (/ IN_RIGHT, OUT_RIGHT, IN_FRONT, OUT_FRONT /)
filt % surfaces = (/ OUT_LEFT, IN_LEFT, IN_RIGHT, OUT_RIGHT, &
OUT_BACK, IN_BACK, IN_FRONT, OUT_FRONT /)
elseif (m % n_dimension == 3) then
filt % surfaces = (/ IN_RIGHT, OUT_RIGHT, IN_FRONT, OUT_FRONT, &
IN_TOP, OUT_TOP /)
filt % surfaces = (/ OUT_LEFT, IN_LEFT, IN_RIGHT, OUT_RIGHT, &
OUT_BACK, IN_BACK, IN_FRONT, OUT_FRONT, &
OUT_BOTTOM, IN_BOTTOM, IN_TOP, OUT_TOP /)
end if
end select
t % find_filter(FILTER_SURFACE) = n_filters

View file

@ -54,7 +54,8 @@ module constants
! Maximum number of external source spatial resamples to encounter before an
! error is thrown.
integer, parameter :: MAX_EXTSRC_RESAMPLES = 10000
integer, parameter :: EXTSRC_REJECT_THRESHOLD = 10000
real(8), parameter :: EXTSRC_REJECT_FRACTION = 0.05
! ============================================================================
! PHYSICAL CONSTANTS
@ -267,6 +268,12 @@ module constants
JENDL_33 = 7, &
JENDL_40 = 8
! Temperature treatment method
integer, parameter :: &
TEMPERATURE_NEAREST = 1, &
TEMPERATURE_INTERPOLATION = 2, &
TEMPERATURE_MULTIPOLE = 3
! ============================================================================
! TALLY-RELATED CONSTANTS
@ -289,7 +296,7 @@ module constants
EVENT_ABSORB = 2
! Tally score type
integer, parameter :: N_SCORE_TYPES = 21
integer, parameter :: N_SCORE_TYPES = 23
integer, parameter :: &
SCORE_FLUX = -1, & ! flux
SCORE_TOTAL = -2, & ! total reaction rate
@ -311,7 +318,9 @@ module constants
SCORE_EVENTS = -18, & ! number of events
SCORE_DELAYED_NU_FISSION = -19, & ! delayed neutron production rate
SCORE_PROMPT_NU_FISSION = -20, & ! prompt neutron production rate
SCORE_INVERSE_VELOCITY = -21 ! flux-weighted inverse velocity
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
@ -356,12 +365,18 @@ module constants
! Tally surface current directions
integer, parameter :: &
IN_RIGHT = 1, &
OUT_RIGHT = 2, &
IN_FRONT = 3, &
OUT_FRONT = 4, &
IN_TOP = 5, &
OUT_TOP = 6
OUT_LEFT = 1, & ! x min
OUT_RIGHT = 2, & ! x max
OUT_BACK = 3, & ! y min
OUT_FRONT = 4, & ! y max
OUT_BOTTOM = 5, & ! z min
OUT_TOP = 6, & ! z max
IN_LEFT = 7, & ! x min
IN_RIGHT = 8, & ! x max
IN_BACK = 9, & ! y min
IN_FRONT = 10, & ! y max
IN_BOTTOM = 11, & ! z min
IN_TOP = 12 ! z max
! Tally trigger types and threshold
integer, parameter :: &
@ -380,11 +395,12 @@ module constants
! ============================================================================
! RANDOM NUMBER STREAM CONSTANTS
integer, parameter :: N_STREAMS = 4
integer, parameter :: N_STREAMS = 5
integer, parameter :: STREAM_TRACKING = 1
integer, parameter :: STREAM_TALLIES = 2
integer, parameter :: STREAM_SOURCE = 3
integer, parameter :: STREAM_URR_PTABLE = 4
integer, parameter :: STREAM_VOLUME = 5
! ============================================================================
! MISCELLANEOUS CONSTANTS
@ -397,12 +413,6 @@ module constants
integer, parameter :: ERROR_INT = -huge(0)
real(8), parameter :: ERROR_REAL = -huge(0.0_8) * 0.917826354_8
! Energy grid methods
integer, parameter :: &
GRID_NUCLIDE = 1, & ! unique energy grid for each nuclide
GRID_MAT_UNION = 2, & ! material union grids with pointers
GRID_LOGARITHM = 3 ! lethargy mapping
! Running modes
integer, parameter :: &
MODE_FIXEDSOURCE = 1, & ! Fixed source mode

View file

@ -1,7 +1,8 @@
module cross_section
use algorithm, only: binary_search
use constants
use energy_grid, only: grid_method, log_spacing
use energy_grid, only: log_spacing
use error, only: fatal_error
use global
use list_header, only: ListElemInt
@ -14,7 +15,6 @@ module cross_section
use particle_header, only: Particle
use random_lcg, only: prn, future_prn, prn_set_stream
use sab_header, only: SAlphaBeta
use search, only: binary_search
implicit none
@ -37,7 +37,6 @@ contains
! union grid
real(8) :: atom_density ! atom density of a nuclide
logical :: check_sab ! should we check for S(a,b) table?
type(Material), pointer :: mat ! current material
! Set all material macroscopic cross sections to zero
material_xs % total = ZERO
@ -49,89 +48,83 @@ contains
! Exit subroutine if material is void
if (p % material == MATERIAL_VOID) return
mat => materials(p % material)
! Find energy index on energy grid
if (grid_method == GRID_MAT_UNION) then
i_grid = find_energy_index(mat, p % E)
else if (grid_method == GRID_LOGARITHM) then
associate (mat => materials(p % material))
! Find energy index on energy grid
i_grid = int(log(p % E/energy_min_neutron)/log_spacing)
end if
! Determine if this material has S(a,b) tables
check_sab = (mat % n_sab > 0)
! Determine if this material has S(a,b) tables
check_sab = (mat % n_sab > 0)
! Initialize position in i_sab_nuclides
j = 1
! Initialize position in i_sab_nuclides
j = 1
! Add contribution from each nuclide in material
do i = 1, mat % n_nuclides
! ========================================================================
! CHECK FOR S(A,B) TABLE
! Add contribution from each nuclide in material
do i = 1, mat % n_nuclides
! ========================================================================
! CHECK FOR S(A,B) TABLE
i_sab = 0
i_sab = 0
! Check if this nuclide matches one of the S(a,b) tables specified -- this
! relies on i_sab_nuclides being in sorted order
if (check_sab) then
if (i == mat % i_sab_nuclides(j)) then
! Get index in sab_tables
i_sab = mat % i_sab_tables(j)
! Check if this nuclide matches one of the S(a,b) tables specified -- this
! relies on i_sab_nuclides being in sorted order
if (check_sab) then
if (i == mat % i_sab_nuclides(j)) then
! Get index in sab_tables
i_sab = mat % i_sab_tables(j)
! If particle energy is greater than the highest energy for the S(a,b)
! table, don't use the S(a,b) table
if (p % E > sab_tables(i_sab) % threshold_inelastic) i_sab = 0
! If particle energy is greater than the highest energy for the S(a,b)
! table, don't use the S(a,b) table
if (p % E > sab_tables(i_sab) % data(1) % threshold_inelastic) i_sab = 0
! Increment position in i_sab_nuclides
j = j + 1
! Increment position in i_sab_nuclides
j = j + 1
! Don't check for S(a,b) tables if there are no more left
if (j > mat % n_sab) check_sab = .false.
! Don't check for S(a,b) tables if there are no more left
if (j > mat % n_sab) check_sab = .false.
end if
end if
end if
! ========================================================================
! CALCULATE MICROSCOPIC CROSS SECTION
! ========================================================================
! CALCULATE MICROSCOPIC CROSS SECTION
! Determine microscopic cross sections for this nuclide
i_nuclide = mat % nuclide(i)
! Determine microscopic cross sections for this nuclide
i_nuclide = mat % nuclide(i)
! Calculate microscopic cross section for this nuclide
if (p % E /= micro_xs(i_nuclide) % last_E &
.or. p % sqrtkT /= micro_xs(i_nuclide) % last_sqrtkT) then
call calculate_nuclide_xs(i_nuclide, i_sab, p % E, p % material, i, &
i_grid, p % sqrtkT)
else if (i_sab /= micro_xs(i_nuclide) % last_index_sab) then
call calculate_nuclide_xs(i_nuclide, i_sab, p % E, p % material, i, &
i_grid, p % sqrtkT)
end if
! Calculate microscopic cross section for this nuclide
if (p % E /= micro_xs(i_nuclide) % last_E &
.or. p % sqrtkT /= micro_xs(i_nuclide) % last_sqrtkT) then
call calculate_nuclide_xs(i_nuclide, i_sab, p % E, i_grid, p % sqrtkT)
else if (i_sab /= micro_xs(i_nuclide) % last_index_sab) then
call calculate_nuclide_xs(i_nuclide, i_sab, p % E, i_grid, p % sqrtkT)
end if
! ========================================================================
! ADD TO MACROSCOPIC CROSS SECTION
! ========================================================================
! ADD TO MACROSCOPIC CROSS SECTION
! Copy atom density of nuclide in material
atom_density = mat % atom_density(i)
! Copy atom density of nuclide in material
atom_density = mat % atom_density(i)
! Add contributions to material macroscopic total cross section
material_xs % total = material_xs % total + &
atom_density * micro_xs(i_nuclide) % total
! Add contributions to material macroscopic total cross section
material_xs % total = material_xs % total + &
atom_density * micro_xs(i_nuclide) % total
! Add contributions to material macroscopic scattering cross section
material_xs % elastic = material_xs % elastic + &
atom_density * micro_xs(i_nuclide) % elastic
! Add contributions to material macroscopic scattering cross section
material_xs % elastic = material_xs % elastic + &
atom_density * micro_xs(i_nuclide) % elastic
! Add contributions to material macroscopic absorption cross section
material_xs % absorption = material_xs % absorption + &
atom_density * micro_xs(i_nuclide) % absorption
! Add contributions to material macroscopic absorption cross section
material_xs % absorption = material_xs % absorption + &
atom_density * micro_xs(i_nuclide) % absorption
! Add contributions to material macroscopic fission cross section
material_xs % fission = material_xs % fission + &
atom_density * micro_xs(i_nuclide) % fission
! Add contributions to material macroscopic fission cross section
material_xs % fission = material_xs % fission + &
atom_density * micro_xs(i_nuclide) % fission
! Add contributions to material macroscopic nu-fission cross section
material_xs % nu_fission = material_xs % nu_fission + &
atom_density * micro_xs(i_nuclide) % nu_fission
end do
! Add contributions to material macroscopic nu-fission cross section
material_xs % nu_fission = material_xs % nu_fission + &
atom_density * micro_xs(i_nuclide) % nu_fission
end do
end associate
end subroutine calculate_xs
@ -140,169 +133,162 @@ contains
! given index in the nuclides array at the energy of the given particle
!===============================================================================
subroutine calculate_nuclide_xs(i_nuclide, i_sab, E, i_mat, i_nuc_mat, &
i_log_union, sqrtkT)
subroutine calculate_nuclide_xs(i_nuclide, i_sab, E, i_log_union, sqrtkT)
integer, intent(in) :: i_nuclide ! index into nuclides array
integer, intent(in) :: i_sab ! index into sab_tables array
real(8), intent(in) :: E ! energy
integer, intent(in) :: i_mat ! index into materials array
integer, intent(in) :: i_nuc_mat ! index into nuclides array for a material
integer, intent(in) :: i_log_union ! index into logarithmic mapping array or
! material union energy grid
real(8), intent(in) :: sqrtkT ! Square root of kT, material dependent
logical :: use_mp ! true if XS can be calculated with windowed multipole
integer :: i_temp ! index for temperature
integer :: i_grid ! index on nuclide energy grid
integer :: i_low ! lower logarithmic mapping index
integer :: i_high ! upper logarithmic mapping index
real(8) :: f ! interp factor on nuclide energy grid
real(8) :: kT ! temperature in MeV
real(8) :: sigT, sigA, sigF ! Intermediate multipole variables
type(Nuclide), pointer :: nuc
type(Material), pointer :: mat
! Set pointer to nuclide and material
nuc => nuclides(i_nuclide)
mat => materials(i_mat)
! Check to see if there is multipole data present at this energy
use_mp = .false.
if (nuc % mp_present) then
if (E >= nuc % multipole % start_E/1.0e6_8 .and. &
E <= nuc % multipole % end_E/1.0e6_8) then
use_mp = .true.
end if
end if
! Evaluate multipole or interpolate
if (use_mp) then
! Call multipole kernel
call multipole_eval(nuc % multipole, E, sqrtkT, sigT, sigA, sigF)
micro_xs(i_nuclide) % total = sigT
micro_xs(i_nuclide) % absorption = sigA
micro_xs(i_nuclide) % elastic = sigT - sigA
if (nuc % fissionable) then
micro_xs(i_nuclide) % fission = sigF
micro_xs(i_nuclide) % nu_fission = sigF * nuc % nu(E, EMISSION_TOTAL)
associate (nuc => nuclides(i_nuclide))
! Check to see if there is multipole data present at this energy
use_mp = .false.
if (nuc % mp_present) then
if (E >= nuc % multipole % start_E/1.0e6_8 .and. &
E <= nuc % multipole % end_E/1.0e6_8) then
use_mp = .true.
else
! If using multipole data but outside the RRR, pick the nearest
! temperature. Note that there is no tolerance here, so this
! temperature could be very far off!
kT = sqrtkT**2
i_temp = minloc(abs(nuclides(i_nuclide) % kTs - kT), dim=1)
end if
else
micro_xs(i_nuclide) % fission = ZERO
micro_xs(i_nuclide) % nu_fission = ZERO
! If not using multipole data, do a linear search on temperature
kT = sqrtkT**2
do i_temp = 1, size(nuclides(i_nuclide) % kTs)
if (abs(nuclides(i_nuclide) % kTs(i_temp) - kT) < &
K_BOLTZMANN*temperature_tolerance) exit
end do
end if
! Ensure these values are set
! Note, the only time either is used is in one of 4 places:
! 1. physics.F90 - scatter - For inelastic scatter.
! 2. physics.F90 - sample_fission - For partial fissions.
! 3. tally.F90 - score_general - For tallying on MTxxx reactions.
! 4. cross_section.F90 - calculate_urr_xs - For unresolved purposes.
! It is worth noting that none of these occur in the resolved
! resonance range, so the value here does not matter.
micro_xs(i_nuclide) % index_grid = 0
micro_xs(i_nuclide) % interp_factor = ZERO
else
! Determine index on nuclide energy grid
select case (grid_method)
case (GRID_MAT_UNION)
! Evaluate multipole or interpolate
if (use_mp) then
! Call multipole kernel
call multipole_eval(nuc % multipole, E, sqrtkT, sigT, sigA, sigF)
i_grid = mat % nuclide_grid_index(i_nuc_mat, i_log_union)
micro_xs(i_nuclide) % total = sigT
micro_xs(i_nuclide) % absorption = sigA
micro_xs(i_nuclide) % elastic = sigT - sigA
case (GRID_LOGARITHM)
! Determine the energy grid index using a logarithmic mapping to reduce
! the energy range over which a binary search needs to be performed
if (E < nuc % energy(1)) then
i_grid = 1
elseif (E > nuc % energy(nuc % n_grid)) then
i_grid = nuc % n_grid - 1
if (nuc % fissionable) then
micro_xs(i_nuclide) % fission = sigF
micro_xs(i_nuclide) % nu_fission = sigF * nuc % nu(E, EMISSION_TOTAL)
else
! Determine bounding indices based on which equal log-spaced interval
! the energy is in
i_low = nuc % grid_index(i_log_union)
i_high = nuc % grid_index(i_log_union + 1) + 1
! Perform binary search over reduced range
i_grid = binary_search(nuc % energy(i_low:i_high), &
i_high - i_low + 1, E) + i_low - 1
micro_xs(i_nuclide) % fission = ZERO
micro_xs(i_nuclide) % nu_fission = ZERO
end if
case (GRID_NUCLIDE)
! Perform binary search on the nuclide energy grid in order to determine
! which points to interpolate between
! Ensure these values are set
! Note, the only time either is used is in one of 4 places:
! 1. physics.F90 - scatter - For inelastic scatter.
! 2. physics.F90 - sample_fission - For partial fissions.
! 3. tally.F90 - score_general - For tallying on MTxxx reactions.
! 4. cross_section.F90 - calculate_urr_xs - For unresolved purposes.
! It is worth noting that none of these occur in the resolved
! resonance range, so the value here does not matter.
micro_xs(i_nuclide) % index_temp = i_temp
micro_xs(i_nuclide) % index_grid = 0
micro_xs(i_nuclide) % interp_factor = ZERO
else
associate (grid => nuc % grid(i_temp), xs => nuc % sum_xs(i_temp))
! Determine the energy grid index using a logarithmic mapping to reduce
! the energy range over which a binary search needs to be performed
if (E <= nuc % energy(1)) then
i_grid = 1
elseif (E > nuc % energy(nuc % n_grid)) then
i_grid = nuc % n_grid - 1
else
i_grid = binary_search(nuc % energy, nuc % n_grid, E)
if (E < grid % energy(1)) then
i_grid = 1
elseif (E > grid % energy(size(grid % energy))) then
i_grid = size(grid % energy) - 1
else
! Determine bounding indices based on which equal log-spaced interval
! the energy is in
i_low = grid % grid_index(i_log_union)
i_high = grid % grid_index(i_log_union + 1) + 1
! Perform binary search over reduced range
i_grid = binary_search(grid % energy(i_low:i_high), &
i_high - i_low + 1, E) + i_low - 1
end if
! check for rare case where two energy points are the same
if (grid % energy(i_grid) == grid % energy(i_grid + 1)) &
i_grid = i_grid + 1
! calculate interpolation factor
f = (E - grid % energy(i_grid)) / &
(grid % energy(i_grid + 1) - grid % energy(i_grid))
micro_xs(i_nuclide) % index_temp = i_temp
micro_xs(i_nuclide) % index_grid = i_grid
micro_xs(i_nuclide) % interp_factor = f
! Initialize nuclide cross-sections to zero
micro_xs(i_nuclide) % fission = ZERO
micro_xs(i_nuclide) % nu_fission = ZERO
! Calculate microscopic nuclide total cross section
micro_xs(i_nuclide) % total = (ONE - f) * xs % total(i_grid) &
+ f * xs % total(i_grid + 1)
! Calculate microscopic nuclide elastic cross section
micro_xs(i_nuclide) % elastic = (ONE - f) * xs % elastic(i_grid) &
+ f * xs % elastic(i_grid + 1)
! Calculate microscopic nuclide absorption cross section
micro_xs(i_nuclide) % absorption = (ONE - f) * xs % absorption( &
i_grid) + f * xs % absorption(i_grid + 1)
if (nuc % fissionable) then
! Calculate microscopic nuclide total cross section
micro_xs(i_nuclide) % fission = (ONE - f) * xs % fission(i_grid) &
+ f * xs % fission(i_grid + 1)
! Calculate microscopic nuclide nu-fission cross section
micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % nu_fission( &
i_grid) + f * xs % nu_fission(i_grid + 1)
end if
end associate
end if
! Initialize sab treatment to false
micro_xs(i_nuclide) % index_sab = NONE
micro_xs(i_nuclide) % elastic_sab = ZERO
! Initialize URR probability table treatment to false
micro_xs(i_nuclide) % use_ptable = .false.
! If there is S(a,b) data for this nuclide, we need to do a few
! things. Since the total cross section was based on non-S(a,b) data, we
! need to correct it by subtracting the non-S(a,b) elastic cross section and
! then add back in the calculated S(a,b) elastic+inelastic cross section.
if (i_sab > 0) call calculate_sab_xs(i_nuclide, i_sab, E, sqrtkT)
! if the particle is in the unresolved resonance range and there are
! probability tables, we need to determine cross sections from the table
if (urr_ptables_on .and. nuc % urr_present .and. .not. use_mp) then
if (E > nuc % urr_data(i_temp) % energy(1) .and. E < nuc % &
urr_data(i_temp) % energy(nuc % urr_data(i_temp) % n_energy)) then
call calculate_urr_xs(i_nuclide, i_temp, E)
end if
end select
! check for rare case where two energy points are the same
if (nuc % energy(i_grid) == nuc % energy(i_grid+1)) i_grid = i_grid + 1
! calculate interpolation factor
f = (E - nuc%energy(i_grid))/(nuc%energy(i_grid+1) - nuc%energy(i_grid))
micro_xs(i_nuclide) % index_grid = i_grid
micro_xs(i_nuclide) % interp_factor = f
! Initialize nuclide cross-sections to zero
micro_xs(i_nuclide) % fission = ZERO
micro_xs(i_nuclide) % nu_fission = ZERO
! Calculate microscopic nuclide total cross section
micro_xs(i_nuclide) % total = (ONE - f) * nuc % total(i_grid) &
+ f * nuc % total(i_grid+1)
! Calculate microscopic nuclide elastic cross section
micro_xs(i_nuclide) % elastic = (ONE - f) * nuc % elastic(i_grid) &
+ f * nuc % elastic(i_grid+1)
! Calculate microscopic nuclide absorption cross section
micro_xs(i_nuclide) % absorption = (ONE - f) * nuc % absorption( &
i_grid) + f * nuc % absorption(i_grid+1)
if (nuc % fissionable) then
! Calculate microscopic nuclide total cross section
micro_xs(i_nuclide) % fission = (ONE - f) * nuc % fission(i_grid) &
+ f * nuc % fission(i_grid+1)
! Calculate microscopic nuclide nu-fission cross section
micro_xs(i_nuclide) % nu_fission = (ONE - f) * nuc % nu_fission( &
i_grid) + f * nuc % nu_fission(i_grid+1)
end if
end if
! Initialize sab treatment to false
micro_xs(i_nuclide) % index_sab = NONE
micro_xs(i_nuclide) % elastic_sab = ZERO
! Initialize URR probability table treatment to false
micro_xs(i_nuclide) % use_ptable = .false.
! If there is S(a,b) data for this nuclide, we need to do a few
! things. Since the total cross section was based on non-S(a,b) data, we
! need to correct it by subtracting the non-S(a,b) elastic cross section and
! then add back in the calculated S(a,b) elastic+inelastic cross section.
if (i_sab > 0) call calculate_sab_xs(i_nuclide, i_sab, E)
! if the particle is in the unresolved resonance range and there are
! probability tables, we need to determine cross sections from the table
if (urr_ptables_on .and. nuc % urr_present) then
if (E > nuc % urr_data % energy(1) .and. &
E < nuc % urr_data % energy(nuc % urr_data % n_energy)) then
call calculate_urr_xs(i_nuclide, E)
end if
end if
micro_xs(i_nuclide) % last_E = E
micro_xs(i_nuclide) % last_index_sab = i_sab
micro_xs(i_nuclide) % last_sqrtkT = sqrtkT
micro_xs(i_nuclide) % last_E = E
micro_xs(i_nuclide) % last_index_sab = i_sab
micro_xs(i_nuclide) % last_sqrtkT = sqrtkT
end associate
end subroutine calculate_nuclide_xs
@ -312,75 +298,85 @@ contains
! whatever data were taken from the normal Nuclide table.
!===============================================================================
subroutine calculate_sab_xs(i_nuclide, i_sab, E)
subroutine calculate_sab_xs(i_nuclide, i_sab, E, sqrtkT)
integer, intent(in) :: i_nuclide ! index into nuclides array
integer, intent(in) :: i_sab ! index into sab_tables array
real(8), intent(in) :: E ! energy
real(8), intent(in) :: sqrtkT ! temperature
integer :: i_grid ! index on S(a,b) energy grid
integer :: i_temp ! temperature index
real(8) :: f ! interp factor on S(a,b) energy grid
real(8) :: inelastic ! S(a,b) inelastic cross section
real(8) :: elastic ! S(a,b) elastic cross section
type(SAlphaBeta), pointer :: sab
real(8) :: kT
! Set flag that S(a,b) treatment should be used for scattering
micro_xs(i_nuclide) % index_sab = i_sab
! Determine temperature for S(a,b) table
kT = sqrtkT**2
do i_temp = 1, size(sab_tables(i_sab) % kTs)
if (abs(sab_tables(i_sab) % kTs(i_temp) - kT) < &
K_BOLTZMANN*temperature_tolerance) exit
end do
! Get pointer to S(a,b) table
sab => sab_tables(i_sab)
associate (sab => sab_tables(i_sab) % data(i_temp))
! Get index and interpolation factor for inelastic grid
if (E < sab % inelastic_e_in(1)) then
i_grid = 1
f = ZERO
else
i_grid = binary_search(sab % inelastic_e_in, sab % n_inelastic_e_in, E)
f = (E - sab%inelastic_e_in(i_grid)) / &
(sab%inelastic_e_in(i_grid+1) - sab%inelastic_e_in(i_grid))
end if
! Get index and interpolation factor for inelastic grid
if (E < sab % inelastic_e_in(1)) then
i_grid = 1
f = ZERO
else
i_grid = binary_search(sab % inelastic_e_in, sab % n_inelastic_e_in, E)
f = (E - sab%inelastic_e_in(i_grid)) / &
(sab%inelastic_e_in(i_grid+1) - sab%inelastic_e_in(i_grid))
end if
! Calculate S(a,b) inelastic scattering cross section
inelastic = (ONE - f) * sab % inelastic_sigma(i_grid) + &
f * sab % inelastic_sigma(i_grid + 1)
! Calculate S(a,b) inelastic scattering cross section
inelastic = (ONE - f) * sab % inelastic_sigma(i_grid) + &
f * sab % inelastic_sigma(i_grid + 1)
! Check for elastic data
if (E < sab % threshold_elastic) then
! Determine whether elastic scattering is given in the coherent or
! incoherent approximation. For coherent, the cross section is
! represented as P/E whereas for incoherent, it is simply P
! Check for elastic data
if (E < sab % threshold_elastic) then
! Determine whether elastic scattering is given in the coherent or
! incoherent approximation. For coherent, the cross section is
! represented as P/E whereas for incoherent, it is simply P
if (sab % elastic_mode == SAB_ELASTIC_EXACT) then
if (E < sab % elastic_e_in(1)) then
! If energy is below that of the lowest Bragg peak, the elastic
! cross section will be zero
elastic = ZERO
if (sab % elastic_mode == SAB_ELASTIC_EXACT) then
if (E < sab % elastic_e_in(1)) then
! If energy is below that of the lowest Bragg peak, the elastic
! cross section will be zero
elastic = ZERO
else
i_grid = binary_search(sab % elastic_e_in, &
sab % n_elastic_e_in, E)
elastic = sab % elastic_P(i_grid) / E
end if
else
i_grid = binary_search(sab % elastic_e_in, &
sab % n_elastic_e_in, E)
elastic = sab % elastic_P(i_grid) / E
! Determine index on elastic energy grid
if (E < sab % elastic_e_in(1)) then
i_grid = 1
else
i_grid = binary_search(sab % elastic_e_in, &
sab % n_elastic_e_in, E)
end if
! Get interpolation factor for elastic grid
f = (E - sab%elastic_e_in(i_grid))/(sab%elastic_e_in(i_grid+1) - &
sab%elastic_e_in(i_grid))
! Calculate S(a,b) elastic scattering cross section
elastic = (ONE - f) * sab % elastic_P(i_grid) + &
f * sab % elastic_P(i_grid + 1)
end if
else
! Determine index on elastic energy grid
if (E < sab % elastic_e_in(1)) then
i_grid = 1
else
i_grid = binary_search(sab % elastic_e_in, &
sab % n_elastic_e_in, E)
end if
! Get interpolation factor for elastic grid
f = (E - sab%elastic_e_in(i_grid))/(sab%elastic_e_in(i_grid+1) - &
sab%elastic_e_in(i_grid))
! Calculate S(a,b) elastic scattering cross section
elastic = (ONE - f) * sab % elastic_P(i_grid) + &
f * sab % elastic_P(i_grid + 1)
! No elastic data
elastic = ZERO
end if
else
! No elastic data
elastic = ZERO
end if
end associate
! Correct total and elastic cross sections
micro_xs(i_nuclide) % total = micro_xs(i_nuclide) % total - &
@ -390,6 +386,9 @@ contains
! Store S(a,b) elastic cross section for sampling later
micro_xs(i_nuclide) % elastic_sab = elastic
! Save temperature index
micro_xs(i_nuclide) % index_temp_sab = i_temp
end subroutine calculate_sab_xs
!===============================================================================
@ -397,9 +396,9 @@ contains
! from probability tables
!===============================================================================
subroutine calculate_urr_xs(i_nuclide, E)
subroutine calculate_urr_xs(i_nuclide, i_temp, E)
integer, intent(in) :: i_nuclide ! index into nuclides array
integer, intent(in) :: i_temp ! temperature index
real(8), intent(in) :: E ! energy
integer :: i_energy ! index for energy
@ -414,7 +413,7 @@ contains
micro_xs(i_nuclide) % use_ptable = .true.
associate (nuc => nuclides(i_nuclide), urr => nuclides(i_nuclide) % urr_data)
associate (nuc => nuclides(i_nuclide), urr => nuclides(i_nuclide) % urr_data(i_temp))
! determine energy table
i_energy = 1
do
@ -433,7 +432,7 @@ contains
! random number for the same nuclide at different temperatures, therefore
! preserving correlation of temperature in probability tables.
call prn_set_stream(STREAM_URR_PTABLE)
r = future_prn(int(nuc_zaid_dict % get_key(nuc % zaid), 8))
r = future_prn(int(i_nuclide, 8))
call prn_set_stream(STREAM_TRACKING)
i_low = 1
@ -497,10 +496,10 @@ contains
f = micro_xs(i_nuclide) % interp_factor
! Determine inelastic scattering cross section
associate (rxn => nuc % reactions(nuc % urr_inelastic))
if (i_energy >= rxn % threshold) then
inelastic = (ONE - f) * rxn % sigma(i_energy - rxn%threshold + 1) + &
f * rxn % sigma(i_energy - rxn%threshold + 2)
associate (xs => nuc % reactions(nuc % urr_inelastic) % xs(i_temp))
if (i_energy >= xs % threshold) then
inelastic = (ONE - f) * xs % value(i_energy - xs % threshold + 1) + &
f * xs % value(i_energy - xs % threshold + 2)
end if
end associate
end if

Some files were not shown because too many files have changed in this diff Show more