Merge pull request #1863 from pshriwise/cell_inst_filter

Enable cell instance tallies for universe/lattice-filled cells
This commit is contained in:
Paul Romano 2021-08-03 14:27:32 -05:00 committed by GitHub
commit 180fefc99d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 156 additions and 48 deletions

View file

@ -40,6 +40,17 @@ inline bool coincident(double d1, double d2) {
bool check_cell_overlap(Particle& p, bool error=true);
//==============================================================================
//! Get the cell instance for a particle at the specified universe level
//!
//! \param p A particle for which to compute the instance using
//! its coordinates
//! \param level The level (zero indexed) of the geometry where the instance should be computed.
//! \return The instance of the cell at the specified level.
//==============================================================================
int cell_instance_at_level(const Particle& p, int level);
//==============================================================================
//! Locate a particle in the geometry tree and set its geometry data fields.
//!

View file

@ -53,8 +53,14 @@ private:
//! The indices of the cells binned by this filter.
vector<CellInstance> cell_instances_;
//! The set of cells used in this filter
std::unordered_set<int32_t> cells_;
//! A map from cell/instance indices to filter bin indices.
std::unordered_map<CellInstance, gsl::index, CellInstanceHash> map_;
//! Indicates if filter uses only material-filled cells
bool material_cells_only_;
};
} // namespace openmc

View file

@ -65,6 +65,37 @@ bool check_cell_overlap(Particle& p, bool error)
//==============================================================================
int cell_instance_at_level(const Particle& p, int level) {
// throw error if the requested level is too deep for the geometry
if (level > model::n_coord_levels) {
fatal_error(fmt::format("Cell instance at level {} requested, but only {} levels exist in the geometry.", level, p.n_coord()));
}
// determine the cell instance
Cell& c {*model::cells[p.coord(level).cell]};
// quick exit if this cell doesn't have distribcell instances
if (c.distribcell_index_ == C_NONE) return C_NONE;
// compute the cell's instance
int instance = 0;
for (int i = 0; i < level; i++) {
const auto& c_i {*model::cells[p.coord(i).cell]};
if (c_i.type_ == Fill::UNIVERSE) {
instance += c_i.offset_[c.distribcell_index_];
} else if (c_i.type_ == Fill::LATTICE) {
auto& lat {*model::lattices[p.coord(i + 1).lattice]};
const auto& i_xyz {p.coord(i + 1).lattice_i};
if (lat.are_valid_indices(i_xyz)) {
instance += lat.offset(c.distribcell_index_, i_xyz);
}
}
}
return instance;
}
//==============================================================================
bool
find_cell_inner(Particle& p, const NeighborList* neighbor_list)
{
@ -129,23 +160,11 @@ find_cell_inner(Particle& p, const NeighborList* neighbor_list)
if (c.type_ == Fill::MATERIAL) {
// Found a material cell which means this is the lowest coord level.
p.cell_instance() = 0;
// Find the distribcell instance number.
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) {
offset += c_i.offset_[c.distribcell_index_];
} else if (c_i.type_ == Fill::LATTICE) {
auto& lat {*model::lattices[p.coord(i + 1).lattice]};
const auto& i_xyz {p.coord(i + 1).lattice_i};
if (lat.are_valid_indices(i_xyz)) {
offset += lat.offset(c.distribcell_index_, i_xyz);
}
}
}
p.cell_instance() = cell_instance_at_level(p, p.n_coord() - 1);
}
p.cell_instance() = offset;
// Set the material and temperature.
p.material_last() = p.material();

View file

@ -319,9 +319,13 @@ prepare_distribcell()
std::unordered_set<int32_t> distribcells;
for (auto& filt : model::tally_filters) {
auto* distrib_filt = dynamic_cast<DistribcellFilter*>(filt.get());
auto* cell_inst_filt = dynamic_cast<CellInstanceFilter*>(filt.get());
if (distrib_filt) {
distribcells.insert(distrib_filt->cell());
}
if (cell_inst_filt) {
for (const auto& c_inst : cell_inst_filt->cell_instances()) distribcells.insert(c_inst.index_cell);
}
}
// By default, add material cells to the list of distributed cells

View file

@ -47,7 +47,7 @@ IdData::set_value(size_t y, size_t x, const Particle& p, int level) {
data_(y, x, 1) = NOT_FOUND;
} else {
data_(y, x, 0) = model::cells.at(p.coord(level).cell)->id_;
data_(y, x, 1) = p.cell_instance();
data_(y, x, 1) = level == p.n_coord() - 1 ? p.cell_instance() : cell_instance_at_level(p, level);
}
// set material data

View file

