Merge pull request #1433 from paulromano/cell-instance-filter

Cell instance filter
This commit is contained in:
Sterling Harper 2020-01-13 14:21:42 -05:00 committed by GitHub
commit d4ad3cfb75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 707 additions and 58 deletions

View file

@ -253,6 +253,7 @@ list(APPEND libopenmc_SOURCES
src/tallies/filter_cellborn.cpp
src/tallies/filter_cellfrom.cpp
src/tallies/filter_cell.cpp
src/tallies/filter_cell_instance.cpp
src/tallies/filter_delayedgroup.cpp
src/tallies/filter_distribcell.cpp
src/tallies/filter_energyfunc.cpp

View file

@ -195,6 +195,18 @@ based on the recommended value in LA-UR-14-24530_.
.. _LA-UR-14-24530: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-14-24530.pdf
---------------------------
``<material_cell_offsets>``
---------------------------
By default, OpenMC will count the number of instances of each cell filled with a
material and generate "offset tables" that are used for cell instance tallies.
The ``<material_cell_offsets>`` element allows a user to override this default
setting and turn off the generation of offset tables, if desired, by setting it
to false.
*Default*: true
---------------------------
``<max_order>`` Element
---------------------------
@ -423,16 +435,16 @@ attributes/sub-elements:
:type:
The type of spatial distribution. Valid options are "box", "fission",
"point", "cartesian", "cylindrical", and "spherical". A "box" spatial
distribution has coordinates sampled uniformly in a parallelepiped. A
"fission" spatial distribution samples locations from a "box"
distribution but only locations in fissionable materials are accepted.
A "point" spatial distribution has coordinates specified by a triplet.
A "cartesian" spatial distribution specifies independent distributions of
"point", "cartesian", "cylindrical", and "spherical". A "box" spatial
distribution has coordinates sampled uniformly in a parallelepiped. A
"fission" spatial distribution samples locations from a "box"
distribution but only locations in fissionable materials are accepted.
A "point" spatial distribution has coordinates specified by a triplet.
A "cartesian" spatial distribution specifies independent distributions of
x-, y-, and z-coordinates. A "cylindrical" spatial distribution specifies
independent distributions of r-, phi-, and z-coordinates where phi is the
azimuthal angle and the origin for the cylindrical coordinate system is
specified by origin. A "spherical" spatial distribution specifies
specified by origin. A "spherical" spatial distribution specifies
independent distributions of r-, theta-, and phi-coordinates where theta
is the angle with respect to the z-axis, phi is the azimuthal angle, and
the sphere is centered on the coordinate (x0,y0,z0).
@ -452,7 +464,7 @@ attributes/sub-elements:
For an "cartesian" distribution, no parameters are specified. Instead,
the ``x``, ``y``, and ``z`` elements must be specified.
For a "cylindrical" distribution, no parameters are specified. Instead,
the ``r``, ``phi``, ``z``, and ``origin`` elements must be specified.
@ -474,15 +486,15 @@ attributes/sub-elements:
:ref:`univariate`).
:z:
For both "cartesian" and "cylindrical" distributions, this element
specifies the distribution of z-coordinates. The necessary
sub-elements/attributes are those of a univariate probability
For both "cartesian" and "cylindrical" distributions, this element
specifies the distribution of z-coordinates. The necessary
sub-elements/attributes are those of a univariate probability
distribution (see the description in :ref:`univariate`).
:r:
For "cylindrical" and "spherical" distributions, this element specifies
the distribution of r-coordinates (cylindrical radius and spherical
radius, respectively). The necessary sub-elements/attributes are those
the distribution of r-coordinates (cylindrical radius and spherical
radius, respectively). The necessary sub-elements/attributes are those
of a univariate probability distribution (see the description in
:ref:`univariate`).
@ -493,13 +505,13 @@ attributes/sub-elements:
:ref:`univariate`).
:phi:
For "cylindrical" and "spherical" distributions, this element specifies
the distribution of phi-coordinates. The necessary
sub-elements/attributes are those of a univariate probability
For "cylindrical" and "spherical" distributions, this element specifies
the distribution of phi-coordinates. The necessary
sub-elements/attributes are those of a univariate probability
distribution (see the description in :ref:`univariate`).
:origin:
For "cylindrical and "spherical" distributions, this element specifies
For "cylindrical and "spherical" distributions, this element specifies
the coordinates for the origin of the coordinate system.
:angle:

View file

@ -107,6 +107,7 @@ Constructing Tallies
openmc.CellFilter
openmc.CellFromFilter
openmc.CellbornFilter
openmc.CellInstanceFilter
openmc.SurfaceFilter
openmc.MeshFilter
openmc.MeshSurfaceFilter

View file

@ -2,12 +2,14 @@
#define OPENMC_CELL_H
#include <cstdint>
#include <functional> // for hash
#include <limits>
#include <memory> // for unique_ptr
#include <string>
#include <unordered_map>
#include <vector>
#include <gsl/gsl>
#include "hdf5.h"
#include "pugixml.hpp"
#include "dagmc.h"
@ -286,6 +288,26 @@ private:
std::vector<std::vector<int32_t>> partitions_;
};
//==============================================================================
//! Define an instance of a particular cell
//==============================================================================
struct CellInstance {
//! Check for equality
bool operator==(const CellInstance& other) const
{ return index_cell == other.index_cell && instance == other.instance; }
gsl::index index_cell;
gsl::index instance;
};
struct CellInstanceHash {
std::size_t operator()(const CellInstance& k) const
{
return 4096*k.index_cell + k.instance;
}
};
//==============================================================================
// Non-member functions
//==============================================================================

View file

@ -31,6 +31,7 @@ extern "C" bool cmfd_run; //!< is a CMFD run?
extern "C" bool dagmc; //!< indicator of DAGMC geometry
extern "C" bool entropy_on; //!< calculate Shannon entropy?
extern bool legendre_to_tabular; //!< convert Legendre distributions to tabular?
extern bool material_cell_offsets; //!< create material cells offsets?
extern "C" bool output_summary; //!< write summary.h5?
extern bool output_tallies; //!< write tallies.out?
extern bool particle_restart_run; //!< particle restart run?

View file

@ -0,0 +1,63 @@
#ifndef OPENMC_TALLIES_FILTER_CELL_INSTANCE_H
#define OPENMC_TALLIES_FILTER_CELL_INSTANCE_H
#include <cstdint>
#include <unordered_map>
#include <vector>
#include <gsl/gsl>
#include "openmc/cell.h"
#include "openmc/tallies/filter.h"
namespace openmc {
//==============================================================================
//! Specifies cell instances that tally events reside in.
//==============================================================================
class CellInstanceFilter : public Filter {
public:
//----------------------------------------------------------------------------
// Constructors, destructors
CellInstanceFilter() = default;
CellInstanceFilter(gsl::span<CellInstance> instances);
~CellInstanceFilter() = default;
//----------------------------------------------------------------------------
// Methods
std::string type() const override {return "cellinstance";}
void from_xml(pugi::xml_node node) override;
void get_all_bins(const Particle* p, int estimator, FilterMatch& match)
const override;
void to_statepoint(hid_t filter_group) const override;
std::string text_label(int bin) const override;
//----------------------------------------------------------------------------
// Accessors
const std::vector<CellInstance>& cell_instances() const { return cell_instances_; }
void set_cell_instances(gsl::span<CellInstance> instances);
private:
//----------------------------------------------------------------------------
// Data members
//! The indices of the cells binned by this filter.
std::vector<CellInstance> cell_instances_;
//! A map from cell/instance indices to filter bin indices.
std::unordered_map<CellInstance, gsl::index, CellInstanceHash> map_;
};
} // namespace openmc
#endif // OPENMC_TALLIES_FILTER_CELL_INSTANCE_H

View file

@ -62,6 +62,8 @@ public:
//----------------------------------------------------------------------------
// Other methods.
void add_filter(Filter* filter) { set_filters({&filter, 1}); }
void init_triggers(pugi::xml_node node);
void init_results();

View file

@ -23,7 +23,7 @@ _FILTER_TYPES = (
'universe', 'material', 'cell', 'cellborn', 'surface', 'mesh', 'energy',
'energyout', 'mu', 'polar', 'azimuthal', 'distribcell', 'delayedgroup',
'energyfunction', 'cellfrom', 'legendre', 'spatiallegendre',
'sphericalharmonics', 'zernike', 'zernikeradial', 'particle'
'sphericalharmonics', 'zernike', 'zernikeradial', 'particle', 'cellinstance'
)
_CURRENT_NAMES = (
@ -266,7 +266,7 @@ class Filter(IDManagerMixin, metaclass=FilterMeta):
# Merge unique filter bins
merged_bins = np.concatenate((self.bins, other.bins))
merged_bins = np.unique(merged_bins)
merged_bins = np.unique(merged_bins, axis=0)
# Create a new filter with these bins and a new auto-generated ID
return type(self)(merged_bins)
@ -518,6 +518,105 @@ class CellbornFilter(WithIDFilter):
expected_type = Cell
class CellInstanceFilter(Filter):
"""Bins tally events based on which cell instance a particle is in.
This filter is similar to :class:`DistribcellFilter` but allows one to
select particular instances to be tallied (instead of obtaining *all*
instances by default) and allows instances from different cells to be
specified in a single filter.
Parameters
----------
bins : iterable of 2-tuples or numpy.ndarray
The cell instances to tally, given as 2-tuples. For the first value in
the tuple, either openmc.Cell objects or their integral ID numbers can
be used.
filter_id : int
Unique identifier for the filter
Attributes
----------
bins : numpy.ndarray
2D numpy array of cell IDs and instances
id : int
Unique identifier for the filter
num_bins : Integral
The number of filter bins
See Also
--------
DistribcellFilter
"""
def __init__(self, bins, filter_id=None):
self.bins = bins
self.id = filter_id
@Filter.bins.setter
def bins(self, bins):
pairs = np.empty((len(bins), 2), dtype=int)
for i, (cell, instance) in enumerate(bins):
cv.check_type('cell', cell, (openmc.Cell, Integral))
cv.check_type('instance', instance, Integral)
pairs[i, 0] = cell if isinstance(cell, Integral) else cell.id
pairs[i, 1] = instance
self._bins = pairs
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
This method constructs a Pandas DataFrame object for the filter with
columns annotated by filter bin information. This is a helper method for
:meth:`Tally.get_pandas_dataframe`.
Parameters
----------
data_size : int
The total number of bins in the tally corresponding to this filter
stride : int
Stride in memory for the filter
Returns
-------
pandas.DataFrame
A Pandas DataFrame with a multi-index column for the cell instance.
The number of rows in the DataFrame is the same as the total number
of bins in the corresponding tally, with the filter bin appropriately
tiled to map to the corresponding tally bins.
See also
--------
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Repeat and tile bins as necessary to account for other filters.
bins = np.repeat(self.bins, stride, axis=0)
tile_factor = data_size // len(bins)
bins = np.tile(bins, (tile_factor, 1))
columns = pd.MultiIndex.from_product([[self.short_name.lower()],
['cell', 'instance']])
return pd.DataFrame(bins, columns=columns)
def to_xml_element(self):
"""Return XML Element representing the Filter.
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing filter data
"""
element = ET.Element('filter')
element.set('id', str(self.id))
element.set('type', self.short_name.lower())
subelement = ET.SubElement(element, 'bins')
subelement.text = ' '.join(str(i) for i in self.bins.ravel())
return element
class SurfaceFilter(WithIDFilter):
"""Filters particles by surface crossing
@ -564,11 +663,7 @@ class ParticleFilter(Filter):
The number of filter bins
"""
@property
def bins(self):
return self._bins
@bins.setter
@Filter.bins.setter
def bins(self, bins):
bins = np.atleast_1d(bins)
cv.check_iterable_type('filter bins', bins, str)
@ -1174,6 +1269,11 @@ def _path_to_levels(path):
class DistribcellFilter(Filter):
"""Bins tally event locations on instances of repeated cells.
This filter provides a separate score for each unique instance of a repeated
cell in a geometry. Note that only one cell can be specified in this filter.
The related :class:`CellInstanceFilter` allows one to obtain scores for
particular cell instances as well as instances from different cells.
Parameters
----------
cell : openmc.Cell or Integral
@ -1194,6 +1294,10 @@ class DistribcellFilter(Filter):
The paths traversed through the CSG tree to reach each distribcell
instance (for 'distribcell' filters only)
See Also
--------
CellInstanceFilter
"""
def __init__(self, cell, filter_id=None):

View file

@ -61,6 +61,9 @@ class Settings(object):
relative error used.
log_grid_bins : int
Number of bins for logarithmic energy grid search
material_cell_offsets : bool
Generate an "offset table" for material cells by default. These tables
are necessary when a particular instance of a cell needs to be tallied.
max_order : None or int
Maximum scattering order to apply globally when in multi-group mode.
no_reduce : bool
@ -216,6 +219,7 @@ class Settings(object):
VolumeCalculation, 'volume calculations')
self._create_fission_neutrons = None
self._material_cell_offsets = None
self._log_grid_bins = None
self._dagmc = False
@ -352,6 +356,10 @@ class Settings(object):
def create_fission_neutrons(self):
return self._create_fission_neutrons
@property
def material_cell_offsets(self):
return self._material_cell_offsets
@property
def log_grid_bins(self):
return self._log_grid_bins
@ -683,6 +691,11 @@ class Settings(object):
create_fission_neutrons, bool)
self._create_fission_neutrons = create_fission_neutrons
@material_cell_offsets.setter
def material_cell_offsets(self, value):
cv.check_type('material cell offsets', value, bool)
self._material_cell_offsets = value
@log_grid_bins.setter
def log_grid_bins(self, log_grid_bins):
cv.check_type('log grid bins', log_grid_bins, Real)
@ -917,6 +930,11 @@ class Settings(object):
elem = ET.SubElement(root, "create_fission_neutrons")
elem.text = str(self._create_fission_neutrons).lower()
def _create_material_cell_offsets_subelement(self, root):
if self._material_cell_offsets is not None:
elem = ET.SubElement(root, "material_cell_offsets")
elem.text = str(self._material_cell_offsets).lower()
def _create_log_grid_bins_subelement(self, root):
if self._log_grid_bins is not None:
elem = ET.SubElement(root, "log_grid_bins")
@ -1148,6 +1166,11 @@ class Settings(object):
if text is not None:
self.create_fission_neutrons = text in ('true', '1')
def _material_cell_offsets_from_xml_element(self, root):
text = get_text(root, 'material_cell_offsets')
if text is not None:
self.material_cell_offsets = text in ('true', '1')
def _log_grid_bins_from_xml_element(self, root):
text = get_text(root, 'log_grid_bins')
if text is not None:
@ -1202,6 +1225,7 @@ class Settings(object):
self._create_resonance_scattering_subelement(root_element)
self._create_volume_calcs_subelement(root_element)
self._create_create_fission_neutrons_subelement(root_element)
self._create_material_cell_offsets_subelement(root_element)
self._create_log_grid_bins_subelement(root_element)
self._create_dagmc_subelement(root_element)
@ -1267,6 +1291,7 @@ class Settings(object):
settings._ufs_mesh_from_xml_element(root)
settings._resonance_scattering_from_xml_element(root)
settings._create_fission_neutrons_from_xml_element(root)
settings._material_cell_offsets_from_xml_element(root)
settings._log_grid_bins_from_xml_element(root)
settings._dagmc_from_xml_element(root)

View file

@ -258,8 +258,13 @@ Cell::set_temperature(double T, int32_t instance)
}
if (instance >= 0) {
// If temperature vector is not big enough, resize it first
if (sqrtkT_.size() != n_instances_) sqrtkT_.resize(n_instances_, sqrtkT_[0]);
// Set temperature for the corresponding instance
sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
} else {
// Set temperature for all instances
for (auto& T_ : sqrtkT_) {
T_ = std::sqrt(K_BOLTZMANN * T);
}

View file

@ -75,6 +75,7 @@ int openmc_finalize()
settings::gen_per_batch = 1;
settings::legendre_to_tabular = true;
settings::legendre_to_tabular_points = -1;
settings::material_cell_offsets = true;
settings::n_particles = -1;
settings::output_summary = true;
settings::output_tallies = true;

View file

@ -132,8 +132,8 @@ find_cell_inner(Particle* p, const NeighborList* neighbor_list)
//! Found a material cell which means this is the lowest coord level.
// Find the distribcell instance number.
if (c.material_.size() > 1 || c.sqrtkT_.size() > 1) {
int offset = 0;
int offset = 0;
if (c.distribcell_index_ >= 0) {
for (int i = 0; i < p->n_coord_; i++) {
const auto& c_i {*model::cells[p->coord_[i].cell]};
if (c_i.type_ == FILL_UNIVERSE) {
@ -148,10 +148,8 @@ find_cell_inner(Particle* p, const NeighborList* neighbor_list)
}
}
}
p->cell_instance_ = offset;
} else {
p->cell_instance_ = 0;
}
p->cell_instance_ = offset;
// Set the material and temperature.
p->material_last_ = p->material_;

View file

@ -18,6 +18,7 @@
#include "openmc/settings.h"
#include "openmc/surface.h"
#include "openmc/tallies/filter.h"
#include "openmc/tallies/filter_cell_instance.h"
#include "openmc/tallies/filter_distribcell.h"
@ -319,7 +320,9 @@ find_root_universe()
void
prepare_distribcell()
{
// Find all cells listed in a DistribcellFilter.
write_message("Preparing distributed cell instances...", 5);
// Find all cells listed in a DistribcellFilter or CellInstanceFilter
std::unordered_set<int32_t> distribcells;
for (auto& filt : model::tally_filters) {
auto* distrib_filt = dynamic_cast<DistribcellFilter*>(filt.get());
@ -328,8 +331,15 @@ prepare_distribcell()
}
}
// Find all cells with distributed materials or temperatures. Make sure that
// the number of materials/temperatures matches the number of cell instances.
// By default, add material cells to the list of distributed cells
if (settings::material_cell_offsets) {
for (gsl::index i = 0; i < model::cells.size(); ++i) {
if (model::cells[i]->type_ == FILL_MATERIAL) distribcells.insert(i);
}
}
// Make sure that the number of materials/temperatures matches the number of
// cell instances.
for (int i = 0; i < model::cells.size(); i++) {
Cell& c {*model::cells[i]};
@ -342,7 +352,6 @@ prepare_distribcell()
"one or the number of instances.";
fatal_error(err_msg);
}
distribcells.insert(i);
}
if (c.sqrtkT_.size() > 1) {
@ -354,20 +363,18 @@ prepare_distribcell()
"one or the number of instances.";
fatal_error(err_msg);
}
distribcells.insert(i);
}
}
// Search through universes for distributed cells and assign each one a
// Search through universes for material cells and assign each one a
// unique distribcell array index.
int distribcell_index = 0;
std::vector<int32_t> target_univ_ids;
for (const auto& u : model::universes) {
for (auto cell_indx : u->cells_) {
if (distribcells.find(cell_indx) != distribcells.end()) {
model::cells[cell_indx]->distribcell_index_ = distribcell_index;
for (auto idx : u->cells_) {
if (distribcells.find(idx) != distribcells.end()) {
model::cells[idx]->distribcell_index_ = distribcell_index++;
target_univ_ids.push_back(u->id_);
++distribcell_index;
}
}
}
@ -387,7 +394,7 @@ prepare_distribcell()
for (int map = 0; map < target_univ_ids.size(); map++) {
auto target_univ_id = target_univ_ids[map];
for (const auto& univ : model::universes) {
int32_t offset {0}; // TODO: is this a bug? It matches F90 implementation.
int32_t offset = 0;
for (int32_t cell_indx : univ->cells_) {
Cell& c = *model::cells[cell_indx];

View file

@ -7,9 +7,15 @@ element settings {
element cutoff {
(element weight { xsd:double } | attribute weight { xsd:double })? &
(element weight_avg { xsd:double } | attribute weight_avg { xsd:double })?
(element weight_avg { xsd:double } | attribute weight_avg { xsd:double })? &
(element energy_neutron { xsd:double } | attribute energy_neutron { xsd:double })? &
(element energy_photon { xsd:double } | attribute energy_photon { xsd:double })? &
(element energy_electron { xsd:double } | attribute energy_electron { xsd:double })? &
(element energy_positron { xsd:double } | attribute energy_positron { xsd:double })?
}? &
element electron_treatment { ( "led" | "ttb" ) }? &
element energy_grid { ( "nuclide" | "log" | "logarithm" | "logarithmic" | "material-union" | "union" ) }? &
element energy_mode { ( "continuous-energy" | "ce" | "CE" | "multi-group" | "mg" | "MG" ) }? &
@ -27,6 +33,8 @@ element settings {
element log_grid_bins { xsd:positiveInteger }? &
element material_cell_offsets { xsd:boolean }? &
element max_order { xsd:nonNegativeInteger }? &
element mesh {
@ -55,6 +63,8 @@ element settings {
element particles { xsd:positiveInteger }? &
element photon_transport { xsd:boolean }? &
element ptables { xsd:boolean }? &
element dagmc { xsd:boolean }? &
@ -78,8 +88,8 @@ element settings {
element z { distribution }? &
element r { distribution }? &
element theta { distribution }? &
element phi { distribution }? &
element origin { list { xsd:double, xsd:double, xsd:double } }?
element phi { distribution }? &
element origin { list { xsd:double, xsd:double, xsd:double } }?
}? &
element angle {
(element type { xsd:string } | attribute type { xsd:string }) &
@ -88,8 +98,7 @@ element settings {
element mu { distribution }? &
element phi { distribution }?
}? &
element energy { distribution }? &
(element write_initial { xsd:boolean } | attribute write_initial { xsd:boolean })?
element energy { distribution }?
distribution =
(element type { xsd:string { maxLength = "16" } } |
attribute type { xsd:string { maxLength = "16" } }) &
@ -165,6 +174,8 @@ element settings {
attribute upper_right { list { xsd:double+ } })
}* &
element write_initial_source { xsd:boolean }? &
element resonance_scattering {
(element enable { xsd:boolean } | attribute enable { xsd:boolean })? &
(element method { xsd:string } | attribute method { xsd:string })? &

View file

@ -39,9 +39,57 @@
</attribute>
</choice>
</optional>
<optional>
<choice>
<element name="energy_neutron">
<data type="double"/>
</element>
<attribute name="energy_neutron">
<data type="double"/>
</attribute>
</choice>
</optional>
<optional>
<choice>
<element name="energy_photon">
<data type="double"/>
</element>
<attribute name="energy_photon">
<data type="double"/>
</attribute>
</choice>
</optional>
<optional>
<choice>
<element name="energy_electron">
<data type="double"/>
</element>
<attribute name="energy_electron">
<data type="double"/>
</attribute>
</choice>
</optional>
<optional>
<choice>
<element name="energy_positron">
<data type="double"/>
</element>
<attribute name="energy_positron">
<data type="double"/>
</attribute>
</choice>
</optional>
</interleave>
</element>
</optional>
<optional>
<element name="electron_treatment">
<choice>
<value>led</value>
<value>ttb</value>
</choice>
</element>
</optional>
<optional>
<element name="energy_grid">
<choice>
@ -108,6 +156,11 @@
<data type="positiveInteger"/>
</element>
</optional>
<optional>
<element name="material_cell_offsets">
<data type="boolean"/>
</element>
</optional>
<optional>
<element name="max_order">
<data type="nonNegativeInteger"/>
@ -249,6 +302,11 @@
<data type="positiveInteger"/>
</element>
</optional>
<optional>
<element name="photon_transport">
<data type="boolean"/>
</element>
</optional>
<optional>
<element name="ptables">
<data type="boolean"/>
@ -422,16 +480,6 @@
<ref name="distribution"/>
</element>
</optional>
<optional>
<choice>
<element name="write_initial">
<data type="boolean"/>
</element>
<attribute name="write_initial">
<data type="boolean"/>
</attribute>
</choice>
</optional>
</interleave>
</start>
<define name="distribution">
@ -743,6 +791,11 @@
</interleave>
</element>
</zeroOrMore>
<optional>
<element name="write_initial_source">
<data type="boolean"/>
</element>
</optional>
<optional>
<element name="resonance_scattering">
<interleave>

View file

@ -51,11 +51,11 @@ element tallies {
( (element type { ( "cell" | "cellfrom" | "cellborn" | "material" |
"universe" | "surface" | "distribcell" | "mesh" | "energy" |
"energyout" | "mu" | "polar" | "azimuthal" | "delayedgroup" |
"energyfunction" | "meshsurface") } |
"energyfunction" | "meshsurface" | "cellinstance") } |
attribute type { ( "cell" | "cellfrom" | "cellborn" | "material" |
"universe" | "surface" | "distribcell" | "mesh" | "energy" |
"energyout" | "mu" | "polar" | "azimuthal" | "delayedgroup" |
"energyfunction" | "meshsurface") }) &
"energyfunction" | "meshsurface" | "cellinstance") }) &
(element bins { list { xsd:double+ } } |
attribute bins { list { xsd:double+ } })
) |

View file

@ -245,6 +245,7 @@
<value>delayedgroup</value>
<value>energyfunction</value>
<value>meshsurface</value>
<value>cellinstance</value>
</choice>
</element>
<attribute name="type">
@ -265,6 +266,7 @@
<value>delayedgroup</value>
<value>energyfunction</value>
<value>meshsurface</value>
<value>cellinstance</value>
</choice>
</attribute>
</choice>

View file

@ -46,6 +46,7 @@ bool create_fission_neutrons {true};
bool dagmc {false};
bool entropy_on {false};
bool legendre_to_tabular {true};
bool material_cell_offsets {true};
bool output_summary {true};
bool output_tallies {true};
bool particle_restart_run {false};
@ -776,6 +777,11 @@ void read_settings_xml()
create_fission_neutrons = get_node_value_bool(root, "create_fission_neutrons");
}
}
// Check whether material cell offsets should be generated
if (check_for_node(root, "material_cell_offsets")) {
material_cell_offsets = get_node_value_bool(root, "material_cell_offsets");
}
}
void free_memory_settings() {

View file

@ -5,6 +5,7 @@
#include "openmc/container_util.h"
#include "openmc/eigenvalue.h"
#include "openmc/error.h"
#include "openmc/geometry_aux.h"
#include "openmc/material.h"
#include "openmc/message_passing.h"
#include "openmc/nuclide.h"

View file

@ -12,6 +12,7 @@
#include "openmc/tallies/filter_cell.h"
#include "openmc/tallies/filter_cellborn.h"
#include "openmc/tallies/filter_cellfrom.h"
#include "openmc/tallies/filter_cell_instance.h"
#include "openmc/tallies/filter_delayedgroup.h"
#include "openmc/tallies/filter_distribcell.h"
#include "openmc/tallies/filter_energyfunc.h"
@ -100,6 +101,8 @@ Filter* Filter::create(const std::string& type, int32_t id)
model::tally_filters.push_back(std::make_unique<CellbornFilter>());
} else if (type == "cellfrom") {
model::tally_filters.push_back(std::make_unique<CellFromFilter>());
} else if (type == "cellinstance") {
model::tally_filters.push_back(std::make_unique<CellInstanceFilter>());
} else if (type == "distribcell") {
model::tally_filters.push_back(std::make_unique<DistribcellFilter>());
} else if (type == "delayedgroup") {

View file

@ -0,0 +1,106 @@
#include "openmc/tallies/filter_cell_instance.h"
#include <sstream>
#include <string>
#include "openmc/capi.h"
#include "openmc/cell.h"
#include "openmc/error.h"
#include "openmc/xml_interface.h"
namespace openmc {
CellInstanceFilter::CellInstanceFilter(gsl::span<CellInstance> instances)
{
this->set_cell_instances(instances);
}
void
CellInstanceFilter::from_xml(pugi::xml_node node)
{
// Get cell IDs/instances
auto cells = get_node_array<int32_t>(node, "bins");
Expects(cells.size() % 2 == 0);
// Convert into vector of CellInstance
std::vector<CellInstance> instances;
for (gsl::index i = 0; i < cells.size() / 2; ++i) {
int32_t cell_id = cells[2*i];
gsl::index instance = cells[2*i + 1];
auto search = model::cell_map.find(cell_id);
if (search == model::cell_map.end()) {
std::stringstream err_msg;
err_msg << "Could not find cell " << cell_id
<< " specified on tally filter.";
throw std::runtime_error{err_msg.str()};
}
gsl::index index = search->second;
instances.push_back({index, instance});
}
this->set_cell_instances(instances);
}
void
CellInstanceFilter::set_cell_instances(gsl::span<CellInstance> instances)
{
// Clear existing cells
cell_instances_.clear();
cell_instances_.reserve(instances.size());
map_.clear();
// Update cells and mapping
for (auto& x : instances) {
Expects(x.index_cell >= 0);
Expects(x.index_cell < model::cells.size());
const auto& c {model::cells[x.index_cell]};
if (c->type_ != FILL_MATERIAL) {
throw std::invalid_argument{"Cell " + std::to_string(c->id_) + " is not "
"filled with a material. Only material cells can be used in a cell "
"instance filter."};
}
cell_instances_.push_back(x);
map_[x] = cell_instances_.size() - 1;
}
n_bins_ = cell_instances_.size();
}
void
CellInstanceFilter::get_all_bins(const Particle* p, int estimator,
FilterMatch& match) const
{
gsl::index index_cell = p->coord_[p->n_coord_ - 1].cell;
gsl::index instance = p->cell_instance_;
auto search = map_.find({index_cell, instance});
if (search != map_.end()) {
int index_bin = search->second;
match.bins_.push_back(index_bin);
match.weights_.push_back(1.0);
}
}
void
CellInstanceFilter::to_statepoint(hid_t filter_group) const
{
Filter::to_statepoint(filter_group);
size_t n = cell_instances_.size();
xt::xtensor<size_t, 2> data({n, 2});
for (gsl::index i = 0; i < n; ++i) {
const auto& x = cell_instances_[i];
data(i, 0) = model::cells[x.index_cell]->id_;
data(i, 1) = x.instance;
}
write_dataset(filter_group, "bins", data);
}
std::string
CellInstanceFilter::text_label(int bin) const
{
const auto& x = cell_instances_[bin];
auto cell_id = model::cells[x.index_cell]->id_;
return "Cell " + std::to_string(cell_id) + ", Instance "
+ std::to_string(x.instance);
}
} // namespace openmc

View file

@ -0,0 +1,64 @@
<?xml version='1.0' encoding='utf-8'?>
<geometry>
<cell id="1" material="1" region="-1" universe="1" />
<cell id="2" material="2" region="1" universe="1" />
<cell id="3" material="1" region="-2" universe="2" />
<cell id="4" material="2" region="2" universe="2" />
<cell fill="3" id="5" region="3 -4 5 -6" universe="4" />
<lattice id="3">
<pitch>2 2</pitch>
<dimension>4 4</dimension>
<lower_left>-4 -4</lower_left>
<universes>
1 2 2 2
2 1 2 2
2 2 1 2
2 2 2 1 </universes>
</lattice>
<surface coeffs="0.0 0.0 0.7" id="1" type="z-cylinder" />
<surface coeffs="0.0 0.0 0.5" id="2" type="z-cylinder" />
<surface boundary="reflective" coeffs="-4.0" id="3" name="minimum x" type="x-plane" />
<surface boundary="reflective" coeffs="4.0" id="4" name="maximum x" type="x-plane" />
<surface boundary="reflective" coeffs="-4.0" id="5" name="minimum y" type="y-plane" />
<surface boundary="reflective" coeffs="4.0" id="6" name="maximum y" type="y-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>
<material depletable="true" id="1">
<density units="g/cc" value="4.5" />
<nuclide ao="1.0" name="U235" />
</material>
<material id="2">
<density units="g/cc" value="1.0" />
<nuclide ao="1.0" name="H1" />
</material>
</materials>
<?xml version='1.0' encoding='utf-8'?>
<settings>
<run_mode>eigenvalue</run_mode>
<particles>1000</particles>
<batches>5</batches>
<inactive>0</inactive>
<source strength="1.0">
<space type="point">
<parameters>0.0 0.0 0.0</parameters>
</space>
</source>
</settings>
<?xml version='1.0' encoding='utf-8'?>
<tallies>
<filter id="1" type="cellinstance">
<bins>3 0 3 1 3 2 3 3 3 4 3 5 3 6 3 7 3 8 3 9 3 10 3 11 2 0 2 1 2 2 2 3</bins>
</filter>
<filter id="2" type="cellinstance">
<bins>2 3 2 2 2 1 2 0 3 11 3 10 3 9 3 8 3 7 3 6 3 5 3 4 3 3 3 2 3 1 3 0</bins>
</filter>
<tally id="1">
<filters>1</filters>
<scores>total</scores>
</tally>
<tally id="2">
<filters>2</filters>
<scores>total</scores>
</tally>
</tallies>

View file

@ -0,0 +1,68 @@
k-combined:
1.060380E+00 4.511966E-03
tally 1:
8.636855E-02
1.795640E-03
1.478489E-01
5.007770E-03
1.687036E-01
6.223488E-03
1.035433E-01
2.767195E-03
2.789537E-01
1.720816E-02
1.420504E-01
4.539772E-03
1.325589E-01
3.998010E-03
2.934613E-01
2.064779E-02
1.231465E-01
3.847169E-03
1.282909E-01
3.680695E-03
1.545480E-01
5.588411E-03
5.110026E-02
5.947113E-04
1.119824E+01
3.030467E+01
2.847726E+01
1.920547E+02
2.819853E+01
1.906807E+02
8.816481E+00
2.016785E+01
tally 2:
8.816481E+00
2.016785E+01
2.819853E+01
1.906807E+02
2.847726E+01
1.920547E+02
1.119824E+01
3.030467E+01
5.110026E-02
5.947113E-04
1.545480E-01
5.588411E-03
1.282909E-01
3.680695E-03
1.231465E-01
3.847169E-03
2.934613E-01
2.064779E-02
1.325589E-01
3.998010E-03
1.420504E-01
4.539772E-03
2.789537E-01
1.720816E-02
1.035433E-01
2.767195E-03
1.687036E-01
6.223488E-03
1.478489E-01
5.007770E-03
8.636855E-02
1.795640E-03

View file

@ -0,0 +1,69 @@
import openmc
import openmc.model
import pytest
from tests.testing_harness import PyAPITestHarness
@pytest.fixture
def model():
model = openmc.model.Model()
# Materials
m1 = openmc.Material()
m1.set_density('g/cc', 4.5)
m1.add_nuclide('U235', 1.0)
m2 = openmc.Material()
m2.set_density('g/cc', 1.0)
m2.add_nuclide('H1', 1.0)
model.materials += [m1, m2]
# Geometry
cyl1 = openmc.ZCylinder(r=0.7)
c1 = openmc.Cell(fill=m1, region=-cyl1)
c2 = openmc.Cell(fill=m2, region=+cyl1)
u1 = openmc.Universe(cells=[c1, c2])
cyl2 = openmc.ZCylinder(r=0.5)
c3 = openmc.Cell(fill=m1, region=-cyl2)
c4 = openmc.Cell(fill=m2, region=+cyl2)
u2 = openmc.Universe(cells=[c3, c4])
lat = openmc.RectLattice()
lat.lower_left = (-4, -4)
lat.pitch = (2, 2)
lat.universes = [
[u1, u2, u2, u2],
[u2, u1, u2, u2],
[u2, u2, u1, u2],
[u2, u2, u2, u1]
]
box = openmc.model.rectangular_prism(8.0, 8.0, boundary_type='reflective')
main_cell = openmc.Cell(fill=lat, region=box)
model.geometry.root_universe = openmc.Universe(cells=[main_cell])
model.geometry.determine_paths()
# Settings
model.settings.batches = 5
model.settings.inactive = 0
model.settings.particles = 1000
model.settings.source = openmc.Source(space=openmc.stats.Point())
instances = ([(c3, i) for i in range(c3.num_instances)] +
[(c2, i) for i in range(c2.num_instances)])
f1 = openmc.CellInstanceFilter(instances)
f2 = openmc.CellInstanceFilter(instances[::-1])
t1 = openmc.Tally()
t1.filters = [f1]
t1.scores = ['total']
t2 = openmc.Tally()
t2.filters = [f2]
t2.scores = ['total']
model.tallies += [t1, t2]
return model
def test_cell_instance(model):
harness = PyAPITestHarness('statepoint.5.h5', model)
harness.main()

View file

@ -22,6 +22,30 @@ def box_model():
return model
def test_cell_instance():
c1 = openmc.Cell()
c2 = openmc.Cell()
f = openmc.CellInstanceFilter([(c1, 0), (c1, 1), (c1, 2), (c2, 0), (c2, 1)])
# Make sure __repr__ works
repr(f)
# to_xml_element()
elem = f.to_xml_element()
assert elem.tag == 'filter'
assert elem.attrib['type'] == 'cellinstance'
bins = [int(x) for x in elem.find('bins').text.split()]
assert all(x == c1.id for x in bins[:6:2])
assert all(x == c2.id for x in bins[6::2])
# get_pandas_dataframe()
df = f.get_pandas_dataframe(f.num_bins, 1)
cells = df['cellinstance', 'cell']
instances = df['cellinstance', 'instance']
assert cells.apply(lambda x: x in (c1.id, c2.id)).all()
assert instances.apply(lambda x: x in (0, 1, 2)).all()
def test_legendre():
n = 5
f = openmc.LegendreFilter(n)
@ -69,7 +93,7 @@ def test_spherical_harmonics():
f.cosine = 'particle'
assert f.order == n
assert f.bins[0] == 'Y0,0'
assert f.bins[-1] == 'Y{0},{0}'.format(n, n)
assert f.bins[-1] == 'Y{0},{0}'.format(n)
assert len(f.bins) == (n + 1)**2
# Make sure __repr__ works