Hexagonal lattice iterators (#2921)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Patrick Shriwise 2024-06-17 16:14:12 -05:00 committed by GitHub
parent 89d4dafa5a
commit ce4176deb5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 192 additions and 33 deletions

View file

@ -56,13 +56,14 @@ public:
virtual ~Lattice() {}
virtual int32_t const& operator[](array<int, 3> const& i_xyz) = 0;
virtual const int32_t& operator[](const array<int, 3>& i_xyz) = 0;
virtual LatticeIter begin();
LatticeIter end();
virtual LatticeIter end();
virtual int32_t& back();
virtual ReverseLatticeIter rbegin();
ReverseLatticeIter rend();
virtual ReverseLatticeIter rend();
//! Convert internal universe values from IDs to indices using universe_map.
void adjust_indices();
@ -81,7 +82,7 @@ public:
//! \param i_xyz[3] The indices for a lattice tile.
//! \return true if the given indices fit within the lattice bounds. False
//! otherwise.
virtual bool are_valid_indices(array<int, 3> const& i_xyz) const = 0;
virtual bool are_valid_indices(const array<int, 3>& i_xyz) const = 0;
//! \brief Find the next lattice surface crossing
//! \param r A 3D Cartesian coordinate.
@ -125,7 +126,7 @@ public:
//! \param i_xyz[3] The indices for a lattice tile.
//! \return Distribcell offset i.e. the largest instance number for the target
//! cell found in the geometry tree under this lattice tile.
virtual int32_t& offset(int map, array<int, 3> const& i_xyz) = 0;
virtual int32_t& offset(int map, const array<int, 3>& i_xyz) = 0;
//! \brief Get the distribcell offset for a lattice tile.
//! \param The map index for the target cell.
@ -167,12 +168,12 @@ public:
LatticeIter& operator++()
{
while (indx_ < lat_.universes_.size()) {
while (indx_ < lat_.end().indx_) {
++indx_;
if (lat_.is_valid_index(indx_))
return *this;
}
indx_ = lat_.universes_.size();
indx_ = lat_.end().indx_;
return *this;
}
@ -190,7 +191,7 @@ public:
ReverseLatticeIter& operator++()
{
while (indx_ > -1) {
while (indx_ > lat_.begin().indx_ - 1) {
--indx_;
if (lat_.is_valid_index(indx_))
return *this;
@ -206,9 +207,9 @@ class RectLattice : public Lattice {
public:
explicit RectLattice(pugi::xml_node lat_node);
int32_t const& operator[](array<int, 3> const& i_xyz) override;
const int32_t& operator[](const array<int, 3>& i_xyz) override;
bool are_valid_indices(array<int, 3> const& i_xyz) const override;
bool are_valid_indices(const array<int, 3>& i_xyz) const override;
std::pair<double, array<int, 3>> distance(
Position r, Direction u, const array<int, 3>& i_xyz) const override;
@ -221,7 +222,7 @@ public:
Position get_local_position(
Position r, const array<int, 3>& i_xyz) const override;
int32_t& offset(int map, array<int, 3> const& i_xyz) override;
int32_t& offset(int map, const array<int, 3>& i_xyz) override;
int32_t offset(int map, int indx) const override;
@ -241,13 +242,19 @@ class HexLattice : public Lattice {
public:
explicit HexLattice(pugi::xml_node lat_node);
int32_t const& operator[](array<int, 3> const& i_xyz) override;
const int32_t& operator[](const array<int, 3>& i_xyz) override;
LatticeIter begin() override;
ReverseLatticeIter rbegin() override;
bool are_valid_indices(array<int, 3> const& i_xyz) const override;
LatticeIter end() override;
int32_t& back() override;
ReverseLatticeIter rend() override;
bool are_valid_indices(const array<int, 3>& i_xyz) const override;
std::pair<double, array<int, 3>> distance(
Position r, Direction u, const array<int, 3>& i_xyz) const override;
@ -262,7 +269,7 @@ public:
bool is_valid_index(int indx) const override;
int32_t& offset(int map, array<int, 3> const& i_xyz) override;
int32_t& offset(int map, const array<int, 3>& i_xyz) override;
int32_t offset(int map, int indx) const override;

View file

@ -530,7 +530,8 @@ std::string distribcell_path_inner(int32_t target_cell, int32_t map,
if (c.type_ != Fill::MATERIAL) {
int32_t temp_offset;
if (c.type_ == Fill::UNIVERSE) {
temp_offset = offset + c.offset_[map];
temp_offset =
offset + c.offset_[map]; // TODO: should also apply to lattice fills?
} else {
Lattice& lat = *model::lattices[c.fill_];
int32_t indx = lat.universes_.size() * map + lat.begin().indx_;

View file

@ -58,6 +58,11 @@ LatticeIter Lattice::end()
return LatticeIter(*this, universes_.size());
}
int32_t& Lattice::back()
{
return universes_.back();
}
ReverseLatticeIter Lattice::rbegin()
{
return ReverseLatticeIter(*this, universes_.size() - 1);
@ -106,9 +111,10 @@ int32_t Lattice::fill_offset_table(int32_t offset, int32_t target_univ_id,
// offsets_ array doesn't actually include the offset accounting for the last
// universe, so we get the before-last offset for the given map and then
// explicitly add the count for the last universe.
if (offsets_[map * universes_.size()] != C_NONE) {
int last_offset = offsets_[(map + 1) * universes_.size() - 1];
int last_univ = universes_.back();
if (offsets_[map * universes_.size() + this->begin().indx_] != C_NONE) {
int last_offset =
offsets_[(map + 1) * universes_.size() - this->begin().indx_ - 1];
int last_univ = this->back();
return last_offset +
count_universe_instances(last_univ, target_univ_id, univ_count_memo);
}
@ -117,6 +123,7 @@ int32_t Lattice::fill_offset_table(int32_t offset, int32_t target_univ_id,
offsets_[map * universes_.size() + it.indx_] = offset;
offset += count_universe_instances(*it, target_univ_id, univ_count_memo);
}
return offset;
}
@ -225,14 +232,14 @@ RectLattice::RectLattice(pugi::xml_node lat_node) : Lattice {lat_node}
//==============================================================================
int32_t const& RectLattice::operator[](array<int, 3> const& i_xyz)
const int32_t& RectLattice::operator[](const array<int, 3>& i_xyz)
{
return universes_[get_flat_index(i_xyz)];
}
//==============================================================================
bool RectLattice::are_valid_indices(array<int, 3> const& i_xyz) const
bool RectLattice::are_valid_indices(const array<int, 3>& i_xyz) const
{
return ((i_xyz[0] >= 0) && (i_xyz[0] < n_cells_[0]) && (i_xyz[1] >= 0) &&
(i_xyz[1] < n_cells_[1]) && (i_xyz[2] >= 0) &&
@ -354,7 +361,7 @@ Position RectLattice::get_local_position(
//==============================================================================
int32_t& RectLattice::offset(int map, array<int, 3> const& i_xyz)
int32_t& RectLattice::offset(int map, const array<int, 3>& i_xyz)
{
return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map +
n_cells_[0] * n_cells_[1] * i_xyz[2] +
@ -676,13 +683,19 @@ void HexLattice::fill_lattice_y(const vector<std::string>& univ_words)
//==============================================================================
int32_t const& HexLattice::operator[](array<int, 3> const& i_xyz)
const int32_t& HexLattice::operator[](const array<int, 3>& i_xyz)
{
return universes_[get_flat_index(i_xyz)];
}
//==============================================================================
// The HexLattice iterators need their own versions b/c the universes array is
// "square", meaning that it is allocated with entries that are intentionally
// left empty. As such, the iterator indices need to skip the empty entries to
// get cell instances and geometry paths correct. See the image in the Theory
// and Methodology section on "Hexagonal Lattice Indexing" for a visual of where
// the empty positions are.
LatticeIter HexLattice::begin()
{
return LatticeIter(*this, n_rings_ - 1);
@ -693,9 +706,24 @@ ReverseLatticeIter HexLattice::rbegin()
return ReverseLatticeIter(*this, universes_.size() - n_rings_);
}
int32_t& HexLattice::back()
{
return universes_[universes_.size() - n_rings_];
}
LatticeIter HexLattice::end()
{
return LatticeIter(*this, universes_.size() - n_rings_ + 1);
}
ReverseLatticeIter HexLattice::rend()
{
return ReverseLatticeIter(*this, n_rings_ - 2);
}
//==============================================================================
bool HexLattice::are_valid_indices(array<int, 3> const& i_xyz) const
bool HexLattice::are_valid_indices(const array<int, 3>& i_xyz) const
{
// Check if (x, alpha, z) indices are valid, accounting for number of rings
return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) &&
@ -992,7 +1020,7 @@ bool HexLattice::is_valid_index(int indx) const
//==============================================================================
int32_t& HexLattice::offset(int map, array<int, 3> const& i_xyz)
int32_t& HexLattice::offset(int map, const array<int, 3>& i_xyz)
{
int nx {2 * n_rings_ - 1};
int ny {2 * n_rings_ - 1};

View file

@ -0,0 +1,119 @@
from math import sqrt
import pytest
import numpy as np
import openmc
import openmc.lib
from tests import cdtemp
@pytest.fixture(scope='module', autouse=True)
def double_hex_lattice_model():
openmc.reset_auto_ids()
radius = 0.9
pin_lattice_pitch = 2.0
# make the hex prism a little larger to make sure test
# locations are definitively in the model
hex_prism_edge = 1.2 * pin_lattice_pitch
model = openmc.Model()
# materials
nat_u = openmc.Material()
nat_u.set_density('g/cm3', 12.0)
nat_u.add_element('U', 1.0)
graphite = openmc.Material()
graphite.set_density('g/cm3', 1.1995)
graphite.add_element('C', 1.0)
# zplanes to define lower and upper region
z_low = openmc.ZPlane(-10, boundary_type='vacuum')
z_mid = openmc.ZPlane(0)
z_high = openmc.ZPlane(10, boundary_type='vacuum')
hex_prism = openmc.model.HexagonalPrism(
edge_length=hex_prism_edge, boundary_type='reflective')
# geometry
cyl = openmc.ZCylinder(r=radius)
univ = openmc.model.pin([cyl], [nat_u, graphite])
# create a hexagonal lattice of compacts
hex_lattice = openmc.HexLattice()
hex_lattice.orientation = 'y'
hex_lattice.pitch = (pin_lattice_pitch,)
hex_lattice.center = (0., 0.)
center = [univ]
ring = [univ, univ, univ, univ, univ, univ]
hex_lattice.universes = [ring, center]
lower_hex_cell = openmc.Cell(fill=hex_lattice, region=-hex_prism & +z_low & -z_mid)
upper_hex_cell = openmc.Cell(fill=hex_lattice, region=-hex_prism & +z_mid & -z_high)
hex_cells = [lower_hex_cell, upper_hex_cell]
model.geometry = openmc.Geometry(hex_cells)
# moderator
cell = next(iter(univ.get_all_cells().values()))
tally = openmc.Tally(tally_id=1)
filter = openmc.DistribcellFilter(cell)
tally.filters = [filter]
tally.scores = ['flux']
model.tallies = [tally]
# settings
# source definition. fission source given bounding box of graphite active region
system_LL = (-pin_lattice_pitch*sqrt(3)/2, -pin_lattice_pitch, -5)
system_UR = (pin_lattice_pitch*sqrt(3)/2, pin_lattice_pitch, 5)
source_dist = openmc.stats.Box(system_LL, system_UR)
model.settings.source = openmc.IndependentSource(space=source_dist)
model.settings.particles = 100
model.settings.inactive = 2
model.settings.batches = 10
with cdtemp():
model.export_to_xml()
openmc.lib.init()
yield
openmc.lib.finalize()
# Lower cell instances
# 6
# 5 4
# 3
# 2 1
# 0
# Upper cell instances
# 13
# 12 11
# 10
# 9 8
# 7
hex_expected_results = [
((0.0, -2.0, -5.0), 0),
((1.732, -1.0, -5.0), 1),
((-1.732, -1.0, -5.0), 2),
((0.0, 0.0, -0.1), 3),
((1.732, 1.0, -5.0), 4),
((-1.732, 1.0, -5.0), 5),
((0.0, 2.0, -0.1), 6),
((0.0, -2.0, 5.0), 7),
((1.732, -1.0, 5.0), 8),
((-1.732, -1.0, 5.0), 9),
((0.0, 0.0, 5.0), 10),
((1.732, 1.0, 5.0), 11),
((-1.732, 1.0, 5.0), 12),
((0.0, 2.0, 5.0), 13),
]
@pytest.mark.parametrize("r,expected_cell_instance", hex_expected_results, ids=str)
def test_cell_instance_hex_multilattice(r, expected_cell_instance):
_, cell_instance = openmc.lib.find_cell(r)
assert cell_instance == expected_cell_instance
def test_cell_instance_multilattice_results():
openmc.lib.run()
tally_results = openmc.lib.tallies[1].mean
assert (tally_results != 0.0).all()

View file

@ -1,5 +1,5 @@
import numpy as np
import pytest
import numpy as np
import openmc
import openmc.lib
@ -8,7 +8,7 @@ from tests import cdtemp
@pytest.fixture(scope='module', autouse=True)
def double_lattice_model():
def double_rect_lattice_model():
openmc.reset_auto_ids()
model = openmc.Model()
@ -40,8 +40,9 @@ def double_lattice_model():
cell_with_lattice2.translation = (2., 0., 0.)
model.geometry = openmc.Geometry([cell_with_lattice1, cell_with_lattice2])
tally = openmc.Tally()
tally.filters = [openmc.DistribcellFilter(c)]
tally = openmc.Tally(tally_id=1)
dcell_filter = openmc.DistribcellFilter(c)
tally.filters = [dcell_filter]
tally.scores = ['flux']
model.tallies = [tally]
@ -50,7 +51,8 @@ def double_lattice_model():
bbox[0][2] = -0.5
bbox[1][2] = 0.5
space = openmc.stats.Box(*bbox)
model.settings.source = openmc.IndependentSource(space=space)
source = openmc.IndependentSource(space=space)
model.settings.source = source
# Add necessary settings and export
model.settings.batches = 10
@ -63,14 +65,13 @@ def double_lattice_model():
yield
openmc.lib.finalize()
# This shows the expected cell instance numbers for each lattice position:
# ┌─┬─┬─┬─┐
# │2│3│6│7│
# ├─┼─┼─┼─┤
# │0│1│4│5│
# └─┴─┴─┴─┘
expected_results = [
rect_expected_results = [
((0.5, 0.5, 0.0), 0),
((1.5, 0.5, 0.0), 1),
((0.5, 1.5, 0.0), 2),
@ -80,13 +81,16 @@ expected_results = [
((2.5, 1.5, 0.0), 6),
((3.5, 1.5, 0.0), 7),
]
@pytest.mark.parametrize("r,expected_cell_instance", expected_results)
def test_cell_instance_multilattice(r, expected_cell_instance):
@pytest.mark.parametrize("r,expected_cell_instance", rect_expected_results, ids=lambda p : f'{p}')
def test_cell_instance_rect_multilattice(r, expected_cell_instance):
_, cell_instance = openmc.lib.find_cell(r)
assert cell_instance == expected_cell_instance
def test_cell_instance_multilattice_results():
openmc.run()
openmc.lib.run()
tally_results = openmc.lib.tallies[1].mean
assert (tally_results != 0.0).all()