@ -7,6 +7,7 @@
#include "openmc/capi.h"
#include "openmc/cell.h"
#include "openmc/error.h"
#include "openmc/geometry.h"
#include "openmc/xml_interface.h"
namespace openmc {
@ -46,23 +47,27 @@ CellInstanceFilter::set_cell_instances(gsl::span<CellInstance> instances)
// Clear existing cells
cell_instances_.clear();
cell_instances_.reserve(instances.size());
cells_.clear();
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{fmt::format(
"Cell {} is not filled with a material. Only material cells can be "
"used in a cell instance filter.", c->id_)};
}
cell_instances_.push_back(x);
cells_.insert(x.index_cell);
map_[x] = cell_instances_.size() - 1;
}
n_bins_ = cell_instances_.size();
material_cells_only_ = true;
for (const auto& cell_inst : cell_instances_) {
const auto& c = *model::cells[cell_inst.index_cell];
if (c.type_ == Fill::MATERIAL) continue;
material_cells_only_ = false;
break;
}
}
void
@ -71,11 +76,30 @@ CellInstanceFilter::get_all_bins(const Particle& p, TallyEstimator estimator,
{
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);
if (cells_.count(index_cell) > 0) {
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);
}
}
if (material_cells_only_) return;
for (int i = 0; i < p.n_coord() - 1; i++) {
gsl::index index_cell = p.coord(i).cell;
// if this cell isn't used on the filter, move on
if (cells_.count(index_cell) == 0) continue;
// if this cell is used in the filter, check the instance as well
gsl::index instance = cell_instance_at_level(p, i);
auto search = map_.find({index_cell, instance});
if (search != map_.end()) {
match.bins_.push_back(search->second);
match.weights_.push_back(1.0);
}
}
}

View file

@ -1,19 +1,20 @@
<?xml version='1.0' encoding='utf-8'?>
<geometry>
<cell id="1" material="1" region="-1" universe="1" />
<cell id="1" material="1" region="-1" universe="2" />
<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">
<cell fill="1" id="3" universe="2" />
<cell id="4" material="1" region="-2" universe="3" />
<cell id="5" material="2" region="2" universe="3" />
<cell fill="4" id="6" region="3 -4 5 -6" universe="5" />
<lattice id="4">
<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>
2 3 3 3
3 2 3 3
3 3 2 3
3 3 3 2 </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" />
@ -48,10 +49,10 @@
<?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>
<bins>4 0 4 1 4 2 4 3 4 4 4 5 4 6 4 7 4 8 4 9 4 10 4 11 2 0 2 1 2 2 2 3 3 0 3 1 3 2 3 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>
<bins>3 3 3 2 3 1 3 0 2 3 2 2 2 1 2 0 4 11 4 10 4 9 4 8 4 7 4 6 4 5 4 4 4 3 4 2 4 1 4 0</bins>
</filter>
<tally id="1">
<filters>1</filters>

View file

@ -33,6 +33,14 @@ tally 1:
2.015188E+02
9.951936E+00
2.345815E+01
1.061333E+01
2.508486E+01
2.767786E+01
1.781264E+02
2.898957E+01
2.015188E+02
9.951936E+00
2.345815E+01
tally 2:
9.951936E+00
2.345815E+01
@ -42,6 +50,14 @@ tally 2:
1.781264E+02
1.061333E+01
2.508486E+01
9.951936E+00
2.345815E+01
2.898957E+01
2.015188E+02
2.767786E+01
1.781264E+02
1.061333E+01
2.508486E+01
6.794395E-02
1.135006E-03
1.299309E-01

View file

@ -1,3 +1,4 @@
from numpy.testing import assert_array_almost_equal
import openmc
import openmc.model
import pytest
@ -5,6 +6,24 @@ import pytest
from tests.testing_harness import PyAPITestHarness
class CellInstanceFilterTest(PyAPITestHarness):
def _compare_results(self):
with openmc.StatePoint(self.statepoint_name) as sp:
# we expect the tally results for the instances of
# cells 2 and 3 to be the same as 2 is nested
# in a universe directly under 3
t1 = sp.tallies[1]
f1 = sp.filters[1]
c2_bins = [tuple(tuple(i) for i in f1.bins if i[0] == 2)]
c2_mean = t1.get_values(filters=[openmc.CellInstanceFilter], filter_bins=c2_bins)
c3_bins = [tuple(tuple(i) for i in f1.bins if i[0] == 3)]
c3_mean = t1.get_values(filters=[openmc.CellInstanceFilter], filter_bins=c3_bins)
assert_array_almost_equal(c2_mean, c3_mean)
return super()._compare_results()
@pytest.fixture
def model():
model = openmc.model.Model()
@ -22,21 +41,24 @@ def model():
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])
# intermediate universe containing only cell 2
u1 = openmc.Universe(cells=[c2])
c3 = openmc.Cell(fill=u1)
u2 = openmc.Universe(cells=[c1, c3])
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])
c4 = openmc.Cell(fill=m1, region=-cyl2)
c5 = openmc.Cell(fill=m2, region=+cyl2)
u3 = openmc.Universe(cells=[c4, c5])
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]
[u2, u3, u3, u3],
[u3, u2, u3, u3],
[u3, u3, u2, u3],
[u3, u3, u3, u2]
]
box = openmc.model.rectangular_prism(8.0, 8.0, boundary_type='reflective')
main_cell = openmc.Cell(fill=lat, region=box)
@ -49,8 +71,9 @@ def model():
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)])
instances = ([(c4, i) for i in range(c4.num_instances)] +
[(c2, i) for i in range(c2.num_instances)] +
[(c3, i) for i in range(c3.num_instances)])
f1 = openmc.CellInstanceFilter(instances)
f2 = openmc.CellInstanceFilter(instances[::-1])
t1 = openmc.Tally()
@ -65,5 +88,5 @@ def model():
def test_cell_instance(model):
harness = PyAPITestHarness('statepoint.5.h5', model)
harness = CellInstanceFilterTest('statepoint.5.h5', model)
harness.main()

View file

@ -115,6 +115,10 @@ class TestHarness:
return outstr
@property
def statepoint_name(self):
return self._sp_name
def _write_results(self, results_string):
"""Write the results to an ASCII file."""
with open('results_test.dat', 'w') as fh: