Merge branch 'tally-capi' into cmfd-capi

This commit is contained in:
Shikhar Kumar 2018-08-09 03:38:20 -04:00
commit 96e4b7c40b
123 changed files with 36878 additions and 1342 deletions

6
.gitignore vendored
View file

@ -59,14 +59,12 @@ src/install_manifest.txt
scripts/nndc
scripts/nndc_hdf5
scripts/wmp
scripts/multipole_lib.tar.gz
scripts/ENDF-B-VII.1-*.tar.gz
scripts/JEFF32-ACE-*.tar.gz
scripts/JEFF32-ACE-*.zip
scripts/TSLs.tar.gz
scripts/jeff-3.2
scripts/jeff-3.2-hdf5
scripts/*.tar.xz
scripts/*.tar.*
scripts/G4EMLOW*/
# Images
*.ppm

View file

@ -319,6 +319,8 @@ add_library(libopenmc SHARED
src/output.F90
src/particle_header.F90
src/particle_restart.F90
src/photon_header.F90
src/photon_physics.F90
src/physics_common.F90
src/physics.F90
src/physics_mg.F90
@ -371,6 +373,7 @@ add_library(libopenmc SHARED
src/tallies/tally_filter_mesh.F90
src/tallies/tally_filter_meshsurface.F90
src/tallies/tally_filter_mu.F90
src/tallies/tally_filter_particle.F90
src/tallies/tally_filter_polar.F90
src/tallies/tally_filter_sph_harm.F90
src/tallies/tally_filter_sptl_legendre.F90
@ -390,10 +393,12 @@ add_library(libopenmc SHARED
src/message_passing.cpp
src/mgxs.cpp
src/mgxs_interface.cpp
src/particle.cpp
src/plot.cpp
src/pugixml/pugixml_c.cpp
src/random_lcg.cpp
src/scattdata.cpp
src/settings.cpp
src/simulation.cpp
src/state_point.cpp
src/string_functions.cpp

View file

@ -12,7 +12,7 @@ openmc@anl.gov.
## Resources
- [GitHub Repository](https://github.com/mit-crpg/openmc)
- [GitHub Repository](https://github.com/openmc-dev/openmc)
- [Documentation](http://openmc.readthedocs.io/en/latest)
- [User's Mailing List](openmc-users@googlegroups.com)
- [Developer's Mailing List](openmc-dev@googlegroups.com)
@ -22,7 +22,7 @@ openmc@anl.gov.
## How to Report Bugs
OpenMC is hosted on GitHub and all bugs are reported and tracked through the
[Issues](https://github.com/mit-crpg/openmc/issues) listed on GitHub.
[Issues](https://github.com/openmc-dev/openmc/issues) listed on GitHub.
## How to Suggest Enhancements

View file

@ -1,4 +1,4 @@
Copyright (c) 2011-2018 Massachusetts Institute of Technology
Copyright (c) 2011-2018 Massachusetts Institute of Technology and OpenMC contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View file

@ -1,8 +1,8 @@
# OpenMC Monte Carlo Particle Transport Code
[![License](https://img.shields.io/github/license/mit-crpg/openmc.svg)](http://openmc.readthedocs.io/en/latest/license.html)
[![Travis CI build status (Linux)](https://travis-ci.org/mit-crpg/openmc.svg?branch=develop)](https://travis-ci.org/mit-crpg/openmc)
[![Code Coverage](https://coveralls.io/repos/github/mit-crpg/openmc/badge.svg?branch=develop)](https://coveralls.io/github/mit-crpg/openmc?branch=develop)
[![License](https://img.shields.io/github/license/openmc-dev/openmc.svg)](http://openmc.readthedocs.io/en/latest/license.html)
[![Travis CI build status (Linux)](https://travis-ci.org/openmc-dev/openmc.svg?branch=develop)](https://travis-ci.org/openmc-dev/openmc)
[![Code Coverage](https://coveralls.io/repos/github/openmc-dev/openmc/badge.svg?branch=develop)](https://coveralls.io/github/openmc-dev/openmc?branch=develop)
The OpenMC project aims to provide a fully-featured Monte Carlo particle
transport code based on modern methods. It is a constructive solid geometry,
@ -44,7 +44,7 @@ list](https://groups.google.com/forum/?fromgroups=#!forum/openmc-users).
## Reporting Bugs
OpenMC is hosted on GitHub and all bugs are reported and tracked through the
[Issues](https://github.com/mit-crpg/openmc/issues) feature on GitHub. However,
[Issues](https://github.com/openmc-dev/openmc/issues) feature on GitHub. However,
GitHub Issues should not be used for common troubleshooting purposes. If you are
having trouble installing the code or getting your model to run properly, you
should first send a message to the User's Group mailing list. If it turns out

View file

@ -71,7 +71,7 @@ master_doc = 'index'
# General information about the project.
project = u'OpenMC'
copyright = u'2011-2018, Massachusetts Institute of Technology'
copyright = u'2011-2018, Massachusetts Institute of Technology and OpenMC contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@ -208,7 +208,7 @@ htmlhelp_basename = 'openmcdoc'
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'openmc.tex', u'OpenMC Documentation',
u'Massachusetts Institute of Technology', 'manual'),
u'OpenMC contributors', 'manual'),
]
latex_elements = {

View file

@ -65,8 +65,8 @@ developer or send a message to the `developers mailing list`_.
.. _property attribute: https://docs.python.org/3.6/library/functions.html#property
.. _XML Schema Part 2: http://www.w3.org/TR/xmlschema-2/
.. _boolean: http://www.w3.org/TR/xmlschema-2/#boolean
.. _xml_interface module: https://github.com/mit-crpg/openmc/blob/develop/src/xml_interface.F90
.. _input_xml module: https://github.com/mit-crpg/openmc/blob/develop/src/input_xml.F90
.. _xml_interface module: https://github.com/openmc-dev/openmc/blob/develop/src/xml_interface.F90
.. _input_xml module: https://github.com/openmc-dev/openmc/blob/develop/src/input_xml.F90
.. _RELAX NG: http://relaxng.org/
.. _compact syntax: http://relaxng.org/compact-tutorial-20030326.html
.. _trang: http://www.thaiopensource.com/relaxng/trang.html

View file

@ -55,7 +55,7 @@ Now that you understand the basic development workflow, let's discuss how an
individual to contribute to development. Note that this would apply to both new
features and bug fixes. The general steps for contributing are as follows:
1. Fork the main openmc repository from `mit-crpg/openmc`_. This will create a
1. Fork the main openmc repository from `openmc-dev/openmc`_. This will create a
repository with the same name under your personal account. As such, you can
commit to it as you please without disrupting other developers.
@ -74,7 +74,7 @@ features and bug fixes. The general steps for contributing are as follows:
ensure that those changes are made on a different branch.
4. Issue a pull request from GitHub and select the *develop* branch of
mit-crpg/openmc as the target.
openmc-dev/openmc as the target.
.. image:: ../_images/pullrequest.png
@ -87,7 +87,7 @@ features and bug fixes. The general steps for contributing are as follows:
request page itself.
6. After the pull request has been thoroughly vetted, it is merged back into the
*develop* branch of mit-crpg/openmc.
*develop* branch of openmc-dev/openmc.
Private Development
-------------------
@ -99,7 +99,7 @@ create a complete copy of the OpenMC repository (not a fork from GitHub). The
private repository can then either be stored just locally or in conjunction with
a private repository on Github (this requires a `paid plan`_). Alternatively,
`Bitbucket`_ offers private repositories for free. If you want to merge some
changes you've made in your private repository back to mit-crpg/openmc
changes you've made in your private repository back to openmc-dev/openmc
repository, simply follow the steps above with an extra step of pulling a branch
from your private repository into a public fork.
@ -128,9 +128,9 @@ can interfere with virtual environments.
.. _GitHub: https://github.com/
.. _git flow: http://nvie.com/git-model
.. _valgrind: http://valgrind.org/
.. _style guide: http://mit-crpg.github.io/openmc/devguide/styleguide.html
.. _style guide: http://openmc.readthedocs.io/en/latest/devguide/styleguide.html
.. _pull request: https://help.github.com/articles/using-pull-requests
.. _mit-crpg/openmc: https://github.com/mit-crpg/openmc
.. _openmc-dev/openmc: https://github.com/openmc-dev/openmc
.. _paid plan: https://github.com/plans
.. _Bitbucket: https://bitbucket.org
.. _ctest: http://www.cmake.org/cmake/help/v2.8.12/ctest.html

View file

@ -24,6 +24,7 @@ Basic Usage
triso
candu
nuclear-data
nuclear-data-resonance-covariance
------------------------------------
Multi-Group Cross Section Generation

View file

@ -0,0 +1,13 @@
.. _notebook_nuclear_data_resonance_covariance:
==================================
Nuclear Data: Resonance Covariance
==================================
.. only:: html
.. notebook:: ../../../examples/jupyter/nuclear-data-resonance-covariance.ipynb
.. only:: latex
IPython notebooks must be viewed in the online HTML documentation.

View file

@ -1,8 +1,8 @@
.. _io_nuclear_data:
========================
Nuclear Data File Format
========================
=========================
Nuclear Data File Formats
=========================
---------------------
Incident Neutron Data
@ -10,7 +10,7 @@ Incident Neutron Data
**/**
:Attributes:
:Attributes: - **filetype** (*char[]*) -- String indicating the type of file
- **version** (*int[2]*) -- Major and minor version of the data
**/<nuclide name>/**
@ -22,7 +22,9 @@ Incident Neutron Data
- **atomic_weight_ratio** (*double*) -- Mass in units of neutron masses
- **n_reaction** (*int*) -- Number of reactions
:Datasets: - **energy** (*double[]*) -- Energy points at which cross sections are tabulated
:Datasets:
- **energy** (*double[]*) -- Energies in [eV] at which cross sections
are tabulated
**/<nuclide name>/kTs/**
@ -31,7 +33,7 @@ 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 eV) for each temperature
- **<TTT>K** (*double*) -- kT values in [eV] for each temperature
TTT (in Kelvin)
**/<nuclide name>/reactions/reaction_<mt>/**
@ -85,33 +87,117 @@ temperature-dependent data set. For example, the data set corresponding to
**/<nuclide name>/fission_energy_release/**
:Datasets: - **fragments** (:ref:`polynomial <1d_polynomial>`) -- Energy
:Datasets: - **fragments** (:ref:`function <1d_functions>`) -- 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
- **prompt_neutrons** (:ref:`function <1d_functions>`) -- Energy
released in the form of prompt neutrons as a function of incident
neutron energy.
- **delayed_neutrons** (:ref:`function <1d_functions>`) -- Energy
released in the form of delayed neutrons as a function of incident
neutron energy.
- **prompt_photons** (:ref:`polynomial <1d_polynomial>`) -- Energy
- **prompt_photons** (:ref:`function <1d_functions>`) -- Energy
released in the form of prompt photons as a function of incident
neutron energy.
- **delayed_photons** (:ref:`polynomial <1d_polynomial>`) -- Energy
- **delayed_photons** (:ref:`function <1d_functions>`) -- 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)
- **betas** (:ref:`function <1d_functions>`) -- Energy released in
the form of betas as a function of incident neutron energy.
- **neutrinos** (:ref:`function <1d_functions>`) -- Energy released
in the form of neutrinos as a function of incident neutron energy.
- **q_prompt** (:ref:`function <1d_functions>`) -- The prompt fission
Q-value (fragments + prompt neutrons + prompt photons - incident
energy)
- **q_recoverable** (:ref:`function <1d_functions>`) -- The
recoverable fission Q-value (Q_prompt + delayed neutrons + delayed
photons + betas)
--------------------
Incident Photon Data
--------------------
**/**
:Attributes: - **filetype** (*char[]*) -- String indicating the type of file
- **version** (*int[2]*) -- Major and minor version of the data
**/<element>/**
:Attributes: - **Z** (*int*) -- Atomic number
:Datasets:
- **energy** (*double[]*) -- Energies in [eV] at which cross sections
are tabulated
**/<element>/bremsstrahlung/**
:Datasets: - **electron_energy** (*double[]*) -- Incident electron energy in [eV]
- **photon_energy** (*double[]*) -- Outgoing photon energy as
fraction of incident electron energy
- **dcs** (*double[][]*) -- Bremsstrahlung differential cross section
at each incident energy in [mb/eV]
**/<element>/coherent/**
:Datasets: - **xs** (*double[]*) -- Coherent scattering cross section in [b]
- **integrated_scattering_factor** (:ref:`tabulated <1d_tabulated>`)
-- Integrated coherent scattering form factor
- **anomalous_real** (:ref:`tabulated <1d_tabulated>`) -- Real part
of the anomalous scattering factor
- **anomalous_imag** (:ref:`tabulated <1d_tabulated>`) -- Imaginary
part of the anomalous scattering factor
**/<element>/compton_profiles/**
:Datasets: - **binding_energy** (*double[]*) -- Binding energy for each subshell in [eV]
- **num_electrons** (*double[]*) -- Number of electrons in each subshell
- **pz** (*double[]*) -- Projection of the electron momentum on the
scattering vector in units of :math:`me^2 / \hbar` where :math:`m`
is the electron rest mass and :math:`e` is the electron charge
- **J** (*double[][]*) -- Compton profile for each subshell in units
of :math:`\hbar / (me^2)`
**/<element>/incoherent/**
:Datasets: - **xs** (*double[]*) -- Incoherent scattering cross section in [b]
- **scattering_factor** (:ref:`tabulated <1d_tabulated>`) --
**/<element>/pair_production_electron/**
:Datasets: - **xs** (*double[]*) -- Pair production (electron field) cross section in [b]
**/<element>/pair_production_nuclear/**
:Datasets: - **xs** (*double[]*) -- Pair production (nuclear field) cross section in [b]
**/<element>/photoelectric/**
:Datasets: - **xs** (*double[]*) -- Total photoionization cross section in [b]
**/<element>/stopping_powers/**
:Datasets: - **I** (*double*) -- Mean excitation energy in [eV]
- **energy** (*double[]*) -- Energies in [eV]
- **s_collision** (*double[]*) -- Collision stopping power in [eV-cm\ :sup:`2`\ /g]
- **s_radiative** (*double[]*) -- Radiative stopping power in [eV-cm\ :sup:`2`\ /g]
**/<element>/subshells/**
:Attributes: - **designators** (*char[][]*) -- Designator for each shell, e.g. 'M2'
**/<element>/subshells/<designator>/**
:Attributes: - **binding_energy** (*double*) -- Binding energy of the subshell in [eV]
- **num_electrons** (*double*) -- Number of electrons in the subshell
:Datasets: - **transitions** (*double[][]*) -- Atomic relaxation data
- **xs** (*double[]*) -- Photoionization cross section for subshell
in [b] tabulated against the main energy grid
:Attributes:
- **threshold_idx** (*int*) -- Index on the energy
grid of the reaction threshold
-------------------------------
Thermal Neutron Scattering Data

View file

@ -32,6 +32,19 @@ standard deviation.
*Default*: false
-------------------------------------
``<create_fission_neutrons>`` Element
-------------------------------------
The ``<create_fission_neutrons>`` element indicates whether fission neutrons
should be created or not. If this element is set to "true", fission neutrons
will be created; otherwise the fission is treated as capture and no fission
neutron will be created. Note that this option is only applied to fixed source
calculation. For eigenvalue calculation, fission will always be treated as real
fission.
*Default*: true
--------------------
``<cutoff>`` Element
--------------------
@ -55,31 +68,35 @@ you care. This element has the following attributes/sub-elements:
*Default*: 1.0
:energy:
The energy under which particles will be killed.
:energy_neutron:
The energy under which neutrons will be killed.
*Default*: 0.0
-------------------------
``<energy_grid>`` Element
-------------------------
:energy_photon:
The energy under which photons will be killed.
The ``<energy_grid>`` element determines the treatment of the energy grid during
a simulation. The valid options are "nuclide", "logarithm", and
"material-union". Setting this element to "nuclide" will cause OpenMC to use a
nuclide's energy grid when determining what points to interpolate between for
determining cross sections (i.e. non-unionized energy grid). Setting this
element to "logarithm" causes OpenMC to use a logarithmic mapping technique
described in LA-UR-14-24530_. Setting this element to "material-union" will
cause OpenMC to create energy grids that are unionized material-by-material and
use these grids when determining the energy-cross section pairs to interpolate
cross section values between.
*Default*: 1000.0
*Default*: logarithm
:energy_electron:
The energy under which electrons will be killed.
.. note:: This element is not used in the multi-group :ref:`energy_mode`.
*Default*: 0.0
.. _LA-UR-14-24530: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-14-24530.pdf
:energy_positron:
The energy under which positrons will be killed.
*Default*: 0.0
--------------------------------
``<electron_treatment>`` Element
--------------------------------
When photon transport is enabled, the ``<electron_treatment>`` element tells
OpenMC whether to deposit all energy from electrons locally (``led``) or create
secondary bremsstrahlung photons (``ttb``).
*Default*: ttb
.. _energy_mode:
@ -153,8 +170,7 @@ the estimated eigenvalue. It has the following attributes/sub-elements:
*Default*: None
.. note:: See section on the :ref:`trigger` for more information.
.. note:: See section on the :ref:`trigger` for more information.
---------------------------
``<log_grid_bins>`` Element
@ -169,6 +185,8 @@ based on the recommended value in LA-UR-14-24530_.
.. note:: This element is not used in the multi-group :ref:`energy_mode`.
.. _LA-UR-14-24530: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-14-24530.pdf
---------------------------
``<max_order>`` Element
---------------------------
@ -259,11 +277,21 @@ out the file and "false" will not.
-----------------------
This element indicates the number of neutrons to simulate per fission source
iteration when a k-eigenvalue calculation is performed or the number of neutrons
per batch for a fixed source simulation.
iteration when a k-eigenvalue calculation is performed or the number of
particles per batch for a fixed source simulation.
*Default*: None
------------------------------
``<photon_transport>`` Element
------------------------------
The ``<photon_transport>`` element determines whether photon transport is
enabled. This element has no attributes or sub-elements and can be set to
either "false" or "true".
*Default*: false
---------------------
``<ptables>`` Element
---------------------
@ -379,6 +407,11 @@ attributes/sub-elements:
*Default*: 1.0
:particle:
The source particle type, either ``neutron`` or ``photon``.
*Default*: neutron
:file:
If this attribute is given, it indicates that the source is to be read from
a binary source file whose path is given by the value of this element. Note,
@ -812,20 +845,6 @@ and 10. The verbosity levels are defined as follows:
*Default*: 7
-------------------------------------
``<create_fission_neutrons>`` Element
-------------------------------------
The ``<create_fission_neutrons>`` element indicates whether fission neutrons
should be created or not. If this element is set to "true", fission neutrons
will be created; otherwise the fission is treated as capture and no fission
neutron will be created. Note that this option is only applied to fixed source
calculation. For eigenvalue calculation, fission will always be treated as real
fission.
*Default*: true
-------------------------
``<volume_calc>`` Element
-------------------------

View file

@ -125,8 +125,8 @@ attributes/sub-elements:
:type:
The type of the filter. Accepted options are "cell", "cellfrom",
"cellborn", "surface", "material", "universe", "energy", "energyout", "mu",
"polar", "azimuthal", "mesh", "distribcell", "delayedgroup", and
"energyfunction".
"polar", "azimuthal", "mesh", "distribcell", "delayedgroup",
"energyfunction", and "particle".
:bins:
A description of the bins for each type of filter can be found in
@ -299,6 +299,9 @@ should be set to:
``energyfunction`` filters do not use the ``bins`` entry. Instead
they use ``energy`` and ``y``.
:particle:
A list of integers indicating the type of particles to tally ('neutron' = 1,
'photon' = 2, 'electron' = 3, 'positron' = 4).
------------------
``<mesh>`` Element

View file

@ -4,7 +4,7 @@
License Agreement
=================
Copyright © 2011-2018 Massachusetts Institute of Technology
Copyright © 2011-2018 Massachusetts Institute of Technology and OpenMC contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View file

@ -12,7 +12,8 @@ Theory and Methodology
geometry
cross_sections
random_numbers
physics
neutron_physics
photon_physics
tallies
eigenvalue
parallelization

View file

@ -1,8 +1,8 @@
.. _methods_physics:
.. _methods_neutron_physics:
=======
Physics
=======
===============
Neutron Physics
===============
There are limited differences between physics treatments used in the
continuous-energy and multi-group modes. If distinctions are necessary, each

View file

@ -0,0 +1,275 @@
.. _methods_photon_physics:
==============
Photon Physics
==============
Photons, being neutral particles, behave much in the same manner as neutrons,
traveling in straight lines and experiencing occasional collisions which change
their energy and direction. Photons undergo four basic interactions as they pass
through matter: coherent (Rayleigh) scattering, incoherent (Compton) scattering,
photoelectric effect, and pair/triplet production. Photons with energy in the
MeV range may also undergo photonuclear reactions with an atomic nucleus. In
addition to these primary interaction mechanisms, all processes other than
coherent scattering can result in the excitation/ionization of atoms. The
de-excitation of these atoms can result in the emission of electrons and
photons. Electrons themselves also can produce photons by means of
bremsstrahlung radiation.
-------------------
Photon Interactions
-------------------
Coherent (Rayleigh) Scattering
------------------------------
The elastic scattering of a photon off a free charged particle is known as
Thomson scattering. The differential cross section is independent of the energy
of the incident photon. For scattering off a free electron, the differential
cross section is
.. math::
:label: thomson
\frac{d\sigma}{d\mu} = \pi r_e^2 ( 1 + \mu^2 )
where :math:`\mu` is the cosine of the scattering angle and :math:`r_e` is the
classical electron radius. Thomson scattering can generally occur when the
photon energy is much less than the rest mass energy of the particle.
In practice, most elastic scattering of photons off electrons happens not with
free electrons but those bound in atoms. This process is known as Rayleigh
scattering. The radiation scattered off of individual bound electrons combines
coherently, and thus Rayleigh scattering is also known as coherent
scattering. Even though conceptually we think of the photon interacting with a
single electron, because the wave functions combine constructively it is really
as though the photon is interacting with the entire atom.
The differential cross section for Rayleigh scattering is given by
.. math::
:label: coherent-xs
\frac{d\sigma(E,E',\mu)}{d\mu} &= \pi r_e^2 ( 1 + \mu^2 )~\left| F(x,Z)
+ F' + iF'' \right|^2 \\
&= \pi r_e^2 ( 1 + \mu^2 ) \left [ ( F(x,Z)
+ F'(E) )^2 + F''(E)^2 \right ]
where :math:`F(x,Z)` is a form factor as a function of the momentum transfer
:math:`x` and the atomic number :math:`Z` and the term :math:`F' + iF''`
accounts for `anomalous scattering`_ which can occur near absorption edges. In
a Monte Carlo simulation, when coherent scattering occurs, we only need to
sample the scattering angle using the differential cross section in
:eq:`coherent-xs` since the energy of the photon does not change. In OpenMC,
anomalous scattering is ignored such that the differential cross section
becomes
.. math::
:label: coherent-xs-openmc
\frac{d\sigma(E,E',\mu)}{d\mu} = \pi r_e^2 ( 1 + \mu^2 ) F(x, Z)^2
To construct a proper probability density, we need to normalize the
differential cross section in :eq:`coherent-xs-openmc` by the integrated
coherent scattering cross section:
.. math::
:label: coherent-pdf-1
p(\mu) d\mu = \frac{\pi r_e^2}{\sigma(E)} ( 1 + \mu^2 ) F(x, Z)^2 d\mu.
Since the form factor is given in terms of the momentum transfer, it is more
convenient to change variables of the probability density to :math:`x^2`. The
momentum transfer is traditionally expressed as
.. math::
:label: momentum-transfer
x = \kappa \alpha \sqrt{1 - \mu}
where :math:`\alpha` is the ratio of the photon energy to the electron rest
mass, and the coefficient :math:`\kappa` can be shown to be
.. math::
:label: kappa
\kappa = \frac{m_e c^2}{\sqrt{2}hc} \approx 29.14329,
where :math:`m_e` is the mass of the electron, :math:`c` is the speed of light
in a vacuum, and :math:`h` is Planck's constant. Using :eq:`momentum-transfer`,
we have :math:`\mu = 1 - [x/(\kappa\alpha)]^2` and :math:`d\mu/dx^2 =
-1/(\kappa\alpha)^2`. The probability density in :math:`x^2` is
.. math::
:label: coherent-pdf-x2
p(x^2) dx^2 = p(\mu) \left | \frac{d\mu}{dx^2} \right | dx^2 = \frac{2\pi
r_e^2 A(\bar{x}^2,Z)}{(\kappa\alpha)^2 \sigma(E)} \left (
\frac{1 + \mu^2}{2} \right ) \left ( \frac{F(x, Z)^2}{A(\bar{x}^2, Z)} \right ) dx^2
where :math:`\bar{x}` is the maximum value of :math:`x` that occurs for
:math:`\mu=-1`,
.. math::
:label: xmax
\bar{x} = \kappa \alpha \sqrt{2} = \frac{m_e c^2}{hc} \alpha,
and :math:`A(x^2, Z)` is the integral of the square of the form factor:
.. math::
:label: coherent-int-ff
A(x^2, Z) = \int_0^{x^2} F(x,Z)^2 dx^2.
As you see, we have multiplied and divided the probability density by the
integral of the squared form factor so that the density in :eq:`coherent-pdf-x2`
is expressed as the product of two separate densities in parentheses. In OpenMC,
a table of :math:`A(x^2, Z)` versus :math:`x^2` is pre-generated and used at
run-time to do a table search on the cumulative distribution function:
.. math::
:label: coherent-form-factor-cdf
\frac{\int_0^{x^2} F(x,Z)^2 dx^2}{\int_0^{\bar{x}^2} F(x,Z)^2 dx^2}
Once a trial :math:`x^2` value has been selected, we can calculate :math:`\mu`
and perform rejection sampling using the Thomson scattering differential cross
section. The complete algorithm is as follows:
1. Determine :math:`\bar{x}^2` using :eq:`xmax`.
2. Determine :math:`A_{max} = A(\bar{x}^2, Z)` using the pre-generated
tabulated data.
3. Sample the cumulative density by calculating :math:`A' = \xi_1 A_{max}` where
:math:`\xi_1` is a uniformly distributed random number.
4. Perform a binary search to determine the value of :math:`x^2` which satisfies
:math:`A(x^2, Z) = A'`.
5. By combining :eq:`momentum-transfer` and :eq:`xmax`, calculate :math:`\mu =
1 - 2x^2/\bar{x}^2`.
6. If :math:`\xi_2 < (1 + \mu^2)/2`, accept :math:`\mu`. Otherwise, repeat the
sampling at step 3.
Incoherent (Compton) Scattering
-------------------------------
Before we noted that the Thomson cross section gives the behavior for photons
scattering off of free electrons valid at low energies. The formula for photon
scattering off of free electrons that is valid for all energies can be found
using quantum electrodynamics and is known as the Klein-Nishina_ formula after
the two authors who discovered it:
.. math::
:label: klein-nishina
\frac{d\sigma_{KN}}{d\mu} = \pi r_e^2 \left ( \frac{\alpha'}{\alpha} \right
)^2 \left [ \frac{\alpha'}{\alpha} + \frac{\alpha}{\alpha'} + \mu^2 - 1
\right ]
where :math:`\alpha` and :math:`\alpha'` are the ratios of the incoming and
exiting photon energies to the electron rest mass energy equivalent (0.511 MeV),
respectively. Although it appears that the outgoing energy and angle are
separate, there is actually a one-to-one relationship between them such that
only one needs to be sampled:
.. math::
:label: compton-energy-angle
\alpha' = \frac{\alpha}{1 + \alpha(1 - \mu)}.
Note that when :math:`\alpha'/\alpha` goes to one, i.e., scattering is elastic,
the Klein-Nishina cross section becomes identical to the Thomson cross
section. In general though, the scattering is inelastic and is known as Compton
scattering. When a photon interacts with a bound electron in an atom, the
Klein-Nishina formula must be modified to account for the binding effects. As in
the case of coherent scattering, this is done by means of a form factor. The
differential cross section for incoherent scattering is given by
.. math::
:label: incoherent-xs
\frac{d\sigma}{d\mu} = \frac{d\sigma_{KN}}{d\mu} S(x,Z) = \pi r_e^2 \left (
\frac{\alpha'}{\alpha} \right )^2 \left [ \frac{\alpha'}{\alpha} +
\frac{\alpha}{\alpha'} + \mu^2 - 1 \right ] S(x,Z)
where :math:`S(x,Z)` is the form factor. The approach in OpenMC is to first
sample the Klein-Nishina cross section and then perform rejection sampling on
the form factor. As in other codes, `Kahn's rejection method`_ is used for
:math:`\alpha < 3` and a direct method by Koblinger_ is used for :math:`\alpha
\ge 3`. The complete algorithm is as follows:
1. If :math:`\alpha < 3`, sample :math:`\mu` from the Klein-Nishina cross
section using Kahn's rejection method. Otherwise, use Koblinger's direct
method.
2. Calculate :math:`x` and :math:`\bar{x}` using :eq:`momentum-transfer` and
:eq:`xmax`, respectively.
3. If :math:`\xi < S(x, Z)/S(\bar{x}, Z)`, accept :math:`\mu`. Otherwise repeat
from step 1.
Doppler Energy Broadening
+++++++++++++++++++++++++
LA-UR-04-0487_ and LA-UR-04-0488_
Compton Electrons
+++++++++++++++++
Photoelectric Effect
--------------------
Pair Production
---------------
-------------------
Secondary Processes
-------------------
New photons may be produced in secondary processes related to the main photon
interactions discussed above. A Compton-scattered photon transfers a portion of
its energy to the kinetic energy of the recoil electron, which in turn may lose
the energy as bremsstrahlung radiation. The vacancy left in the shell by the
ejected electron is filled through atomic relaxation, creating a shower of
electrons and fluorescence photons. Similarly, the vacancy left by the electron
emitted in the photoelectric effect is filled through atomic relaxation. Pair
production generates an electron and a positron, both of which can emit
bremsstrahlung radiation before the positron eventually collides with an
electron, resulting in annihilation of the pair and the creation of two
additional photons.
Atomic Relaxation
-----------------
Electron-Positron Annihilation
------------------------------
Bremsstrahlung
--------------
.. _ttb:
Thick-Target Bremsstrahlung Approximation
+++++++++++++++++++++++++++++++++++++++++
.. _Koblinger: https://doi.org/10.13182/NSE75-A26663
.. _anomalous scattering: http://pd.chem.ucl.ac.uk/pdnn/diff1/anomscat.htm
.. _Kahn's rejection method: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/aecu-3259_kahn.pdf
.. _Klein-Nishina: https://en.wikipedia.org/wiki/Klein%E2%80%93Nishina_formula
.. _LA-UR-04-0487: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-04-0487.pdf
.. _LA-UR-04-0488: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-04-0488.pdf

View file

@ -122,6 +122,7 @@ Constructing Tallies
openmc.SpatialLegendreFilter
openmc.SphericalHarmonicsFilter
openmc.ZernikeFilter
openmc.ParticleFilter
openmc.Mesh
openmc.Trigger
openmc.TallyDerivative

View file

@ -19,6 +19,9 @@ Core Classes
openmc.data.CoherentElastic
openmc.data.FissionEnergyRelease
openmc.data.DataLibrary
openmc.data.IncidentPhoton
openmc.data.PhotonReaction
openmc.data.AtomicRelaxation
openmc.data.Decay
openmc.data.FissionProductYields
openmc.data.WindowedMultipole
@ -79,6 +82,11 @@ Resonance Data
openmc.data.MultiLevelBreitWigner
openmc.data.ReichMoore
openmc.data.RMatrixLimited
openmc.data.ResonanceCovariances
openmc.data.ResonanceCovarianceRange
openmc.data.SingleLevelBreitWignerCovariance
openmc.data.MultiLevelBreitWignerCovariance
openmc.data.ReichMooreCovariance
openmc.data.ParticlePair
openmc.data.SpinGroup
openmc.data.Unresolved

View file

@ -83,7 +83,7 @@ Installing from Source on Linux or Mac OS X
-------------------------------------------
All OpenMC source code is hosted on `GitHub
<https://github.com/mit-crpg/openmc>`_. If you have `git
<https://github.com/openmc-dev/openmc>`_. If you have `git
<https://git-scm.com>`_, the `gcc <https://gcc.gnu.org/>`_ compiler suite,
`CMake <http://www.cmake.org>`_, and `HDF5 <https://www.hdfgroup.org/HDF5/>`_
installed, you can download and install OpenMC be entering the following
@ -91,7 +91,7 @@ commands in a terminal:
.. code-block:: sh
git clone https://github.com/mit-crpg/openmc.git
git clone https://github.com/openmc-dev/openmc.git
cd openmc
mkdir build && cd build
cmake ..

View file

@ -71,14 +71,14 @@ Bug Fixes
- 0c6915_: Bugfix for generating thermal scattering data
- 61ecb4_: Fix bugs in Python multipole objects
.. _937469: https://github.com/mit-crpg/openmc/commit/937469
.. _a149ef: https://github.com/mit-crpg/openmc/commit/a149ef
.. _2c9b21: https://github.com/mit-crpg/openmc/commit/2c9b21
.. _8047f6: https://github.com/mit-crpg/openmc/commit/8047f6
.. _0beb4c: https://github.com/mit-crpg/openmc/commit/0beb4c
.. _f124be: https://github.com/mit-crpg/openmc/commit/f124be
.. _0c6915: https://github.com/mit-crpg/openmc/commit/0c6915
.. _61ecb4: https://github.com/mit-crpg/openmc/commit/61ecb4
.. _937469: https://github.com/openmc-dev/openmc/commit/937469
.. _a149ef: https://github.com/openmc-dev/openmc/commit/a149ef
.. _2c9b21: https://github.com/openmc-dev/openmc/commit/2c9b21
.. _8047f6: https://github.com/openmc-dev/openmc/commit/8047f6
.. _0beb4c: https://github.com/openmc-dev/openmc/commit/0beb4c
.. _f124be: https://github.com/openmc-dev/openmc/commit/f124be
.. _0c6915: https://github.com/openmc-dev/openmc/commit/0c6915
.. _61ecb4: https://github.com/openmc-dev/openmc/commit/61ecb4
------------
Contributors

View file

@ -8,19 +8,19 @@ A Beginner's Guide to OpenMC
What does OpenMC do?
--------------------
In a nutshell, OpenMC simulates neutral particles (presently only neutrons)
moving stochastically through an arbitrarily defined model that represents an
real-world experimental setup. The experiment could be as simple as a sphere of
metal or as complicated as a full-scale `nuclear reactor`_. This is what's known
as `Monte Carlo`_ simulation. In the case of a nuclear reactor model, neutrons
are especially important because they are the particles that induce `fission`_
in isotopes of uranium and other elements. Knowing the behavior of neutrons
allows one to determine how often and where fission occurs. The amount of energy
released is then directly proportional to the fission reaction rate since most
heat is produced by fission. By simulating many neutrons (millions or billions),
it is possible to determine the average behavior of these neutrons (or the
behavior of the energy produced, or any other quantity one is interested in)
very accurately.
In a nutshell, OpenMC simulates neutral particles (presently neutrons and
photons) moving stochastically through an arbitrarily defined model that
represents an real-world experimental setup. The experiment could be as simple
as a sphere of metal or as complicated as a full-scale `nuclear reactor`_. This
is what's known as `Monte Carlo`_ simulation. In the case of a nuclear reactor
model, neutrons are especially important because they are the particles that
induce `fission`_ in isotopes of uranium and other elements. Knowing the
behavior of neutrons allows one to determine how often and where fission
occurs. The amount of energy released is then directly proportional to the
fission reaction rate since most heat is produced by fission. By simulating
many neutrons (millions or billions), it is possible to determine the average
behavior of these neutrons (or the behavior of the energy produced, or any
other quantity one is interested in) very accurately.
Using Monte Carlo methods to determine the average behavior of various physical
quantities in a system is quite different from other means of solving the same
@ -153,9 +153,9 @@ and `Volume II`_. You may also find it helpful to review the following terms:
.. _Reactor Concepts Manual: http://www.tayloredge.com/periodic/trivia/ReactorConcepts.pdf
.. _Volume I: https://www.standards.doe.gov/standards-documents/1000/1019-bhdbk-1993-v1
.. _Volume II: https://www.standards.doe.gov/standards-documents/1000/1019-bhdbk-1993-v2
.. _OpenMC source code: https://github.com/mit-crpg/openmc
.. _OpenMC source code: https://github.com/openmc-dev/openmc
.. _GitHub: https://github.com/
.. _bug reports: https://github.com/mit-crpg/openmc/issues
.. _bug reports: https://github.com/openmc-dev/openmc/issues
.. _Neutron cross section: http://en.wikipedia.org/wiki/Neutron_cross_section
.. _Effective multiplication factor: https://en.wikipedia.org/wiki/Nuclear_chain_reaction#Effective_neutron_multiplication_factor
.. _Flux: http://en.wikipedia.org/wiki/Neutron_flux

View file

@ -258,6 +258,49 @@ method using :attr:`Settings.resonance_scattering`.
running the :meth:`IncidentNeutron.add_elastic_0K_from_endf` method
may take several minutes to complete.
Photon Cross Sections
---------------------
Photon interaction data is needed to run OpenMC with photon transport enabled.
Some of this data, namely bremsstrahlung cross sections from `Seltzer and
Berger`_, stopping powers from the `NIST ESTAR database`_, and Compton profiles
calculated by `Biggs et al.`_ and available in the Geant4 G4EMLOW data file, is
distributed with OpenMC. The rest is available from the NNDC, which provides
ENDF data from the photo-atomic and atomic relaxation sublibraries of the
ENDF/B-VII.1 library. By default, the :ref:`scripts_nndc` script will download
the ENDF data in addition to the neutron and thermal scattering data, extract
it, combine it with the data from other sources, and convert it to an HDF5
library. Alternatively, the :ref:`scripts_photon` script can be used to
download the photon data on its own and create the HDF5 library:
.. code-block:: sh
openmc-get-photon-data
As with neutrons and thermal scattering, it is possible to use the Python API
directly to convert photon interaction data from an ENDF or ACE file to an HDF5
file. The :class:`openmc.data.IncidentPhoton` class contains an
:meth:`IncidentPhoton.from_ace` method that will generate photon data from an
ACE table and an :meth:`IncidentPhoton.export_to_hdf5` method that writes the
data to an HDF5 file:
::
u = openmc.data.IncidentPhoton.from_ace('92000.12p')
u.export_to_hdf5('U.h5')
Similarly, the :meth:`IncidentPhoton.from_endf` method can be used to read
photon data from an ENDF file. In the case, both the photo-atomic and atomic
relaxation sublibrary files are required:
::
u = openmc.data.IncidentPhoton.from_endf('photoat-092_U_000.endf',
'atom-092_U_000.endf')
Once the HDF5 files have been generated, a library can be created using the
:class:`DataLibrary` class as described in :ref:`create_xs_library`.
-----------------------
Windowed Multipole Data
-----------------------
@ -291,3 +334,6 @@ For an example of how to create a multi-group library, see
.. _MCNP: http://mcnp.lanl.gov
.. _Serpent: http://montecarlo.vtt.fi
.. _TENDL: https://tendl.web.psi.ch/tendl_2015/tendl2015.html
.. _Seltzer and Berger: https://www.sciencedirect.com/science/article/pii/0092640X86900148?via%3Dihub
.. _NIST ESTAR database: https://physics.nist.gov/PhysRefData/Star/Text/ESTAR.html
.. _Biggs et al.: https://www.sciencedirect.com/science/article/pii/0092640X75900303

View file

@ -181,7 +181,7 @@ with GitHub since this involves setting up ssh_ keys. With git installed and
setup, the following command will download the full source code from the GitHub
repository::
git clone https://github.com/mit-crpg/openmc.git
git clone https://github.com/openmc-dev/openmc.git
By default, the cloned repository will be set to the development branch. To
switch to the source of the latest stable release, run the following commands::
@ -189,7 +189,7 @@ switch to the source of the latest stable release, run the following commands::
cd openmc
git checkout master
.. _GitHub: https://github.com/mit-crpg/openmc
.. _GitHub: https://github.com/openmc-dev/openmc
.. _git: https://git-scm.com
.. _ssh: https://en.wikipedia.org/wiki/Secure_Shell

View file

@ -166,12 +166,57 @@ ENDF/B-VII.1. It has the following optional arguments:
``openmc-get-nndc-data``
------------------------
This script downloads `ENDF/B-VII.1 ACE data
<http://www.nndc.bnl.gov/endf/b7.1/acefiles.html>`_ from NNDC and converts it to
an HDF5 library for use with OpenMC. This script has the following optional
arguments:
This script downloads `ENDF/B-VII.1
<http://www.nndc.bnl.gov/endf/b7.1/acefiles.html>`_ incident neutron ACE data
and incident photon ENDF data from NNDC and converts it to an HDF5 library for
use with OpenMC. This script has the following optional arguments:
-b, --batch Suppress standard in
-b, --batch
Suppress standard in
-n, --neutron_only
Whether to exclude photon interaction/atomic data
.. _scripts_photon:
--------------------------
``openmc-get-photon-data``
--------------------------
This script downloads `ENDF data <http://www.nndc.bnl.gov/endf/b7.1/zips/>`_
from NNDC for photo-atomic and atomic relaxation sublibraries and converts it
to an HDF5 library for use with photon transport in OpenMC. This script has the
following optional arguments:
-b, --batch
Suppress standard in
-c, --cross-sections
cross_sections.xml file to append libraries to
.. _scripts_compton:
-----------------------
``openmc-make-compton``
-----------------------
This script generates an HDF5 file called ``compton_profiles.h5`` that contains
Compton profile data using an existing data library from `Geant4
<http://geant4.cern.ch/>`_. Note that OpenMC includes this data file by default
so it should not be necessary in practice to generate it yourself.
.. _scripts_stopping:
-------------------------------
``openmc-make-stopping-powers``
-------------------------------
This script generates an HDF5 file called ``stopping_power.h5`` that contains
radiative and collision stopping powers and mean excitation energy pulled from
the `NIST ESTAR database
<https://physics.nist.gov/PhysRefData/Star/Text/ESTAR.html>`_. Note that OpenMC
includes this data file by default so it should not be necessary in practice to
generate it yourself.
.. _scripts_plot:

View file

@ -151,10 +151,19 @@ time and another that should be sampled 30% of the time::
settings.source = [src1, src2]
Finally, the :attr:`Source.particle` attribute can be used to indicate the
source should be composed of particles other than neutrons. For example, the
following would generate a photon source::
source = openmc.Source()
source.particle = 'photon'
...
settings.source = source
For a full list of all classes related to statistical distributions, see
:ref:`pythonapi_stats`.
---------------
Shannon Entropy
---------------
@ -190,6 +199,52 @@ property::
settings.entropy_mesh = m
----------------
Photon Transport
----------------
In addition to neutrons, OpenMC is also capable of simulating the passage of
photons through matter. This allows the modeling of photon production from
neutrons as well as pure photon calculations. The
:attr:`Settings.photon_transport` attribute can be used to enable photon
transport::
settings.photon_transport = True
The way in which OpenMC handles secondary charged particles can be specified
with the :attr:`Settings.electron_treatment` attribute. By default, the
:ref:`thick-target bremsstrahlung <ttb>` (TTB) approximation is used to generate
bremsstrahlung radiation emitted by electrons and positrons created in photon
interactions. To neglect secondary bremsstrahlung photons and instead deposit
all energy from electrons locally, the local energy deposition option can be
selected::
settings.electron_treatment = 'led'
.. warning::
Currently, collision stopping powers used in the TTB approximation come from
the `NIST ESTAR database`_, which provides data for each element calculated
using by default the material density at standard temperature and pressure.
In OpenMC, stopping powers for compounds are calculated from this elemental
data using Bragg's additivity rule. However, this is not a good
approximation --- the collision stopping power is a function of certain
quantities, such as the mean excitation energy and particularly the density
effect correction, that depend on material properties. Data for constituent
elements in a compound cannot simply be summed together, but rather these
quantities should be calculated for the material. This treatment will be
especially poor when the density of a material is different from the
densities used in the NIST data.
.. note::
Some features related to photon transport are not currently implemented,
including:
* Tallying photon energy deposition.
* Properly accounting for energy deposition in coupled n-p calculations.
* Generating a photon source from a neutron calculation that can be used
for a later fixed source photon calculation.
* Photoneutron reactions.
--------------------------
Generation of Output Files
--------------------------
@ -224,3 +279,5 @@ As an example, to write a statepoint file every five batches::
settings.batches = n
settings.statepoint = {'batches': range(5, n + 5, 5)}
.. _NIST ESTAR database: https://physics.nist.gov/PhysRefData/Star/Text/ESTAR.html

File diff suppressed because one or more lines are too long

View file

@ -14,6 +14,7 @@ extern "C" {
double uvw[3];
double E;
int delayed_group;
int particle;
};
int openmc_calculate_volumes();
@ -51,7 +52,7 @@ extern "C" {
int openmc_init_f(const int* intracomm);
int openmc_legendre_filter_get_order(int32_t index, int* order);
int openmc_legendre_filter_set_order(int32_t index, int order);
int openmc_load_nuclide(char name[]);
int openmc_load_nuclide(const char name[]);
int openmc_material_add_nuclide(int32_t index, const char name[], double density);
int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n);
int openmc_material_get_id(int32_t index, int32_t* id);
@ -92,18 +93,23 @@ extern "C" {
int openmc_sphharm_filter_set_cosine(int32_t index, const char cosine[]);
int openmc_statepoint_write(const char filename[]);
int openmc_tally_get_active(int32_t index, bool* active);
int openmc_tally_get_estimator(int32_t index, int32_t* estimator);
int openmc_tally_get_id(int32_t index, int32_t* id);
int openmc_tally_get_filters(int32_t index, int32_t** indices, int* n);
int openmc_tally_get_n_realizations(int32_t index, int32_t* n);
int openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n);
int openmc_tally_get_scores(int32_t index, int** scores, int* n);
int openmc_tally_get_type(int32_t index, int32_t* type);
int openmc_tally_reset(int32_t index);
int openmc_tally_results(int32_t index, double** ptr, int shape_[3]);
int openmc_tally_set_active(int32_t index, bool active);
int openmc_tally_set_estimator(int32_t index, const char* estimator);
int openmc_tally_set_filters(int32_t index, int n, const int32_t* indices);
int openmc_tally_set_id(int32_t index, int32_t id);
int openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides);
int openmc_tally_set_scores(int32_t index, int n, const char** scores);
int openmc_tally_set_type(int32_t index, const char* type);
int openmc_tally_update_type(int32_t index, const char* type);
int openmc_zernike_filter_get_order(int32_t index, int* order);
int openmc_zernike_filter_get_params(int32_t index, double* x, double* y, double* r);
int openmc_zernike_filter_set_order(int32_t index, int order);
@ -127,6 +133,7 @@ extern "C" {
extern char openmc_err_msg[256];
extern double openmc_keff;
extern double openmc_keff_std;
extern int32_t gen_per_batch;
extern int32_t n_batches;
extern int32_t n_cells;
extern int32_t n_filters;

View file

@ -59,7 +59,8 @@ Indicates the default path to a directory containing windowed multipole data if
the user has not specified the <multipole_library> tag in
.I materials.xml\fP.
.SH LICENSE
Copyright \(co 2011-2018 Massachusetts Institute of Technology.
Copyright \(co 2011-2018 Massachusetts Institute of Technology and OpenMC
contributors.
.PP
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@ -79,7 +80,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
.SH REPORTING BUGS
The OpenMC source code is hosted on GitHub at
https://github.com/mit-crpg/openmc. With a github account, you can submit issues
https://github.com/openmc-dev/openmc. With a github account, you can submit issues
directly on the github repository that will then be reviewed by OpenMC
developers. Alternatively, you can send a bug report to
.I openmc-users@googlegroups.com\fP.

View file

@ -305,7 +305,7 @@ def run_in_memory(intracomm=None):
MPI intracommunicator
"""
init(intracomm)
init(intracomm=intracomm)
try:
yield
finally:

View file

@ -29,6 +29,9 @@ _dll.openmc_global_tallies.errcheck = _error_handler
_dll.openmc_tally_get_active.argtypes = [c_int32, POINTER(c_bool)]
_dll.openmc_tally_get_active.restype = c_int
_dll.openmc_tally_get_active.errcheck = _error_handler
_dll.openmc_tally_get_estimator.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_tally_get_estimator.restype = c_int
_dll.openmc_tally_get_estimator.errcheck = _error_handler
_dll.openmc_tally_get_id.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_tally_get_id.restype = c_int
_dll.openmc_tally_get_id.errcheck = _error_handler
@ -47,6 +50,9 @@ _dll.openmc_tally_get_scores.argtypes = [
c_int32, POINTER(POINTER(c_int)), POINTER(c_int)]
_dll.openmc_tally_get_scores.restype = c_int
_dll.openmc_tally_get_scores.errcheck = _error_handler
_dll.openmc_tally_get_type.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_tally_get_type.restype = c_int
_dll.openmc_tally_get_type.errcheck = _error_handler
_dll.openmc_tally_results.argtypes = [
c_int32, POINTER(POINTER(c_double)), POINTER(c_int*3)]
_dll.openmc_tally_results.restype = c_int
@ -57,6 +63,9 @@ _dll.openmc_tally_set_active.errcheck = _error_handler
_dll.openmc_tally_set_filters.argtypes = [c_int32, c_int, POINTER(c_int32)]
_dll.openmc_tally_set_filters.restype = c_int
_dll.openmc_tally_set_filters.errcheck = _error_handler
_dll.openmc_tally_set_estimator.argtypes = [c_int32, c_char_p]
_dll.openmc_tally_set_estimator.restype = c_int
_dll.openmc_tally_set_estimator.errcheck = _error_handler
_dll.openmc_tally_set_id.argtypes = [c_int32, c_int32]
_dll.openmc_tally_set_id.restype = c_int
_dll.openmc_tally_set_id.errcheck = _error_handler
@ -69,6 +78,9 @@ _dll.openmc_tally_set_scores.errcheck = _error_handler
_dll.openmc_tally_set_type.argtypes = [c_int32, c_char_p]
_dll.openmc_tally_set_type.restype = c_int
_dll.openmc_tally_set_type.errcheck = _error_handler
_dll.openmc_tally_update_type.argtypes = [c_int32, c_char_p]
_dll.openmc_tally_update_type.restype = c_int
_dll.openmc_tally_update_type.errcheck = _error_handler
_SCORES = {
@ -78,6 +90,13 @@ _SCORES = {
-12: 'prompt-nu-fission', -13: 'inverse-velocity', -14: 'fission-q-prompt',
-15: 'fission-q-recoverable', -16: 'decay-rate'
}
_ESTIMATORS = {
1: 'analog', 2: 'tracklength', 3: 'collision'
}
_TALLY_TYPES = {
1: 'volume', 2: 'mesh-surface', 3: 'surface'
}
def global_tallies():
@ -140,6 +159,8 @@ class Tally(_FortranObjectWithID):
----------
id : int
ID of the tally
estimator: str
Estimator type of tally (analog, tracklength, collision)
filters : list
List of tally filters
mean : numpy.ndarray
@ -152,6 +173,8 @@ class Tally(_FortranObjectWithID):
Array of tally results
std_dev : numpy.ndarray
An array containing the sample standard deviation for each bin
type : str
Type of tally (volume, mesh_surface, surface)
"""
__instances = WeakValueDictionary()
@ -190,6 +213,30 @@ class Tally(_FortranObjectWithID):
_dll.openmc_tally_get_active(self._index, active)
return active.value
@property
def type(self):
type = c_int32()
_dll.openmc_tally_get_type(self._index, type)
return _TALLY_TYPES[type.value]
@type.setter
def type(self, type):
_dll.openmc_tally_update_type(self._index, type.encode())
@property
def estimator(self):
estimator = c_int32()
try:
_dll.openmc_tally_get_estimator(self._index, estimator)
except AllocationError:
return ""
else:
return _ESTIMATORS[estimator.value]
@estimator.setter
def estimator(self, estimator):
_dll.openmc_tally_set_estimator(self._index, estimator.encode())
@active.setter
def active(self, active):
_dll.openmc_tally_set_active(self._index, active)

28521
openmc/data/BREMX.DAT Normal file

File diff suppressed because it is too large Load diff

View file

@ -9,12 +9,13 @@ WMP_VERSION = 'v0.2'
from .data import *
from .neutron import *
from .photon import *
from .decay import *
from .reaction import *
from .ace import *
from . import ace
from .angle_distribution import *
from .function import *
from .endf import *
from . import endf
from .energy_distribution import *
from .product import *
from .angle_energy import *
@ -27,5 +28,6 @@ from .urr import *
from .library import *
from .fission_energy import *
from .resonance import *
from .resonance_covariance import *
from .multipole import *
from .grid import *

View file

@ -16,13 +16,82 @@ generates ACE-format cross sections.
"""
from os import SEEK_CUR
from pathlib import PurePath
import struct
import sys
import numpy as np
from openmc.mixin import EqualityMixin
from openmc.data.endf import _ENDF_FLOAT_RE
import openmc.checkvalue as cv
from .data import ATOMIC_SYMBOL, gnd_name
from .endf import ENDF_FLOAT_RE
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 = gnd_name(Z, mass_number, metastable)
return (name, element, Z, mass_number, metastable)
def ascii_to_binary(ascii_file, binary_file):
"""Convert an ACE file in ASCII format (type 1) to binary format (type 2).
@ -160,7 +229,7 @@ class Library(EqualityMixin):
# Determine whether file is ASCII or binary
try:
fh = open(filename, 'rb')
fh = open(str(filename), 'rb')
# Grab 10 lines of the library
sb = b''.join([fh.readline() for i in range(10)])
@ -349,7 +418,7 @@ class Library(EqualityMixin):
# after it). If it's too short, then we apply the ENDF float regular
# expression. We don't do this by default because it's expensive!
if xss.size != nxs[1] + 1:
datastr = _ENDF_FLOAT_RE.sub(r'\1e\2', datastr)
datastr = ENDF_FLOAT_RE.sub(r'\1e\2', datastr)
xss = np.fromstring(datastr, sep=' ')
assert xss.size == nxs[1] + 1

Binary file not shown.

View file

@ -22,10 +22,31 @@ from .function import Tabulated1D, INTERPOLATION_SCHEME
from openmc.stats.univariate import Uniform, Tabular, Legendre
LIBRARIES = {0: 'ENDF/B', 1: 'ENDF/A', 2: 'JEFF', 3: 'EFF',
4: 'ENDF/B High Energy', 5: 'CENDL', 6: 'JENDL',
31: 'INDL/V', 32: 'INDL/A', 33: 'FENDL', 34: 'IRDF',
35: 'BROND', 36: 'INGDB-90', 37: 'FENDL/A', 41: 'BROND'}
_LIBRARY = {0: 'ENDF/B', 1: 'ENDF/A', 2: 'JEFF', 3: 'EFF',
4: 'ENDF/B High Energy', 5: 'CENDL', 6: 'JENDL',
17: 'TENDL', 18: 'ROSFOND', 21: 'SG-21', 31: 'INDL/V',
32: 'INDL/A', 33: 'FENDL', 34: 'IRDF', 35: 'BROND',
36: 'INGDB-90', 37: 'FENDL/A', 41: 'BROND'}
_SUBLIBRARY = {
0: 'Photo-nuclear data',
1: 'Photo-induced fission product yields',
3: 'Photo-atomic data',
4: 'Radioactive decay data',
5: 'Spontaneous fission product yields',
6: 'Atomic relaxation data',
10: 'Incident-neutron data',
11: 'Neutron-induced fission product yields',
12: 'Thermal neutron scattering data',
19: 'Neutron standards',
113: 'Electro-atomic data',
10010: 'Incident-proton data',
10011: 'Proton-induced fission product yields',
10020: 'Incident-deuteron data',
10030: 'Incident-triton data',
20030: 'Incident-helion (3He) data',
20040: 'Incident-alpha data'
}
SUM_RULES = {1: [2, 3],
3: [4, 5, 11, 16, 17, 22, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35,
@ -45,7 +66,7 @@ SUM_RULES = {1: [2, 3],
106: list(range(750, 800)),
107: list(range(800, 850))}
_ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)')
ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)')
def float_endf(s):
@ -68,7 +89,25 @@ def float_endf(s):
The number
"""
return float(_ENDF_FLOAT_RE.sub(r'\1e\2', s))
return float(ENDF_FLOAT_RE.sub(r'\1e\2', s))
def _int_endf(s):
"""Convert string to int. Used for INTG records where blank entries
indicate a 0.
Parameters
----------
s : str
Integer or spaces
Returns
-------
integer
The number or 0
"""
s = s.strip()
return int(s) if s else 0
def get_text_record(file_obj):
@ -250,6 +289,50 @@ def get_tab2_record(file_obj):
return params, Tabulated2D(breakpoints, interpolation)
def get_intg_record(file_obj):
"""
Return data from an INTG record in an ENDF-6 file. Used to store the
covariance matrix in a compact format.
Parameters
----------
file_obj : file-like object
ENDF-6 file to read from
Returns
-------
numpy.ndarray
The correlation matrix described in the INTG record
"""
# determine how many items are in list and NDIGIT
items = get_cont_record(file_obj)
ndigit = int(items[2])
npar = int(items[3]) # Number of parameters
nlines = int(items[4]) # Lines to read
NROW_RULES = {2: 18, 3: 12, 4: 11, 5: 9, 6: 8}
nrow = NROW_RULES[ndigit]
# read lines and build correlation matrix
corr = np.identity(npar)
for i in range(nlines):
line = file_obj.readline()
ii = _int_endf(line[:5]) - 1 # -1 to account for 0 indexing
jj = _int_endf(line[5:10]) - 1
factor = 10**ndigit
for j in range(nrow):
if jj+j >= ii:
break
element = _int_endf(line[11+(ndigit+1)*j:11+(ndigit+1)*(j+1)])
if element > 0:
corr[ii, jj] = (element+0.5)/factor
elif element < 0:
corr[ii, jj] = (element-0.5)/factor
# Symmetrize the correlation matrix
corr = corr + corr.T - np.diag(corr.diagonal())
return corr
def get_evaluations(filename):
"""Return a list of all evaluations within an ENDF file.
@ -288,7 +371,7 @@ class Evaluation(object):
Attributes
----------
info : dict
Miscallaneous information about the evaluation.
Miscellaneous information about the evaluation.
target : dict
Information about the target material, such as its mass, isomeric state,
whether it's stable, and whether it's fissionable.
@ -348,6 +431,14 @@ class Evaluation(object):
self._read_header()
def __repr__(self):
if 'zsymam' in self.target:
name = self.target['zsymam'].replace(' ', '')
else:
name = 'Unknown'
return '<{} for {} {}>'.format(self.info['sublibrary'], name,
self.info['library'])
def _read_header(self):
file_obj = io.StringIO(self.section[1, 451])
@ -360,8 +451,7 @@ class Evaluation(object):
self._LRP = items[2]
self.target['fissionable'] = (items[3] == 1)
try:
global LIBRARIES
library = LIBRARIES[items[4]]
library = _LIBRARY[items[4]]
except KeyError:
library = 'Unknown'
self.info['modification'] = items[5]
@ -384,7 +474,7 @@ class Evaluation(object):
self.projectile['mass'] = items[0]
self.info['energy_max'] = items[1]
library_release = items[2]
self.info['sublibrary'] = items[4]
self.info['sublibrary'] = _SUBLIBRARY[items[4]]
library_version = items[5]
self.info['library'] = (library, library_version, library_release)

View file

@ -6,173 +6,18 @@ import sys
import h5py
import numpy as np
from .data import ATOMIC_SYMBOL, EV_PER_MEV
from .endf import get_cont_record, get_list_record, Evaluation
from .function import Function1D, Tabulated1D, Polynomial, Sum
from .data import EV_PER_MEV
from .endf import get_cont_record, get_list_record, get_tab1_record, Evaluation
from .function import Function1D, Tabulated1D, Polynomial, sum_functions
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
def _extract_458_data(ev):
"""Read an ENDF file and extract the MF=1, MT=458 values.
Parameters
----------
ev : openmc.data.Evaluation
ENDF evaluation
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.
"""
cv.check_type('evaluation', ev, Evaluation)
if not ev.target['fissionable']:
# This nuclide isn't fissionable.
return None
if (1, 458) not in ev.section:
# No 458 data here.
return None
file_obj = StringIO(ev.section[1, 458])
# Read the number of coefficients in this LIST record.
items = get_cont_record(file_obj)
NPL = items[3]
# Parse the ENDF LIST into an array.
items, data = get_list_record(file_obj)
# 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] /= EV_PER_MEV
for coeffs in uncertainty.values():
coeffs[2] /= EV_PER_MEV
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='earliest')
# 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)
ev = Evaluation(fname)
# Skip non-fissionable nuclides.
if not ev.target['fissionable']:
continue
# Get the important bits.
data = _extract_458_data(ev)
if data is None: continue
value, uncertainty = data
# Make a group for this isomer.
name = ATOMIC_SYMBOL[ev.target['atomic_number']] + \
str(ev.target['mass_number'])
if ev.target['isomeric_state'] != 0:
name += '_m' + str(ev.target['isomeric_state'])
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()
_NAMES = (
'fragments', 'prompt_neutrons', 'delayed_neutrons',
'prompt_photons', 'delayed_photons', 'betas',
'neutrinos', 'recoverable', 'total'
)
class FissionEnergyRelease(EqualityMixin):
@ -249,14 +94,15 @@ class FissionEnergyRelease(EqualityMixin):
- 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
def __init__(self, fragments, prompt_neutrons, delayed_neutrons,
prompt_photons, delayed_photons, betas, neutrinos):
self.fragments = fragments
self.prompt_neutrons = prompt_neutrons
self.delayed_neutrons = delayed_neutrons
self.prompt_photons = prompt_photons
self.delayed_photons = delayed_photons
self.betas = betas
self.neutrinos = neutrinos
@property
def fragments(self):
@ -288,27 +134,33 @@ class FissionEnergyRelease(EqualityMixin):
@property
def recoverable(self):
return Sum([self.fragments, self.prompt_neutrons, self.delayed_neutrons,
self.prompt_photons, self.delayed_photons, self.betas])
components = ['fragments', 'prompt_neutrons', 'delayed_neutrons',
'prompt_photons', 'delayed_photons', 'betas']
return sum_functions(getattr(self, c) for c in components)
@property
def total(self):
return Sum([self.fragments, self.prompt_neutrons, self.delayed_neutrons,
self.prompt_photons, self.delayed_photons, self.betas,
self.neutrinos])
components = ['fragments', 'prompt_neutrons', 'delayed_neutrons',
'prompt_photons', 'delayed_photons', 'betas',
'neutrinos']
return sum_functions(getattr(self, c) for c in components)
@property
def q_prompt(self):
return Sum([self.fragments, self.prompt_neutrons, self.prompt_photons,
lambda E: -E])
# Use a polynomial to subtract incident energy.
funcs = [self.fragments, self.prompt_neutrons, self.prompt_photons,
Polynomial((0.0, -1.0))]
return sum_functions(funcs)
@property
def q_recoverable(self):
return Sum([self.recoverable, lambda E: -E])
# Use a polynomial to subtract incident energy.
return sum_functions([self.recoverable, Polynomial((0.0, -1.0))])
@property
def q_total(self):
return Sum([self.total, lambda E: -E])
# Use a polynomial to subtract incident energy.
return sum_functions([self.total, Polynomial((0.0, -1.0))])
@fragments.setter
def fragments(self, energy_release):
@ -345,92 +197,6 @@ class FissionEnergyRelease(EqualityMixin):
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 = [p.yield_ for p in incident_neutron[18].products
if p.particle == 'neutron'
and p.emission_mode in ('prompt', 'total')]
elif 19 in incident_neutron.reactions:
nu = [p.yield_ for p in incident_neutron[19].products
if p.particle == 'neutron'
and p.emission_mode in ('prompt', 'total')]
else:
raise ValueError('IncidentNeutron data has no fission '
'reaction.')
if len(nu) == 0:
raise ValueError('Nu data is needed to compute fission energy '
'release with the Sher-Beck format.')
if len(nu) > 1:
raise ValueError('Ambiguous prompt/total nu value.')
nu = nu[0]
if isinstance(nu, Tabulated1D):
ENP = deepcopy(nu)
ENP.y = (energy_release['ENP'] + 1.307 * nu.x
- 8.07e6 * (nu.y - nu.y[0]))
elif isinstance(nu, Polynomial):
if len(nu) == 1:
ENP = Polynomial([energy_release['ENP'][0], 1.307])
else:
ENP = Polynomial(
[energy_release['ENP'][0], 1.307 - 8.07e6*nu.coef[1]]
+ [-8.07e6*c for c in nu.coef[2:]])
out.prompt_neutrons = ENP
return out
@classmethod
def from_endf(cls, ev, incident_neutron):
"""Generate fission energy release data from an ENDF file.
@ -458,16 +224,117 @@ class FissionEnergyRelease(EqualityMixin):
raise ValueError('The atomic mass of the ENDF evaluation does '
'not match the given IncidentNeutron.')
if ev.target['isomeric_state'] != incident_neutron.metastable:
raise ValueError('The metastable state of the ENDF evaluation does '
'not match the given IncidentNeutron.')
raise ValueError('The metastable state of the ENDF evaluation '
'does not match the given IncidentNeutron.')
if not ev.target['fissionable']:
raise ValueError('The ENDF evaluation is not fissionable.')
# Read the 458 data from the ENDF file.
value, uncertainty = _extract_458_data(ev)
if (1, 458) not in ev.section:
raise ValueError('ENDF evaluation does not have MF=1, MT=458.')
# Build the object.
return cls._from_dictionary(value, incident_neutron)
file_obj = StringIO(ev.section[1, 458])
# Read first record and check whether any components appear as
# tabulated functions
items = get_cont_record(file_obj)
lfc = items[3]
nfc = items[5]
# Parse the ENDF LIST into an array.
items, data = get_list_record(file_obj)
npoly = items[3]
# Associate each set of values and uncertainties with its label.
functions = {}
for i, name in enumerate(_NAMES):
coeffs = data[2*i::18]
# Ignore recoverable and total since we recalculate those directly
if name in ('recoverable', 'total'):
continue
# In ENDF/B-VII.1, data for 2nd-order coefficients were mistakenly
# not converted from MeV to eV. Check for this error and fix it if
# present.
if npoly == 2: # Only check 2nd-order data.
# If a 5 MeV neutron causes a change of more than 100 MeV, we
# know something is wrong.
second_order = coeffs[2]
if abs(second_order) * (5e6)**2 > 1e8:
# If we found the error, reduce 2nd-order coeff by 10**6.
coeffs[2] /= EV_PER_MEV
# If multiple coefficients were given, we can create the polynomial
# and move on to the next component
if npoly > 0:
functions[name] = Polynomial(coeffs)
continue
# If a single coefficient was given, we need to use the Sher-Beck
# formula for energy dependence
zeroth_order = coeffs[0]
if name in ('delayed_photons', 'betas'):
func = Polynomial((zeroth_order, -0.075))
elif name == 'neutrinos':
func = Polynomial((zeroth_order, -0.105))
elif name == 'prompt_neutrons':
# 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 = [p.yield_ for p in incident_neutron[18].products
if p.particle == 'neutron'
and p.emission_mode in ('prompt', 'total')]
elif 19 in incident_neutron.reactions:
nu = [p.yield_ for p in incident_neutron[19].products
if p.particle == 'neutron'
and p.emission_mode in ('prompt', 'total')]
else:
raise ValueError('IncidentNeutron data has no fission '
'reaction.')
if len(nu) == 0:
raise ValueError(
'Nu data is needed to compute fission energy '
'release with the Sher-Beck format.'
)
if len(nu) > 1:
raise ValueError('Ambiguous prompt/total nu value.')
nu = nu[0]
if isinstance(nu, Tabulated1D):
# Evaluate Sher-Beck polynomial form at each tabulated value
func = deepcopy(nu)
func.y = (zeroth_order + 1.307*nu.x - 8.07e6*(nu.y - nu.y[0]))
elif isinstance(nu, Polynomial):
# Combine polynomials
if len(nu) == 1:
func = Polynomial([zeroth_order, 1.307])
else:
func = Polynomial(
[zeroth_order, 1.307 - 8.07e6*nu.coef[1]]
+ [-8.07e6*c for c in nu.coef[2:]])
else:
func = Polynomial(coeffs)
functions[name] = func
# Check for tabulated data
if lfc == 1:
for _ in range(nfc):
# Get tabulated function
items, eifc = get_tab1_record(file_obj)
# Determine which component it is
ifc = items[3]
name = _NAMES[ifc - 1]
# Replace value in dictionary
functions[name] = eifc
# Build the object
return cls(**functions)
@classmethod
def from_hdf5(cls, group):
@ -485,54 +352,16 @@ class FissionEnergyRelease(EqualityMixin):
"""
obj = cls()
fragments = Function1D.from_hdf5(group['fragments'])
prompt_neutrons = Function1D.from_hdf5(group['prompt_neutrons'])
delayed_neutrons = Function1D.from_hdf5(group['delayed_neutrons'])
prompt_photons = Function1D.from_hdf5(group['prompt_photons'])
delayed_photons = Function1D.from_hdf5(group['delayed_photons'])
betas = Function1D.from_hdf5(group['betas'])
neutrinos = Function1D.from_hdf5(group['neutrinos'])
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)
return cls(fragments, prompt_neutrons, delayed_neutrons, prompt_photons,
delayed_photons, betas, neutrinos)
def to_hdf5(self, group):
"""Write energy release data to an HDF5 group
@ -551,33 +380,5 @@ class FissionEnergyRelease(EqualityMixin):
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')
self.q_prompt.to_hdf5(group, 'q_prompt')
self.q_recoverable.to_hdf5(group, 'q_recoverable')

View file

@ -1,5 +1,7 @@
from abc import ABCMeta, abstractmethod
from collections.abc import Iterable, Callable
from functools import reduce
from itertools import zip_longest
from numbers import Real, Integral
import numpy as np
@ -13,6 +15,46 @@ INTERPOLATION_SCHEME = {1: 'histogram', 2: 'linear-linear', 3: 'linear-log',
4: 'log-linear', 5: 'log-log'}
def sum_functions(funcs):
"""Add tabulated/polynomials functions together
Parameters
----------
funcs : list of Function1D
Functions to add
Returns
-------
Function1D
Sum of polynomial/tabulated functions
"""
# Copy so we can iterate multiple times
funcs = list(funcs)
# Get x values for all tabulated components
xs = []
for f in funcs:
if isinstance(f, Tabulated1D):
xs.append(f.x)
if not np.all(f.interpolation == 2):
raise ValueError('Only linear-linear tabulated functions '
'can be combined')
if xs:
# Take the union of all energies (sorted)
x = reduce(np.union1d, xs)
# Evaluate each function and add together
y = sum(f(x) for f in funcs)
return Tabulated1D(x, y)
else:
# If no tabulated functions are present, we need to combine the
# polynomials by adding their coefficients
coeffs = [sum(x) for x in zip_longest(*funcs, fillvalue=0.0)]
return Polynomial(coeffs)
class Function1D(EqualityMixin, metaclass=ABCMeta):
"""A function of one independent variable with HDF5 support."""
@abstractmethod

View file

@ -52,14 +52,17 @@ class DataLibrary(EqualityMixin):
Path to the file to be registered.
"""
h5file = h5py.File(filename, 'r')
with h5py.File(filename, 'r') as h5file:
materials = []
filetype = 'neutron'
for name in h5file:
if name.startswith('c_'):
filetype = 'thermal'
materials.append(name)
materials = []
if 'filetype' in h5file.attrs:
filetype = h5file.attrs['filetype'].decode().lstrip('data_')
else:
filetype = 'neutron'
for name in h5file:
if name.startswith('c_'):
filetype = 'thermal'
materials.append(name)
library = {'path': filename, 'type': filetype, 'materials': materials}
self.libraries.append(library)
@ -81,10 +84,9 @@ class DataLibrary(EqualityMixin):
if common_dir == '':
common_dir = '.'
directory = os.path.relpath(common_dir, os.path.dirname(path))
if directory != '.':
if os.path.relpath(common_dir, os.path.dirname(path)) != '.':
dir_element = ET.SubElement(root, "directory")
dir_element.text = directory
dir_element.text = os.path.realpath(common_dir)
for library in self.libraries:
lib_element = ET.SubElement(root, "library")

View file

@ -14,8 +14,8 @@ import numpy as np
import h5py
from . import HDF5_VERSION, HDF5_VERSION_MAJOR
from .ace import Library, Table, get_table
from .data import ATOMIC_SYMBOL, K_BOLTZMANN, EV_PER_MEV, gnd_name
from .ace import Library, Table, get_table, get_metadata
from .data import ATOMIC_SYMBOL, K_BOLTZMANN, EV_PER_MEV
from .endf import Evaluation, SUM_RULES, get_head_record, get_tab1_record
from .fission_energy import FissionEnergyRelease
from .function import Tabulated1D, Sum, ResonancesWithBackground
@ -24,6 +24,7 @@ from .njoy import make_ace
from .product import Product
from .reaction import Reaction, _get_photon_products_ace
from . import resonance as res
from . import resonance_covariance as res_cov
from .urr import ProbabilityTables
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
@ -33,88 +34,26 @@ from openmc.mixin import EqualityMixin
_RESONANCE_ENERGY_GRID = np.logspace(-3, 3, 61)
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 = gnd_name(Z, mass_number, 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
created using the factory methods :meth:`IncidentNeutron.from_hdf5` and
:meth:`IncidentNeutron.from_ace`.
This class stores data derived from an ENDF-6 format neutron interaction
sublibrary. Instances of this class are not normally instantiated by the
user but rather created using the factory methods
:meth:`IncidentNeutron.from_hdf5`, :meth:`IncidentNeutron.from_ace`, and
:meth:`IncidentNeutron.from_endf`.
Parameters
----------
name : str
Name of the nuclide using the GND naming convention
atomic_number : int
Number of protons in the nucleus
Number of protons in the target nucleus
mass_number : int
Number of nucleons in the nucleus
Number of nucleons in the target nucleus
metastable : int
Metastable state of the nucleus. A value of zero indicates ground state.
Metastable state of the target nucleus. A value of zero indicates ground
state.
atomic_weight_ratio : float
Atomic mass ratio of the target nuclide.
kTs : Iterable of float
@ -124,22 +63,19 @@ class IncidentNeutron(EqualityMixin):
Attributes
----------
atomic_number : int
Number of protons in the nucleus
Number of protons in the target nucleus
atomic_symbol : str
Atomic symbol of the nuclide, e.g., 'Zr'
atomic_weight_ratio : float
Atomic weight ratio of the target nuclide.
energy : dict of numpy.ndarray
The energy values (eV) 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
Number of nucleons in the target nucleus
metastable : int
Metastable state of the nucleus. A value of zero indicates ground state.
Metastable state of the target nucleus. A value of zero indicates ground
state.
name : str
Name of the nuclide using the GND naming convention
reactions : collections.OrderedDict
@ -148,6 +84,8 @@ class IncidentNeutron(EqualityMixin):
and the values are Reaction objects.
resonances : openmc.data.Resonances or None
Resonance parameters
resonance_covariance : openmc.data.ResonanceCovariance or None
Covariance for resonance parameters
summed_reactions : collections.OrderedDict
Contains summed cross sections, e.g., the total cross section. The keys
are the MT values and the values are Reaction objects.
@ -228,6 +166,10 @@ class IncidentNeutron(EqualityMixin):
def resonances(self):
return self._resonances
@property
def resonance_covariance(self):
return self._resonance_covariance
@property
def summed_reactions(self):
return self._summed_reactions
@ -289,6 +231,12 @@ class IncidentNeutron(EqualityMixin):
cv.check_type('resonances', resonances, res.Resonances)
self._resonances = resonances
@resonance_covariance.setter
def resonance_covariance(self, resonance_covariance):
cv.check_type('resonance covariance', resonance_covariance,
res_cov.ResonanceCovariances)
self._resonance_covariance = resonance_covariance
@summed_reactions.setter
def summed_reactions(self, summed_reactions):
cv.check_type('summed reactions', summed_reactions, Mapping)
@ -499,6 +447,7 @@ class IncidentNeutron(EqualityMixin):
# Open file and write version
f = h5py.File(path, mode, libver=libver)
f.attrs['filetype'] = np.string_('data_neutron')
f.attrs['version'] = np.array(HDF5_VERSION)
# Write basic data
@ -673,7 +622,7 @@ class IncidentNeutron(EqualityMixin):
# If mass number hasn't been specified, make an educated guess
zaid, xs = ace.name.split('.')
name, element, Z, mass_number, metastable = \
_get_metadata(int(zaid), metastable_scheme)
get_metadata(int(zaid), metastable_scheme)
# Assign temperature to the running list
kTs = [ace.temperature*EV_PER_MEV]
@ -744,7 +693,7 @@ class IncidentNeutron(EqualityMixin):
return data
@classmethod
def from_endf(cls, ev_or_filename):
def from_endf(cls, ev_or_filename, covariance=False):
"""Generate incident neutron continuous-energy data from an ENDF evaluation
Parameters
@ -753,6 +702,10 @@ class IncidentNeutron(EqualityMixin):
ENDF evaluation to read from. If given as a string, it is assumed to
be the filename for the ENDF file.
covariance : bool
Flag to indicate whether or not covariance data from File 32 should be
retrieved
Returns
-------
openmc.data.IncidentNeutron
@ -784,6 +737,11 @@ class IncidentNeutron(EqualityMixin):
if (2, 151) in ev.section:
data.resonances = res.Resonances.from_endf(ev)
if (32, 151) in ev.section and covariance:
data.resonance_covariance = (
res_cov.ResonanceCovariances.from_endf(ev, data.resonances)
)
# Read each reaction
for mf, mt, nc, mod in ev.reaction_list:
if mf == 3:

1005
openmc/data/photon.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1093,7 +1093,7 @@ class Reaction(EqualityMixin):
ev : openmc.data.endf.Evaluation
ENDF evaluation
mt : int
The MT value of the reaction to get angular distributions for
The MT value of the reaction to get data for
Returns
-------

View file

@ -16,6 +16,7 @@ except ImportError:
_reconstruct = False
import openmc.checkvalue as cv
class Resonances(object):
"""Resolved and unresolved resonance data
@ -90,14 +91,14 @@ class Resonances(object):
# Determine whether discrete or continuous representation
items = get_head_record(file_obj)
n_isotope = items[4] # Number of isotopes
n_isotope = items[4] # Number of isotopes
ranges = []
for iso in range(n_isotope):
items = get_cont_record(file_obj)
abundance = items[1]
fission_widths = (items[3] == 1) # fission widths are given?
n_ranges = items[4] # number of resonance energy ranges
fission_widths = (items[3] == 1) # fission widths are given?
n_ranges = items[4] # number of resonance energy ranges
for j in range(n_ranges):
items = get_cont_record(file_obj)
@ -112,7 +113,7 @@ class Resonances(object):
# unresolved resonance region
erange = Unresolved.from_endf(file_obj, items, fission_widths)
#erange.material = self
# erange.material = self
ranges.append(erange)
return cls(ranges)
@ -162,6 +163,13 @@ class ResonanceRange(object):
self._prepared = False
self._parameter_matrix = {}
def __copy__(self):
cls = type(self)
new_copy = cls.__new__(cls)
new_copy.__dict__.update(self.__dict__)
new_copy._prepared = False
return new_copy
@classmethod
def from_endf(cls, ev, file_obj, items):
"""Create resonance range from an ENDF evaluation.
@ -437,7 +445,7 @@ class MultiLevelBreitWigner(ResonanceRange):
self._l_values = np.array(l_values)
self._competitive = np.array(competitive)
for l in l_values:
self._parameter_matrix[l] = df[df.L == l].as_matrix()
self._parameter_matrix[l] = df[df.L == l].values
self._prepared = True
@ -682,7 +690,7 @@ class ReichMoore(ResonanceRange):
self._l_values = np.array(l_values)
for (l, J) in lj_values:
self._parameter_matrix[l, J] = df[(df.L == l) &
(abs(df.J) == J)].as_matrix()
(abs(df.J) == J)].values
self._prepared = True

View file

@ -0,0 +1,708 @@
from collections import MutableSequence
import warnings
import io
import copy
import numpy as np
import pandas as pd
from . import endf
import openmc.checkvalue as cv
from .resonance import Resonances
def _add_file2_contributions(file32params, file2params):
"""Function for aiding in adding resonance parameters from File 2 that are
not always present in File 32. Uses already imported resonance data.
Paramaters
----------
file32params : pandas.Dataframe
Incomplete set of resonance parameters contained in File 32.
file2params : pandas.Dataframe
Resonance parameters from File 2. Ordered by energy.
Returns
-------
parameters : pandas.Dataframe
Complete set of parameters ordered by L-values and then energy
"""
# Use l-values and competitiveWidth from File 2 data
# Re-sort File 2 by energy to match File 32
file2params = file2params.sort_values(by=['energy'])
file2params.reset_index(drop=True, inplace=True)
# Sort File 32 parameters by energy as well (maintaining index)
file32params.sort_values(by=['energy'], inplace=True)
# Add in values (.values converts to array first to ignore index)
file32params['L'] = file2params['L'].values
if 'competitiveWidth' in file2params.columns:
file32params['competitiveWidth'] = file2params['competitiveWidth'].values
# Resort to File 32 order (by L then by E) for use with covariance
file32params.sort_index(inplace=True)
return file32params
class ResonanceCovariances(Resonances):
"""Resolved resonance covariance data
Parameters
----------
ranges : list of openmc.data.ResonanceCovarianceRange
Distinct energy ranges for resonance data
Attributes
----------
ranges : list of openmc.data.ResonanceCovarianceRange
Distinct energy ranges for resonance data
"""
@property
def ranges(self):
return self._ranges
@ranges.setter
def ranges(self, ranges):
cv.check_type('resonance ranges', ranges, MutableSequence)
self._ranges = cv.CheckedList(ResonanceCovarianceRange,
'resonance range', ranges)
@classmethod
def from_endf(cls, ev, resonances):
"""Generate resonance covariance data from an ENDF evaluation.
Parameters
----------
ev : openmc.data.endf.Evaluation
ENDF evaluation
resonances : openmc.data.Resonance object
openmc.data.Resonanance object generated from the same evaluation
used to import values not contained in File 32
Returns
-------
openmc.data.ResonanceCovariances
Resonance covariance data
"""
file_obj = io.StringIO(ev.section[32, 151])
# Determine whether discrete or continuous representation
items = endf.get_head_record(file_obj)
n_isotope = items[4] # Number of isotopes
ranges = []
for iso in range(n_isotope):
items = endf.get_cont_record(file_obj)
abundance = items[1]
fission_widths = (items[3] == 1) # Flag for fission widths
n_ranges = items[4] # Number of resonance energy ranges
for j in range(n_ranges):
items = endf.get_cont_record(file_obj)
# Unresolved flags - 0: only scattering radius given
# 1: resolved parameters given
# 2: unresolved parameters given
unresolved_flag = items[2]
formalism = items[3] # resonance formalism
# Throw error for unsupported formalisms
if formalism in [0, 7]:
error = 'LRF='+str(formalism)+' covariance not supported '\
'for this formalism'
raise NotImplementedError(error)
if unresolved_flag in (0, 1):
# Resolved resonance region
resonance = resonances.ranges[j]
erange = _FORMALISMS[formalism].from_endf(ev, file_obj,
items, resonance)
ranges.append(erange)
elif unresolved_flag == 2:
warn = 'Unresolved resonance not supported. Covariance '\
'values for the unresolved region not imported.'
warnings.warn(warn)
return cls(ranges)
class ResonanceCovarianceRange:
"""Resonace covariance range. Base class for different formalisms.
Parameters
----------
energy_min : float
Minimum energy of the resolved resonance range in eV
energy_max : float
Maximum energy of the resolved resonance range in eV
Attributes
----------
energy_min : float
Minimum energy of the resolved resonance range in eV
energy_max : float
Maximum energy of the resolved resonance range in eV
parameters : pandas.DataFrame
Resonance parameters
covariance : numpy.array
The covariance matrix contained within the ENDF evaluation
lcomp : int
Flag indicating format of the covariance matrix within the ENDF file
file2res : openmc.data.ResonanceRange object
Corresponding resonance range with File 2 data.
mpar : int
Number of parameters in covariance matrix for each individual resonance
formalism : str
String descriptor of formalism
"""
def __init__(self, energy_min, energy_max):
self.energy_min = energy_min
self.energy_max = energy_max
def subset(self, parameter_str, bounds):
"""Produce a subset of resonance parameters and the corresponding
covariance matrix to an IncidentNeutron object.
Parameters
----------
parameter_str : str
parameter to be discriminated
(i.e. 'energy', 'captureWidth', 'fissionWidthA'...)
bounds : np.array
[low numerical bound, high numerical bound]
Returns
-------
res_cov_range : openmc.data.ResonanceCovarianceRange
ResonanceCovarianceRange object that contains a subset of the
covariance matrix (upper triangular) as well as a subset parameters
within self.file2params
"""
# Copy range and prevent change of original
res_cov_range = copy.deepcopy(self)
parameters = self.file2res.parameters
cov = res_cov_range.covariance
mpar = res_cov_range.mpar
# Create mask
mask1 = parameters[parameter_str] >= bounds[0]
mask2 = parameters[parameter_str] <= bounds[1]
mask = mask1 & mask2
res_cov_range.parameters = parameters[mask]
indices = res_cov_range.parameters.index.values
# Build subset of covariance
sub_cov_dim = len(indices)*mpar
cov_subset_vals = []
for index1 in indices:
for i in range(mpar):
for index2 in indices:
for j in range(mpar):
if index2*mpar+j >= index1*mpar+i:
cov_subset_vals.append(cov[index1*mpar+i,
index2*mpar+j])
cov_subset = np.zeros([sub_cov_dim, sub_cov_dim])
tri_indices = np.triu_indices(sub_cov_dim)
cov_subset[tri_indices] = cov_subset_vals
res_cov_range.file2res.parameters = parameters[mask]
res_cov_range.covariance = cov_subset
return res_cov_range
def sample(self, n_samples):
"""Sample resonance parameters based on the covariances provided
within an ENDF evaluation.
Parameters
----------
n_samples : int
The number of samples to produce
Returns
-------
samples : list of openmc.data.ResonanceCovarianceRange objects
List of samples size `n_samples`
"""
warn_str = 'Sampling routine does not guarantee positive values for '\
'parameters. This can lead to undefined behavior in the '\
'reconstruction routine.'
warnings.warn(warn_str)
parameters = self.parameters
cov = self.covariance
# Symmetrizing covariance matrix
cov = cov + cov.T - np.diag(cov.diagonal())
formalism = self.formalism
mpar = self.mpar
samples = []
# Handling MLBW/SLBW sampling
if formalism == 'mlbw' or formalism == 'slbw':
params = ['energy', 'neutronWidth', 'captureWidth', 'fissionWidth',
'competitiveWidth']
param_list = params[:mpar]
mean_array = parameters[param_list].values
mean = mean_array.flatten()
par_samples = np.random.multivariate_normal(mean, cov,
size=n_samples)
spin = parameters['J'].values
l_value = parameters['L'].values
for sample in par_samples:
energy = sample[0::mpar]
gn = sample[1::mpar]
gg = sample[2::mpar]
gf = sample[3::mpar] if mpar > 3 else parameters['fissionWidth'].values
gx = sample[4::mpar] if mpar > 4 else parameters['competitiveWidth'].values
gt = gn + gg + gf + gx
records = []
for j, E in enumerate(energy):
records.append([energy[j], l_value[j], spin[j], gt[j],
gn[j], gg[j], gf[j], gx[j]])
columns = ['energy', 'L', 'J', 'totalWidth', 'neutronWidth',
'captureWidth', 'fissionWidth', 'competitiveWidth']
sample_params = pd.DataFrame.from_records(records,
columns=columns)
# Copy ResonanceRange object
res_range = copy.copy(self.file2res)
res_range.parameters = sample_params
samples.append(res_range)
# Handling RM sampling
elif formalism == 'rm':
params = ['energy', 'neutronWidth', 'captureWidth',
'fissionWidthA', 'fissionWidthB']
param_list = params[:mpar]
mean_array = parameters[param_list].values
mean = mean_array.flatten()
par_samples = np.random.multivariate_normal(mean, cov,
size=n_samples)
spin = parameters['J'].values
l_value = parameters['L'].values
for sample in par_samples:
energy = sample[0::mpar]
gn = sample[1::mpar]
gg = sample[2::mpar]
gfa = sample[3::mpar] if mpar > 3 else parameters['fissionWidthA'].values
gfb = sample[4::mpar] if mpar > 3 else parameters['fissionWidthB'].values
records = []
for j, E in enumerate(energy):
records.append([energy[j], l_value[j], spin[j], gn[j],
gg[j], gfa[j], gfb[j]])
columns = ['energy', 'L', 'J', 'neutronWidth',
'captureWidth', 'fissionWidthA', 'fissionWidthB']
sample_params = pd.DataFrame.from_records(records,
columns=columns)
# Copy ResonanceRange object
res_range = copy.copy(self.file2res)
res_range.parameters = sample_params
samples.append(res_range)
return samples
class MultiLevelBreitWignerCovariance(ResonanceCovarianceRange):
"""Multi-level Breit-Wigner resolved resonance formalism covariance data.
Parameters
----------
energy_min : float
Minimum energy of the resolved resonance range in eV
energy_max : float
Maximum energy of the resolved resonance range in eV
Attributes
----------
energy_min : float
Minimum energy of the resolved resonance range in eV
energy_max : float
Maximum energy of the resolved resonance range in eV
parameters : pandas.DataFrame
Resonance parameters
covariance : numpy.array
The covariance matrix contained within the ENDF evaluation
mpar : int
Number of parameters in covariance matrix for each individual resonance
lcomp : int
Flag indicating format of the covariance matrix within the ENDF file
file2res : openmc.data.ResonanceRange object
Corresponding resonance range with File 2 data.
formalism : str
String descriptor of formalism
"""
def __init__(self, energy_min, energy_max, parameters, covariance, mpar,
lcomp, file2res):
super().__init__(energy_min, energy_max)
self.parameters = parameters
self.covariance = covariance
self.mpar = mpar
self.lcomp = lcomp
self.file2res = copy.copy(file2res)
self.formalism = 'mlbw'
@classmethod
def from_endf(cls, ev, file_obj, items, resonance):
"""Create MLBW covariance data from an ENDF evaluation.
Parameters
----------
ev : openmc.data.endf.Evaluation
ENDF evaluation
file_obj : file-like object
ENDF file positioned at the second record of a resonance range
subsection in MF=32, MT=151
items : list
Items from the CONT record at the start of the resonance range
subsection
resonance : openmc.data.ResonanceRange object
Corresponding resonance range with File 2 data.
Returns
-------
openmc.data.MultiLevelBreitWignerCovariance
Multi-level Breit-Wigner resonance covariance parameters
"""
# Read energy-dependent scattering radius if present
energy_min, energy_max = items[0:2]
nro, naps = items[4:6]
if nro != 0:
params, ape = endf.get_tab1_record(file_obj)
# Other scatter radius parameters
items = endf.get_cont_record(file_obj)
target_spin = items[0]
lcomp = items[3] # Flag for compatibility 0, 1, 2 - 2 is compact form
nls = items[4] # number of l-values
# Build covariance matrix for General Resolved Resonance Formats
if lcomp == 1:
items = endf.get_cont_record(file_obj)
# Number of short range type resonance covariances
num_short_range = items[4]
# Number of long range type resonance covariances
num_long_range = items[5]
# Read resonance widths, J values, etc
records = []
for i in range(num_short_range):
items, values = endf.get_list_record(file_obj)
mpar = items[2]
num_res = items[5]
num_par_vals = num_res*6
res_values = values[:num_par_vals]
cov_values = values[num_par_vals:]
energy = res_values[0::6]
spin = res_values[1::6]
gt = res_values[2::6]
gn = res_values[3::6]
gg = res_values[4::6]
gf = res_values[5::6]
for i, E in enumerate(energy):
records.append([energy[i], spin[i], gt[i], gn[i],
gg[i], gf[i]])
# Build the upper-triangular covariance matrix
cov_dim = mpar*num_res
cov = np.zeros([cov_dim, cov_dim])
indices = np.triu_indices(cov_dim)
cov[indices] = cov_values
# Compact format - Resonances and individual uncertainties followed by
# compact correlations
elif lcomp == 2:
items, values = endf.get_list_record(file_obj)
mean = items
num_res = items[5]
energy = values[0::12]
spin = values[1::12]
gt = values[2::12]
gn = values[3::12]
gg = values[4::12]
gf = values[5::12]
par_unc = []
for i in range(num_res):
res_unc = values[i*12+6 : i*12+12]
# Delete 0 values (not provided, no fission width)
# DAJ/DGT always zero, DGF sometimes nonzero [1, 2, 5]
res_unc_nonzero = []
for j in range(6):
if j in [1, 2, 5] and res_unc[j] != 0.0:
res_unc_nonzero.append(res_unc[j])
elif j in [0, 3, 4]:
res_unc_nonzero.append(res_unc[j])
par_unc.extend(res_unc_nonzero)
records = []
for i, E in enumerate(energy):
records.append([energy[i], spin[i], gt[i], gn[i],
gg[i], gf[i]])
corr = endf.get_intg_record(file_obj)
cov = np.diag(par_unc).dot(corr).dot(np.diag(par_unc))
# Compatible resolved resonance format
elif lcomp == 0:
cov = np.zeros([4, 4])
records = []
cov_index = 0
for i in range(nls):
items, values = endf.get_list_record(file_obj)
num_res = items[5]
for j in range(num_res):
one_res = values[18*j:18*(j+1)]
res_values = one_res[:6]
cov_values = one_res[6:]
records.append(list(res_values))
# Populate the coviariance matrix for this resonance
# There are no covariances between resonances in lcomp=0
cov[cov_index, cov_index] = cov_values[0]
cov[cov_index+1, cov_index+1 : cov_index+2] = cov_values[1:2]
cov[cov_index+1, cov_index+3] = cov_values[4]
cov[cov_index+2, cov_index+2] = cov_values[3]
cov[cov_index+2, cov_index+3] = cov_values[5]
cov[cov_index+3, cov_index+3] = cov_values[6]
cov_index += 4
if j < num_res-1: # Pad matrix for additional values
cov = np.pad(cov, ((0, 4), (0, 4)), 'constant',
constant_values=0)
# Create pandas DataFrame with resonance data, currently
# redundant with data.IncidentNeutron.resonance
columns = ['energy', 'J', 'totalWidth', 'neutronWidth',
'captureWidth', 'fissionWidth']
parameters = pd.DataFrame.from_records(records, columns=columns)
# Determine mpar (number of parameters for each resonance in
# covariance matrix)
nparams, params = parameters.shape
covsize = cov.shape[0]
mpar = int(covsize/nparams)
# Add parameters from File 2
parameters = _add_file2_contributions(parameters,
resonance.parameters)
# Create instance of class
mlbw = cls(energy_min, energy_max, parameters, cov, mpar, lcomp,
resonance)
return mlbw
class SingleLevelBreitWignerCovariance(MultiLevelBreitWignerCovariance):
"""Single-level Breit-Wigner resolved resonance formalism covariance data.
Single-level Breit-Wigner resolved resonance data is is identified by LRF=1
in the ENDF-6 format.
Parameters
----------
energy_min : float
Minimum energy of the resolved resonance range in eV
energy_max : float
Maximum energy of the resolved resonance range in eV
Attributes
----------
energy_min : float
Minimum energy of the resolved resonance range in eV
energy_max : float
Maximum energy of the resolved resonance range in eV
parameters : pandas.DataFrame
Resonance parameters
covariance : numpy.array
The covariance matrix contained within the ENDF evaluation
mpar : int
Number of parameters in covariance matrix for each individual resonance
formalism : str
String descriptor of formalism
lcomp : int
Flag indicating format of the covariance matrix within the ENDF file
file2res : openmc.data.ResonanceRange object
Corresponding resonance range with File 2 data.
"""
def __init__(self, energy_min, energy_max, parameters, covariance, mpar,
lcomp, file2res):
super().__init__(energy_min, energy_max, parameters, covariance, mpar,
lcomp, file2res)
self.formalism = 'slbw'
class ReichMooreCovariance(ResonanceCovarianceRange):
"""Reich-Moore resolved resonance formalism covariance data.
Reich-Moore resolved resonance data is identified by LRF=3 in the ENDF-6
format.
Parameters
----------
energy_min : float
Minimum energy of the resolved resonance range in eV
energy_max : float
Maximum energy of the resolved resonance range in eV
Attributes
----------
energy_min : float
Minimum energy of the resolved resonance range in eV
energy_max : float
Maximum energy of the resolved resonance range in eV
parameters : pandas.DataFrame
Resonance parameters
covariance : numpy.array
The covariance matrix contained within the ENDF evaluation
lcomp : int
Flag indicating format of the covariance matrix within the ENDF file
mpar : int
Number of parameters in covariance matrix for each individual resonance
file2res : openmc.data.ResonanceRange object
Corresponding resonance range with File 2 data.
formalism : str
String descriptor of formalism
"""
def __init__(self, energy_min, energy_max, parameters, covariance, mpar,
lcomp, file2res):
super().__init__(energy_min, energy_max)
self.parameters = parameters
self.covariance = covariance
self.mpar = mpar
self.lcomp = lcomp
self.file2res = copy.copy(file2res)
self.formalism = 'rm'
@classmethod
def from_endf(cls, ev, file_obj, items, resonance):
"""Create Reich-Moore resonance covariance data from an ENDF
evaluation. Includes the resonance parameters contained separately in
File 32.
Parameters
----------
ev : openmc.data.endf.Evaluation
ENDF evaluation
file_obj : file-like object
ENDF file positioned at the second record of a resonance range
subsection in MF=2, MT=151
items : list
Items from the CONT record at the start of the resonance range
subsection
resonance : openmc.data.Resonance object
openmc.data.Resonanance object generated from the same evaluation
used to import values not contained in File 32
Returns
-------
openmc.data.ReichMooreCovariance
Reich-Moore resonance covariance parameters
"""
# Read energy-dependent scattering radius if present
energy_min, energy_max = items[0:2]
nro, naps = items[4:6]
if nro != 0:
params, ape = endf.get_tab1_record(file_obj)
# Other scatter radius parameters
items = endf.get_cont_record(file_obj)
target_spin = items[0]
lcomp = items[3] # Flag for compatibility 0, 1, 2 - 2 is compact form
nls = items[4] # Number of l-values
# Build covariance matrix for General Resolved Resonance Formats
if lcomp == 1:
items = endf.get_cont_record(file_obj)
# Number of short range type resonance covariances
num_short_range = items[4]
# Number of long range type resonance covariances
num_long_range = items[5]
# Read resonance widths, J values, etc
channel_radius = {}
scattering_radius = {}
records = []
for i in range(num_short_range):
items, values = endf.get_list_record(file_obj)
mpar = items[2]
num_res = items[5]
num_par_vals = num_res*6
res_values = values[:num_par_vals]
cov_values = values[num_par_vals:]
energy = res_values[0::6]
spin = res_values[1::6]
gn = res_values[2::6]
gg = res_values[3::6]
gfa = res_values[4::6]
gfb = res_values[5::6]
for i, E in enumerate(energy):
records.append([energy[i], spin[i], gn[i], gg[i],
gfa[i], gfb[i]])
# Build the upper-triangular covariance matrix
cov_dim = mpar*num_res
cov = np.zeros([cov_dim, cov_dim])
indices = np.triu_indices(cov_dim)
cov[indices] = cov_values
# Compact format - Resonances and individual uncertainties followed by
# compact correlations
elif lcomp == 2:
items, values = endf.get_list_record(file_obj)
num_res = items[5]
energy = values[0::12]
spin = values[1::12]
gn = values[2::12]
gg = values[3::12]
gfa = values[4::12]
gfb = values[5::12]
par_unc = []
for i in range(num_res):
res_unc = values[i*12+6 : i*12+12]
# Delete 0 values (not provided in evaluation)
res_unc = [x for x in res_unc if x != 0.0]
par_unc.extend(res_unc)
records = []
for i, E in enumerate(energy):
records.append([energy[i], spin[i], gn[i], gg[i],
gfa[i], gfb[i]])
corr = endf.get_intg_record(file_obj)
cov = np.diag(par_unc).dot(corr).dot(np.diag(par_unc))
# Create pandas DataFrame with resonacne data
columns = ['energy', 'J', 'neutronWidth', 'captureWidth',
'fissionWidthA', 'fissionWidthB']
parameters = pd.DataFrame.from_records(records, columns=columns)
# Determine mpar (number of parameters for each resonance in
# covariance matrix)
nparams, params = parameters.shape
covsize = cov.shape[0]
mpar = int(covsize/nparams)
# Add parameters from File 2
parameters = _add_file2_contributions(parameters,
resonance.parameters)
# Create instance of ReichMooreCovariance
rmc = cls(energy_min, energy_max, parameters, cov, mpar, lcomp,
resonance)
return rmc
_FORMALISMS = {
0: ResonanceCovarianceRange,
1: SingleLevelBreitWignerCovariance,
2: MultiLevelBreitWignerCovariance,
3: ReichMooreCovariance
# 7: RMatrixLimitedCovariance
}

Binary file not shown.

View file

@ -279,6 +279,7 @@ class ThermalScattering(EqualityMixin):
"""
# Open file and write version
f = h5py.File(path, mode, libver=libver)
f.attrs['filetype'] = np.string_('data_thermal')
f.attrs['version'] = np.array(HDF5_VERSION)
# Write basic data

View file

@ -22,7 +22,7 @@ _FILTER_TYPES = (
'universe', 'material', 'cell', 'cellborn', 'surface', 'mesh', 'energy',
'energyout', 'mu', 'polar', 'azimuthal', 'distribcell', 'delayedgroup',
'energyfunction', 'cellfrom', 'legendre', 'spatiallegendre',
'sphericalharmonics', 'zernike'
'sphericalharmonics', 'zernike', 'particle'
)
_CURRENT_NAMES = (
@ -31,6 +31,7 @@ _CURRENT_NAMES = (
'z-min out', 'z-min in', 'z-max out', 'z-max in'
)
_PARTICLE_IDS = {'neutron': 1, 'photon': 2, 'electron': 3, 'positron': 4}
class FilterMeta(ABCMeta):
def __new__(cls, name, bases, namespace, **kwargs):
@ -540,6 +541,46 @@ class SurfaceFilter(WithIDFilter):
expected_type = Surface
class ParticleFilter(Filter):
"""Bins tally events based on the Particle type.
Parameters
----------
bins : str, int, or iterable of Integral
The Particles to tally. Either str with particle type or their
ID numbers can be used ('neutron' = 1, 'photon' = 2, 'electron' = 3,
'positron' = 4).
filter_id : int
Unique identifier for the filter
Attributes
----------
bins : Iterable of Integral
The Particles to tally
id : int
Unique identifier for the filter
num_bins : Integral
The number of filter bins
"""
@property
def bins(self):
return self._bins
@bins.setter
def bins(self, bins):
bins = np.atleast_1d(bins)
cv.check_iterable_type('filter bins', bins, (Integral, str))
for edge in bins:
if isinstance(edge, Integral):
cv.check_value('filter bin', edge, _PARTICLE_IDS.values())
else:
cv.check_value('filter bin', edge, _PARTICLE_IDS.keys())
bins = np.atleast_1d([b if isinstance(b, Integral) else _PARTICLE_IDS[b]
for b in bins])
self._bins = bins
class MeshFilter(Filter):
"""Bins tally event locations onto a regular, rectangular mesh.

View file

@ -27,6 +27,8 @@ class Particle(object):
Type of simulation (criticality or fixed source)
id : long
Identifier of the particle
type : int
Particle type (1 = neutron, 2 = photon, 3 = electron, 4 = positron)
weight : float
Weight of the particle
energy : float
@ -65,6 +67,10 @@ class Particle(object):
def id(self):
return self._f['id'].value
@property
def type(self):
return self._f['type'].value
@property
def n_particles(self):
return self._f['n_particles'].value

View file

@ -30,12 +30,16 @@ class Settings(object):
Indicate whether fission neutrons should be created or not.
cutoff : dict
Dictionary defining weight cutoff and energy cutoff. The dictionary may
have three keys, 'weight', 'weight_avg' and 'energy'. Value for 'weight'
have six keys, 'weight', 'weight_avg', 'energy_neutron', 'energy_photon',
'energy_electron', and 'energy_positron'. Value for 'weight'
should be a float indicating weight cutoff below which particle undergo
Russian roulette. Value for 'weight_avg' should be a float indicating
weight assigned to particles that are not killed after Russian
roulette. Value of energy should be a float indicating energy in eV
below which particle will be killed.
below which particle type will be killed.
electron_treatment : {'led', 'ttb'}
Whether to deposit all energy from electrons locally ('led') or create
secondary bremsstrahlung photons ('ttb').
energy_mode : {'continuous-energy', 'multi-group'}
Set whether the calculation should be continuous-energy or multi-group.
entropy_mesh : openmc.Mesh
@ -68,6 +72,8 @@ class Settings(object):
:tallies: Whether the 'tallies.out' file should be written (bool)
particles : int
Number of particles per generation
photon_transport : bool
Whether to use photon transport.
ptables : bool
Determine whether probability tables are used.
resonance_scattering : dict
@ -171,7 +177,9 @@ class Settings(object):
self._confidence_intervals = None
self._cross_sections = None
self._electron_treatment = None
self._multipole_library = None
self._photon_transport = None
self._ptables = None
self._run_cmfd = None
self._seed = None
@ -256,10 +264,18 @@ class Settings(object):
def confidence_intervals(self):
return self._confidence_intervals
@property
def electron_treatment(self):
return self._electron_treatment
@property
def ptables(self):
return self._ptables
@property
def photon_transport(self):
return self._photon_transport
@property
def run_cmfd(self):
return self._run_cmfd
@ -485,6 +501,16 @@ class Settings(object):
cv.check_type('confidence interval', confidence_intervals, bool)
self._confidence_intervals = confidence_intervals
@electron_treatment.setter
def electron_treatment(self, electron_treatment):
cv.check_value('electron treatment', electron_treatment, ['led', 'ttb'])
self._electron_treatment = electron_treatment
@photon_transport.setter
def photon_transport(self, photon_transport):
cv.check_type('photon transport', photon_transport, bool)
self._photon_transport = photon_transport
@ptables.setter
def ptables(self, ptables):
cv.check_type('probability tables', ptables, bool)
@ -514,16 +540,16 @@ class Settings(object):
raise ValueError(msg)
for key in cutoff:
if key == 'weight':
cv.check_type('weight cutoff', cutoff['weight'], Real)
cv.check_greater_than('weight cutoff', cutoff['weight'], 0.0)
cv.check_type('weight cutoff', cutoff[key], Real)
cv.check_greater_than('weight cutoff', cutoff[key], 0.0)
elif key == 'weight_avg':
cv.check_type('average survival weight', cutoff['weight_avg'],
Real)
cv.check_type('average survival weight', cutoff[key], Real)
cv.check_greater_than('average survival weight',
cutoff['weight_avg'], 0.0)
elif key == 'energy':
cv.check_type('energy cutoff', cutoff['energy'], Real)
cv.check_greater_than('energy cutoff', cutoff['energy'], 0.0)
cutoff[key], 0.0)
elif key in ['energy_neutron', 'energy_photon', 'energy_electron',
'energy_positron']:
cv.check_type('energy cutoff', cutoff[key], Real)
cv.check_greater_than('energy cutoff', cutoff[key], 0.0)
else:
msg = 'Unable to set cutoff to "{0}" which is unsupported by '\
'OpenMC'.format(key)
@ -777,6 +803,16 @@ class Settings(object):
element = ET.SubElement(root, "confidence_intervals")
element.text = str(self._confidence_intervals).lower()
def _create_electron_treatment_subelement(self, root):
if self._electron_treatment is not None:
element = ET.SubElement(root, "electron_treatment")
element.text = str(self._electron_treatment)
def _create_photon_transport_subelement(self, root):
if self._photon_transport is not None:
element = ET.SubElement(root, "photon_transport")
element.text = str(self._photon_transport).lower()
def _create_ptables_subelement(self, root):
if self._ptables is not None:
element = ET.SubElement(root, "ptables")
@ -800,17 +836,9 @@ class Settings(object):
def _create_cutoff_subelement(self, root):
if self._cutoff is not None:
element = ET.SubElement(root, "cutoff")
if 'weight' in self._cutoff:
subelement = ET.SubElement(element, "weight")
subelement.text = str(self._cutoff['weight'])
if 'weight_avg' in self._cutoff:
subelement = ET.SubElement(element, "weight_avg")
subelement.text = str(self._cutoff['weight_avg'])
if 'energy' in self._cutoff:
subelement = ET.SubElement(element, "energy")
subelement.text = str(self._cutoff['energy'])
for key, value in self._cutoff.items():
subelement = ET.SubElement(element, key)
subelement.text = str(value)
def _create_entropy_mesh_subelement(self, root):
if self.entropy_mesh is not None:
@ -941,8 +969,10 @@ class Settings(object):
self._create_statepoint_subelement(root_element)
self._create_sourcepoint_subelement(root_element)
self._create_confidence_intervals(root_element)
self._create_electron_treatment_subelement(root_element)
self._create_energy_mode_subelement(root_element)
self._create_max_order_subelement(root_element)
self._create_photon_transport_subelement(root_element)
self._create_ptables_subelement(root_element)
self._create_run_cmfd_subelement(root_element)
self._create_seed_subelement(root_element)

View file

@ -22,6 +22,8 @@ class Source(object):
Source file from which sites should be sampled
strength : Real
Strength of the source
particle : {'neutron', 'photon'}
Source particle type
Attributes
----------
@ -35,10 +37,13 @@ class Source(object):
Source file from which sites should be sampled
strength : Real
Strength of the source
particle : {'neutron', 'photon'}
Source particle type
"""
def __init__(self, space=None, angle=None, energy=None, filename=None, strength=1.0):
def __init__(self, space=None, angle=None, energy=None, filename=None,
strength=1.0, particle='neutron'):
self._space = None
self._angle = None
self._energy = None
@ -53,6 +58,7 @@ class Source(object):
if filename is not None:
self.file = filename
self.strength = strength
self.particle = particle
@property
def file(self):
@ -74,6 +80,10 @@ class Source(object):
def strength(self):
return self._strength
@property
def particle(self):
return self._particle
@file.setter
def file(self, filename):
cv.check_type('source file', filename, str)
@ -100,6 +110,11 @@ class Source(object):
cv.check_greater_than('source strength', strength, 0.0, True)
self._strength = strength
@particle.setter
def particle(self, particle):
cv.check_value('source particle', particle, ['neutron', 'photon'])
self._particle = particle
def to_xml_element(self):
"""Return XML representation of the source
@ -111,6 +126,8 @@ class Source(object):
"""
element = ET.Element("source")
element.set("strength", str(self.strength))
if self.particle != 'neutron':
element.set("particle", self.particle)
if self.file is not None:
element.set("file", self.file)
if self.space is not None:

View file

@ -83,6 +83,8 @@ class StatePoint(object):
Number of tally realizations
path : str
Working directory for simulation
photon_transport : bool
Indicate whether photon transport is active
run_mode : str
Simulation run mode, e.g. 'eigenvalue'
runtime : dict
@ -322,6 +324,10 @@ class StatePoint(object):
def path(self):
return self._f.attrs['path'].decode()
@property
def photon_transport(self):
return self._f.attrs['photon_transport'] > 0
@property
def run_mode(self):
return self._f['run_mode'].value.decode()

View file

@ -25,13 +25,6 @@ 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,
@ -55,8 +48,6 @@ 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')
parser.add_argument('--libver', choices=['earliest', 'latest'],
default='earliest', help="Output HDF5 versioning. Use "
"'earliest' for backwards compatibility or 'latest' for "
@ -142,13 +133,6 @@ for filename in ace_libraries:
print('Failed to convert {}: {}'.format(table.name, e))
continue
# 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
print('Converting {} (ACE) to {} (HDF5)'.format(table.name,
neutron.name))

View file

@ -1,5 +1,11 @@
#!/usr/bin/env python
"""
Download ENDF/B-VII.1 incident neutron ACE data and incident photon ENDF data
from NNDC and convert it to an HDF5 library for use with OpenMC. This data is
used for OpenMC's regression test suite.
"""
import os
import shutil
import subprocess
@ -13,31 +19,25 @@ from urllib.request import urlopen
import openmc.data
description = """
Download ENDF/B-VII.1 ACE data from NNDC and convert it to an HDF5 library for
use with OpenMC. This data is used for OpenMC's regression test suite.
"""
class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter):
pass
parser = argparse.ArgumentParser(
description=description,
description=__doc__,
formatter_class=CustomFormatter
)
parser.add_argument('-b', '--batch', action='store_true',
help='supresses standard in')
parser.add_argument('-n', '--neutron-only', action='store_true',
help='Whether to exclude photon interaction/atomic data')
parser.add_argument('--libver', choices=['earliest', 'latest'],
default='earliest', help="Output HDF5 versioning. Use "
"'earliest' for backwards compatibility or 'latest' for "
"performance")
args = parser.parse_args()
baseUrl = 'http://www.nndc.bnl.gov/endf/b7.1/aceFiles/'
base_url = 'http://www.nndc.bnl.gov/endf/b7.1/aceFiles/'
files = ['ENDF-B-VII.1-neutron-293.6K.tar.gz',
'ENDF-B-VII.1-tsl.tar.gz']
checksums = ['9729a17eb62b75f285d8a7628ace1449',
@ -47,32 +47,29 @@ block_size = 16384
# ==============================================================================
# DOWNLOAD FILES FROM NNDC SITE
filesComplete = []
files_complete = []
for f in files:
# Establish connection to URL
url = baseUrl + f
url = base_url + f
req = urlopen(url)
# Get file size from header
if sys.version_info[0] < 3:
file_size = int(req.info().getheaders('Content-Length')[0])
else:
file_size = req.length
file_size = req.length
downloaded = 0
# Check if file already downloaded
if os.path.exists(f):
if os.path.getsize(f) == file_size:
print('Skipping ' + f)
filesComplete.append(f)
files_complete.append(f)
continue
else:
overwrite = input('Overwrite {0}? ([y]/n) '.format(f))
overwrite = input('Overwrite {}? ([y]/n) '.format(f))
if overwrite.lower().startswith('n'):
continue
# Copy file to disk
print('Downloading {0}... '.format(f), end='')
print('Downloading {}... '.format(f), end='')
with open(f, 'wb') as fh:
while True:
chunk = req.read(block_size)
@ -83,7 +80,7 @@ for f in files:
downloaded, downloaded * 100. / file_size)
print(status + chr(8)*len(status), end='')
print('')
filesComplete.append(f)
files_complete.append(f)
# ==============================================================================
# VERIFY MD5 CHECKSUMS
@ -101,18 +98,18 @@ for f, checksum in zip(files, checksums):
# EXTRACT FILES FROM TGZ
for f in files:
if f not in filesComplete:
if f not in files_complete:
continue
# Extract files
suffix = f[f.rindex('-') + 1:].rstrip('.tar.gz')
with tarfile.open(f, 'r') as tgz:
print('Extracting {0}...'.format(f))
print('Extracting {}...'.format(f))
tgz.extractall(path='nndc/' + suffix)
# Move ACE files down one level
for filename in glob.glob('nndc/293.6K/ENDF-B-VII.1-neutron-293.6K/*'):
shutil.move(filename, 'nndc/293.6K/')
shutil.move(filename, 'nndc/293.6K/' + os.path.basename(filename))
# ==============================================================================
# FIX ZAID ASSIGNMENTS FOR VARIOUS S(A,B) TABLES
@ -142,7 +139,7 @@ else:
if not response or response.lower().startswith('y'):
for f in files:
if os.path.exists(f):
print('Removing {0}...'.format(f))
print('Removing {}...'.format(f))
os.remove(f)
# ==============================================================================
@ -162,3 +159,10 @@ subprocess.call([ace2hdf5,
'-d', 'nndc_hdf5',
'--fission_energy_release', fer_file,
'--libver', args.libver] + ace_files)
# Generate photo interaction library files
if not args.neutron_only:
pwd = os.path.dirname(os.path.realpath(__file__))
photo_endf = os.path.join(pwd, 'openmc-get-photon-data')
subprocess.call([photo_endf, '-c', 'cross_sections.xml'],
cwd='nndc_hdf5')

106
scripts/openmc-get-photon-data Executable file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env python
"""
Download ENDF/B-VII.1 ENDF data from NNDC for photo-atomic and atomic
relaxation data and convert it to an HDF5 library for use with OpenMC.
This data is used for photon transport in OpenMC.
"""
import os
import sys
import shutil
import zipfile
import argparse
from io import BytesIO
from urllib.request import urlopen
import openmc.data
class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter):
pass
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=CustomFormatter
)
parser.add_argument('-c', '--cross-sections',
help='cross_sections.xml file to append libraries to')
args = parser.parse_args()
base_url = 'http://www.nndc.bnl.gov/endf/b7.1/zips/'
files = ['ENDF-B-VII.1-photoat.zip', 'ENDF-B-VII.1-atomic_relax.zip']
block_size = 16384
# ==============================================================================
# DOWNLOAD FILES FROM NNDC SITE
if not os.path.exists('photon_hdf5'):
os.mkdir('photon_hdf5')
for f in files:
# Establish connection to URL
url = base_url + f
req = urlopen(url)
# Get file size from header
file_size = req.length
downloaded = 0
# Check if file already downloaded
if os.path.exists(f):
if os.path.getsize(f) == file_size:
print('Skipping ' + f)
continue
else:
overwrite = input('Overwrite {}? ([y]/n) '.format(f))
if overwrite.lower().startswith('n'):
continue
# Copy file to disk
print('Downloading {}... '.format(f), end='')
with open(f, 'wb') as fh:
while True:
chunk = req.read(block_size)
if not chunk: break
fh.write(chunk)
downloaded += len(chunk)
status = '{0:10} [{1:3.2f}%]'.format(
downloaded, downloaded * 100. / file_size)
print(status + chr(8)*len(status), end='')
print('')
# ==============================================================================
# EXTRACT FILES
for f in files:
print('Extracting {0}...'.format(f))
zipfile.ZipFile(f).extractall()
# ==============================================================================
# GENERATE HDF5 DATA LIBRARY
# If previous cross_sections.xml was specified, load it in
if args.cross_sections is not None:
lib_path = args.cross_sections
library = openmc.data.DataLibrary.from_xml(lib_path)
else:
lib_path = os.path.join('photon_hdf5', 'cross_sections.xml')
library = openmc.data.DataLibrary()
for z in range(1, 101):
element = openmc.data.ATOMIC_SYMBOL[z]
print('Generating HDF5 file for Z={} ({})...'.format(z, element))
# Generate instance of IncidentPhoton
photo_file = os.path.join('photoat', 'photoat-{:03}_{}_000.endf'.format(z, element))
atom_file = os.path.join('atomic_relax', 'atom-{:03}_{}_000.endf'.format(z, element))
f = openmc.data.IncidentPhoton.from_endf(photo_file, atom_file)
# Write HDF5 file and register it
hdf5_file = os.path.join('photon_hdf5', element + '.h5')
f.export_to_hdf5(hdf5_file, 'w')
library.register_file(hdf5_file)
library.export_to_xml(lib_path)

99
scripts/openmc-make-compton Executable file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env python
import os
import sys
import tarfile
from urllib.request import urlopen
import numpy as np
import h5py
base_url = 'http://geant4.cern.ch/support/source/'
filename = 'G4EMLOW.6.48.tar.gz'
block_size = 16384
# ==============================================================================
# DOWNLOAD FILES FROM GEANT4 SITE
# Establish connection to URL
req = urlopen(base_url + filename)
# Get file size from header
file_size = req.length
downloaded = 0
# Check if file already downloaded
download = True
if os.path.exists(filename):
if os.path.getsize(filename) == file_size:
print('Already downloaded ' + filename)
download = False
else:
overwrite = input('Overwrite {}? ([y]/n) '.format(filename))
if overwrite.lower().startswith('n'):
download = False
if download:
# Copy file to disk
print('Downloading {}... '.format(filename), end='')
with open(filename, 'wb') as fh:
while True:
chunk = req.read(block_size)
if not chunk: break
fh.write(chunk)
downloaded += len(chunk)
status = '{0:10} [{1:3.2f}%]'.format(
downloaded, downloaded * 100. / file_size)
print(status + chr(8)*len(status), end='')
print('')
# ==============================================================================
# EXTRACT FILES FROM TGZ
if not os.path.isdir('G4EMLOW6.48'):
with tarfile.open(filename, 'r') as tgz:
print('Extracting {0}...'.format(filename))
tgz.extractall()
# ==============================================================================
# GENERATE COMPTON PROFILE HDF5 FILE
print('Generating compton_profiles.h5...')
shell_file = os.path.join('G4EMLOW6.48', 'doppler', 'shell-doppler.dat')
with open(shell_file, 'r') as shell:
with h5py.File('compton_profiles.h5', 'w') as f:
# Read/write electron momentum values
pz = np.loadtxt(os.path.join('G4EMLOW6.48', 'doppler', 'p-biggs.dat'))
f.create_dataset('pz', data=pz)
for Z in range(1, 101):
# Create group for this element
group = f.create_group('{:03}'.format(Z))
# Read data into one long array
path = os.path.join('G4EMLOW6.48', 'doppler', 'profile-{}.dat'.format(Z))
J = np.fromstring(open(path, 'r').read(), sep=' ')
# Determine number of electron shells and reshape
n_shells = J.size // 31
J.shape = (n_shells, 31)
# Write Compton profile for this Z
group.create_dataset('J', data=J)
# Determine binding energies and number of electrons for each shell
num_electrons = []
binding_energy = []
while True:
words = shell.readline().split()
if words[0] == '-1':
break
num_electrons.append(float(words[0]))
binding_energy.append(float(words[1]))
# Write binding energies and number of electrons
group.create_dataset('num_electrons', data=num_electrons)
group.create_dataset('binding_energy', data=binding_energy)

View file

@ -0,0 +1,50 @@
#!/usr/bin/env python
from urllib.parse import urlencode
from urllib.request import urlopen
from lxml import html
import numpy as np
import h5py
from openmc.data import ATOMIC_SYMBOL
base_url = 'https://physics.nist.gov/cgi-bin/Star/e_table-t.pl'
energies = np.logspace(-3, 3, 200)
data = {'matno': '', 'Energies': '\n'.join(str(x) for x in energies)}
columns = {1: 's_collision', 2: 's_radiative'}
# ==============================================================================
# SCRAPE DATA FROM ESTAR SITE AND GENERATE STOPPING POWER HDF5 FILE
print('Generating stopping_powers.h5...')
with h5py.File('stopping_powers.h5', 'w') as f:
# Write energies
f.create_dataset('energy', data=energies)
for Z in range(1, 99):
print('Processing {} data...'.format(ATOMIC_SYMBOL[Z]))
# Update form-encoded data to send in POST request for this element
data['matno'] = '{:03}'.format(Z)
payload = urlencode(data).encode("utf-8")
# Retrieve data from ESTAR site
r = urlopen(url=base_url, data=payload).read()
# Remove text and reformat data
r = html.fromstring(r).xpath('//pre//text()')
values = np.fromstring(' '.join(r[12:-5]), sep=' ').reshape((-1, 5)).T
# Create group for this element
group = f.create_group('{:03}'.format(Z))
# Write the mean excitation energy
attributes = np.fromstring(r[3], sep=' ')
group.attrs['I'] = attributes[2]
# Write collision and radiative stopping powers
for i in columns:
group.create_dataset(columns[i], data=values[i])

View file

@ -248,8 +248,7 @@ def update_materials(root):
# If a nuclide name is in the ZAID notation (e.g., a number),
# convert it to the proper nuclide name.
if nucname.strip().isnumeric():
nucname = \
openmc.data.neutron._get_metadata(int(nucname))[0]
nucname = openmc.data.ace.get_metadata(int(nucname))[0]
nucname = nucname.replace('Nat', '0')
if nucname.endswith('m'):
nucname = nucname[:-1] + '_m1'

View file

@ -32,14 +32,14 @@ kwargs = {
# Data files and librarries
'package_data': {
'openmc.capi': ['libopenmc.{}'.format(suffix)],
'openmc.data': ['mass.mas12', 'fission_Q_data_endfb71.h5']
'openmc.data': ['mass.mas12', '*.h5']
},
# Metadata
'author': 'The OpenMC Development Team',
'author_email': 'openmc-dev@googlegroups.com',
'description': 'OpenMC',
'url': 'https://github.com/mit-crpg/openmc',
'url': 'https://github.com/openmc-dev/openmc',
'classifiers': [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',

View file

@ -15,7 +15,7 @@ module openmc_api
use message_passing
use nuclide_header
use initialize, only: openmc_init_f
use particle_header, only: Particle
use particle_header
use plot, only: openmc_plot_geometry
use random_lcg, only: openmc_get_seed, openmc_set_seed
use settings
@ -120,9 +120,10 @@ contains
check_overlaps = .false.
confidence_intervals = .false.
create_fission_neutrons = .true.
energy_cutoff = ZERO
energy_max_neutron = INFINITY
energy_min_neutron = ZERO
electron_treatment = ELECTRON_LED
energy_cutoff(:) = [ZERO, 1000.0_8, ZERO, ZERO]
energy_max(:) = [INFINITY, INFINITY]
energy_min(:) = [ZERO, ZERO]
entropy_on = .false.
gen_per_batch = 1
index_entropy_mesh = -1
@ -139,6 +140,7 @@ contains
output_summary = .true.
output_tallies = .true.
particle_restart_run = .false.
photon_transport = .false.
pred_batches = .false.
reduce_tallies = .true.
res_scat_on = .false.
@ -196,7 +198,7 @@ contains
logical :: found
type(Particle) :: p
call p % initialize()
call particle_initialize(p)
p % coord(1) % xyz(:) = xyz
p % coord(1) % uvw(:) = [ZERO, ZERO, ONE]
call find_cell(p, found)
@ -310,6 +312,7 @@ contains
subroutine free_memory()
use cmfd_header
use photon_header
use plot_header
use sab_header
use settings
@ -326,6 +329,7 @@ contains
call free_memory_volume()
call free_memory_simulation()
call free_memory_nuclide()
call free_memory_photon()
call free_memory_settings()
call free_memory_sab()
call free_memory_source()

View file

@ -18,6 +18,7 @@ module bank_header
real(C_DOUBLE) :: uvw(3) ! diretional cosines
real(C_DOUBLE) :: E ! energy / energy group if in MG mode.
integer(C_INT) :: delayed_group ! delayed group
integer(C_INT) :: particle ! particle type (neutron, photon, etc.)
end type Bank
! Source and fission bank

View file

@ -329,8 +329,7 @@ Cell::to_hdf5(hid_t cell_group) const
}
//TODO: Fix the off-by-one indexing.
write_int(cell_group, 0, nullptr, "universe",
&global_universes[universe-1]->id, false);
write_dataset(cell_group, "universe", global_universes[universe-1]->id);
// Write the region specification.
if (!region.empty()) {

View file

@ -116,7 +116,7 @@ contains
end if
else
if(.not.allocated(cmfd % egrid)) allocate(cmfd % egrid(2))
cmfd % egrid = [ ZERO, energy_max_neutron ]
cmfd % egrid = [ ZERO, energy_max(NEUTRON) ]
cmfd % indices(4) = 1 ! one energy group
end if

View file

@ -78,6 +78,9 @@ module constants
MASS_NEUTRON = 1.00866491588_8, & ! mass of a neutron in amu
MASS_NEUTRON_EV = 939.5654133e6_8, & ! mass of a neutron in eV/c^2
MASS_PROTON = 1.007276466879_8, & ! mass of a proton in amu
MASS_ELECTRON_EV = 0.5109989461e6_8, & ! electron mass energy equivalent in eV/c^2
FINE_STRUCTURE = 137.035999139_8, & ! inverse fine structure constant
PLANCK_C = 1.2398419739062977e4_8,& ! Planck's constant times c in eV-Angstroms
AMU = 1.660539040e-27_8, & ! 1 amu in kg
C_LIGHT = 2.99792458e8_8, & ! speed of light in m/s
N_AVOGADRO = 0.6022140857_8, & ! Avogadro's number in 10^24/mol
@ -91,6 +94,14 @@ module constants
FOUR = 4.0_8
complex(8), parameter :: ONEI = (ZERO, ONE)
! Electron subshell labels
character(3), parameter :: SUBSHELLS(39) = [ &
'K ', 'L1 ', 'L2 ', 'L3 ', 'M1 ', 'M2 ', 'M3 ', 'M4 ', 'M5 ', &
'N1 ', 'N2 ', 'N3 ', 'N4 ', 'N5 ', 'N6 ', 'N7 ', 'O1 ', 'O2 ', &
'O3 ', 'O4 ', 'O5 ', 'O6 ', 'O7 ', 'O8 ', 'O9 ', 'P1 ', 'P2 ', &
'P3 ', 'P4 ', 'P5 ', 'P6 ', 'P7 ', 'P8 ', 'P9 ', 'P10', 'P11', &
'Q1 ', 'Q2 ', 'Q3 ']
! ============================================================================
! GEOMETRY-RELATED CONSTANTS
@ -172,7 +183,8 @@ module constants
integer, parameter :: &
NEUTRON = 1, &
PHOTON = 2, &
ELECTRON = 3
ELECTRON = 3, &
POSITRON = 4
! Angular distribution type
integer, parameter :: &
@ -219,7 +231,9 @@ module constants
N_3HEA = 193, N_4N2P = 194, N_4N2A = 195, N_4NPA = 196, N_3P = 197, &
N_N3P = 198, N_3N2PA = 199, N_5N2P = 200, N_P0 = 600, N_PC = 649, &
N_D0 = 650, N_DC = 699, N_T0 = 700, N_TC = 749, N_3HE0 = 750, &
N_3HEC = 799, N_A0 = 800, N_AC = 849, N_2N0 = 875, N_2NC = 891
N_3HEC = 799, N_A0 = 800, N_AC = 849, N_2N0 = 875, N_2NC = 891, &
COHERENT = 502, INCOHERENT = 504, PHOTOELECTRIC = 522, &
PAIR_PROD_ELEC = 515, PAIR_PROD = 516, PAIR_PROD_NUC = 517
! Depletion reactions
integer, parameter :: DEPLETION_RX(6) = [N_GAMMA, N_P, N_A, N_2N, N_3N, N_4N]
@ -343,7 +357,7 @@ module constants
integer, parameter :: NO_BIN_FOUND = -1
! Tally filter and map types
integer, parameter :: N_FILTER_TYPES = 20
integer, parameter :: N_FILTER_TYPES = 21
integer, parameter :: &
FILTER_UNIVERSE = 1, &
FILTER_MATERIAL = 2, &
@ -364,7 +378,8 @@ module constants
FILTER_LEGENDRE = 17, &
FILTER_SPH_HARMONICS = 18, &
FILTER_SPTL_LEGENDRE = 19, &
FILTER_ZERNIKE = 20
FILTER_ZERNIKE = 20, &
FILTER_PARTICLE = 21
! Mesh types
integer, parameter :: &
@ -433,6 +448,7 @@ module constants
integer(C_INT), bind(C, name='STREAM_SOURCE') :: STREAM_SOURCE
integer(C_INT), bind(C, name='STREAM_URR_PTABLE') :: STREAM_URR_PTABLE
integer(C_INT), bind(C, name='STREAM_VOLUME') :: STREAM_VOLUME
integer(C_INT), bind(C, name='STREAM_PHOTON') :: STREAM_PHOTON
integer(C_INT64_T), parameter :: DEFAULT_SEED = 1_8
! ============================================================================
@ -455,6 +471,11 @@ module constants
MODE_PARTICLE = 4, & ! Particle restart mode
MODE_VOLUME = 5 ! Volume calculation mode
! Electron treatments
integer, parameter :: &
ELECTRON_LED = 1, & ! Local Energy Deposition
ELECTRON_TTB = 2 ! Thick Target Bremsstrahlung
!=============================================================================
! CMFD CONSTANTS

View file

@ -160,6 +160,20 @@ contains
string = '(n,Xa)'
case (444)
string = '(damage)'
case (COHERENT)
string = 'coherent scatter'
case (INCOHERENT)
string = 'incoherent scatter'
case (PAIR_PROD_ELEC)
string = 'pair production, electron'
case (PAIR_PROD)
string = 'pair production'
case (PAIR_PROD_NUC)
string = 'pair production, nuclear'
case (PHOTOELECTRIC)
string = 'photoelectric'
case (534 : 572)
string = 'photoelectric, ' // trim(SUBSHELLS(MT - 533)) // ' subshell'
case (600 : 648)
string = '(n,p' // trim(to_str(MT-600)) // ')'
case (649)

View file

@ -331,8 +331,8 @@ contains
c_k = c_k1
end do
! Check to make sure k is <= NP - 1
k = min(k, n_energy_out - 1)
! Check to make sure 1 <= k <= NP - 1
k = max(1, min(k, n_energy_out - 1))
E_l_k = this%distribution(l)%e_out(k)
p_l_k = this%distribution(l)%p(k)
@ -359,7 +359,7 @@ contains
end if
! Now interpolate between incident energy bins i and i + 1
if (.not. histogram_interp) then
if (.not. histogram_interp .and. n_energy_out > 1) then
if (l == i) then
E_out = E_1 + (E_out - E_i_1)*(E_K - E_1)/(E_i_K - E_i_1)
else

View file

@ -3,7 +3,7 @@ module geometry
use constants
use error, only: fatal_error, warning, write_message
use geometry_header
use particle_header, only: LocalCoord, Particle
use particle_header
use simulation_header
use settings
use surface_header
@ -113,12 +113,18 @@ contains
logical :: use_search_cells ! use cells provided as argument
do j = p % n_coord + 1, MAX_COORD
call p % coord(j) % reset()
call reset_coord(p % coord(j))
end do
j = p % n_coord
! set size of list to search
! Determine universe (if not yet set, use root universe)
i_universe = p % coord(j) % universe
if (i_universe == C_NONE) then
p % coord(j) % universe = root_universe
i_universe = root_universe
end if
! set size of list to search
if (present(search_cells)) then
use_search_cells = .true.
n = size(search_cells)
@ -330,7 +336,7 @@ contains
call find_cell(p, found)
if (.not. found) then
if (p % alive) then ! Particle may have been killed in find_cell
call p % mark_as_lost("Could not locate particle " &
call particle_mark_as_lost(p, "Could not locate particle " &
// trim(to_str(p % id)) // " after crossing a lattice boundary.")
return
end if
@ -354,7 +360,7 @@ contains
! Search for particle
call find_cell(p, found)
if (.not. found) then
call p % mark_as_lost("Could not locate particle " // &
call particle_mark_as_lost(p, "Could not locate particle " // &
trim(to_str(p % id)) // " after crossing a lattice boundary.")
return
end if
@ -434,7 +440,7 @@ contains
end select LAT_TYPE
if (d_lat < ZERO) then
call p % mark_as_lost("Particle " // trim(to_str(p % id)) &
call particle_mark_as_lost(p, "Particle " // trim(to_str(p % id)) &
//" had a negative distance to a lattice boundary. d = " &
//trim(to_str(d_lat)))
end if

6
src/geometry.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef GEOMETRY_H
#define GEOMETRY_H
extern "C" int openmc_root_universe;
#endif // GEOMETRY_H

View file

@ -288,7 +288,7 @@ module geometry_header
end type Cell
! array index of the root universe
integer :: root_universe = -1
integer(C_INT), bind(C, name='openmc_root_universe') :: root_universe = -1
integer(C_INT32_T), bind(C) :: n_cells ! # of cells
integer(C_INT32_T), bind(C) :: n_universes ! # of universes

View file

@ -169,9 +169,9 @@ file_open(const char* filename, char mode, bool parallel)
}
hid_t
file_open(const std::string& filename, char mode, bool parallel=false)
file_open(const std::string& filename, char mode, bool parallel)
{
file_open(filename.c_str(), mode, parallel);
return file_open(filename.c_str(), mode, parallel);
}
void file_close(hid_t file_id)
@ -302,26 +302,24 @@ object_exists(hid_t object_id, const char* name)
hid_t
open_dataset(hid_t group_id, const char* name)
{
if (object_exists(group_id, name)) {
return H5Dopen(group_id, name, H5P_DEFAULT);
} else {
if (!object_exists(group_id, name)) {
std::stringstream err_msg;
err_msg << "Group \"" << name << "\" does not exist";
fatal_error(err_msg);
}
return H5Dopen(group_id, name, H5P_DEFAULT);
}
hid_t
open_group(hid_t group_id, const char* name)
{
if (object_exists(group_id, name)) {
return H5Gopen(group_id, name, H5P_DEFAULT);
} else {
if (!object_exists(group_id, name)) {
std::stringstream err_msg;
err_msg << "Group \"" << name << "\" does not exist";
fatal_error(err_msg);
}
return H5Gopen(group_id, name, H5P_DEFAULT);
}
void
@ -821,4 +819,14 @@ using_mpio_device(hid_t obj_id)
return driver == H5FD_MPIO;
}
// Specializations of the H5TypeMap template struct
template<>
const hid_t H5TypeMap<int>::type_id = H5T_NATIVE_INT;
template<>
const hid_t H5TypeMap<int64_t>::type_id = H5T_NATIVE_INT64;
template<>
const hid_t H5TypeMap<double>::type_id = H5T_NATIVE_DOUBLE;
template <>
const hid_t H5TypeMap<char>::type_id = H5T_NATIVE_CHAR;
} // namespace openmc

View file

@ -5,6 +5,7 @@
#include "hdf5_hl.h"
#include <array>
#include <cstddef>
#include <string>
#include <sstream>
#include <vector>
@ -13,48 +14,28 @@
namespace openmc {
extern "C" bool attribute_exists(hid_t obj_id, const char* name);
extern "C" size_t attribute_typesize(hid_t obj_id, const char* name);
extern "C" hid_t create_group(hid_t parent_id, const char* name);
hid_t create_group(hid_t parent_id, const std::string& name);
extern "C" void close_dataset(hid_t dataset_id);
extern "C" void close_group(hid_t group_id);
extern "C" int dataset_ndims(hid_t dset);
extern "C" size_t dataset_typesize(hid_t dset);
extern "C" hid_t file_open(const char* filename, char mode, bool parallel);
hid_t file_open(const std::string& filename, char mode, bool parallel);
extern "C" void file_close(hid_t file_id);
extern "C" void get_name(hid_t obj_id, char* name);
extern "C" int get_num_datasets(hid_t group_id);
extern "C" int get_num_groups(hid_t group_id);
extern "C" void get_datasets(hid_t group_id, char* name[]);
extern "C" void get_groups(hid_t group_id, char* name[]);
extern "C" void get_shape(hid_t obj_id, hsize_t* dims);
extern "C" void get_shape_attr(hid_t obj_id, const char* name, hsize_t* dims);
extern "C" bool object_exists(hid_t object_id, const char* name);
extern "C" hid_t open_dataset(hid_t group_id, const char* name);
extern "C" hid_t open_group(hid_t group_id, const char* name);
bool using_mpio_device(hid_t obj_id);
//==============================================================================
// Low-level internal functions
//==============================================================================
void read_attr(hid_t obj_id, const char* name, hid_t mem_type_id,
const void* buffer);
extern "C" void read_attr_double(hid_t obj_id, const char* name, double* buffer);
extern "C" void read_attr_int(hid_t obj_id, const char* name, int* buffer);
extern "C" void read_attr_string(hid_t obj_id, const char* name, size_t slen,
char* buffer);
void write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name,
hid_t mem_type_id, const void* buffer);
void read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id,
void* buffer, bool indep);
extern "C" void read_double(hid_t obj_id, const char* name, double* buffer,
bool indep);
extern "C" void read_int(hid_t obj_id, const char* name, int* buffer,
bool indep);
extern "C" void read_llong(hid_t obj_id, const char* name, long long* buffer,
bool indep);
extern "C" void read_string(hid_t obj_id, const char* name, size_t slen,
char* buffer, bool indep);
extern "C" void read_complex(hid_t obj_id, const char* name,
double _Complex* buffer, bool indep);
void write_dataset(hid_t group_id, int ndim, const hsize_t* dims, const char* name,
hid_t mem_type_id, const void* buffer, bool indep);
bool using_mpio_device(hid_t obj_id);
//==============================================================================
// Normal functions that are used to read/write files
//==============================================================================
hid_t create_group(hid_t parent_id, const std::string& name);
hid_t file_open(const std::string& filename, char mode, bool parallel=false);
void write_string(hid_t group_id, const char* name, const std::string& buffer,
bool indep);
void
read_nd_vector(hid_t obj_id, const char* name, std::vector<double>& result,
@ -89,51 +70,115 @@ read_nd_vector(hid_t obj_id, const char* name,
std::vector<std::vector<std::vector<std::vector<std::vector<double> > > > >& result,
bool must_have = false);
extern "C" void read_tally_results(hid_t group_id, hsize_t n_filter,
//==============================================================================
// Fortran compatibility functions
//==============================================================================
extern "C" {
bool attribute_exists(hid_t obj_id, const char* name);
size_t attribute_typesize(hid_t obj_id, const char* name);
hid_t create_group(hid_t parent_id, const char* name);
void close_dataset(hid_t dataset_id);
void close_group(hid_t group_id);
int dataset_ndims(hid_t dset);
size_t dataset_typesize(hid_t dset);
hid_t file_open(const char* filename, char mode, bool parallel);
void file_close(hid_t file_id);
void get_name(hid_t obj_id, char* name);
int get_num_datasets(hid_t group_id);
int get_num_groups(hid_t group_id);
void get_datasets(hid_t group_id, char* name[]);
void get_groups(hid_t group_id, char* name[]);
void get_shape(hid_t obj_id, hsize_t* dims);
void get_shape_attr(hid_t obj_id, const char* name, hsize_t* dims);
bool object_exists(hid_t object_id, const char* name);
hid_t open_dataset(hid_t group_id, const char* name);
hid_t open_group(hid_t group_id, const char* name);
void read_attr_double(hid_t obj_id, const char* name, double* buffer);
void read_attr_int(hid_t obj_id, const char* name, int* buffer);
void read_attr_string(hid_t obj_id, const char* name, size_t slen,
char* buffer);
void read_complex(hid_t obj_id, const char* name,
double _Complex* buffer, bool indep);
void read_double(hid_t obj_id, const char* name, double* buffer,
bool indep);
void read_int(hid_t obj_id, const char* name, int* buffer,
bool indep);
void read_llong(hid_t obj_id, const char* name, long long* buffer,
bool indep);
void read_string(hid_t obj_id, const char* name, size_t slen,
char* buffer, bool indep);
void read_tally_results(hid_t group_id, hsize_t n_filter,
hsize_t n_score, double* results);
void write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name,
hid_t mem_type_id, const void* buffer);
extern "C" void write_attr_double(hid_t obj_id, int ndim, const hsize_t* dims,
void write_attr_double(hid_t obj_id, int ndim, const hsize_t* dims,
const char* name, const double* buffer);
extern "C" void write_attr_int(hid_t obj_id, int ndim, const hsize_t* dims,
void write_attr_int(hid_t obj_id, int ndim, const hsize_t* dims,
const char* name, const int* buffer);
extern "C" void write_attr_string(hid_t obj_id, const char* name, const char* buffer);
void write_dataset(hid_t group_id, int ndim, const hsize_t* dims, const char* name,
hid_t mem_type_id, const void* buffer, bool indep);
extern "C" void write_double(hid_t group_id, int ndim, const hsize_t* dims,
void write_attr_string(hid_t obj_id, const char* name, const char* buffer);
void write_double(hid_t group_id, int ndim, const hsize_t* dims,
const char* name, const double* buffer, bool indep);
extern "C" void write_int(hid_t group_id, int ndim, const hsize_t* dims,
void write_int(hid_t group_id, int ndim, const hsize_t* dims,
const char* name, const int* buffer, bool indep);
extern "C" void write_llong(hid_t group_id, int ndim, const hsize_t* dims,
void write_llong(hid_t group_id, int ndim, const hsize_t* dims,
const char* name, const long long* buffer, bool indep);
extern "C" void write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen,
void write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen,
const char* name, char const* buffer, bool indep);
void write_string(hid_t group_id, const char* name, const std::string& buffer, bool indep);
extern "C" void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score,
void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score,
const double* results);
} // extern "C"
template<std::size_t array_len> void
write_int(hid_t group_id, char const *name,
const std::array<int, array_len> &buffer, bool indep)
//==============================================================================
// Template struct used to map types to HDF5 datatype IDs, which are stored
// using the type hid_t. By having a single static data member, the template can
// be specialized for each type we know of. The specializations appear in the
// .cpp file since they are definitions.
//==============================================================================
template<typename T>
struct H5TypeMap { static const hid_t type_id; };
//==============================================================================
// Template functions used to provide simple interface to lower-level functions
//==============================================================================
template<typename T> inline void
write_attribute(hid_t obj_id, const char* name, T buffer)
{
hsize_t dims[1] {array_len};
write_dataset(group_id, 1, dims, name, H5T_NATIVE_INT, buffer.data(), indep);
write_attr(obj_id, name, 0, nullptr, H5TypeMap<T>::type_id, &buffer);
}
template<std::size_t array_len> void
write_double(hid_t group_id, char const *name,
const std::array<double, array_len> &buffer, bool indep)
template<> inline void
write_attribute<const char*>(hid_t obj_id, const char* name, const char* buffer)
{
hsize_t dims[1] {array_len};
write_dataset(group_id, 1, dims, name, H5T_NATIVE_DOUBLE,
buffer.data(), indep);
write_attr_string(obj_id, name, buffer);
}
template<typename T, std::size_t N> inline void
write_attribute(hid_t obj_id, const char* name, const std::array<T, N>& buffer)
{
hsize_t dims[] {N};
write_attr(obj_id, 1, dims, name, H5TypeMap<T>::type_id, buffer.data());
}
template<typename T> inline void
write_dataset(hid_t obj_id, const char* name, T buffer)
{
write_dataset(obj_id, 0, nullptr, name, H5TypeMap<T>::type_id, &buffer, false);
}
template<> inline void
write_dataset<const char*>(hid_t obj_id, const char* name, const char* buffer)
{
write_string(obj_id, name, buffer, false);
}
template<typename T, std::size_t N> inline void
write_dataset(hid_t obj_id, const char* name, const std::array<T, N>& buffer)
{
hsize_t dims[] {N};
write_dataset(obj_id, 1, dims, name, H5TypeMap<T>::type_id, buffer.data(), false);
}
} // namespace openmc

View file

@ -10,25 +10,17 @@
#include "hdf5_interface.h"
#include "message_passing.h"
#include "openmc.h"
#include "settings.h"
#ifdef _OPENMP
#include "omp.h"
#endif
// data/functions from Fortran side
extern "C" bool openmc_check_overlaps;
extern "C" bool openmc_write_all_tracks;
extern "C" bool openmc_particle_restart_run;
extern "C" bool openmc_restart_run;
extern "C" void print_usage();
extern "C" void print_version();
// Paths to various files
extern "C" {
char* openmc_path_input;
char* openmc_path_statepoint;
char* openmc_path_sourcepoint;
char* openmc_path_particle_restart;
bool is_null(void* ptr) {return !ptr;}
}

View file

@ -22,6 +22,7 @@ module input_xml
use mgxs_interface
use nuclide_header
use output, only: title, header, print_plot
use photon_header
use plot_header
use random_lcg, only: prn, openmc_set_seed
use surface_header
@ -81,6 +82,11 @@ module input_xml
type(C_PTR) :: node_ptr
end subroutine read_lattices
subroutine read_settings(node_ptr) bind(C)
import C_PTR
type(C_PTR) :: node_ptr
end subroutine read_settings
function find_root_universe() bind(C) result(root)
import C_INT32_T
integer(C_INT32_T) :: root
@ -228,7 +234,7 @@ contains
&not exist! In order to run OpenMC, you first need a set of input &
&files; at a minimum, this includes settings.xml, geometry.xml, &
&and materials.xml. Please consult the user's guide at &
&http://mit-crpg.github.io/openmc for further information.")
&http://openmc.readthedocs.io for further information.")
else
! The settings.xml file is optional if we just want to make a plot.
return
@ -239,6 +245,9 @@ contains
call doc % load_file(filename)
root = doc % document_element()
! Read settings from C++ side
call read_settings(root % ptr)
! Verbosity
if (check_for_node(root, "verbosity")) then
call get_node_value(root, "verbosity", verbosity)
@ -389,6 +398,30 @@ contains
call openmc_set_seed(seed)
end if
! Check for electron treatment
if (check_for_node(root, "electron_treatment")) then
call get_node_value(root, "electron_treatment", temp_str)
select case (to_lower(temp_str))
case ("led")
electron_treatment = ELECTRON_LED
case ("ttb")
electron_treatment = ELECTRON_TTB
case default
call fatal_error("Unrecognized electron treatment: " // &
trim(temp_str) // ".")
end select
end if
! Check for photon transport
if (check_for_node(root, "photon_transport")) then
call get_node_value(root, "photon_transport", photon_transport)
if (.not. run_CE .and. photon_transport) then
call fatal_error("Photon transport is not currently supported &
&in Multi-group mode")
end if
end if
! Number of bins for logarithmic grid
if (check_for_node(root, "log_grid_bins")) then
call get_node_value(root, "log_grid_bins", n_log_bins)
@ -425,6 +458,7 @@ contains
if (n == 0) then
! Default source is isotropic point source at origin with Watt spectrum
allocate(external_source(1))
external_source % particle = NEUTRON
external_source % strength = ONE
allocate(SpatialPoint :: external_source(1) % space)
@ -476,8 +510,21 @@ contains
if (check_for_node(node_cutoff, "weight_avg")) then
call get_node_value(node_cutoff, "weight_avg", weight_survive)
end if
if (check_for_node(node_cutoff, "energy")) then
call get_node_value(node_cutoff, "energy", energy_cutoff)
if (check_for_node(node_cutoff, "energy_neutron")) then
call get_node_value(node_cutoff, "energy_neutron", energy_cutoff(1))
elseif (check_for_node(node_cutoff, "energy")) then
call warning("The use of an <energy> cutoff is deprecated and should &
&be replaced by <energy_neutron>.")
call get_node_value(node_cutoff, "energy", energy_cutoff(1))
end if
if (check_for_node(node_cutoff, "energy_photon")) then
call get_node_value(node_cutoff, "energy_photon", energy_cutoff(2))
end if
if (check_for_node(node_cutoff, "energy_electron")) then
call get_node_value(node_cutoff, "energy_electron", energy_cutoff(3))
end if
if (check_for_node(node_cutoff, "energy_positron")) then
call get_node_value(node_cutoff, "energy_positron", energy_cutoff(4))
end if
end if
@ -1193,8 +1240,8 @@ contains
end if
! Read cell temperatures. If the temperature is not specified, set it to
! ERROR_REAL for now. During initialization we'll replace ERROR_REAL with
! the temperature from the material data.
! a negative number for now. During initialization we'll replace
! negatives with the temperature from the material data.
if (check_for_node(node_cell, "temperature")) then
n = node_word_count(node_cell, "temperature")
if (n > 0) then
@ -1219,11 +1266,11 @@ contains
c % sqrtkT(:) = sqrt(K_BOLTZMANN * c % sqrtkT(:))
else
allocate(c % sqrtkT(1))
c % sqrtkT(1) = ERROR_REAL
c % sqrtkT(1) = -1.0
end if
else
allocate(c % sqrtkT(1))
c % sqrtkT = ERROR_REAL
c % sqrtkT = -1.0
end if
! Add cell to dictionary
@ -1369,7 +1416,7 @@ contains
&materials.xml, settings.xml, or in the OPENMC_CROSS_SECTIONS&
& environment variable. OpenMC needs such a file to identify &
&where to find ACE cross section libraries. Please consult the&
& user's guide at http://mit-crpg.github.io/openmc for &
& user's guide at http://openmc.readthedocs.io for &
&information on how to set up ACE cross section libraries.")
else
call warning("The CROSS_SECTIONS environment variable is &
@ -1387,7 +1434,7 @@ contains
&materials.xml or in the OPENMC_MG_CROSS_SECTIONS environment &
&variable. OpenMC needs such a file to identify where to &
&find MG cross section libraries. Please consult the user's &
&guide at http://mit-crpg.github.io/openmc for information on &
&guide at http://openmc.readthedocs.io for information on &
&how to set up MG cross section libraries.")
else if (len_trim(env_variable) /= 0) then
path_cross_sections = trim(env_variable)
@ -1450,9 +1497,11 @@ contains
integer :: n_sab ! number of sab tables for a material
integer :: i_library ! index in libraries array
integer :: index_nuclide ! index in nuclides
integer :: index_element ! index in elements
integer :: index_sab ! index in sab_tables
logical :: file_exists ! does materials.xml exist?
character(20) :: name ! name of nuclide, e.g. 92235.03c
character(20) :: name ! name of nuclide, e.g. U235
character(3) :: element ! name of element, e.g. Zr
character(MAX_WORD_LEN) :: units ! units on density
character(MAX_LINE_LEN) :: filename ! absolute path to materials.xml
character(MAX_LINE_LEN) :: temp_str ! temporary string when reading
@ -1501,6 +1550,7 @@ contains
! Initialize count for number of nuclides/S(a,b) tables
index_nuclide = 0
index_element = 0
index_sab = 0
do i = 1, n_materials
@ -1538,7 +1588,7 @@ contains
if (check_for_node(node_mat, "temperature")) then
call get_node_value(node_mat, "temperature", material_temps(i))
else
material_temps(i) = ERROR_REAL
material_temps(i) = -1.0
end if
! Get pointer to density element
@ -1723,6 +1773,7 @@ contains
mat % n_nuclides = n
allocate(mat % names(n))
allocate(mat % nuclide(n))
allocate(mat % element(n))
allocate(mat % atom_density(n))
ALL_NUCLIDES: do j = 1, mat % n_nuclides
@ -1753,6 +1804,27 @@ contains
mat % nuclide(j) = nuclide_dict % get(to_lower(name))
end if
! If the corresponding element hasn't been encountered yet and photon
! transport will be used, we need to add its symbol to the element_dict
if (photon_transport) then
element = name(1:scan(name, '0123456789') - 1)
! Make sure photon cross section data is available
if (.not. library_dict % has(to_lower(element))) then
call fatal_error("Could not find element " // trim(element) &
// " in cross_sections data file!")
end if
if (.not. element_dict % has(element)) then
index_element = index_element + 1
mat % element(j) = index_element
call element_dict % set(element, index_element)
else
mat % element(j) = element_dict % get(element)
end if
end if
! Copy name and atom/weight percent
mat % names(j) = name
mat % atom_density(j) = densities % data(j)
@ -1868,6 +1940,7 @@ contains
! Set total number of nuclides and S(a,b) tables
n_nuclides = index_nuclide
n_elements = index_element
n_sab_tables = index_sab
! Close materials XML file
@ -1885,6 +1958,7 @@ contains
integer :: i ! loop over user-specified tallies
integer :: j ! loop over words
integer :: k ! another loop index
integer :: l ! loop over bins
integer :: filter_id ! user-specified identifier for filter
integer :: i_filt ! index in filters array
integer :: i_elem ! index of entry in dictionary
@ -2510,6 +2584,47 @@ contains
end if
end do
end do
! Check if tally is compatible with particle type
if (photon_transport) then
if (t % find_filter(FILTER_PARTICLE) == 0) then
do j = 1, n_scores
select case (t % score_bins(j))
case (SCORE_INVERSE_VELOCITY)
call fatal_error("Particle filter must be used with photon &
&transport on and inverse velocity score")
case (SCORE_FLUX, SCORE_TOTAL, SCORE_SCATTER, SCORE_NU_SCATTER, &
SCORE_ABSORPTION, SCORE_FISSION, SCORE_NU_FISSION, &
SCORE_CURRENT, SCORE_EVENTS, SCORE_DELAYED_NU_FISSION, &
SCORE_PROMPT_NU_FISSION, SCORE_DECAY_RATE)
call warning("Particle filter is not used with photon transport&
& on and " // trim(to_str(t % score_bins(j))) // " score")
end select
end do
else
select type(filt => filters(t % find_filter(FILTER_PARTICLE)) % obj)
type is (ParticleFilter)
do l = 1, filt % n_bins
if (filt % particles(l) == ELECTRON .or. filt % particles(l) == POSITRON) then
t % estimator = ESTIMATOR_ANALOG
end if
end do
end select
end if
else
if (t % find_filter(FILTER_PARTICLE) > 0) then
select type(filt => filters(t % find_filter(FILTER_PARTICLE)) % obj)
type is (ParticleFilter)
do l = 1, filt % n_bins
if (filt % particles(l) /= NEUTRON) then
call warning("Particle filter other than NEUTRON used with &
&photon transport turned off. All tallies for particle &
&type " // trim(to_str(filt % particles(l))) // " will have no scores")
end if
end do
end select
end if
end if
else
call fatal_error("No <scores> specified on tally " &
// trim(to_str(t % id)) // ".")
@ -3319,6 +3434,8 @@ contains
libraries(i) % type = LIBRARY_NEUTRON
case ('thermal')
libraries(i) % type = LIBRARY_THERMAL
case ('photon')
libraries(i) % type = LIBRARY_PHOTON
end select
else
call fatal_error("Missing library type")
@ -3408,8 +3525,8 @@ contains
end do
! Get the minimum and maximum energies
energy_min_neutron = energy_bins(num_energy_groups + 1)
energy_max_neutron = energy_bins(1)
energy_min(NEUTRON) = energy_bins(num_energy_groups + 1)
energy_max(NEUTRON) = energy_bins(1)
! Get the datasets present in the library
call get_groups(file_id, names)
@ -3519,15 +3636,22 @@ contains
integer :: i, j
integer :: i_library
integer :: i_nuclide
integer :: i_element
integer :: i_sab
integer(HID_T) :: file_id
integer(HID_T) :: group_id
logical :: mp_found ! if windowed multipole libraries were found
character(MAX_WORD_LEN) :: name
character(3) :: element
type(SetChar) :: already_read
type(SetChar) :: element_already_read
allocate(nuclides(n_nuclides))
allocate(elements(n_elements))
allocate(sab_tables(n_sab_tables))
if (photon_transport .and. electron_treatment == ELECTRON_TTB) then
allocate(ttb(n_materials))
end if
! Read cross sections
do i = 1, size(materials)
@ -3559,15 +3683,50 @@ contains
! Determine if minimum/maximum energy for this nuclide is greater/less
! than the previous
if (size(nuclides(i_nuclide) % grid) >= 1) then
energy_min_neutron = max(energy_min_neutron, &
energy_min(NEUTRON) = max(energy_min(NEUTRON), &
nuclides(i_nuclide) % grid(1) % energy(1))
energy_max_neutron = min(energy_max_neutron, nuclides(i_nuclide) % &
energy_max(NEUTRON) = min(energy_max(NEUTRON), nuclides(i_nuclide) % &
grid(1) % energy(size(nuclides(i_nuclide) % grid(1) % energy)))
end if
! Add name and alias to dictionary
call already_read % add(name)
! Check if elemental data has been read, if needed
element = name(1:scan(name, '0123456789') - 1)
if (photon_transport) then
if (.not. element_already_read % contains(element)) then
! Read photon interaction data from HDF5 photon library
i_library = library_dict % get(to_lower(element))
i_element = element_dict % get(element)
call write_message('Reading ' // trim(element) // ' from ' // &
trim(libraries(i_library) % path), 6)
! Open file and make sure version is sufficient
file_id = file_open(libraries(i_library) % path, 'r')
call check_data_version(file_id)
! Read element data from HDF5
group_id = open_group(file_id, element)
call elements(i_element) % from_hdf5(group_id)
call close_group(group_id)
call file_close(file_id)
! Determine if minimum/maximum energy for this element is
! greater/less than the previous
if (size(elements(i_element) % energy) >= 1) then
energy_min(PHOTON) = max(energy_min(PHOTON), &
exp(elements(i_element) % energy(1)))
energy_max(PHOTON) = min(energy_max(PHOTON), &
exp(elements(i_element) % energy(size(elements(i_element) &
% energy))))
end if
! Add element to set
call element_already_read % add(element)
end if
end if
! Read multipole file into the appropriate entry on the nuclides array
if (temperature_multipole) call read_multipole_data(i_nuclide)
end if
@ -3577,14 +3736,43 @@ contains
materials(i) % fissionable = .true.
end if
end do
! Generate material bremsstrahlung data for electrons and positrons
if (photon_transport .and. electron_treatment == ELECTRON_TTB) then
call bremsstrahlung_init(ttb(i) % electron, i, ELECTRON)
call bremsstrahlung_init(ttb(i) % positron, i, POSITRON)
end if
end do
if (photon_transport .and. electron_treatment == ELECTRON_TTB) then
! Deallocate element bremsstrahlung DCS and stopping power data since
! only the material bremsstrahlung data is needed
do i = 1, size(elements)
if (allocated(elements(i) % stopping_power_collision)) &
deallocate(elements(i) % stopping_power_collision)
if (allocated(elements(i) % stopping_power_radiative)) &
deallocate(elements(i) % stopping_power_radiative)
if (allocated(elements(i) % dcs)) deallocate(elements(i) % dcs)
if (allocated(ttb_k_grid)) deallocate(ttb_k_grid)
end do
! Determine if minimum/maximum energy for bremsstrahlung is greater/less
! than the current minimum/maximum
if (size(ttb_e_grid) >= 1) then
energy_min(PHOTON) = max(energy_min(PHOTON), ttb_e_grid(1))
energy_max(PHOTON) = min(energy_max(PHOTON), ttb_e_grid(size(ttb_e_grid)))
end if
! Take logarithm of energies since they are log-log interpolated
ttb_e_grid = log(ttb_e_grid)
end if
! Set up logarithmic grid for nuclides
do i = 1, size(nuclides)
call nuclides(i) % init_grid(energy_min_neutron, &
energy_max_neutron, n_log_bins)
call nuclides(i) % init_grid(energy_min(NEUTRON), &
energy_max(NEUTRON), n_log_bins)
end do
log_spacing = log(energy_max_neutron/energy_min_neutron) / n_log_bins
log_spacing = log(energy_max(NEUTRON)/energy_min(NEUTRON)) / n_log_bins
do i = 1, size(materials)
! Skip materials with no S(a,b) tables
@ -3627,9 +3815,9 @@ contains
! grid has not been allocated
if (size(nuclides(i) % grid) > 0) then
if (nuclides(i) % grid(1) % energy(size(nuclides(i) % grid(1) % energy)) &
== energy_max_neutron) then
== energy_max(NEUTRON)) then
call write_message("Maximum neutron transport energy: " // &
trim(to_str(energy_max_neutron)) // " eV for " // &
trim(to_str(energy_max(NEUTRON))) // " eV for " // &
trim(adjustl(nuclides(i) % name)), 7)
exit
end if
@ -3667,7 +3855,7 @@ contains
do i = 1, n_cells
! Ignore non-normal cells and cells with defined temperature.
if (cells(i) % material(1) == NONE) cycle
if (cells(i) % sqrtkT(1) /= ERROR_REAL) cycle
if (cells(i) % sqrtkT(1) >= ZERO) cycle
! Set the number of temperatures equal to the number of materials.
deallocate(cells(i) % sqrtkT)
@ -3683,7 +3871,7 @@ contains
! Use material default or global default temperature
i_material = cells(i) % material(j)
if (material_temps(i_material) /= ERROR_REAL) then
if (material_temps(i_material) >= ZERO) then
cells(i) % sqrtkT(j) = sqrt(K_BOLTZMANN * &
material_temps(i_material))
else

View file

@ -119,9 +119,9 @@ Lattice::to_hdf5(hid_t lattices_group) const
if (outer != NO_OUTER_UNIVERSE) {
int32_t outer_id = global_universes[outer]->id;
write_int(lat_group, 0, nullptr, "outer", &outer_id, false);
write_dataset(lat_group, "outer", outer_id);
} else {
write_int(lat_group, 0, nullptr, "outer", &outer, false);
write_dataset(lat_group, "outer", outer);
}
// Call subclass-overriden function to fill in other details.
@ -348,16 +348,16 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const
// Write basic lattice information.
write_string(lat_group, "type", "rectangular", false);
if (is_3d) {
write_double(lat_group, "pitch", pitch, false);
write_double(lat_group, "lower_left", lower_left, false);
write_int(lat_group, "dimension", n_cells, false);
write_dataset(lat_group, "pitch", pitch);
write_dataset(lat_group, "lower_left", lower_left);
write_dataset(lat_group, "dimension", n_cells);
} else {
std::array<double, 2> pitch_short {{pitch[0], pitch[1]}};
write_double(lat_group, "pitch", pitch_short, false);
write_dataset(lat_group, "pitch", pitch_short);
std::array<double, 2> ll_short {{lower_left[0], lower_left[1]}};
write_double(lat_group, "lower_left", ll_short, false);
write_dataset(lat_group, "lower_left", ll_short);
std::array<int, 2> nc_short {{n_cells[0], n_cells[1]}};
write_int(lat_group, "dimension", nc_short, false);
write_dataset(lat_group, "dimension", nc_short);
}
// Write the universe ids. The convention here is to switch the ordering on
@ -826,16 +826,16 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const
{
// Write basic lattice information.
write_string(lat_group, "type", "hexagonal", false);
write_int(lat_group, 0, nullptr, "n_rings", &n_rings, false);
write_int(lat_group, 0, nullptr, "n_axial", &n_axial, false);
write_dataset(lat_group, "n_rings", n_rings);
write_dataset(lat_group, "n_axial", n_axial);
if (is_3d) {
write_double(lat_group, "pitch", pitch, false);
write_double(lat_group, "center", center, false);
write_dataset(lat_group, "pitch", pitch);
write_dataset(lat_group, "center", center);
} else {
std::array<double, 1> pitch_short {{pitch[0]}};
write_double(lat_group, "pitch", pitch_short, false);
write_dataset(lat_group, "pitch", pitch_short);
std::array<double, 2> center_short {{center[0], center[1]}};
write_double(lat_group, "center", center_short, false);
write_dataset(lat_group, "center", center_short);
}
// Write the universe ids.

View file

@ -5,7 +5,10 @@ module material_header
use constants
use dict_header, only: DictIntInt
use error
use math, only: spline, spline_integrate
use nuclide_header
use particle_header, only: Particle
use photon_header
use sab_header
use simulation_header, only: log_spacing
use stl_vector, only: VectorReal, VectorInt
@ -14,6 +17,7 @@ module material_header
implicit none
private
public :: bremsstrahlung_init
public :: free_memory_material
public :: openmc_extend_materials
public :: openmc_get_material_index
@ -33,6 +37,7 @@ module material_header
character(len=104) :: name = "" ! User-defined name
integer :: n_nuclides = 0 ! number of nuclides
integer, allocatable :: nuclide(:) ! index in nuclides array
integer, allocatable :: element(:) ! index in elements array
real(8) :: density ! total atom density in atom/b-cm
real(C_DOUBLE), allocatable :: atom_density(:) ! nuclide atom density in atom/b-cm
real(8) :: density_gpcc ! total density in g/cm^3
@ -66,6 +71,8 @@ module material_header
procedure :: init_nuclide_index => material_init_nuclide_index
procedure :: assign_sab_tables => material_assign_sab_tables
procedure :: calculate_xs => material_calculate_xs
procedure, private :: calculate_neutron_xs
procedure, private :: calculate_photon_xs
end type Material
integer(C_INT32_T), public, bind(C) :: n_materials ! # of materials
@ -251,18 +258,36 @@ contains
end subroutine material_assign_sab_tables
!===============================================================================
! MATERIAL_CALCULATE_XS determines the macroscopic cross sections for the material the
! particle is currently traveling through.
! MATERIAL_CALCULATE_XS determines the macroscopic cross sections for the
! material the particle is currently traveling through.
!===============================================================================
subroutine material_calculate_xs(this, E, sqrtkT, micro_xs, nuclides, &
material_xs)
subroutine material_calculate_xs(this, p)
class(Material), intent(in) :: this
real(8), intent(in) :: E ! Particle energy
real(8), intent(in) :: sqrtkT ! Last temperature sampled
type(Nuclide), allocatable, intent(in) :: nuclides(:)
type(NuclideMicroXS), allocatable, intent(inout) :: micro_xs(:) ! Cache for each nuclide
type(MaterialMacroXS), intent(inout) :: material_xs ! Cache for current material
type(Particle), intent(in) :: p
! Set all material macroscopic cross sections to zero
material_xs % total = ZERO
material_xs % absorption = ZERO
material_xs % fission = ZERO
material_xs % nu_fission = ZERO
if (p % type == NEUTRON) then
call this % calculate_neutron_xs(p)
elseif (p % type == PHOTON) then
call this % calculate_photon_xs(p)
end if
end subroutine material_calculate_xs
!===============================================================================
! CALCULATE_NEUTRON_XS determines the neutron cross section for the material the
! particle is traveling through
!===============================================================================
subroutine calculate_neutron_xs(this, p)
class(Material), intent(in) :: this
type(Particle), intent(in) :: p
integer :: i ! loop index over nuclides
integer :: i_nuclide ! index into nuclides array
@ -274,14 +299,8 @@ contains
real(8) :: sab_frac ! fraction of atoms affected by S(a,b)
logical :: check_sab ! should we check for S(a,b) table?
! Set all material macroscopic cross sections to zero
material_xs % total = ZERO
material_xs % absorption = ZERO
material_xs % fission = ZERO
material_xs % nu_fission = ZERO
! Find energy index on energy grid
i_grid = int(log(E/energy_min_neutron)/log_spacing)
i_grid = int(log(p % E/energy_min(NEUTRON))/log_spacing)
! Determine if this material has S(a,b) tables
check_sab = (this % n_sab > 0)
@ -307,7 +326,7 @@ contains
! If particle energy is greater than the highest energy for the
! S(a,b) table, then don't use the S(a,b) table
if (E > sab_tables(i_sab) % data(1) % threshold_inelastic) then
if (p % E > sab_tables(i_sab) % data(1) % threshold_inelastic) then
i_sab = 0
end if
@ -326,12 +345,12 @@ contains
i_nuclide = this % nuclide(i)
! Calculate microscopic cross section for this nuclide
if (E /= micro_xs(i_nuclide) % last_E &
.or. sqrtkT /= micro_xs(i_nuclide) % last_sqrtkT &
if (p % E /= micro_xs(i_nuclide) % last_E &
.or. p % sqrtkT /= micro_xs(i_nuclide) % last_sqrtkT &
.or. i_sab /= micro_xs(i_nuclide) % index_sab &
.or. sab_frac /= micro_xs(i_nuclide) % sab_frac) then
call nuclides(i_nuclide) % calculate_xs(i_sab, E, i_grid, &
sqrtkT, sab_frac, micro_xs(i_nuclide))
call nuclides(i_nuclide) % calculate_xs(i_sab, p % E, i_grid, &
p % sqrtkT, sab_frac, micro_xs(i_nuclide))
end if
! ======================================================================
@ -357,7 +376,68 @@ contains
atom_density * micro_xs(i_nuclide) % nu_fission
end do
end subroutine material_calculate_xs
end subroutine calculate_neutron_xs
!===============================================================================
! CALCULATE_PHOTON_XS determines the macroscopic photon cross sections for the
! material the particle is currently traveling through.
!===============================================================================
subroutine calculate_photon_xs(this, p)
class(Material), intent(in) :: this
type(Particle), intent(in) :: p
integer :: i ! loop index over nuclides
integer :: i_element ! index into elements array
real(8) :: atom_density ! atom density of a nuclide
material_xs % coherent = ZERO
material_xs % incoherent = ZERO
material_xs % photoelectric = ZERO
material_xs % pair_production = ZERO
! Add contribution from each nuclide in material
do i = 1, this % n_nuclides
! ========================================================================
! CALCULATE MICROSCOPIC CROSS SECTION
! Determine microscopic cross sections for this nuclide
i_element = this % element(i)
! Calculate microscopic cross section for this nuclide
if (p % E /= micro_photon_xs(i_element) % last_E) then
call elements(i_element) % calculate_xs(&
p % E, micro_photon_xs(i_element))
end if
! ========================================================================
! ADD TO MACROSCOPIC CROSS SECTION
! Copy atom density of nuclide in material
atom_density = this % atom_density(i)
! Add contributions to material macroscopic total cross section
material_xs % total = material_xs % total + &
atom_density * micro_photon_xs(i_element) % total
! Add contributions to material macroscopic coherent cross section
material_xs % coherent = material_xs % coherent + &
atom_density * micro_photon_xs(i_element) % coherent
! Add contributions to material macroscopic incoherent cross section
material_xs % incoherent = material_xs % incoherent + &
atom_density * micro_photon_xs(i_element) % incoherent
! Add contributions to material macroscopic photoelectric cross section
material_xs % photoelectric = material_xs % photoelectric + &
atom_density * micro_photon_xs(i_element) % photoelectric
! Add contributions to material macroscopic pair production cross section
material_xs % pair_production = material_xs % pair_production + &
atom_density * micro_photon_xs(i_element) % pair_production
end do
end subroutine calculate_photon_xs
!===============================================================================
! FREE_MEMORY_MATERIAL deallocates global arrays defined in this module
@ -620,4 +700,215 @@ contains
end function openmc_material_set_densities
subroutine bremsstrahlung_init(this, i_material, particle)
class(BremsstrahlungData), intent(inout) :: this
integer, intent(in) :: i_material
integer, intent(in) :: particle
integer :: i, j
integer :: i_k
integer :: n, n_e, n_k
real(8) :: c
real(8) :: k, k_l, k_r
real(8) :: e, e_l, e_r
real(8) :: w, w_l, w_r
real(8) :: x, x_l, x_r
real(8) :: t
real(8) :: r
real(8) :: awr
real(8) :: beta
real(8) :: Z_eq_sq
real(8) :: atom_density
real(8) :: mass_density
real(8) :: sum_density
real(8), allocatable :: stopping_power_collision(:)
real(8), allocatable :: stopping_power_radiative(:)
real(8), allocatable :: stopping_power(:)
real(8), allocatable :: dcs(:,:)
real(8), allocatable :: f(:)
real(8), allocatable :: z(:)
logical :: positron_
type(Material), pointer :: mat
type(PhotonInteraction), pointer :: elm
! Get pointer to this material
mat => materials(i_material)
! Determine whether we are generating electron or positron data
positron_ = (particle == POSITRON)
! Get the size of the energy grids
n_k = size(ttb_k_grid)
n_e = size(ttb_e_grid)
! Allocate arrays for TTB data
allocate(this % pdf(n_e, n_e), source=ZERO)
allocate(this % cdf(n_e, n_e), source=ZERO)
allocate(this % yield(n_e))
! Allocate temporary arrays
allocate(stopping_power_collision(n_e), source=ZERO)
allocate(stopping_power_radiative(n_e), source=ZERO)
allocate(stopping_power(n_e))
allocate(dcs(n_k, n_e), source=ZERO)
allocate(f(n_e))
allocate(z(n_e))
Z_eq_sq = ZERO
sum_density = ZERO
! Calculate the molecular DCS and the molecular total stopping power using
! Bragg's additivity rule.
! TODO: The collision stopping power cannot be accurately calculated using
! Bragg's additivity rule since the mean excitation energies and the
! density effect corrections cannot simply be summed together. Bragg's
! additivity rule fails especially when a higher-density compound is
! composed of elements that are in lower-density form at normal temperature
! and pressure (at which the NIST stopping powers are given). It will be
! used to approximate the collision stopping powers for now, but should be
! fixed in the future.
do i = 1, mat % n_nuclides
! Get pointer to current element
elm => elements(mat % element(i))
awr = nuclides(mat % nuclide(i)) % awr
! Get atomic density and mass density of nuclide given atom percent
if (mat % atom_density(1) > ZERO) then
atom_density = mat % atom_density(i)
mass_density = mat % atom_density(i) * awr
! Given weight percent
else
atom_density = -mat % atom_density(i) / awr
mass_density = -mat % atom_density(i)
end if
! Calculate the "equivalent" atomic number Zeq of the material
Z_eq_sq = Z_eq_sq + atom_density * elm % Z**2
sum_density = sum_density + atom_density
! Accumulate material DCS
dcs = dcs + atom_density * elm % Z**2 * elm % dcs
! Accumulate material collision stopping power
stopping_power_collision = stopping_power_collision + mass_density &
* MASS_NEUTRON / N_AVOGADRO * elm % stopping_power_collision
! Accumulate material radiative stopping power
stopping_power_radiative = stopping_power_radiative + mass_density &
* MASS_NEUTRON / N_AVOGADRO * elm % stopping_power_radiative
end do
Z_eq_sq = Z_eq_sq / sum_density
! Calculate the positron DCS and radiative stopping power. These are
! obtained by multiplying the electron DCS and radiative stopping powers by
! a factor r, which is a numerical approximation of the ratio of the
! radiative stopping powers for positrons and electrons. Source: F. Salvat,
! J. M. Fernández-Varea, and J. Sempau, "PENELOPE-2011: A Code System for
! Monte Carlo Simulation of Electron and Photon Transport," OECD-NEA,
! Issy-les-Moulineaux, France (2011).
if (positron_) then
do i = 1, n_e
t = log(ONE + 1.0e6_8*ttb_e_grid(i)/(Z_eq_sq*MASS_ELECTRON_EV))
r = ONE - exp(-1.2359e-1_8*t + 6.1274e-2_8*t**2 - 3.1516e-2_8*t**3 + &
7.7446e-3_8*t**4 - 1.0595e-3_8*t**5 + 7.0568e-5_8*t**6 - &
1.808e-6_8*t**7)
stopping_power_radiative(i) = r*stopping_power_radiative(i)
dcs(:,i) = r*dcs(:,i)
end do
end if
! Total material stopping power
stopping_power = stopping_power_collision + stopping_power_radiative
! Loop over photon energies
do i = 1, n_e - 1
w = ttb_e_grid(i)
! Loop over incident particle energies
do j = i, n_e
e = ttb_e_grid(j)
! Reduced photon energy
k = w / e
! Find the lower bounding index of the reduced photon energy
i_k = binary_search(ttb_k_grid, n_k, k)
! Get the interpolation bounds
k_l = ttb_k_grid(i_k)
k_r = ttb_k_grid(i_k+1)
x_l = dcs(i_k, j)
x_r = dcs(i_k+1, j)
! Find the value of the DCS using linear interpolation in reduced
! photon energy k
x = x_l + (k - k_l) * (x_r - x_l) / (k_r - k_l)
! Ratio of the velocity of the charged particle to the speed of light
beta = sqrt(e*(e + TWO*MASS_ELECTRON_EV)) / (e + MASS_ELECTRON_EV)
! Compute the integrand of the PDF
f(j) = x / (beta**2 * stopping_power(j) * w)
end do
! Number of points to integrate
n = n_e - i + 1
! Integrate the PDF using cubic spline integration over the incident
! particle energy
if (n > 2) then
call spline(n, ttb_e_grid(i:), f(i:), z(i:))
c = ZERO
do j = i, n_e - 1
c = c + spline_integrate(n, ttb_e_grid(i:), f(i:), z(i:), &
ttb_e_grid(j), ttb_e_grid(j+1))
this % pdf(i,j+1) = c
end do
! Integrate the last two points using trapezoidal rule in log-log space
else
e_l = log(ttb_e_grid(i))
e_r = log(ttb_e_grid(i+1))
x_l = log(f(i))
x_r = log(f(i+1))
this % pdf(i,i+1) = HALF * (e_r - e_l) * (exp(e_l + x_l) + exp(e_r + x_r))
end if
end do
! Loop over incident particle energies
do j = 2, n_e
! Set last element of PDF to small non-zero value to enable log-log
! interpolation
this % pdf(j,j) = exp(-500.0_8)
! Loop over photon energies
c = ZERO
do i = 1, j - 1
! Integrate the CDF from the PDF using the trapezoidal rule in log-log
! space
w_l = log(ttb_e_grid(i))
w_r = log(ttb_e_grid(i+1))
x_l = log(this % pdf(i,j))
x_r = log(this % pdf(i+1,j))
c = c + HALF * (w_r - w_l) * (exp(w_l + x_l) + exp(w_r + x_r))
this % cdf(i+1,j) = c
end do
! Set photon number yield
this % yield(j) = c
end do
! Use logarithm of number yield since it is log-log interpolated
where (this % yield > ZERO)
this % yield = log(this % yield)
elsewhere
this % yield = -500.0_8
end where
end subroutine bremsstrahlung_init
end module material_header

View file

@ -18,11 +18,9 @@ module math
public :: faddeeva
public :: w_derivative
public :: broaden_wmp_polynomials
!===============================================================================
! FADDEEVA_W evaluates the scaled complementary error function. This
! interfaces with the MIT C library
!===============================================================================
public :: spline
public :: spline_interpolate
public :: spline_integrate
interface
@ -105,6 +103,40 @@ module math
real(C_DOUBLE), intent(inout) :: factors(n)
end subroutine broaden_wmp_polynomials
subroutine spline(n, x, y, z) bind(C, name='spline_c')
use ISO_C_BINDING
implicit none
integer(C_INT), value, intent(in) :: n
real(C_DOUBLE), intent(in) :: x(n)
real(C_DOUBLE), intent(in) :: y(n)
real(C_DOUBLE), intent(in) :: z(n)
end subroutine spline
function spline_interpolate(n, x, y, z, xint) &
bind(C, name='spline_interpolate_c') result(yint)
use ISO_C_BINDING
implicit none
integer(C_INT), value, intent(in) :: n
real(C_DOUBLE), intent(in) :: x(n)
real(C_DOUBLE), intent(in) :: y(n)
real(C_DOUBLE), intent(in) :: z(n)
real(C_DOUBLE), value, intent(in) :: xint
real(C_DOUBLE) :: yint
end function spline_interpolate
function spline_integrate(n, x, y, z, xa, xb) &
bind(C, name='spline_integrate_c') result(s)
use ISO_C_BINDING
implicit none
integer(C_INT), value, intent(in) :: n
real(C_DOUBLE), intent(in) :: x(n)
real(C_DOUBLE), intent(in) :: y(n)
real(C_DOUBLE), intent(in) :: z(n)
real(C_DOUBLE), value, intent(in) :: xa
real(C_DOUBLE), value, intent(in) :: xb
real(C_DOUBLE) :: s
end function spline_integrate
function faddeeva_w(z, relerr) bind(C, name='Faddeeva_w') result(w)
use ISO_C_BINDING
implicit none

View file

@ -690,4 +690,96 @@ void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) {
}
}
void spline_c(int n, const double x[], const double y[], double z[])
{
double c_new[n-1];
// Set natural boundary conditions
c_new[0] = 0.0;
z[0] = 0.0;
z[n-1] = 0.0;
// Solve using tridiagonal matrix algorithm; first do forward sweep
for (int i = 1; i < n - 1; i++) {
double a = x[i] - x[i-1];
double c = x[i+1] - x[i];
double b = 2.0*(a + c);
double d = 6.0*((y[i+1] - y[i])/c - (y[i] - y[i-1])/a);
c_new[i] = c/(b - a*c_new[i-1]);
z[i] = (d - a*z[i-1])/(b - a*c_new[i-1]);
}
// Back substitution
for (int i = n - 2; i >= 0; i--) {
z[i] = z[i] - c_new[i]*z[i+1];
}
}
double spline_interpolate_c(int n, const double x[], const double y[],
const double z[], double xint)
{
// Find the lower bounding index in x of xint
int i = n - 1;
while (--i) {
if (xint >= x[i]) break;
}
double h = x[i+1] - x[i];
double r = xint - x[i];
// Compute the coefficients
double b = (y[i+1] - y[i])/h - (h/6.0)*(z[i+1] + 2.0*z[i]);
double c = z[i]/2.0;
double d = (z[i+1] - z[i])/(h*6.0);
return y[i] + b*r + c*r*r + d*r*r*r;
}
double spline_integrate_c(int n, const double x[], const double y[],
const double z[], double xa, double xb)
{
// Find the lower bounding index in x of the lower limit of integration.
int ia = n - 1;
while (--ia) {
if (xa >= x[ia]) break;
}
// Find the lower bounding index in x of the upper limit of integration.
int ib = n - 1;
while (--ib) {
if (xb >= x[ib]) break;
}
// Evaluate the integral
double s = 0.0;
for (int i = ia; i <= ib; i++) {
double h = x[i+1] - x[i];
// Compute the coefficients
double b = (y[i+1] - y[i])/h - (h/6.0)*(z[i+1] + 2.0*z[i]);
double c = z[i]/2.0;
double d = (z[i+1] - z[i])/(h*6.0);
// Subtract the integral from x[ia] to xa
if (i == ia) {
double r = xa - x[ia];
s = s - (y[i]*r + b/2.0*r*r + c/3.0*r*r*r + d/4.0*r*r*r*r);
}
// Integrate from x[ib] to xb in final interval
if (i == ib) {
h = xb - x[ib];
}
// Accumulate the integral
s = s + y[i]*h + b/2.0*h*h + c/3.0*h*h*h + d/4.0*h*h*h*h;
}
return s;
}
} // namespace openmc

View file

@ -149,5 +149,57 @@ extern "C" double watt_spectrum_c(double a, double b);
extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n,
double factors[]);
//==============================================================================
//! Constructs a natural cubic spline.
//!
//! Given a tabulated function y_i = f(x_i), this computes the second
//! derivative of the interpolating function at each x_i, which can then be
//! used in any subsequent calls to spline_interpolate or spline_integrate for
//! the same set of x and y values.
//!
//! @param n Number of points
//! @param x Values of the independent variable, which must be strictly
//! increasing.
//! @param y Values of the dependent variable.
//! @param[out] z The second derivative of the interpolating function at each
//! value of x.
//==============================================================================
extern "C" void spline_c(int n, const double x[], const double y[], double z[]);
//==============================================================================
//! Determine the cubic spline interpolated y-value for a given x-value.
//!
//! @param n Number of points
//! @param x Values of the independent variable, which must be strictly
//! increasing.
//! @param y Values of the dependent variable.
//! @param z The second derivative of the interpolating function at each
//! value of x.
//! @param xint Point at which to evaluate the cubic spline polynomial
//! @result Interpolated value
//==============================================================================
extern "C" double spline_interpolate_c(int n, const double x[], const double y[],
const double z[], double xint);
//==============================================================================
//! Evaluate the definite integral of the interpolating cubic spline between
//! the given endpoints.
//!
//! @param n Number of points
//! @param x Values of the independent variable, which must be strictly
//! increasing.
//! @param y Values of the dependent variable.
//! @param z The second derivative of the interpolating function at each
//! value of x.
//! @param xa Lower limit of integration
//! @param xb Upper limit of integration
//! @result Integral
//==============================================================================
extern "C" double spline_integrate_c(int n, const double x[], const double y[],
const double z[], double xa, double xb);
} // namespace openmc
#endif // MATH_FUNCTIONS_H
#endif // MATH_FUNCTIONS_H

View file

@ -42,14 +42,15 @@ module nuclide_header
! Positions for first dimension of Nuclide % xs
integer, parameter :: &
XS_TOTAL = 1, &
XS_ABSORPTION = 2, &
XS_FISSION = 3, &
XS_NU_FISSION = 4
XS_TOTAL = 1, &
XS_ABSORPTION = 2, &
XS_FISSION = 3, &
XS_NU_FISSION = 4, &
XS_PHOTON_PROD = 5
! The array within SumXS is of shape (4, n_energy) where the first dimension
! The array within SumXS is of shape (5, n_energy) where the first dimension
! corresponds to the following values: 1) total, 2) absorption (MT > 100), 3)
! fission, 4) neutron production
! fission, 4) neutron production, 5) photon production
type SumXS
real(8), allocatable :: value(:,:)
end type SumXS
@ -104,8 +105,8 @@ module nuclide_header
integer :: reaction_index(891)
! Fission energy release
class(Function1D), allocatable :: fission_q_prompt ! prompt neutrons, gammas
class(Function1D), allocatable :: fission_q_recov ! neutrons, gammas, betas
class(Function1D), allocatable :: fission_q_prompt ! fragments and prompt neutrons, gammas
class(Function1D), allocatable :: fission_q_recov ! fragments, neutrons, gammas, betas
contains
procedure :: assign_0K_elastic_scattering
@ -138,6 +139,7 @@ module nuclide_header
! averaged over bound and non-bound nuclei
real(8) :: thermal ! Bound thermal elastic & inelastic scattering
real(8) :: thermal_elastic ! Bound thermal elastic scattering
real(8) :: photon_prod ! microscopic photon production xs
! Cross sections for depletion reactions (note that these are not stored in
! macroscopic cache)
@ -169,6 +171,13 @@ module nuclide_header
real(C_DOUBLE) :: absorption ! macroscopic absorption xs
real(C_DOUBLE) :: fission ! macroscopic fission xs
real(C_DOUBLE) :: nu_fission ! macroscopic production xs
real(C_DOUBLE) :: photon_prod ! macroscopic photon production xs
! Photon cross sections
real(C_DOUBLE) :: coherent ! macroscopic coherent xs
real(C_DOUBLE) :: incoherent ! macroscopic incoherent xs
real(C_DOUBLE) :: photoelectric ! macroscopic photoelectric xs
real(C_DOUBLE) :: pair_production ! macroscopic pair production xs
end type MaterialMacroXS
!===============================================================================
@ -196,8 +205,8 @@ module nuclide_header
!$omp threadprivate(micro_xs, material_xs)
! Minimum/maximum energies
real(8) :: energy_min_neutron = ZERO
real(8) :: energy_max_neutron = INFINITY
real(8) :: energy_min(2) = [ZERO, ZERO]
real(8) :: energy_max(2) = [INFINITY, INFINITY]
contains
@ -549,34 +558,36 @@ contains
if (object_exists(group_id, 'fission_energy_release')) then
fer_group = open_group(group_id, 'fission_energy_release')
! Check to see if this is polynomial or tabulated data
! Q-PROMPT
fer_dset = open_dataset(fer_group, 'q_prompt')
call read_attribute(temp_str, fer_dset, 'type')
if (temp_str == 'Polynomial') then
! Read the prompt Q-value
allocate(Polynomial :: this % fission_q_prompt)
call this % fission_q_prompt % from_hdf5(fer_dset)
call close_dataset(fer_dset)
! Read the recoverable energy Q-value
allocate(Polynomial :: this % fission_q_recov)
fer_dset = open_dataset(fer_group, 'q_recoverable')
call this % fission_q_recov % from_hdf5(fer_dset)
call close_dataset(fer_dset)
else if (temp_str == 'Tabulated1D') then
! Read the prompt Q-value
allocate(Tabulated1D :: this % fission_q_prompt)
call this % fission_q_prompt % from_hdf5(fer_dset)
call close_dataset(fer_dset)
else
call fatal_error('Unrecognized fission prompt energy release format.')
end if
! Read the recoverable energy Q-value
! Q-RECOV
fer_dset = open_dataset(fer_group, 'q_recoverable')
call read_attribute(temp_str, fer_dset, 'type')
if (temp_str == 'Polynomial') then
allocate(Polynomial :: this % fission_q_recov)
call this % fission_q_recov % from_hdf5(fer_dset)
call close_dataset(fer_dset)
else if (temp_str == 'Tabulated1D') then
allocate(Tabulated1D :: this % fission_q_recov)
fer_dset = open_dataset(fer_group, 'q_recoverable')
call this % fission_q_recov % from_hdf5(fer_dset)
call close_dataset(fer_dset)
else
call fatal_error('Unrecognized fission energy release format.')
call fatal_error('Unrecognized fission recoverable energy release format.')
end if
call close_group(fer_group)
end if
@ -591,7 +602,7 @@ contains
subroutine nuclide_create_derived(this)
class(Nuclide), intent(inout) :: this
integer :: i, j, k
integer :: i, j, k, l
integer :: t
integer :: m
integer :: n
@ -606,7 +617,7 @@ contains
do i = 1, n_temperature
! Allocate and initialize derived cross sections
n_grid = size(this % grid(i) % energy)
allocate(this % xs(i) % value(4,n_grid))
allocate(this % xs(i) % value(5,n_grid))
this % xs(i) % value(:,:) = ZERO
end do
@ -638,6 +649,18 @@ contains
this % xs(t) % value(XS_TOTAL,j:j+n-1) = this % xs(t) % &
value(XS_TOTAL,j:j+n-1) + rx % xs(t) % value
! Calculate photon production cross section
do k = 1, size(rx % products)
if (rx % products(k) % particle == PHOTON) then
do l = 1, n
this % xs(t) % value(XS_PHOTON_PROD,l+j-1) = &
this % xs(t) % value(XS_PHOTON_PROD,l+j-1) + &
rx % xs(t) % value(l) * rx % products(k) % &
yield % evaluate(this % grid(t) % energy(l+j-1))
end do
end if
end do
! Add contribution to absorption cross section
if (is_disappearance(rx % MT)) then
this % xs(t) % value(XS_ABSORPTION,j:j+n-1) = this % xs(t) % &
@ -664,10 +687,6 @@ contains
this % xs(t) % value(XS_ABSORPTION,j:j+n-1) = this % xs(t) % &
value(XS_ABSORPTION,j:j+n-1) + rx % xs(t) % value
! If total fission reaction is present, there's no need to store the
! reaction cross-section since it was copied to this % fission
if (rx % MT == N_FISSION) deallocate(rx % xs(t) % value)
! Keep track of this reaction for easy searching later
if (t == 1) then
i_fission = i_fission + 1
@ -975,6 +994,10 @@ contains
micro_xs % fission = ZERO
micro_xs % nu_fission = ZERO
end if
! Calculate microscopic nuclide photon production cross section
micro_xs % photon_prod = (ONE - f) * xs % value(XS_PHOTON_PROD,i_grid) &
+ f * xs % value(XS_PHOTON_PROD,i_grid + 1)
end associate
! Depletion-related reactions
@ -1695,8 +1718,8 @@ contains
if (res_scat_on) call nuclides(n) % assign_0K_elastic_scattering()
! Initialize nuclide grid
call nuclides(n) % init_grid(energy_min_neutron, &
energy_max_neutron, n_log_bins)
call nuclides(n) % init_grid(energy_min(NEUTRON), &
energy_max(NEUTRON), n_log_bins)
else
err = E_DATA
call set_errmsg("Nuclide '" // trim(name_) // "' is not present &

View file

@ -76,7 +76,7 @@ contains
write(UNIT=OUTPUT_UNIT, FMT=*) &
' | The OpenMC Monte Carlo Code'
write(UNIT=OUTPUT_UNIT, FMT=*) &
' Copyright | 2011-2018 Massachusetts Institute of Technology'
' Copyright | 2011-2018 MIT and OpenMC contributors'
write(UNIT=OUTPUT_UNIT, FMT=*) &
' License | http://openmc.readthedocs.io/en/latest/license.html'
write(UNIT=OUTPUT_UNIT, FMT='(11X,"Version | ",I1,".",I2,".",I1)') &
@ -171,7 +171,7 @@ contains
write(UNIT=OUTPUT_UNIT, FMT='(1X,A,A)') "Git SHA1: ", GIT_SHA1
#endif
write(UNIT=OUTPUT_UNIT, FMT=*) "Copyright (c) 2011-2018 &
&Massachusetts Institute of Technology"
&Massachusetts Institute of Technology and OpenMC contributors"
write(UNIT=OUTPUT_UNIT, FMT=*) "MIT/X license at &
&<http://openmc.readthedocs.io/en/latest/license.html>"
end if
@ -547,7 +547,7 @@ contains
! format for write statements
100 format (1X,A,T36,"= ",ES11.4," seconds")
101 format (1X,A,T36,"= ",A," neutrons/second")
101 format (1X,A,T36,"= ",A," particles/second")
end subroutine print_runtime

225
src/particle.cpp Normal file
View file

@ -0,0 +1,225 @@
#include "particle.h"
#include <algorithm>
#include <sstream>
#include "constants.h"
#include "error.h"
#include "hdf5_interface.h"
#include "openmc.h"
#include "settings.h"
#include "simulation.h"
namespace openmc {
//==============================================================================
// LocalCoord implementation
//==============================================================================
void
LocalCoord::reset()
{
cell = 0;
universe = 0;
lattice = 0;
lattice_x = 0;
lattice_y = 0;
rotated = false;
}
//==============================================================================
// Particle implementation
//==============================================================================
void
Particle::clear()
{
// reset any coordinate levels
for (int i=0; i<MAX_COORD; ++i) coord[i].reset();
}
void
Particle::create_secondary(const double* uvw, double E, int type, bool run_CE)
{
if (n_secondary == MAX_SECONDARY) {
fatal_error("Too many secondary particles created.");
}
int64_t n = n_secondary;
secondary_bank[n].particle = type;
secondary_bank[n].wgt = wgt;
std::copy(coord[0].xyz, coord[0].xyz + 3, secondary_bank[n].xyz);
std::copy(uvw, uvw + 3, secondary_bank[n].uvw);
secondary_bank[n].E = E;
if (!run_CE) secondary_bank[n].E = g;
n_secondary += 1;
}
void
Particle::initialize()
{
// Clear coordinate lists
clear();
// Set particle to neutron that's alive
type = NEUTRON;
alive = true;
// clear attributes
surface = 0;
cell_born = 0;
material = 0;
last_material = 0;
last_sqrtkT = 0;
wgt = 1.0;
last_wgt = 1.0;
absorb_wgt = 0.0;
n_bank = 0;
wgt_bank = 0.0;
sqrtkT = -1.0;
n_collision = 0;
fission = false;
delayed_group = 0;
for (int i=0; i<MAX_DELAYED_GROUPS; ++i) {
n_delayed_bank[i] = 0;
}
g = 0;
// Set up base level coordinates
coord[0].universe = C_NONE;
n_coord = 1;
last_n_coord = 1;
}
void
Particle::from_source(const Bank* src, bool run_CE, const double* energy_bin_avg)
{
// set defaults
initialize();
// copy attributes from source bank site
type = src->particle;
wgt = src->wgt;
last_wgt = src->wgt;
std::copy(src->xyz, src->xyz + 3, coord[0].xyz);
std::copy(src->uvw, src->uvw + 3, coord[0].uvw);
std::copy(src->xyz, src->xyz + 3, last_xyz_current);
std::copy(src->xyz, src->xyz + 3, last_xyz);
std::copy(src->uvw, src->uvw + 3, last_uvw);
if (run_CE) {
E = src->E;
g = 0;
} else {
g = static_cast<int>(src->E);
last_g = static_cast<int>(src->E);
E = energy_bin_avg[g - 1];
}
last_E = E;
}
void
Particle::mark_as_lost(const char* message)
{
// Print warning and write lost particle file
warning(message);
write_restart();
// Increment number of lost particles
alive = false;
#pragma omp atomic
openmc_n_lost_particles += 1;
// Count the total number of simulated particles (on this processor)
auto n = openmc_current_batch * gen_per_batch * openmc_work;
// Abort the simulation if the maximum number of lost particles has been
// reached
if (openmc_n_lost_particles >= MAX_LOST_PARTICLES &&
openmc_n_lost_particles >= REL_MAX_LOST_PARTICLES*n) {
fatal_error("Maximum number of lost particles has been reached.");
}
}
void
Particle::write_restart()
{
// Dont write another restart file if in particle restart mode
if (openmc_run_mode == RUN_MODE_PARTICLE) return;
// Set up file name
std::stringstream filename;
filename << path_output << "particle_" << openmc_current_batch << '_' << id << ".h5";
#pragma omp critical (WriteParticleRestart)
{
// Create file
hid_t file_id = file_open(filename.str(), 'w');
// Write filetype and version info
write_attribute(file_id, "filetype", "particle restart");
write_attribute(file_id, "version", VERSION_PARTICLE_RESTART);
write_attribute(file_id, "openmc_version", VERSION);
#ifdef GIT_SHA1
write_attr_string(file_id, "git_sha1", GIT_SHA1);
#endif
// Write data to file
write_dataset(file_id, "current_batch", openmc_current_batch);
write_dataset(file_id, "generations_per_batch", gen_per_batch);
write_dataset(file_id, "current_generation", openmc_current_gen);
write_dataset(file_id, "n_particles", n_particles);
switch (openmc_run_mode) {
case RUN_MODE_FIXEDSOURCE:
write_dataset(file_id, "run_mode", "fixed source");
break;
case RUN_MODE_EIGENVALUE:
write_dataset(file_id, "run_mode", "eigenvalue");
break;
case RUN_MODE_PARTICLE:
write_dataset(file_id, "run_mode", "particle restart");
break;
}
write_dataset(file_id, "id", id);
write_dataset(file_id, "type", type);
// Get pointer to source bank
Bank* src;
int64_t n;
openmc_source_bank(&src, &n);
int64_t i = openmc_current_work;
write_dataset(file_id, "weight", src[i-1].wgt);
write_dataset(file_id, "energy", src[i-1].E);
hsize_t dims[] {3};
write_double(file_id, 1, dims, "xyz", src[i-1].xyz, false);
write_double(file_id, 1, dims, "uvw", src[i-1].uvw, false);
// Close file
file_close(file_id);
} // #pragma omp critical
}
//==============================================================================
// Fortran compatibility functions
//==============================================================================
void reset_coord(LocalCoord* c) { c->reset(); }
void particle_clear(Particle* p) { p->clear(); }
void particle_create_secondary(Particle* p, const double* uvw, double E,
int type, bool run_CE)
{
p->create_secondary(uvw, E, type, run_CE);
}
void particle_initialize(Particle* p) { p->initialize(); }
void particle_from_source(Particle* p, const Bank* src, bool run_CE,
const double* energy_bin_avg)
{
p->from_source(src, run_CE, energy_bin_avg);
}
void particle_mark_as_lost(Particle* p, const char* message)
{
p->mark_as_lost(message);
}
void particle_write_restart(Particle* p) { p->write_restart(); }
} // namespace openmc

163
src/particle.h Normal file
View file

@ -0,0 +1,163 @@
#ifndef OPENMC_PARTICLE_H
#define OPENMC_PARTICLE_H
//! \file particle.h
//! \brief Particle type
#include <cstdint>
#include <array>
#include "openmc.h"
namespace openmc {
//==============================================================================
// Constants
//==============================================================================
constexpr int MAX_DELAYED_GROUPS {8};
constexpr int MAX_SECONDARY {1000};
constexpr int NEUTRON {1};
constexpr int MAX_LOST_PARTICLES {10};
constexpr double REL_MAX_LOST_PARTICLES {1.0e-6};
extern "C" {
struct LocalCoord {
int cell {-1};
int universe {-1};
int lattice {-1};
int lattice_x {-1};
int lattice_y {-1};
int lattice_z {-1};
double xyz[3]; //!< particle position
double uvw[3]; //!< particle direction
bool rotated {false}; //!< Is the level rotated?
//! clear data from a single coordinate level
void reset();
};
//============================================================================
//! State of a particle being transported through geometry
//============================================================================
struct Particle {
int64_t id; //!< Unique ID
int type; //!< Particle type (n, p, e, etc.)
int n_coord; //!< number of current coordinate levels
int cell_instance; //!< offset for distributed properties
LocalCoord coord[MAX_COORD]; //!< coordinates for all levels
// Particle coordinates before crossing a surface
int last_n_coord; //!< number of current coordinates
int last_cell[MAX_COORD]; //!< coordinates for all levels
// Energy data
double E; //!< post-collision energy in eV
double last_E; //!< pre-collision energy in eV
int g; //!< post-collision energy group (MG only)
int last_g; //!< pre-collision energy group (MG only)
// Other physical data
double wgt; //!< particle weight
double mu; //!< angle of scatter
bool alive; //!< is particle alive?
// Other physical data
double last_xyz_current[3]; //!< coordinates of the last collision or
//!< reflective/periodic surface crossing for
//!< current tallies
double last_xyz[3]; //!< previous coordinates
double last_uvw[3]; //!< previous direction coordinates
double last_wgt; //!< pre-collision particle weight
double absorb_wgt; //!< weight absorbed for survival biasing
// What event took place
bool fission; //!< did particle cause implicit fission
int event; //!< scatter, absorption
int event_nuclide; //!< index in nuclides array
int event_MT; //!< reaction MT
int delayed_group; //!< delayed group
// Post-collision physical data
int n_bank; //!< number of fission sites banked
double wgt_bank; //!< weight of fission sites banked
int n_delayed_bank[MAX_DELAYED_GROUPS]; //!< number of delayed fission
//!< sites banked
// Indices for various arrays
int surface; //!< index for surface particle is on
int cell_born; //!< index for cell particle was born in
int material; //!< index for current material
int last_material; //!< index for last material
// Temperature of current cell
double sqrtkT; //!< sqrt(k_Boltzmann * temperature) in eV
double last_sqrtkT; //!< last temperature
// Statistical data
int n_collision; //!< number of collisions
// Track output
bool write_track {false};
// Secondary particles created
int64_t n_secondary {};
Bank secondary_bank[MAX_SECONDARY];
//! resets all coordinate levels for the particle
void clear();
//! create a secondary particle
//
//! stores the current phase space attributes of the particle in the
//! secondary bank and increments the number of sites in the secondary bank.
//! \param uvw Direction of the secondary particle
//! \param E Energy of the secondary particle in [eV]
//! \param type Particle type
//! \param run_CE Whether continuous-energy data is being used
void create_secondary(const double* uvw, double E, int type, bool run_CE);
//! sets default attributes for a particle
void initialize();
//! initialize from a source site
//
//! initializes a particle from data stored in a source site. The source
//! site may have been produced from an external source, from fission, or
//! simply as a secondary particle.
//! \param src Source site data
//! \param run_CE Whether continuous-energy data is being used
//! \param energy_bin_avg An array of energy group bin averages
void from_source(const Bank* src, bool run_CE, const double* energy_bin_avg);
//! mark a particle as lost and create a particle restart file
//! \param message A warning message to display
void mark_as_lost(const char* message);
//! create a particle restart HDF5 file
void write_restart();
};
//============================================================================
// Fortran compatibility functions
//============================================================================
void reset_coord(LocalCoord* c);
void particle_clear(Particle* p);
void particle_create_secondary(Particle* p, const double* uvw, double E,
int type, bool run_CE);
void particle_initialize(Particle* p);
void particle_from_source(Particle* p, const Bank* src, bool run_CE,
const double* energy_bin_avg);
void particle_mark_as_lost(Particle* p, const char* message);
void particle_write_restart(Particle* p);
} // extern "C"
} // namespace openmc
#endif // OPENMC_PARTICLE_H

View file

@ -1,42 +1,34 @@
module particle_header
use bank_header, only: Bank, source_bank
use, intrinsic :: ISO_C_BINDING
use bank_header, only: Bank
use constants
use error, only: fatal_error, warning
use geometry_header, only: root_universe
use hdf5_interface
use settings
use simulation_header
use string, only: to_str
use string, only: to_c_string
implicit none
private
!===============================================================================
! LOCALCOORD describes the location of a particle local to a single
! universe. When the geometry consists of nested universes, a particle will have
! a list of coordinates in each level
!===============================================================================
type, public :: LocalCoord
type, bind(C) :: LocalCoord
! Indices in various arrays for this level
integer :: cell = NONE
integer :: universe = NONE
integer :: lattice = NONE
integer :: lattice_x = NONE
integer :: lattice_y = NONE
integer :: lattice_z = NONE
integer(C_INT) :: cell = NONE
integer(C_INT) :: universe = NONE
integer(C_INT) :: lattice = NONE
integer(C_INT) :: lattice_x = NONE
integer(C_INT) :: lattice_y = NONE
integer(C_INT) :: lattice_z = NONE
! Particle position and direction for this level
real(8) :: xyz(3)
real(8) :: uvw(3)
real(C_DOUBLE) :: xyz(3)
real(C_DOUBLE) :: uvw(3)
! Is this level rotated?
logical :: rotated = .false.
contains
procedure :: reset => reset_coord
logical(C_BOOL) :: rotated = .false.
end type LocalCoord
!===============================================================================
@ -44,307 +36,126 @@ module particle_header
! geometry
!===============================================================================
type, public :: Particle
type, bind(C) :: Particle
! Basic data
integer(8) :: id ! Unique ID
integer :: type ! Particle type (n, p, e, etc)
integer(C_INT64_T) :: id ! Unique ID
integer(C_INT) :: type ! Particle type (n, p, e, etc)
! Particle coordinates
integer :: n_coord ! number of current coordinates
integer :: cell_instance ! offset for distributed properties
integer(C_INT) :: n_coord ! number of current coordinates
integer(C_INT) :: cell_instance ! offset for distributed properties
type(LocalCoord) :: coord(MAX_COORD) ! coordinates for all levels
! Particle coordinates before crossing a surface
integer :: last_n_coord ! number of current coordinates
integer :: last_cell(MAX_COORD) ! coordinates for all levels
integer(C_INT) :: last_n_coord ! number of current coordinates
integer(C_INT) :: last_cell(MAX_COORD) ! coordinates for all levels
! Energy Data
real(8) :: E ! post-collision energy
real(8) :: last_E ! pre-collision energy
integer :: g ! post-collision energy group (MG only)
integer :: last_g ! pre-collision energy group (MG only)
real(C_DOUBLE) :: E ! post-collision energy
real(C_DOUBLE) :: last_E ! pre-collision energy
integer(C_INT) :: g ! post-collision energy group (MG only)
integer(C_INT) :: last_g ! pre-collision energy group (MG only)
! Other physical data
real(8) :: wgt ! particle weight
real(8) :: mu ! angle of scatter
logical :: alive ! is particle alive?
real(C_DOUBLE) :: wgt ! particle weight
real(C_DOUBLE) :: mu ! angle of scatter
logical(C_BOOL) :: alive ! is particle alive?
! Pre-collision physical data
real(8) :: last_xyz_current(3) ! coordinates of the last collision or
! reflective/periodic surface crossing
! for current tallies
real(8) :: last_xyz(3) ! previous coordinates
real(8) :: last_uvw(3) ! previous direction coordinates
real(8) :: last_wgt ! pre-collision particle weight
real(8) :: absorb_wgt ! weight absorbed for survival biasing
real(C_DOUBLE) :: last_xyz_current(3) ! coordinates of the last collision or
! reflective/periodic surface crossing
! for current tallies
real(C_DOUBLE) :: last_xyz(3) ! previous coordinates
real(C_DOUBLE) :: last_uvw(3) ! previous direction coordinates
real(C_DOUBLE) :: last_wgt ! pre-collision particle weight
real(C_DOUBLE) :: absorb_wgt ! weight absorbed for survival biasing
! What event last took place
logical :: fission ! did the particle cause implicit fission
integer :: event ! scatter, absorption
integer :: event_nuclide ! index in nuclides array
integer :: event_MT ! reaction MT
integer :: delayed_group ! delayed group
logical(C_BOOL) :: fission ! did the particle cause implicit fission
integer(C_INT) :: event ! scatter, absorption
integer(C_INT) :: event_nuclide ! index in nuclides array
integer(C_INT) :: event_MT ! reaction MT
integer(C_INT) :: delayed_group ! delayed group
! Post-collision physical data
integer :: n_bank ! number of fission sites banked
real(8) :: wgt_bank ! weight of fission sites banked
integer :: n_delayed_bank(MAX_DELAYED_GROUPS) ! number of delayed fission
integer(C_INT) :: n_bank ! number of fission sites banked
real(C_DOUBLE) :: wgt_bank ! weight of fission sites banked
integer(C_INT) :: n_delayed_bank(MAX_DELAYED_GROUPS) ! number of delayed fission
! sites banked
! Indices for various arrays
integer :: surface ! index for surface particle is on
integer :: cell_born ! index for cell particle was born in
integer :: material ! index for current material
integer :: last_material ! index for last material
integer(C_INT) :: surface ! index for surface particle is on
integer(C_INT) :: cell_born ! index for cell particle was born in
integer(C_INT) :: material ! index for current material
integer(C_INT) :: last_material ! index for last material
! Temperature of the current cell
real(8) :: sqrtkT ! sqrt(k_Boltzmann * temperature) in eV
real(8) :: last_sqrtKT ! last temperature
real(C_DOUBLE) :: sqrtkT ! sqrt(k_Boltzmann * temperature) in eV
real(C_DOUBLE) :: last_sqrtKT ! last temperature
! Statistical data
integer :: n_collision ! # of collisions
integer(C_INT) :: n_collision ! # of collisions
! Track output
logical :: write_track = .false.
logical(C_BOOL) :: write_track = .false.
! Secondary particles created
integer(8) :: n_secondary = 0
type(Bank) :: secondary_bank(MAX_SECONDARY)
contains
procedure :: clear
procedure :: create_secondary
procedure :: initialize
procedure :: initialize_from_source
procedure :: mark_as_lost
procedure :: write_restart
integer(C_INT64_T) :: n_secondary = 0
type(Bank) :: secondary_bank(MAX_SECONDARY)
end type Particle
interface
subroutine reset_coord(c) bind(C)
import LocalCoord
type(LocalCoord), intent(inout) :: c
end subroutine reset_coord
subroutine particle_clear(p) bind(C)
import Particle
type(Particle), intent(inout) :: p
end subroutine particle_clear
subroutine particle_create_secondary(p, uvw, E, type, run_CE) bind(C)
import Particle, C_DOUBLE, C_INT, C_BOOL
type(Particle), intent(inout) :: p
real(C_DOUBLE), intent(in) :: uvw(3)
real(C_DOUBLE), value :: E
integer(C_INT), value :: type
logical(C_BOOL), value :: run_CE
end subroutine particle_create_secondary
subroutine particle_initialize(p) bind(C)
import Particle
type(Particle), intent(inout) :: p
end subroutine particle_initialize
subroutine particle_from_source(p, src, run_CE, energy_bin_avg) bind(C)
import Particle, Bank, C_BOOL, C_DOUBLE
type(Particle), intent(inout) :: p
type(Bank), intent(in) :: src
logical(C_BOOL), value :: run_CE
real(C_DOUBLE), intent(in) :: energy_bin_avg(*)
end subroutine particle_from_source
subroutine particle_mark_as_lost_c(p, message) bind(C, name='particle_mark_as_lost')
import Particle, C_CHAR
type(Particle), intent(in) :: p
character(kind=C_CHAR), intent(in) :: message(*)
end subroutine particle_mark_as_lost_c
subroutine particle_write_restart(p) bind(C)
import Particle
type(Particle), intent(in) :: p
end subroutine particle_write_restart
end interface
contains
!===============================================================================
! RESET_COORD clears data from a single coordinate level
!===============================================================================
subroutine particle_mark_as_lost(this, message)
type(Particle), intent(inout) :: this
character(*) :: message
elemental subroutine reset_coord(this)
class(LocalCoord), intent(inout) :: this
this % cell = NONE
this % universe = NONE
this % lattice = NONE
this % lattice_x = NONE
this % lattice_y = NONE
this % lattice_z = NONE
this % rotated = .false.
end subroutine reset_coord
!===============================================================================
! CLEAR_PARTICLE resets all coordinate levels for the particle
!===============================================================================
subroutine clear(this)
class(Particle) :: this
integer :: i
! remove any coordinate levels
do i = 1, MAX_COORD
call this % coord(i) % reset()
end do
end subroutine clear
!===============================================================================
! CREATE_SECONDARY stores the current phase space attributes of the particle in
! the secondary bank and increments the number of sites in the secondary bank.
!===============================================================================
subroutine create_secondary(this, uvw, type, run_CE)
class(Particle), intent(inout) :: this
real(8), intent(in) :: uvw(3)
integer, intent(in) :: type
logical, intent(in) :: run_CE
integer(8) :: n
! Check to make sure that the hard-limit on secondary particles is not
! exceeded.
if (this % n_secondary == MAX_SECONDARY) then
call fatal_error("Too many secondary particles created.")
end if
n = this % n_secondary + 1
this % secondary_bank(n) % wgt = this % wgt
this % secondary_bank(n) % xyz(:) = this % coord(1) % xyz
this % secondary_bank(n) % uvw(:) = uvw
this % n_secondary = n
this % secondary_bank(this % n_secondary) % E = this % E
if (.not. run_CE) then
this % secondary_bank(this % n_secondary) % E = real(this % g, 8)
end if
end subroutine create_secondary
!===============================================================================
! INITIALIZE sets default attributes for a particle from the source bank
!===============================================================================
subroutine initialize(this)
class(Particle) :: this
! Clear coordinate lists
call this % clear()
! Set particle to neutron that's alive
this % type = NEUTRON
this % alive = .true.
! clear attributes
this % surface = NONE
this % cell_born = NONE
this % material = NONE
this % last_material = NONE
this % last_sqrtkT = NONE
this % wgt = ONE
this % last_wgt = ONE
this % absorb_wgt = ZERO
this % n_bank = 0
this % wgt_bank = ZERO
this % sqrtkT = ERROR_REAL
this % n_collision = 0
this % fission = .false.
this % delayed_group = 0
this % n_delayed_bank(:) = 0
this % g = NONE
! Set up base level coordinates
this % coord(1) % universe = root_universe
this % n_coord = 1
this % last_n_coord = 1
end subroutine initialize
!===============================================================================
! INITIALIZE_FROM_SOURCE initializes a particle from data stored in a source
! site. The source site may have been produced from an external source, from
! fission, or simply as a secondary particle.
!===============================================================================
subroutine initialize_from_source(this, src, run_CE, energy_bin_avg)
class(Particle), intent(inout) :: this
type(Bank), intent(in) :: src
logical, intent(in) :: run_CE
real(8), allocatable, intent(in) :: energy_bin_avg(:)
! set defaults
call this % initialize()
! copy attributes from source bank site
this % wgt = src % wgt
this % last_wgt = src % wgt
this % coord(1) % xyz = src % xyz
this % coord(1) % uvw = src % uvw
this % last_xyz_current = src % xyz
this % last_xyz = src % xyz
this % last_uvw = src % uvw
if (run_CE) then
this % E = src % E
this % g = NONE
else
this % g = int(src % E)
this % last_g = int(src % E)
this % E = energy_bin_avg(this % g)
end if
this % last_E = this % E
end subroutine initialize_from_source
!===============================================================================
! MARK_AS_LOST
!===============================================================================
subroutine mark_as_lost(this, message)
class(Particle), intent(inout) :: this
character(*) :: message
integer(8) :: tot_n_particles
! Print warning and write lost particle file
call warning(message)
call this % write_restart()
! Increment number of lost particles
this % alive = .false.
!$omp atomic
n_lost_particles = n_lost_particles + 1
! Count the total number of simulated particles (on this processor)
tot_n_particles = current_batch * gen_per_batch * work
! Abort the simulation if the maximum number of lost particles has been
! reached
if (n_lost_particles >= MAX_LOST_PARTICLES .and. &
n_lost_particles >= REL_MAX_LOST_PARTICLES * tot_n_particles) then
call fatal_error("Maximum number of lost particles has been reached.")
end if
end subroutine mark_as_lost
!===============================================================================
! WRITE_RESTART creates a particle restart file
!===============================================================================
subroutine write_restart(this)
class(Particle), intent(in) :: this
integer(HID_T) :: file_id
character(MAX_FILE_LEN) :: filename
! Dont write another restart file if in particle restart mode
if (run_mode == MODE_PARTICLE) return
! Set up file name
filename = trim(path_output) // 'particle_' // trim(to_str(current_batch)) &
// '_' // trim(to_str(this % id)) // '.h5'
!$omp critical (WriteParticleRestart)
! Create file
file_id = file_open(filename, 'w')
associate (src => source_bank(current_work))
! Write filetype and version info
call write_attribute(file_id, 'filetype', 'particle restart')
call write_attribute(file_id, 'version', VERSION_PARTICLE_RESTART)
call write_attribute(file_id, "openmc_version", VERSION)
#ifdef GIT_SHA1
call write_attribute(file_id, "git_sha1", GIT_SHA1)
#endif
! Write data to file
call write_dataset(file_id, 'current_batch', current_batch)
call write_dataset(file_id, 'generations_per_batch', gen_per_batch)
call write_dataset(file_id, 'current_generation', current_gen)
call write_dataset(file_id, 'n_particles', n_particles)
select case(run_mode)
case (MODE_FIXEDSOURCE)
call write_dataset(file_id, 'run_mode', 'fixed source')
case (MODE_EIGENVALUE)
call write_dataset(file_id, 'run_mode', 'eigenvalue')
case (MODE_PARTICLE)
call write_dataset(file_id, 'run_mode', 'particle restart')
end select
call write_dataset(file_id, 'id', this % id)
call write_dataset(file_id, 'weight', src % wgt)
call write_dataset(file_id, 'energy', src % E)
call write_dataset(file_id, 'xyz', src % xyz)
call write_dataset(file_id, 'uvw', src % uvw)
end associate
! Close file
call file_close(file_id)
!$omp end critical (WriteParticleRestart)
end subroutine write_restart
call particle_mark_as_lost_c(this, to_c_string(message))
end subroutine particle_mark_as_lost
end module particle_header

View file

@ -9,7 +9,7 @@ module particle_restart
use mgxs_interface, only: energy_bin_avg
use nuclide_header, only: micro_xs, n_nuclides
use output, only: print_particle
use particle_header, only: Particle
use particle_header
use random_lcg, only: set_particle_seed
use settings
use simulation_header
@ -41,7 +41,7 @@ contains
allocate(micro_xs(n_nuclides))
! Initialize the particle to be tracked
call p % initialize()
call particle_initialize(p)
! Read in the restart information
call read_particle_restart(p, previous_run_mode)
@ -100,6 +100,7 @@ contains
previous_run_mode = MODE_FIXEDSOURCE
end select
call read_dataset(p % id, file_id, 'id')
call read_dataset(p % type, file_id, 'type')
call read_dataset(p % wgt, file_id, 'weight')
call read_dataset(p % E, file_id, 'energy')
call read_dataset(p % coord(1) % xyz, file_id, 'xyz')

506
src/photon_header.F90 Normal file
View file

@ -0,0 +1,506 @@
module photon_header
use algorithm, only: binary_search
use constants
use dict_header, only: DictIntInt, DictCharInt
use endf_header, only: Tabulated1D
use hdf5_interface
use nuclide_header, only: nuclides
use settings
real(8), allocatable :: compton_profile_pz(:)
real(8), allocatable :: ttb_e_grid(:) ! energy T of incident electron
real(8), allocatable :: ttb_k_grid(:) ! reduced energy W/T of emitted photon
type ElectronSubshell
integer :: index_subshell ! index in SUBSHELLS
integer :: threshold
real(8) :: n_electrons
real(8) :: binding_energy
real(8), allocatable :: cross_section(:)
! Transition data
integer :: n_transitions
integer, allocatable :: transition_subshells(:,:)
real(8), allocatable :: transition_energy(:)
real(8), allocatable :: transition_probability(:)
end type ElectronSubshell
type PhotonInteraction
character(3) :: name ! atomic symbol, e.g. 'Zr'
integer :: Z ! atomic number
! Microscopic cross sections
real(8), allocatable :: energy(:)
real(8), allocatable :: coherent(:)
real(8), allocatable :: incoherent(:)
real(8), allocatable :: photoelectric_total(:)
real(8), allocatable :: pair_production_total(:)
real(8), allocatable :: pair_production_electron(:)
real(8), allocatable :: pair_production_nuclear(:)
! Form factors
type(Tabulated1D) :: incoherent_form_factor
type(Tabulated1D) :: coherent_int_form_factor
type(Tabulated1D) :: coherent_anomalous_real
type(Tabulated1D) :: coherent_anomalous_imag
! Photoionization and atomic relaxation data
type(DictIntInt) :: shell_dict ! Given a shell designator, e.g. 3, this
! dictionary gives an index in shells(:)
type(ElectronSubshell), allocatable :: shells(:)
! Compton profile data
real(8), allocatable :: profile_pdf(:,:)
real(8), allocatable :: profile_cdf(:,:)
real(8), allocatable :: binding_energy(:)
real(8), allocatable :: electron_pdf(:)
! Stopping power data
real(8) :: I ! mean excitation energy
real(8), allocatable :: stopping_power_collision(:)
real(8), allocatable :: stopping_power_radiative(:)
! Bremsstrahlung scaled DCS
real(8), allocatable :: dcs(:,:)
contains
procedure :: from_hdf5 => photon_from_hdf5
procedure :: calculate_xs => photon_calculate_xs
end type PhotonInteraction
type BremsstrahlungData
real(8), allocatable :: pdf(:,:) ! Bremsstrahlung energy PDF
real(8), allocatable :: cdf(:,:) ! Bremsstrahlung energy CDF
real(8), allocatable :: yield(:) ! Photon number yield
end type BremsstrahlungData
type Bremsstrahlung
type(BremsstrahlungData) :: electron
type(BremsstrahlungData) :: positron
end type Bremsstrahlung
type(PhotonInteraction), allocatable, target :: elements(:) ! Photon cross sections
integer :: n_elements ! Number of photon cross section tables
type(DictCharInt) :: element_dict
type(Bremsstrahlung), allocatable, target :: ttb(:) ! Bremsstrahlung data
!===============================================================================
! ELEMENTMICROXS contains cached microscopic photon cross sections for a
! particular element at the current energy
!===============================================================================
type ElementMicroXS
integer :: index_grid ! index on element energy grid
real(8) :: last_E = ZERO ! last evaluated energy
real(8) :: interp_factor ! interpolation factor on energy grid
real(8) :: total ! microscropic total photon xs
real(8) :: coherent ! microscopic coherent xs
real(8) :: incoherent ! microscopic incoherent xs
real(8) :: photoelectric ! microscopic photoelectric xs
real(8) :: pair_production ! microscopic pair production xs
end type ElementMicroXS
type(ElementMicroXS), allocatable :: micro_photon_xs(:) ! Cache for each element
!$omp threadprivate(micro_photon_xs)
contains
subroutine photon_from_hdf5(this, group_id)
class(PhotonInteraction), intent(inout) :: this
integer(HID_T), intent(in) :: group_id
integer :: i, j
integer(HID_T) :: rgroup, tgroup
integer(HID_T) :: dset_id
integer(HSIZE_T) :: dims(1), dims2(2)
integer :: n_energy
integer :: n_shell
integer :: n_profile
integer :: n_transition
integer :: n_k
integer :: n_e
character(3), allocatable :: designators(:)
real(8) :: c
real(8) :: f
real(8) :: y
real(8), allocatable :: electron_energy(:)
real(8), allocatable :: matrix(:,:)
real(8), allocatable :: dcs(:,:)
! Get name of nuclide from group
this % name = get_name(group_id)
! Get rid of leading '/'
this % name = trim(this % name(2:))
! Get atomic number
call read_attribute(this % Z, group_id, 'Z')
! Determine number of energies and read energy grid
dset_id = open_dataset(group_id, 'energy')
call get_shape(dset_id, dims)
n_energy = int(dims(1), 4)
allocate(this % energy(dims(1)))
call read_dataset(this % energy, dset_id)
call close_dataset(dset_id)
! Allocate arrays
allocate(this % coherent(n_energy))
allocate(this % incoherent(n_energy))
allocate(this % pair_production_total(n_energy))
allocate(this % pair_production_nuclear(n_energy))
allocate(this % pair_production_electron(n_energy))
allocate(this % photoelectric_total(n_energy))
! Read coherent scattering
rgroup = open_group(group_id, 'coherent')
call read_dataset(this % coherent, rgroup, 'xs')
dset_id = open_dataset(rgroup, 'integrated_scattering_factor')
call this % coherent_int_form_factor % from_hdf5(dset_id)
call close_dataset(dset_id)
dset_id = open_dataset(rgroup, 'anomalous_real')
call this % coherent_anomalous_real % from_hdf5(dset_id)
call close_dataset(dset_id)
dset_id = open_dataset(rgroup, 'anomalous_imag')
call this % coherent_anomalous_imag % from_hdf5(dset_id)
call close_dataset(dset_id)
call close_group(rgroup)
! Read incoherent scattering
rgroup = open_group(group_id, 'incoherent')
call read_dataset(this % incoherent, rgroup, 'xs')
dset_id = open_dataset(rgroup, 'scattering_factor')
call this % incoherent_form_factor % from_hdf5(dset_id)
call close_dataset(dset_id)
call close_group(rgroup)
! Read pair production
rgroup = open_group(group_id, 'pair_production_electron')
call read_dataset(this % pair_production_electron, rgroup, 'xs')
call close_group(rgroup)
! Read pair production
if (object_exists(group_id, 'pair_production_nuclear')) then
rgroup = open_group(group_id, 'pair_production_nuclear')
call read_dataset(this % pair_production_nuclear, rgroup, 'xs')
call close_group(rgroup)
else
this % pair_production_nuclear(:) = ZERO
end if
! Read photoelectric
rgroup = open_group(group_id, 'photoelectric')
call read_dataset(this % photoelectric_total, rgroup, 'xs')
call close_group(rgroup)
! Read subshell photoionization cross section and atomic relaxation data
rgroup = open_group(group_id, 'subshells')
call read_attribute(designators, rgroup, 'designators')
n_shell = size(designators)
allocate(this % shells(n_shell))
do i = 1, n_shell
! Create mapping from designator to index
do j = 1, size(SUBSHELLS)
if (designators(i) == SUBSHELLS(j)) then
call this % shell_dict % set(j, i)
this % shells(i) % index_subshell = j
exit
end if
end do
! Read binding energy and number of electrons
tgroup = open_group(rgroup, trim(designators(i)))
call read_attribute(this % shells(i) % binding_energy, tgroup, &
'binding_energy')
call read_attribute(this % shells(i) % n_electrons, tgroup, &
'num_electrons')
! Read subshell cross section
dset_id = open_dataset(tgroup, 'xs')
call read_attribute(j, dset_id, 'threshold_idx')
this % shells(i) % threshold = j
allocate(this % shells(i) % cross_section(n_energy - j))
call read_dataset(this % shells(i) % cross_section, dset_id)
call close_dataset(dset_id)
where (this % shells(i) % cross_section > ZERO)
this % shells(i) % cross_section = log(this % shells(i) % cross_section)
elsewhere
this % shells(i) % cross_section = -500.0_8
end where
if (object_exists(tgroup, 'transitions')) then
dset_id = open_dataset(tgroup, 'transitions')
call get_shape(dset_id, dims2)
n_transition = int(dims2(2), 4)
this % shells(i) % n_transitions = n_transition
if (n_transition > 0) then
allocate(this % shells(i) % transition_subshells(2, n_transition))
allocate(this % shells(i) % transition_energy(n_transition))
allocate(this % shells(i) % transition_probability(n_transition))
allocate(matrix(dims2(1), dims2(2)))
call read_dataset(matrix, dset_id)
this % shells(i) % transition_subshells(:,:) = int(matrix(1:2, :), 4)
this % shells(i) % transition_energy(:) = matrix(3, :)
this % shells(i) % transition_probability(:) = matrix(4, :) &
/ sum(matrix(4, :))
deallocate(matrix)
end if
call close_dataset(dset_id)
else
this % shells(i) % n_transitions = 0
end if
call close_group(tgroup)
end do
call close_group(rgroup)
deallocate(designators)
! Determine number of electron shells
rgroup = open_group(group_id, 'compton_profiles')
! Determine number of shells
dset_id = open_dataset(rgroup, 'num_electrons')
call get_shape(dset_id, dims)
n_shell = int(dims(1), 4)
! Read electron shell PDF and binding energies
allocate(this % electron_pdf(n_shell), this % binding_energy(n_shell))
call read_dataset(this % electron_pdf, dset_id)
call close_dataset(dset_id)
call read_dataset(this % binding_energy, rgroup, 'binding_energy')
this % electron_pdf(:) = this % electron_pdf / sum(this % electron_pdf)
! Read Compton profiles
dset_id = open_dataset(rgroup, 'J')
call get_shape(dset_id, dims2)
n_profile = int(dims2(1), 4)
allocate(this % profile_pdf(n_profile, n_shell))
call read_dataset(this % profile_pdf, dset_id)
call close_dataset(dset_id)
! Get Compton profile momentum grid
if (.not. allocated(compton_profile_pz)) then
allocate(compton_profile_pz(n_profile))
call read_dataset(compton_profile_pz, rgroup, 'pz')
end if
call close_group(rgroup)
! Create Compton profile CDF
allocate(this % profile_cdf(n_profile, n_shell))
do i = 1, n_shell
c = ZERO
this % profile_cdf(1,i) = ZERO
do j = 1, n_profile - 1
c = c + HALF*(compton_profile_pz(j+1) - compton_profile_pz(j)) * &
(this%profile_pdf(j,i) + this%profile_pdf(j+1,i))
this % profile_cdf(j+1,i) = c
end do
end do
! Calculate total pair production
this % pair_production_total(:) = this % pair_production_nuclear + &
this % pair_production_electron
if (electron_treatment == ELECTRON_TTB) then
! Read bremsstrahlung scaled DCS
rgroup = open_group(group_id, 'bremsstrahlung')
dset_id = open_dataset(rgroup, 'dcs')
call get_shape(dset_id, dims2)
n_k = int(dims2(1), 4)
n_e = int(dims2(2), 4)
allocate(this % dcs(n_k, n_e))
call read_dataset(this % dcs, dset_id)
call close_dataset(dset_id)
! Get energy grids used for bremsstrahlung DCS and for stopping powers
allocate(electron_energy(n_e))
call read_dataset(electron_energy, rgroup, 'electron_energy')
if (.not. allocated(ttb_k_grid)) then
allocate(ttb_k_grid(n_k))
call read_dataset(ttb_k_grid, rgroup, 'photon_energy')
end if
call close_group(rgroup)
! Read stopping power data
if (this % Z < 99) then
rgroup = open_group(group_id, 'stopping_powers')
allocate(this % stopping_power_collision(n_e))
allocate(this % stopping_power_radiative(n_e))
call read_dataset(this % stopping_power_collision, rgroup, 's_collision')
call read_dataset(this % stopping_power_radiative, rgroup, 's_radiative')
call read_attribute(this % I, rgroup, 'I')
call close_group(rgroup)
end if
! Truncate the bremsstrahlung data at the cutoff energy
if (energy_cutoff(PHOTON) > electron_energy(1)) then
i_grid = binary_search(electron_energy, n_e, energy_cutoff(PHOTON))
! calculate interpolation factor
f = (log(energy_cutoff(PHOTON)) - log(electron_energy(i_grid))) / &
(log(electron_energy(i_grid+1)) - log(electron_energy(i_grid)))
! Interpolate collision stopping power at the cutoff energy and
! truncate
y = exp(log(this % stopping_power_collision(i_grid)) + &
f*(log(this % stopping_power_collision(i_grid+1)) - &
log(this % stopping_power_collision(i_grid))))
this % stopping_power_collision = &
[y, this % stopping_power_collision(i_grid+1:n_e)]
! Interpolate radiative stopping power at the cutoff energy and
! truncate
y = exp(log(this % stopping_power_radiative(i_grid)) + &
f*(log(this % stopping_power_radiative(i_grid+1)) - &
log(this % stopping_power_radiative(i_grid))))
this % stopping_power_radiative = &
[y, this % stopping_power_radiative(i_grid+1:n_e)]
! Interpolate bremsstrahlung DCS at the cutoff energy and truncate
allocate(dcs(n_k, n_e-i_grid+1))
do i = 1, n_k
y = exp(log(this % dcs(i,i_grid)) + &
f*(log(this % dcs(i,i_grid+1)) - log(this % dcs(i,i_grid))))
dcs(i,:) = [y, this % dcs(i,i_grid+1:n_e)]
end do
call move_alloc(dcs, this % dcs)
electron_energy = [energy_cutoff(PHOTON), electron_energy(i_grid+1:n_e)]
end if
! Set incident particle energy grid
if (.not. allocated(ttb_e_grid)) then
call move_alloc(electron_energy, ttb_e_grid)
end if
end if
! Take logarithm of energies and cross sections since they are log-log
! interpolated
this % energy = log(this % energy)
where (this % coherent > ZERO)
this % coherent = log(this % coherent)
elsewhere
this % coherent = -500.0_8
end where
where (this % incoherent > ZERO)
this % incoherent = log(this % incoherent)
elsewhere
this % incoherent = -500.0_8
end where
where (this % photoelectric_total > ZERO)
this % photoelectric_total = log(this % photoelectric_total)
elsewhere
this % photoelectric_total = -500.0_8
end where
where (this % pair_production_total > ZERO)
this % pair_production_total = log(this % pair_production_total)
elsewhere
this % pair_production_total = -500.0_8
end where
end subroutine photon_from_hdf5
!===============================================================================
! CALCULATE_ELEMENT_XS determines microscopic photon cross sections for an
! element of a given index in the elements array at the energy of the given
! particle
!===============================================================================
subroutine photon_calculate_xs(this, E, xs)
class(PhotonInteraction), intent(in) :: this ! index into elements array
real(8), intent(in) :: E ! energy
type(ElementMicroXS), intent(inout) :: xs
integer :: i_grid ! index on element energy grid
integer :: i_shell ! index in subshells
integer :: i_start ! threshold index
integer :: n_grid ! number of grid points
real(8) :: f ! interp factor on element energy grid
real(8) :: log_E ! logarithm of the energy
! Perform binary search on the element energy grid in order to determine
! which points to interpolate between
n_grid = size(this % energy)
log_E = log(E)
if (log_E <= this % energy(1)) then
i_grid = 1
elseif (log_E > this % energy(n_grid)) then
i_grid = n_grid - 1
else
i_grid = binary_search(this % energy, n_grid, log_E)
end if
! check for case where two energy points are the same
if (this % energy(i_grid) == this % energy(i_grid+1)) i_grid = i_grid + 1
! calculate interpolation factor
f = (log_E - this % energy(i_grid)) / &
(this % energy(i_grid+1) - this % energy(i_grid))
xs % index_grid = i_grid
xs % interp_factor = f
! Calculate microscopic coherent cross section
xs % coherent = exp(this % coherent(i_grid) + f * &
(this % coherent(i_grid+1) - this % coherent(i_grid)))
! Calculate microscopic incoherent cross section
xs % incoherent = exp(this % incoherent(i_grid) + &
f*(this % incoherent(i_grid+1) - this % incoherent(i_grid)))
! Calculate microscopic photoelectric cross section
xs % photoelectric = ZERO
do i_shell = 1, size(this % shells)
! Check threshold of reaction
i_start = this % shells(i_shell) % threshold
if (i_grid <= i_start) cycle
! Evaluation subshell photoionization cross section
xs % photoelectric = xs % photoelectric + &
exp(this % shells(i_shell) % cross_section(i_grid-i_start) + &
f*(this % shells(i_shell) % cross_section(i_grid+1-i_start) - &
this % shells(i_shell) % cross_section(i_grid-i_start)))
end do
! Calculate microscopic pair production cross section
xs % pair_production = exp(&
this % pair_production_total(i_grid) + f*(&
this % pair_production_total(i_grid+1) - &
this % pair_production_total(i_grid)))
! Calculate microscopic total cross section
xs % total = xs % coherent + xs % incoherent + xs % photoelectric + &
xs % pair_production
xs % last_E = E
end subroutine photon_calculate_xs
!===============================================================================
! FREE_MEMORY_PHOTON deallocates/resets global variables in this module
!===============================================================================
subroutine free_memory_photon()
! Deallocate photon cross section data
if (allocated(elements)) deallocate(elements)
if (allocated(compton_profile_pz)) deallocate(compton_profile_pz)
n_elements = 0
call element_dict % clear()
! Clear TTB-related arrays
if (allocated(ttb_e_grid)) deallocate(ttb_e_grid)
if (allocated(ttb)) deallocate(ttb)
end subroutine free_memory_photon
end module photon_header

641
src/photon_physics.F90 Normal file
View file

@ -0,0 +1,641 @@
module photon_physics
use algorithm, only: binary_search
use constants
use particle_header
use photon_header, only: PhotonInteraction, BremsstrahlungData, &
compton_profile_pz, ttb_e_grid, ttb
use random_lcg, only: prn
use settings
contains
!===============================================================================
! KLEIN_NISHINA
!===============================================================================
subroutine klein_nishina(alpha, alpha_out, mu)
real(8), intent(in) :: alpha
real(8), intent(out) :: alpha_out
real(8), intent(out) :: mu
real(8) :: beta ! 1 + 2a
real(8) :: t ! (1 + 2a)/(9 + 2a)
real(8) :: r, s, x
real(8) :: gamma
beta = ONE + TWO*alpha
if (alpha < THREE) then
! Kahn's rejection method
t = beta/(beta + 8.0_8)
do
if (prn() < t) then
! Left branch of flow chart
r = TWO*prn()
x = ONE + alpha*r
if (prn() < FOUR/x*(ONE - ONE/x)) then
mu = 1 - r
exit
end if
else
! Right branch of flow chart
x = beta/(ONE + TWO*alpha*prn())
mu = ONE + (ONE - x)/alpha
if (prn() < HALF*(mu**2 + ONE/x)) exit
end if
end do
alpha_out = alpha/x
else
! Koblinger's direct method
gamma = ONE - beta**(-2)
s = prn()*(FOUR/alpha + HALF*gamma + &
(ONE - (ONE + beta)/alpha**2)*log(beta))
if (s <= 2./alpha) then
! For first term, x = 1 + 2ar
! Therefore, a' = a/(1 + 2ar)
alpha_out = alpha/(ONE + TWO*alpha*prn())
elseif (s <= FOUR/alpha) then
! For third term, x = beta/(1 + 2ar)
! Therefore, a' = a(1 + 2ar)/beta
alpha_out = alpha*(ONE + TWO*alpha*prn())/beta
elseif (s <= FOUR/alpha + HALF*gamma) then
! For fourth term, x = 1/sqrt(1 - gamma*r)
! Therefore, a' = a*sqrt(1 - gamma*r)
alpha_out = alpha*sqrt(ONE - gamma*prn())
else
! For third term, x = beta^r
! Therefore, a' = a/beta^r
alpha_out = alpha/beta**prn()
end if
! Calculate cosine of scattering angle based on basic relation
mu = ONE + ONE/alpha - ONE/alpha_out
end if
end subroutine klein_nishina
!===============================================================================
! COMPTON_SCATTER
!===============================================================================
subroutine compton_scatter(el, alpha, alpha_out, mu, i_shell, use_doppler)
type(PhotonInteraction), intent(in) :: el
real(8), intent(in) :: alpha
real(8), intent(out) :: alpha_out
real(8), intent(out) :: mu
integer, intent(out) :: i_shell
logical, intent(in), optional :: use_doppler
real(8) :: x
real(8) :: form_factor_xmax
real(8) :: form_factor_x
real(8) :: e_out
logical :: use_doppler_
if (present(use_doppler)) then
use_doppler_ = use_doppler
else
use_doppler_ = .false.
end if
form_factor_xmax = ZERO
do
! Sample Klein-Nishina distribution for trial energy and angle
call klein_nishina(alpha, alpha_out, mu)
! Note that the parameter used here does not correspond exactly to the
! momentum transfer q in ENDF-102 Eq. (27.2). Rather, this is the
! parameter as defined by Hubbell, where the actual data comes from
x = MASS_ELECTRON_EV/PLANCK_C*alpha*sqrt(HALF*(ONE - mu))
! Calculate S(x, Z) and S(x_max, Z)
form_factor_x = el % incoherent_form_factor % evaluate(x)
if (form_factor_xmax == ZERO) then
form_factor_xmax = el % incoherent_form_factor % evaluate(&
MASS_ELECTRON_EV/PLANCK_C*alpha)
end if
! Perform rejection on form factor
if (prn() < form_factor_x / form_factor_xmax) then
if (use_doppler_) then
call compton_doppler(el, alpha, mu, e_out, i_shell)
alpha_out = e_out/MASS_ELECTRON_EV
else
i_shell = 0
end if
exit
end if
end do
end subroutine compton_scatter
!===============================================================================
! COMPTON_DOPPLER
!===============================================================================
subroutine compton_doppler(el, alpha, mu, e_out, i_shell)
type(PhotonInteraction), intent(in) :: el
real(8), intent(in) :: alpha
real(8), intent(in) :: mu
real(8), intent(out) :: e_out
integer, intent(out) :: i_shell
integer :: i
integer :: n
real(8) :: rn, m
real(8) :: c, c_l, c_max
real(8) :: pz_l, pz_r, pz, pz_max
real(8) :: p_l, p_r
real(8) :: e, e_b
real(8) :: e_out1, e_out2
real(8) :: a, b, quad
real(8) :: f
real(8) :: momentum_sq
n = size(compton_profile_pz)
do
! Sample electron shell
rn = prn()
c = ZERO
do i_shell = 1, size(el % electron_pdf)
c = c + el % electron_pdf(i_shell)
if (rn < c) exit
end do
! Determine binding energy of shell
e_b = el % binding_energy(i_shell)
! Determine p_z,max
e = alpha*MASS_ELECTRON_EV
if (e < e_b) then
e_out = alpha/(1 + alpha*(1 - mu))*MASS_ELECTRON_EV
exit
end if
pz_max = -FINE_STRUCTURE*(e_b - (e - e_b)*alpha*(ONE - mu)) / &
sqrt(TWO*e*(e - e_b)*(ONE - mu) + e_b**2)
if (pz_max < ZERO) then
e_out = alpha/(1 + alpha*(1 - mu))*MASS_ELECTRON_EV
exit
end if
! Determine profile cdf value corresponding to p_z,max
if (pz_max > compton_profile_pz(n)) then
c_max = el % profile_cdf(n, i_shell)
else
i = binary_search(compton_profile_pz, n, pz_max)
pz_l = compton_profile_pz(i)
pz_r = compton_profile_pz(i + 1)
p_l = el % profile_pdf(i, i_shell)
p_r = el % profile_pdf(i + 1, i_shell)
c_l = el % profile_cdf(i, i_shell)
if (pz_l == pz_r) then
c_max = c_l
elseif (p_l == p_r) then
c_max = c_l + (pz_max - pz_l)*p_l
else
m = (p_l - p_r)/(pz_l - pz_r)
c_max = c_l + ((m*(pz_max - pz_l) + p_l)**2 - p_l**2)/(TWO*m)
end if
end if
! Sample value on bounded cdf
c = prn()*c_max
! Determine pz corresponding to sampled cdf value
i = binary_search(el % profile_cdf(:, i_shell), n, c)
pz_l = compton_profile_pz(i)
pz_r = compton_profile_pz(i + 1)
p_l = el % profile_pdf(i, i_shell)
p_r = el % profile_pdf(i + 1, i_shell)
c_l = el % profile_cdf(i, i_shell)
if (pz_l == pz_r) then
pz = pz_l
elseif (p_l == p_r) then
pz = pz_l + (c - c_l)/p_l
else
m = (p_l - p_r)/(pz_l - pz_r)
pz = pz_l + (sqrt(p_l**2 + TWO*m*(c - c_l)) - p_l)/m
end if
! Determine outgoing photon energy corresponding to electron momentum
! (solve Eq. 39 in LA-UR-04-0487 for E')
momentum_sq = (pz/FINE_STRUCTURE)**2
f = ONE + alpha*(ONE - mu)
a = momentum_sq - f*f
b = TWO*e*(f - momentum_sq*mu)
c = e**2*(momentum_sq - ONE)
quad = b**2 - FOUR*a*c
if (quad < 0) then
e_out = alpha/(1 + alpha*(1 - mu))*MASS_ELECTRON_EV
exit
end if
quad = sqrt(quad)
e_out1 = -(b + quad)/(TWO*a)
e_out2 = -(b - quad)/(TWO*a)
! Determine solution to quadratic equation that is positive
if (e_out1 > ZERO) then
if (e_out2 > ZERO) then
! If both are positive, pick one at random
if (prn() < HALF) then
e_out = e_out1
else
e_out = e_out2
end if
else
e_out = e_out1
end if
else
if (e_out2 > ZERO) then
e_out = e_out2
else
! No positive solution -- resample
cycle
end if
end if
if (e_out < e - e_b) exit
end do
end subroutine compton_doppler
!===============================================================================
! RAYLEIGH_SCATTER
!===============================================================================
subroutine rayleigh_scatter(el, alpha, mu)
type(PhotonInteraction), intent(in) :: el
real(8), intent(in) :: alpha
real(8), intent(out) :: mu
integer :: i
real(8) :: F
real(8) :: F_max
real(8) :: x2
real(8) :: x2_max
real(8) :: r
do
! Determine maximum value of x^2
x2_max = (MASS_ELECTRON_EV/PLANCK_C*alpha)**2
! Determine F(x^2_max, Z)
F_max = el % coherent_int_form_factor % evaluate(x2_max)
! Sample cumulative distribution
F = prn()*F_max
! Determine x^2 corresponding to F
i = binary_search(el%coherent_int_form_factor%y, &
size(el%coherent_int_form_factor%y), F)
r = (F - el%coherent_int_form_factor%y(i)) / &
(el%coherent_int_form_factor%y(i+1) - el%coherent_int_form_factor%y(i))
x2 = el%coherent_int_form_factor%x(i) + r*(el%coherent_int_form_factor%x(i+1) - &
el%coherent_int_form_factor%x(i))
! Calculate mu
mu = ONE - TWO*x2/x2_max
if (prn() < HALF*(ONE + mu**2)) exit
end do
end subroutine rayleigh_scatter
!===============================================================================
! ATOMIC_RELAXATION
!===============================================================================
recursive subroutine atomic_relaxation(p, elm, i_shell)
type(Particle), intent(inout) :: p
type(PhotonInteraction), intent(in) :: elm
integer, intent(in) :: i_shell
integer :: i_hole
integer :: i_transition
integer :: primary
integer :: secondary
real(8) :: c
real(8) :: rn
real(8) :: E
real(8) :: mu
real(8) :: phi
real(8) :: uvw(3)
! If no transitions, assume fluorescent photon from captured free electron
if (elm % shells(i_shell) % n_transitions == 0) then
mu = TWO*prn() - ONE
phi = TWO*PI*prn()
uvw(1) = mu
uvw(2) = sqrt(ONE - mu*mu)*cos(phi)
uvw(3) = sqrt(ONE - mu*mu)*sin(phi)
E = elm % shells(i_shell) % binding_energy
call particle_create_secondary(p, uvw, E, PHOTON, run_ce=.true._C_BOOL)
return
end if
! Sample transition
rn = prn()
c = ZERO
do i_transition = 1, elm % shells(i_shell) % n_transitions
c = c + elm % shells(i_shell) % &
transition_probability(i_transition)
if (rn < c) exit
end do
! Get primary and secondary subshell designators
primary = elm % shells(i_shell) % transition_subshells(1, i_transition)
secondary = elm % shells(i_shell) % transition_subshells(2, i_transition)
! Sample angle isotropically
mu = TWO*prn() - ONE
phi = TWO*PI*prn()
uvw(1) = mu
uvw(2) = sqrt(ONE - mu*mu)*cos(phi)
uvw(3) = sqrt(ONE - mu*mu)*sin(phi)
! Get the transition energy
E = elm % shells(i_shell) % transition_energy(i_transition)
if (secondary /= 0) then
! Non-radiative transition -- Auger/Coster-Kronig effect
! Create auger electron
call particle_create_secondary(p, uvw, E, ELECTRON, run_ce=.true._C_BOOL)
! Fill hole left by emitted auger electron
i_hole = elm % shell_dict % get(secondary)
call atomic_relaxation(p, elm, i_hole)
else
! Radiative transition -- get X-ray energy
! Create fluorescent photon
call particle_create_secondary(p, uvw, E, PHOTON, run_ce=.true._C_BOOL)
end if
! Fill hole created by electron transitioning to the photoelectron hole
i_hole = elm % shell_dict % get(primary)
call atomic_relaxation(p, elm, i_hole)
end subroutine atomic_relaxation
!===============================================================================
! PAIR_PRODUCTION samples the kinetic energy and direction of the electron and
! positron created when a photon is absorbed near an atomic nucleus. The
! simulation procedure follows the semiempirical model outlined in F. Salvat, J.
! M. Fernández-Varea, and J. Sempau, "PENELOPE-2011: A Code System for Monte
! Carlo Simulation of Electron and Photon Transport," OECD-NEA,
! Issy-les-Moulineaux, France (2011).
!===============================================================================
subroutine pair_production(elm, alpha, E_electron, E_positron, mu_electron, &
mu_positron)
type(PhotonInteraction), intent(in) :: elm
real(8), intent(in) :: alpha
real(8), intent(out) :: E_electron
real(8), intent(out) :: E_positron
real(8), intent(out) :: mu_electron
real(8), intent(out) :: mu_positron
integer :: i
real(8) :: f
real(8) :: c
real(8) :: a
real(8) :: b
real(8) :: q
real(8) :: rn
real(8) :: beta
real(8) :: e, e_min, e_max
real(8) :: t1, t2, t3, t4
real(8) :: u1, u2
real(8) :: phi1, phi2
real(8) :: phi1_max, phi2_max
real(8), parameter :: r(99) = (/ &
122.81_8, 73.167_8, 69.228_8, 67.301_8, 64.696_8, 61.228_8, &
57.524_8, 54.033_8, 50.787_8, 47.851_8, 46.373_8, 45.401_8, &
44.503_8, 43.815_8, 43.074_8, 42.321_8, 41.586_8, 40.953_8, &
40.524_8, 40.256_8, 39.756_8, 39.144_8, 38.462_8, 37.778_8, &
37.174_8, 36.663_8, 35.986_8, 35.317_8, 34.688_8, 34.197_8, &
33.786_8, 33.422_8, 33.068_8, 32.740_8, 32.438_8, 32.143_8, &
31.884_8, 31.622_8, 31.438_8, 31.142_8, 30.950_8, 30.758_8, &
30.561_8, 30.285_8, 30.097_8, 29.832_8, 29.581_8, 29.411_8, &
29.247_8, 29.085_8, 28.930_8, 28.721_8, 28.580_8, 28.442_8, &
28.312_8, 28.139_8, 27.973_8, 27.819_8, 27.675_8, 27.496_8, &
27.285_8, 27.093_8, 26.911_8, 26.705_8, 26.516_8, 26.304_8, &
26.108_8, 25.929_8, 25.730_8, 25.577_8, 25.403_8, 25.245_8, &
25.100_8, 24.941_8, 24.790_8, 24.655_8, 24.506_8, 24.391_8, &
24.262_8, 24.145_8, 24.039_8, 23.922_8, 23.813_8, 23.712_8, &
23.621_8, 23.523_8, 23.430_8, 23.331_8, 23.238_8, 23.139_8, &
23.048_8, 22.967_8, 22.833_8, 22.694_8, 22.624_8, 22.545_8, &
22.446_8, 22.358_8, 22.264_8 /)
! The reduced screening radius r is the ratio of the screening radius to
! the Compton wavelength of the electron, where the screening radius is
! obtained under the assumption that the Coulomb field of the nucleus is
! exponentially screened by atomic electrons. This allows us to use a
! simplified atomic form factor and analytical approximations of the
! screening functions in the pair production DCS instead of computing the
! screening functions numerically. The reduced screening radii above for
! Z = 1-99 come from F. Salvat, J. M. Fernández-Varea, and J. Sempau,
! "PENELOPE-2011: A Code System for Monte Carlo Simulation of Electron and
! Photon Transport," OECD-NEA, Issy-les-Moulineaux, France (2011).
! Compute the minimum and maximum values of the electron reduced energy,
! i.e. the fraction of the photon energy that is given to the electron
e_min = ONE/alpha
e_max = ONE - ONE/alpha
! Compute the high-energy Coulomb correction
a = elm % Z / FINE_STRUCTURE
c = a**2*(ONE/(ONE + a**2) + 0.202059_8 - 0.03693_8*a**2 + 0.00835_8*a**4 &
- 0.00201_8*a**6 + 0.00049_8*a**8 - 0.00012_8*a**10 + 0.00003_8*a**12)
! The analytical approximation of the DCS underestimates the cross section
! at low energies. The correction factor f compensates for this.
q = sqrt(TWO/alpha)
f = q*(-0.1774_8 - 12.10_8*a + 11.18_8*a**2) &
+ q**2*(8.523_8 + 73.26_8*a - 44.41_8*a**2) &
+ q**3*(-13.52_8 - 121.1_8*a + 96.41_8*a**2) &
+ q**4*(8.946_8 + 62.05_8*a - 63.41_8*a**2)
! Calculate phi_1(1/2) and phi_2(1/2). The unnormalized PDF for the reduced
! energy is given by p = 2*(1/2 - e)^2*phi_1(e) + phi_2(e), where phi_1 and
! phi_2 are non-negative and maximum at e = 1/2.
b = TWO*r(elm % Z)/alpha
t1 = TWO*log(ONE + b**2)
t2 = b*atan(ONE/b)
t3 = b**2*(FOUR - FOUR*t2 - THREE*log(ONE + ONE/b**2))
t4 = FOUR*log(r(elm % Z)) - FOUR*c + f
phi1_max = 7.0_8/THREE - t1 - 6.0_8*t2 - t3 + t4
phi2_max = 11.0_8/6.0_8 - t1 - THREE*t2 + HALF*t3 + t4
! To aid sampling, the unnormalized PDF can be expressed as
! p = u_1*U_1(e)*pi_1(e) + u_2*U_2(e)*pi_2(e), where pi_1 and pi_2 are
! normalized PDFs on the interval (e_min, e_max) from which values of e can
! be sampled using the inverse transform method, and
! U_1 = phi_1(e)/phi_1(1/2) and U_2 = phi_2(e)/phi_2(1/2) are valid
! rejection functions. The reduced energy can now be sampled using a
! combination of the composition and rejection methods.
u1 = TWO/THREE*(HALF - ONE/alpha)**2*phi1_max
u2 = phi2_max
do
rn = prn()
! Sample the index i in (1, 2) using the point probabilities
! p(1) = u_1/(u_1 + u_2) and p(2) = u_2/(u_1 + u_2)
if (prn() < u1/(u1 + u2)) then
i = 1
! Sample e from pi_1 using the inverse transform method
if (rn >= HALF) then
e = HALF + (HALF - ONE/alpha)*(TWO*rn - ONE)**(ONE/THREE)
else
e = HALF - (HALF - ONE/alpha)*(ONE - TWO*rn)**(ONE/THREE)
end if
else
i = 2
! Sample e from pi_2 using the inverse transform method
e = ONE/alpha + (HALF - ONE/alpha)*TWO*rn
end if
! Calculate phi_i(e) and deliver e if rn <= U_i(e)
b = r(elm % Z)/(TWO*alpha*e*(ONE - e))
t1 = TWO*log(ONE + b**2)
t2 = b*atan(ONE/b)
t3 = b**2*(FOUR - FOUR*t2 - THREE*log(ONE + ONE/b**2))
if (i == 1) then
phi1 = 7.0_8/THREE - t1 - 6.0_8*t2 - t3 + t4
if (prn() <= phi1/phi1_max) exit
else
phi2 = 11.0_8/6.0_8 - t1 - THREE*t2 + HALF*t3 + t4
if (prn() <= phi2/phi2_max) exit
end if
end do
! Compute the kinetic energy of the electron and the positron
E_electron = (alpha*e - ONE)*MASS_ELECTRON_EV
E_positron = (alpha*(ONE - e) - ONE)*MASS_ELECTRON_EV
! Sample the scattering angle of the electron. The cosine of the polar
! angle of the direction relative to the incident photon is sampled from
! p(mu) = C/(1 - beta*mu)^2 using the inverse transform method.
beta = sqrt(E_electron*(E_electron + TWO*MASS_ELECTRON_EV)) &
/ (E_electron + MASS_ELECTRON_EV)
rn = TWO*prn() - ONE
mu_electron = (rn + beta)/(rn*beta + ONE)
! Sample the scattering angle of the positron
beta = sqrt(E_positron*(E_positron + TWO*MASS_ELECTRON_EV)) &
/ (E_positron + MASS_ELECTRON_EV)
rn = TWO*prn() - ONE
mu_positron = (rn + beta)/(rn*beta + ONE)
end subroutine pair_production
!===============================================================================
! THICK_TARGET_BREMSSTRAHLUNG
!===============================================================================
subroutine thick_target_bremsstrahlung(p, E_lost)
type(Particle), intent(inout) :: p
real(8), intent(inout) :: E_lost
integer :: i, j
integer :: i_e, i_w
integer :: n
integer :: n_e
real(8) :: a
real(8) :: f
real(8) :: e, e_l, e_r
real(8) :: y, y_l, y_r
real(8) :: w, w_l, w_r
real(8) :: p_l, p_r
real(8) :: c, c_l, c_max
type(BremsstrahlungData), pointer :: mat
if (p % E < energy_cutoff(PHOTON)) return
! Get bremsstrahlung data for this material and particle type
if (p % type == POSITRON) then
mat => ttb(p % material) % positron
else
mat => ttb(p % material) % electron
end if
e = log(p % E)
n_e = size(ttb_e_grid)
! Find the lower bounding index of the incident electron energy
j = binary_search(ttb_e_grid, n_e, e)
if (j == n_e) j = j - 1
! Get the interpolation bounds
e_l = ttb_e_grid(j)
e_r = ttb_e_grid(j+1)
y_l = mat % yield(j)
y_r = mat % yield(j+1)
! Calculate the interpolation weight w_j+1 of the bremsstrahlung energy PDF
! interpolated in log energy, which can be interpreted as the probability
! of index j+1
f = (e - e_l)/(e_r - e_l)
! Get the photon number yield for the given energy using linear
! interpolation on a log-log scale
y = exp(y_l + (y_r - y_l)*f)
! Sample number of secondary bremsstrahlung photons
n = int(y + prn())
E_lost = ZERO
if (n == 0) return
! Sample index of the tabulated PDF in the energy grid, j or j+1
if (prn() <= f .or. j == 1) then
i_e = j + 1
! Interpolate the maximum value of the CDF at the incoming particle
! energy on a log-log scale
p_l = mat % pdf(i_e-1, i_e)
p_r = mat % pdf(i_e, i_e)
c_l = mat % cdf(i_e-1, i_e)
a = log(p_r/p_l)/(e_r - e_l) + ONE
c_max = c_l + exp(e_l)*p_l/a*(exp(a*(e - e_l)) - ONE)
else
i_e = j
! Maximum value of the CDF
c_max = mat % cdf(i_e, i_e)
end if
! Sample the energies of the emitted photons
do i = 1, n
! Generate a random number r and determine the index i for which
! cdf(i) <= r*cdf,max <= cdf(i+1)
c = prn()*c_max
i_w = binary_search(mat % cdf(:i_e,i_e), i_e, c)
! Sample the photon energy
w_l = ttb_e_grid(i_w)
w_r = ttb_e_grid(i_w+1)
p_l = mat % pdf(i_w, i_e)
p_r = mat % pdf(i_w+1, i_e)
c_l = mat % cdf(i_w, i_e)
a = log(p_r/p_l)/(w_r - w_l) + ONE
w = exp(w_l)*(a*(c - c_l)/(exp(w_l)*p_l) + ONE)**(ONE/a)
if (w > energy_cutoff(PHOTON)) then
! Create secondary photon
call particle_create_secondary(p, p % coord(1) % uvw, w, PHOTON, &
run_ce=.true._C_BOOL)
E_lost = E_lost + w
end if
end do
end subroutine thick_target_bremsstrahlung
end module photon_physics

View file

@ -9,7 +9,11 @@ module physics
use mesh_header, only: meshes
use message_passing
use nuclide_header
use particle_header, only: Particle
use particle_header
use photon_header
use photon_physics, only: rayleigh_scatter, compton_scatter, &
atomic_relaxation, pair_production, &
thick_target_bremsstrahlung
use physics_common
use random_lcg, only: prn, advance_prn_seed, prn_set_stream
use reaction_header, only: Reaction
@ -36,40 +40,48 @@ contains
! Add to collision counter for particle
p % n_collision = p % n_collision + 1
! Sample nuclide/reaction for the material the particle is in
call sample_reaction(p)
! Sample reaction for the material the particle is in
if (p % type == NEUTRON) then
call sample_neutron_reaction(p)
else if (p % type == PHOTON) then
call sample_photon_reaction(p)
else if (p % type == ELECTRON) then
call sample_electron_reaction(p)
else if (p % type == POSITRON) then
call sample_positron_reaction(p)
end if
! Kill particle if energy falls below cutoff
if (p % E < energy_cutoff(p % type)) then
p % alive = .false.
p % wgt = ZERO
p % last_wgt = ZERO
end if
! Display information about collision
if (verbosity >= 10 .or. trace) then
call write_message(" " // trim(reaction_name(p % event_MT)) &
&// " with " // trim(adjustl(nuclides(p % event_nuclide) % name)) &
&// ". Energy = " // trim(to_str(p % E)) // " eV.")
if (p % type == NEUTRON) then
call write_message(" " // trim(reaction_name(p % event_MT)) &
&// " with " // trim(adjustl(nuclides(p % event_nuclide) % name)) &
&// ". Energy = " // trim(to_str(p % E)) // " eV.")
else
call write_message(" " // trim(reaction_name(p % event_MT)) &
&// " with " // trim(adjustl(elements(p % event_nuclide) % name)) &
&// ". Energy = " // trim(to_str(p % E)) // " eV.")
end if
end if
! check for very low energy
if (p % E < 1.0e-100_8) then
p % alive = .false.
if (master) call warning("Killing neutron with extremely low energy")
end if
! Advance URR seed stream 'N' times after energy changes
if (p % E /= p % last_E) then
call prn_set_stream(STREAM_URR_PTABLE)
call advance_prn_seed(size(nuclides, kind=8))
call prn_set_stream(STREAM_TRACKING)
endif
end subroutine collision
!===============================================================================
! SAMPLE_REACTION samples a nuclide based on the macroscopic cross sections for
! each nuclide within a material and then samples a reaction for that nuclide
! and calls the appropriate routine to process the physics. Note that there is
! special logic when suvival biasing is turned on since fission and
! disappearance are treated implicitly.
! SAMPLE_NEUTRON_REACTION samples a nuclide based on the macroscopic cross
! sections for each nuclide within a material and then samples a reaction for
! that nuclide and calls the appropriate routine to process the physics. Note
! that there is special logic when suvival biasing is turned on since fission
! and disappearance are treated implicitly.
!===============================================================================
subroutine sample_reaction(p)
subroutine sample_neutron_reaction(p)
type(Particle), intent(inout) :: p
@ -102,6 +114,13 @@ contains
end if
end if
! Create secondary photons
if (photon_transport) then
call prn_set_stream(STREAM_PHOTON)
call sample_secondary_photons(p, i_nuclide)
call prn_set_stream(STREAM_TRACKING)
end if
! If survival biasing is being used, the following subroutine adjusts the
! weight of the particle. Otherwise, it checks to see if absorption occurs
@ -116,21 +135,259 @@ contains
! exiting neutron
call scatter(p, i_nuclide, i_nuc_mat)
! Play russian roulette if survival biasing is turned on
! Advance URR seed stream 'N' times after energy changes
if (p % E /= p % last_E) then
call prn_set_stream(STREAM_URR_PTABLE)
call advance_prn_seed(size(nuclides, kind=8))
call prn_set_stream(STREAM_TRACKING)
end if
! Play russian roulette if survival biasing is turned on
if (survival_biasing) then
call russian_roulette(p)
if (.not. p % alive) return
end if
! Kill neutron under certain energy
if (p % E < energy_cutoff) then
end subroutine sample_neutron_reaction
!===============================================================================
! SAMPLE_PHOTON_REACTION samples an element based on the macroscopic cross
! sections for each nuclide within a material and then samples a reaction for
! that element and calls the appropriate routine to process the physics.
!===============================================================================
subroutine sample_photon_reaction(p)
type(Particle), intent(inout) :: p
integer :: i_shell ! index in subshells
integer :: i_grid ! index on energy grid
integer :: i_element ! index in nuclides array
integer :: i_start ! threshold index
real(8) :: prob ! cumulative probability
real(8) :: cutoff ! sampled total cross section
real(8) :: f ! interpolation factor
real(8) :: xs ! photoionization cross section
real(8) :: r ! random number
real(8) :: prob_after
real(8) :: alpha ! photon energy divided by electron rest mass
real(8) :: alpha_out ! outgoing photon energy over electron rest mass
real(8) :: mu ! scattering cosine
real(8) :: mu_electron ! electron scattering cosine
real(8) :: mu_positron ! positron scattering cosine
real(8) :: phi ! azimuthal angle
real(8) :: uvw(3) ! new direction
real(8) :: rel_vel ! relative velocity of electron
real(8) :: e_b ! binding energy of electron
real(8) :: E_electron ! electron energy
real(8) :: E_positron ! positron energy
! Kill photon if below energy cutoff -- an extra check is made here because
! photons with energy below the cutoff may have been produced by neutrons
! reactions or atomic relaxation
if (p % E < energy_cutoff(PHOTON)) then
p % E = ZERO
p % alive = .false.
p % wgt = ZERO
p % last_wgt = ZERO
return
end if
end subroutine sample_reaction
! Sample element within material
i_element = sample_element(p)
p % event_nuclide = i_element
! Calculate photon energy over electron rest mass equivalent
alpha = p % E/MASS_ELECTRON_EV
! For tallying purposes, this routine might be called directly. In that
! case, we need to sample a reaction via the cutoff variable
prob = ZERO
cutoff = prn() * micro_photon_xs(i_element) % total
associate (elm => elements(i_element))
! Coherent (Rayleigh) scattering
prob = prob + micro_photon_xs(i_element) % coherent
if (prob > cutoff) then
call rayleigh_scatter(elm, alpha, mu)
p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, mu)
p % event_MT = COHERENT
return
end if
! Incoherent (Compton) scattering
prob = prob + micro_photon_xs(i_element) % incoherent
if (prob > cutoff) then
call compton_scatter(elm, alpha, alpha_out, mu, i_shell, .true.)
! Determine binding energy of shell. The binding energy is zero if
! doppler broadening is not used.
if (i_shell == 0) then
e_b = ZERO
else
e_b = elm % binding_energy(i_shell)
end if
! Create Compton electron
E_electron = (alpha - alpha_out)*MASS_ELECTRON_EV - e_b
mu_electron = (alpha - alpha_out*mu) &
/ sqrt(alpha**2 + alpha_out**2 - TWO*alpha*alpha_out*mu)
phi = TWO*PI*prn()
uvw = rotate_angle(p % coord(1) % uvw, mu_electron, phi)
call particle_create_secondary(p, uvw, E_electron, ELECTRON, .true._C_BOOL)
! TODO: Compton subshell data does not match atomic relaxation data
! Allow electrons to fill orbital and produce auger electrons
! and fluorescent photons
if (i_shell > 0) then
call atomic_relaxation(p, elm, i_shell)
end if
phi = phi + PI
p % E = alpha_out*MASS_ELECTRON_EV
p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, mu, phi)
p % event_MT = INCOHERENT
return
end if
! Photoelectric effect
prob_after = prob + micro_photon_xs(i_element) % photoelectric
if (prob_after > cutoff) then
do i_shell = 1, size(elm % shells)
! Get grid index and interpolation factor
i_grid = micro_photon_xs(i_element) % index_grid
f = micro_photon_xs(i_element) % interp_factor
! Check threshold of reaction
i_start = elm % shells(i_shell) % threshold
if (i_grid <= i_start) cycle
! Evaluation subshell photoionization cross section
xs = exp(elm % shells(i_shell) % cross_section(i_grid - i_start) + &
f*(elm % shells(i_shell) % cross_section(i_grid + 1 - i_start) - &
elm % shells(i_shell) % cross_section(i_grid - i_start)))
prob = prob + xs
if (prob > cutoff) then
E_electron = p % E - elm % shells(i_shell) % binding_energy
! Sample mu using non-relativistic Sauter distribution.
! See Eqns 3.19 and 3.20 in "Implementing a photon physics
! model in Serpent 2" by Toni Kaltiaisenaho
SAMPLE_MU: do
r = prn()
if (FOUR * (ONE - r) * r >= prn()) then
rel_vel = sqrt(E_electron * (E_electron + TWO * MASS_ELECTRON_EV))&
/ (E_electron + MASS_ELECTRON_EV)
mu = (TWO * r + rel_vel - ONE) / &
(TWO * rel_vel * r - rel_vel + ONE)
exit SAMPLE_MU
end if
end do SAMPLE_MU
phi = TWO*PI*prn()
uvw(1) = mu
uvw(2) = sqrt(ONE - mu*mu)*cos(phi)
uvw(3) = sqrt(ONE - mu*mu)*sin(phi)
! Create secondary electron
call particle_create_secondary(p, uvw, E_electron, ELECTRON, &
run_CE=.true._C_BOOL)
! Allow electrons to fill orbital and produce auger electrons
! and fluorescent photons
call atomic_relaxation(p, elm, i_shell)
p % event_MT = 533 + elm % shells(i_shell) % index_subshell
p % alive = .false.
p % E = ZERO
return
end if
end do
end if
prob = prob_after
! Pair production
prob = prob + micro_photon_xs(i_element) % pair_production
if (prob > cutoff) then
call pair_production(elm, alpha, E_electron, E_positron, mu_electron, &
mu_positron)
! Create secondary electron
uvw = rotate_angle(p % coord(1) % uvw, mu_electron)
call particle_create_secondary(p, uvw, E_electron, ELECTRON, .true._C_BOOL)
! Create secondary positron
uvw = rotate_angle(p % coord(1) % uvw, mu_positron)
call particle_create_secondary(p, uvw, E_positron, POSITRON, .true._C_BOOL)
p % event_MT = PAIR_PROD
p % alive = .false.
p % E = ZERO
end if
end associate
end subroutine sample_photon_reaction
!===============================================================================
! SAMPLE_ELECTRON_REACTION terminates the particle and either deposits all
! energy locally (electron_treatment = ELECTRON_LED) or creates secondary
! bremsstrahlung photons from electron deflections with charged particles
! (electron_treatment = ELECTRON_TTB).
!===============================================================================
subroutine sample_electron_reaction(p)
type(Particle), intent(inout) :: p
real(8) :: E_lost ! energy lost to bremsstrahlung photons
! TODO: create reaction types
if (electron_treatment == ELECTRON_TTB) then
call thick_target_bremsstrahlung(p, E_lost)
end if
p % E = ZERO
p % alive = .false.
end subroutine sample_electron_reaction
!===============================================================================
! SAMPLE_POSITRON_REACTION terminates the particle and either deposits all
! energy locally (electron_treatment = ELECTRON_LED) or creates secondary
! bremsstrahlung photons from electron deflections with charged particles
! (electron_treatment = ELECTRON_TTB). Two annihilation photons of energy
! MASS_ELECTRON_EV (0.511 MeV) are created and travel in opposite directions.
!===============================================================================
subroutine sample_positron_reaction(p)
type(Particle), intent(inout) :: p
real(8) :: mu ! scattering cosine
real(8) :: phi ! azimuthal angle
real(8) :: uvw(3) ! new direction
real(8) :: E_lost ! energy lost to bremsstrahlung photons
! TODO: create reaction types
if (electron_treatment == ELECTRON_TTB) then
call thick_target_bremsstrahlung(p, E_lost)
end if
! Sample angle isotropically
mu = TWO*prn() - ONE
phi = TWO*PI*prn()
uvw(1) = mu
uvw(2) = sqrt(ONE - mu*mu)*cos(phi)
uvw(3) = sqrt(ONE - mu*mu)*sin(phi)
! Create annihilation photon pair traveling in opposite directions
call particle_create_secondary(p, uvw, MASS_ELECTRON_EV, PHOTON, .true._C_BOOL)
call particle_create_secondary(p, -uvw, MASS_ELECTRON_EV, PHOTON, .true._C_BOOL)
p % E = ZERO
p % alive = .false.
end subroutine sample_positron_reaction
!===============================================================================
! SAMPLE_NUCLIDE
@ -169,7 +426,7 @@ contains
! Check to make sure that a nuclide was sampled
if (i_nuc_mat > mat % n_nuclides) then
call p % write_restart()
call particle_write_restart(p)
call fatal_error("Did not sample any nuclide during collision.")
end if
@ -194,6 +451,49 @@ contains
end subroutine sample_nuclide
!===============================================================================
! SAMPLE_ELEMENT
!===============================================================================
function sample_element(p) result(i_element)
type(Particle), intent(in) :: p
integer :: i_element
integer :: i
real(8) :: prob
real(8) :: cutoff
real(8) :: atom_density ! atom density of nuclide in atom/b-cm
real(8) :: sigma ! microscopic total xs for nuclide
associate (mat => materials(p % material))
! Sample cumulative distribution function
cutoff = prn() * material_xs % total
i = 0
prob = ZERO
do while (prob < cutoff)
i = i + 1
! Check to make sure that a nuclide was sampled
if (i > mat % n_nuclides) then
call particle_write_restart(p)
call fatal_error("Did not sample any element during collision.")
end if
! Find atom density
i_element = mat % element(i)
atom_density = mat % atom_density(i)
! Determine microscopic cross section
sigma = atom_density * micro_photon_xs(i_element) % total
! Increment probability to compare to cutoff
prob = prob + sigma
end do
end associate
end function sample_element
!===============================================================================
! SAMPLE_FISSION
!===============================================================================
@ -260,6 +560,65 @@ contains
end subroutine sample_fission
!===============================================================================
! SAMPLE_PHOTON_PRODUCT
!===============================================================================
subroutine sample_photon_product(i_nuclide, E, i_reaction, i_product)
integer, intent(in) :: i_nuclide ! index in nuclides array
real(8), intent(in) :: E ! energy of neutron
integer, intent(out) :: i_reaction ! index in nuc % reactions array
integer, intent(out) :: i_product ! index in reaction % products array
integer :: i_grid
integer :: i_temp
integer :: threshold
integer :: last_valid_reaction
integer :: last_valid_product
real(8) :: f
real(8) :: prob
real(8) :: cutoff
real(8) :: yield
! Get pointer to nuclide
associate (nuc => nuclides(i_nuclide))
! Get grid index and interpolation factor and sample photon production cdf
i_temp = micro_xs(i_nuclide) % index_temp
i_grid = micro_xs(i_nuclide) % index_grid
f = micro_xs(i_nuclide) % interp_factor
cutoff = prn() * micro_xs(i_nuclide) % photon_prod
prob = ZERO
! Loop through each reaction type
REACTION_LOOP: do i_reaction = 1, size(nuc % reactions)
associate (rx => nuc % reactions(i_reaction))
threshold = rx % xs(i_temp) % threshold
! if energy is below threshold for this reaction, skip it
if (i_grid < threshold) cycle
do i_product = 1, size(rx % products)
if (rx % products(i_product) % particle == PHOTON) then
! add to cumulative probability
yield = rx % products(i_product) % yield % evaluate(E)
prob = prob + ((ONE - f) * rx % xs(i_temp) % value(i_grid - threshold + 1) &
+ f*(rx % xs(i_temp) % value(i_grid - threshold + 2))) * yield
if (prob > cutoff) return
last_valid_reaction = i_reaction
last_valid_product = i_product
end if
end do
end associate
end do REACTION_LOOP
end associate
i_reaction = last_valid_reaction
i_product = last_valid_product
end subroutine sample_photon_product
!===============================================================================
! ABSORPTION
!===============================================================================
@ -386,7 +745,7 @@ contains
! Check to make sure inelastic scattering reaction sampled
if (i > size(nuc % reactions)) then
call p % write_restart()
call particle_write_restart(p)
call fatal_error("Did not sample any reaction for nuclide " &
&// trim(nuc % name))
end if
@ -1093,7 +1452,7 @@ contains
! Determine indices on ufs mesh for current location
call m % get_bin(p % coord(1) % xyz, mesh_bin)
if (mesh_bin == NO_BIN_FOUND) then
call p % write_restart()
call particle_write_restart(p)
call fatal_error("Source site outside UFS mesh!")
end if
@ -1145,6 +1504,9 @@ contains
! Bank source neutrons by copying particle data
bank_array(i) % xyz = p % coord(1) % xyz
! Set particle as neutron
bank_array(i) % particle = NEUTRON
! Set weight of fission bank site
bank_array(i) % wgt = ONE/weight
@ -1241,12 +1603,12 @@ contains
call rxn % products(1 + group) % sample(E_in, site % E, mu)
! resample if energy is greater than maximum neutron energy
if (site % E < energy_max_neutron) exit
if (site % E < energy_max(NEUTRON)) exit
! check for large number of resamples
n_sample = n_sample + 1
if (n_sample == MAX_SAMPLE) then
! call p % write_restart()
! call particle_write_restart(p)
call fatal_error("Resampled energy distribution maximum number of " &
// "times for nuclide " // nuc % name)
end if
@ -1265,12 +1627,12 @@ contains
call rxn % products(1) % sample(E_in, site % E, mu)
! resample if energy is greater than maximum neutron energy
if (site % E < energy_max_neutron) exit
if (site % E < energy_max(NEUTRON)) exit
! check for large number of resamples
n_sample = n_sample + 1
if (n_sample == MAX_SAMPLE) then
! call p % write_restart()
! call particle_write_restart(p)
call fatal_error("Resampled energy distribution maximum number of " &
// "times for nuclide " // nuc % name)
end if
@ -1334,7 +1696,8 @@ contains
if (mod(yield, ONE) == ZERO) then
! If yield is integral, create exactly that many secondary particles
do i = 1, nint(yield) - 1
call p % create_secondary(p % coord(1) % uvw, NEUTRON, run_CE=.true.)
call particle_create_secondary(p, p % coord(1) % uvw, p % E, &
NEUTRON, run_CE=.true._C_BOOL)
end do
else
! Otherwise, change weight of particle based on yield
@ -1343,4 +1706,50 @@ contains
end subroutine inelastic_scatter
!===============================================================================
! SAMPLE_SECONDARY_PHOTONS
!===============================================================================
subroutine sample_secondary_photons(p, i_nuclide)
type(Particle), intent(inout) :: p
integer, intent(in) :: i_nuclide
integer :: i_reaction ! index in nuc % reactions array
integer :: i_product ! index in nuc % reactions % products array
real(8) :: nu_t
real(8) :: mu
real(8) :: E
real(8) :: uvw(3)
integer :: nu
integer :: i
! Sample the number of photons produced
nu_t = p % wgt * micro_xs(i_nuclide) % photon_prod / &
micro_xs(i_nuclide) % total
if (prn() > nu_t - int(nu_t)) then
nu = int(nu_t)
else
nu = int(nu_t) + 1
end if
! Sample each secondary photon
do i = 1, nu
! Sample the reaction and product
call sample_photon_product(i_nuclide, p % E, i_reaction, i_product)
! Sample the outgoing energy and angle
call nuclides(i_nuclide) % reactions(i_reaction) % products(i_product) &
% sample(p % E, E, mu)
! Sample the new direction
uvw = rotate_angle(p % coord(1) % uvw, mu)
! Create the secondary photon
call particle_create_secondary(p, uvw, E, PHOTON, run_CE=.true._C_BOOL)
end do
end subroutine sample_secondary_photons
end module physics

View file

@ -11,7 +11,7 @@ module physics_mg
use mgxs_interface
use message_passing
use nuclide_header, only: material_xs
use particle_header, only: Particle
use particle_header
use physics_common
use random_lcg, only: prn
use settings
@ -185,7 +185,7 @@ contains
call m % get_bin(p % coord(1) % xyz, mesh_bin)
if (mesh_bin == NO_BIN_FOUND) then
call p % write_restart()
call particle_write_restart(p)
call fatal_error("Source site outside UFS mesh!")
end if
@ -237,6 +237,9 @@ contains
! Bank source neutrons by copying particle data
bank_array(i) % xyz = p % coord(1) % xyz
! Set particle as neutron
bank_array(i) % particle = NEUTRON
! Set weight of fission bank site
bank_array(i) % wgt = ONE/weight

View file

@ -9,7 +9,7 @@ module plot
use hdf5_interface
use output, only: time_stamp
use material_header, only: materials
use particle_header, only: LocalCoord, Particle
use particle_header
use plot_header
use progress_header, only: ProgressBar
use settings, only: check_overlaps
@ -161,7 +161,7 @@ contains
end if
! allocate and initialize particle
call p % initialize()
call particle_initialize(p)
p % coord(1) % xyz = xyz
p % coord(1) % uvw = [ HALF, HALF, HALF ]
p % coord(1) % universe = root_universe
@ -388,7 +388,7 @@ contains
ll = pl % origin - pl % width / TWO
! allocate and initialize particle
call p % initialize()
call particle_initialize(p)
p % coord(1) % xyz = ll
p % coord(1) % uvw = [ HALF, HALF, HALF ]
p % coord(1) % universe = root_universe

View file

@ -6,12 +6,13 @@ namespace openmc {
// Constants
extern "C" const int N_STREAMS {5};
extern "C" const int N_STREAMS {6};
extern "C" const int STREAM_TRACKING {0};
extern "C" const int STREAM_TALLIES {1};
extern "C" const int STREAM_SOURCE {2};
extern "C" const int STREAM_URR_PTABLE {3};
extern "C" const int STREAM_VOLUME {4};
extern "C" const int STREAM_PHOTON {5};
// Starting seed
int64_t seed {1};

View file

@ -16,6 +16,7 @@ extern "C" const int STREAM_TALLIES;
extern "C" const int STREAM_SOURCE;
extern "C" const int STREAM_URR_PTABLE;
extern "C" const int STREAM_VOLUME;
extern "C" const int STREAM_PHOTON;
//==============================================================================
//! Generate a pseudo-random number using a linear congruential generator.

View file

@ -66,6 +66,7 @@ element settings {
element source {
grammar {
start =
(element particle { xsd:string } | attribute particle { xsd:string })? &
(element strength { xsd:double } | attribute strength { xsd:double })? &
(element file { xsd:string } | attribute file { xsd:string })? &
element space {

View file

@ -274,6 +274,16 @@
<grammar>
<start>
<interleave>
<optional>
<choice>
<element name="particle">
<data type="string"/>
</element>
<attribute name="particle">
<data type="string"/>
</attribute>
</choice>
</optional>
<optional>
<choice>
<element name="strength">

View file

@ -9,7 +9,7 @@ module settings
! ============================================================================
! ENERGY TREATMENT RELATED VARIABLES
logical :: run_CE = .true. ! Run in CE mode?
logical(C_BOOL) :: run_CE = .true. ! Run in CE mode?
! ============================================================================
! CONTINUOUS-ENERGY CROSS SECTION RELATED VARIABLES
@ -26,6 +26,9 @@ module settings
integer :: n_log_bins ! number of bins for logarithmic grid
logical :: photon_transport = .false.
integer :: electron_treatment = ELECTRON_TTB
! ============================================================================
! MULTI-GROUP CROSS SECTION RELATED VARIABLES
@ -71,7 +74,7 @@ module settings
! Variance reduction settins
logical :: survival_biasing = .false.
real(8) :: weight_cutoff = 0.25_8
real(8) :: energy_cutoff = ZERO
real(8) :: energy_cutoff(4) = [ZERO, 1000.0_8, ZERO, ZERO]
real(8) :: weight_survive = ONE
! Mode to run in (fixed source, eigenvalue, plotting, etc)

70
src/settings.cpp Normal file
View file

@ -0,0 +1,70 @@
#include "settings.h"
#include "error.h"
#include "openmc.h"
#include "string_utils.h"
#include "xml_interface.h"
namespace openmc {
//==============================================================================
// Global variables
//==============================================================================
char* openmc_path_input;
char* openmc_path_statepoint;
char* openmc_path_sourcepoint;
char* openmc_path_particle_restart;
std::string path_cross_sections;
std::string path_multipole;
std::string path_output;
std::string path_source;
//==============================================================================
// Functions
//==============================================================================
void read_settings(pugi::xml_node* root)
{
// Look for deprecated cross_sections.xml file in settings.xml
if (check_for_node(*root, "cross_sections")) {
warning("Setting cross_sections in settings.xml has been deprecated."
" The cross_sections are now set in materials.xml and the "
"cross_sections input to materials.xml and the OPENMC_CROSS_SECTIONS"
" environment variable will take precendent over setting "
"cross_sections in settings.xml.");
path_cross_sections = get_node_value(*root, "cross_sections");
}
// Look for deprecated windowed_multipole file in settings.xml
if (openmc_run_mode != RUN_MODE_PLOTTING) {
if (check_for_node(*root, "multipole_library")) {
warning("Setting multipole_library in settings.xml has been "
"deprecated. The multipole_library is now set in materials.xml and"
" the multipole_library input to materials.xml and the "
"OPENMC_MULTIPOLE_LIBRARY environment variable will take "
"precendent over setting multipole_library in settings.xml.");
path_multipole = get_node_value(*root, "multipole_library");
}
if (!ends_with(path_multipole, "/")) {
path_multipole += "/";
}
}
// Check for output options
if (check_for_node(*root, "output")) {
// Get pointer to output node
pugi::xml_node node_output = root->child("output");
// Set output directory if a path has been specified
if (check_for_node(node_output, "path")) {
path_output = get_node_value(node_output, "path");
if (!ends_with(path_output, "/")) {
path_output += "/";
}
}
}
}
} // namespace openmc

43
src/settings.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef OPENMC_SETTINGS_H
#define OPENMC_SETTINGS_H
//! \file settings.h
//! \brief Settings for OpenMC
#include <string>
#include "pugixml.hpp"
namespace openmc {
//==============================================================================
// Global variable declarations
//==============================================================================
// Defined on Fortran side
extern "C" bool openmc_check_overlaps;
extern "C" bool openmc_particle_restart_run;
extern "C" bool openmc_restart_run;
extern "C" bool openmc_write_all_tracks;
// Defined in .cpp
// TODO: Make strings instead of char* once Fortran is gone
extern "C" char* openmc_path_input;
extern "C" char* openmc_path_statepoint;
extern "C" char* openmc_path_sourcepoint;
extern "C" char* openmc_path_particle_restart;
extern std::string path_cross_sections;
extern std::string path_multipole;
extern std::string path_output;
extern std::string path_source;
//==============================================================================
//! Read settings from XML file
//! \param[in] root XML node for <settings>
//==============================================================================
extern "C" void read_settings(pugi::xml_node* root);
} // namespace openmc
#endif // OPENMC_SETTINGS_H

View file

@ -25,7 +25,8 @@ module simulation
use output, only: header, print_columns, &
print_batch_keff, print_generation, print_runtime, &
print_results, print_overlap_check, write_tallies
use particle_header, only: Particle
use particle_header
use photon_header, only: micro_photon_xs, n_elements
use random_lcg, only: set_particle_seed
use settings
use simulation_header
@ -141,7 +142,7 @@ contains
integer :: i
! set defaults
call p % initialize_from_source(source_bank(index_source), run_CE, &
call particle_from_source(p, source_bank(index_source), run_CE, &
energy_bin_avg)
! set identifier for particle
@ -428,6 +429,7 @@ contains
!$omp parallel
! Allocate array for microscopic cross section cache
allocate(micro_xs(n_nuclides))
allocate(micro_photon_xs(n_elements))
! Allocate array for matching filter bins
allocate(filter_matches(n_filters))
@ -499,7 +501,7 @@ contains
deallocate(materials(i) % mat_nuclide_index)
end do
!$omp parallel
deallocate(micro_xs, filter_matches)
deallocate(micro_xs, micro_photon_xs, filter_matches)
!$omp end parallel
! Increment total number of generations

13
src/simulation.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef SIMULATION_H
#define SIMULATION_H
#include <cstdint>
extern "C" int openmc_current_batch;
extern "C" int openmc_current_gen;
extern "C" int64_t openmc_current_work;
extern "C" int openmc_n_lost_particles;
#pragma omp threadprivate(openmc_current_work)
#endif // SIMULATION_H

View file

@ -13,15 +13,15 @@ module simulation_header
! GEOMETRY-RELATED VARIABLES
! Number of lost particles
integer :: n_lost_particles = 0
integer(C_INT), bind(C, name='openmc_n_lost_particles') :: n_lost_particles = 0
real(8) :: log_spacing ! spacing on logarithmic grid
! ============================================================================
! SIMULATION VARIABLES
integer :: current_batch ! current batch
integer :: current_gen ! current generation within a batch
integer(C_INT), bind(C, name='openmc_current_batch') :: current_batch ! current batch
integer(C_INT), bind(C, name='openmc_current_gen') :: current_gen ! current generation within a batch
integer :: total_gen = 0 ! total number of generations simulated
logical(C_BOOL), bind(C, name='openmc_simulation_initialized') :: &
simulation_initialized = .false.
@ -34,7 +34,7 @@ module simulation_header
integer(C_INT64_T), bind(C, name='openmc_work') :: work ! number of particles per processor
integer(C_INT64_T), allocatable :: work_index(:) ! starting index in source bank for each process
integer(8) :: current_work ! index in source bank of current history simulated
integer(C_INT64_T), bind(C, name='openmc_current_work') :: current_work ! index in source bank of current history simulated
! ============================================================================
! K-EIGENVALUE SIMULATION VARIABLES

View file

@ -9,8 +9,9 @@ module source_header
use error
use geometry, only: find_cell
use material_header, only: materials
use nuclide_header, only: energy_min_neutron, energy_max_neutron
use particle_header, only: Particle
use nuclide_header, only: energy_min, energy_max
use particle_header
use settings, only: photon_transport
use string, only: to_lower
use xml_interface
@ -29,6 +30,7 @@ module source_header
!===============================================================================
type, public :: SourceDistribution
integer :: particle ! particle type
real(8) :: strength = ONE ! source strength
class(SpatialDistribution), allocatable :: space ! spatial distribution
class(UnitSphereDistribution), allocatable :: angle ! angle distribution
@ -53,11 +55,27 @@ contains
integer :: n
logical :: file_exists
character(MAX_WORD_LEN) :: type
character(MAX_WORD_LEN) :: type, temp_str
type(XMLNode) :: node_space
type(XMLNode) :: node_angle
type(XMLNode) :: node_dist
! Check for particle type
if (check_for_node(node, "particle")) then
call get_node_value(node, "particle", temp_str)
select case (to_lower(temp_str))
case ('neutron')
this % particle = NEUTRON
case ('photon')
this % particle = PHOTON
photon_transport = .true.
case default
call fatal_error('Unknown source particle type: ' // trim(temp_str))
end select
else
this % particle = NEUTRON
end if
! Check for source strength
if (check_for_node(node, "strength")) then
call get_node_value(node, "strength", this % strength)
@ -229,7 +247,10 @@ contains
found = .false.
do while (.not. found)
! Set particle defaults
call p % initialize()
call particle_initialize(p)
! Set particle type
site % particle = this % particle
! Sample spatial distribution
site % xyz(:) = this % space % sample()
@ -269,18 +290,18 @@ contains
! Increment number of accepted samples
n_accept = n_accept + 1
call p % clear()
call particle_clear(p)
! Sample angle
site % uvw(:) = this % angle % sample()
! Check for monoenergetic source above maximum neutron energy
! Check for monoenergetic source above maximum particle energy
select type (energy => this % energy)
type is (Discrete)
if (any(energy % x > energy_max_neutron)) then
if (any(energy % x > energy_max(this % particle))) then
call fatal_error("Source energy above range of energies of at least &
&one cross section table")
else if (any(energy % x < energy_min_neutron)) then
else if (any(energy % x < energy_min(this % particle))) then
call fatal_error("Source energy below range of energies of at least &
&one cross section table")
end if
@ -290,8 +311,9 @@ contains
! Sample energy spectrum
site % E = this % energy % sample()
! Resample if energy falls outside minimum or maximum neutron energy
if (site % E < energy_max_neutron .and. site % E > energy_min_neutron) exit
! Resample if energy falls outside minimum or maximum particle energy
if (site % E < energy_max(this % particle) .and. &
site % E > energy_min(this % particle)) exit
end do
! Set delayed group

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