This commit is contained in:
GuySten 2026-07-16 17:07:16 -06:00 committed by GitHub
commit 4feb966682
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 182 additions and 7 deletions

View file

@ -252,6 +252,20 @@ public:
//! \return Density in [g/cm3]
double density(int32_t instance = -1) const;
//! Determine forced collision flag corresponding
//! to a specific cell instance, taking into account
//! presence of distribcell
//! \param[in] instance of the cell
//! \return is forced collision enabled
bool forced_collision(int32_t instance) const
{
if (forced_collision_.size() > 1) {
return forced_collision_[instance];
} else {
return forced_collision_[0];
}
}
//! Set the temperature of a cell instance
//! \param[in] T Temperature in [K]
//! \param[in] instance Instance index. If -1 is given, the temperature for
@ -369,6 +383,11 @@ public:
//! \brief Index corresponding to this cell in distribcell arrays
int distribcell_index_ {C_NONE};
//! \brief Enable forced collision within this cell.
//!
//! May be multiple for distribcell.
vector<bool> forced_collision_ {false};
//! \brief Material(s) within this cell.
//!
//! May be multiple materials for distribcell.

View file

@ -25,6 +25,7 @@ namespace simulation {
extern int ct_current_file; //!< current collision track file index
extern "C" int current_batch; //!< current batch
extern "C" int current_gen; //!< current fission generation
extern bool forced_collision; //!< is forced collision used in the problem
extern "C" bool initialized; //!< has simulation been initialized?
extern "C" double keff; //!< average k over batches
extern "C" double keff_std; //!< standard deviation of average k

View file

@ -40,6 +40,26 @@ vector<T> get_node_array(
return values;
}
template<>
inline vector<bool> get_node_array<bool>(
pugi::xml_node node, const char* name, bool lowercase)
{
// Get value of node attribute/child
std::string s {get_node_value(node, name, lowercase)};
// Read values one by one into vector
std::stringstream iss {s};
std::string value;
char first;
vector<bool> values;
while (iss >> value) {
first = value[0];
values.push_back((first == '1' || first == 't' || first == 'T' ||
first == 'y' || first == 'Y'));
}
return values;
}
template<typename T>
tensor::Tensor<T> get_node_tensor(
pugi::xml_node node, const char* name, bool lowercase = false)

View file

@ -76,6 +76,10 @@ class Cell(IDManagerMixin):
Density of the cell in [g/cm3]. Multiple densities can be given to give
each distributed cell instance a unique density. Densities set here will
override the density set on materials used to fill the cell.
forced_collision : bool or iterable of bool
Forced collision flag in the cell. Multiple flags can be given to give
each distributed cell instance a unique forced collision flag.
Defaults to False.
translation : Iterable of float
If the cell is filled with a universe, this array specifies a vector
that is used to translate (shift) the universe.
@ -114,6 +118,7 @@ class Cell(IDManagerMixin):
self._rotation_matrix = None
self._temperature = None
self._density = None
self._forced_collision = False
self._translation = None
self._paths = None
self._num_instances = None
@ -287,6 +292,17 @@ class Cell(IDManagerMixin):
else:
self._density = density
@property
def forced_collision(self):
return self._forced_collision
@forced_collision.setter
def forced_collision(self, forced_collision):
cv.check_type('cell forced collision flag', forced_collision, (Iterable, bool))
if isinstance(forced_collision, Iterable):
cv.check_type('cell forced collision flag', forced_collision, Iterable, bool)
self._forced_collision = forced_collision
@property
def translation(self):
return self._translation
@ -692,6 +708,15 @@ class Cell(IDManagerMixin):
else:
element.set("density", str(self.density))
if self.forced_collision:
if isinstance(self.density, Iterable):
if any(self.forced_collision):
forced_collision_subelement = ET.SubElement(element, "forced_collision")
forced_collision_subelement.text = ' '.join(str(f)
for f in self.forced_collision)
else:
element.set("forced_collision", str(self.forced_collision))
if self.translation is not None:
element.set("translation", ' '.join(map(str, self.translation)))
@ -747,6 +772,10 @@ class Cell(IDManagerMixin):
c.region = Region.from_expression(region, surfaces)
# Check for other attributes
forced_collision = get_elem_list(elem, 'forced_collision', bool)
if forced_collision:
c.forced_collision = (forced_collision if len(forced_collision) > 1
else forced_collision[0])
v = get_text(elem, 'volume')
if v is not None:
c.volume = float(v)

View file

@ -22,6 +22,7 @@
#include "openmc/material.h"
#include "openmc/nuclide.h"
#include "openmc/settings.h"
#include "openmc/simulation.h"
#include "openmc/xml_interface.h"
namespace openmc {
@ -498,6 +499,15 @@ CSGCell::CSGCell(pugi::xml_node cell_node)
}
}
if (check_for_node(cell_node, "forced_collision")) {
forced_collision_ = get_node_array<bool>(cell_node, "forced_collision");
forced_collision_.shrink_to_fit();
for (auto flag : forced_collision_) {
if (flag)
simulation::forced_collision = true;
}
}
// Read the region specification.
std::string region_spec;
if (check_for_node(cell_node, "region")) {
@ -1140,6 +1150,8 @@ vector<int32_t> Region::surfaces() const
void read_cells(pugi::xml_node node)
{
simulation::forced_collision = false;
// Count the number of cells.
int n_cells = 0;
for (pugi::xml_node cell_node : node.children("cell")) {

View file

@ -23,6 +23,7 @@
#include "openmc/photon.h"
#include "openmc/physics.h"
#include "openmc/physics_mg.h"
#include "openmc/random_dist.h"
#include "openmc/random_lcg.h"
#include "openmc/settings.h"
#include "openmc/simulation.h"
@ -270,6 +271,13 @@ void Particle::event_advance()
// Find the distance to the nearest boundary
boundary() = distance_to_boundary(*this);
bool forced_collision = false;
double speed = this->speed();
double time_cutoff = settings::time_cutoff[type().transport_index()];
double distance_cutoff =
(time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY;
// Sample a distance to collision
if (type() == ParticleType::electron() ||
type() == ParticleType::positron()) {
@ -277,21 +285,74 @@ void Particle::event_advance()
} else if (macro_xs().total == 0.0) {
collision_distance() = INFINITY;
} else {
collision_distance() = -std::log(prn(current_seed())) / macro_xs().total;
if (simulation::forced_collision &&
boundary().distance() <= distance_cutoff &&
boundary().distance() > TINY_BIT) {
auto c_id = coord(n_coord() - 1).cell();
auto& c = model::cells[c_id];
if (c->forced_collision(cell_instance())) {
if (cell_last(n_coord_last() - 1) != c_id)
forced_collision = true;
}
}
double U;
if (forced_collision) {
U = uniform_distribution(
std::exp(-boundary().distance() * macro_xs().total), 1, current_seed());
} else {
U = prn(current_seed());
}
collision_distance() = -std::log(U) / macro_xs().total;
}
double speed = this->speed();
double time_cutoff = settings::time_cutoff[type().transport_index()];
double distance_cutoff =
(time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY;
double distance;
double dt;
if (forced_collision) {
distance = boundary().distance() - TINY_BIT;
double uncollided_wgt = wgt() * std::exp(-distance * macro_xs().total);
double collided_wgt = wgt() * -expm1(-distance * macro_xs().total);
wgt() = uncollided_wgt;
dt = distance / speed;
this->move_distance(distance);
this->time() += dt;
this->lifetime() += dt;
// Score timed track-length tallies
if (!model::active_timed_tracklength_tallies.empty()) {
score_timed_tracklength_tally(*this, distance);
}
// Score track-length tallies
if (!model::active_tracklength_tallies.empty()) {
score_tracklength_tally(*this, distance);
}
// Score track-length estimate of k-eff
if (settings::run_mode == RunMode::EIGENVALUE && type().is_neutron()) {
keff_tally_tracklength() += wgt() * distance * macro_xs().nu_fission;
}
// Score flux derivative accumulators for differential tallies.
if (!model::active_tallies.empty()) {
score_track_derivative(*this, distance);
}
split(uncollided_wgt);
this->move_distance(-distance);
this->time() -= dt;
this->lifetime() -= dt;
wgt() = collided_wgt;
}
// Select smaller of the three distances
double distance =
distance =
std::min({boundary().distance(), collision_distance(), distance_cutoff});
// Advance particle in space and time
this->move_distance(distance);
double dt = distance / speed;
dt = distance / speed;
this->time() += dt;
this->lifetime() += dt;

View file

@ -328,6 +328,7 @@ namespace simulation {
int ct_current_file;
int current_batch;
int current_gen;
bool forced_collision {false};
bool initialized {false};
double keff {1.0};
double keff_std;

View file

@ -0,0 +1,32 @@
import openmc
def test_forced_collision(run_in_tmpdir):
m = openmc.Material()
m.add_element('Li', 1.0)
m.set_density('g/cm3', 1.0)
surf1 = openmc.Sphere(r=99.9)
surf2 = openmc.Sphere(r=100, boundary_type='vacuum')
cell1 = openmc.Cell(region=-surf1)
cell2 = openmc.Cell(fill=m, region=-surf2 & +surf1)
cell2.forced_collision = True
model = openmc.Model()
model.geometry = openmc.Geometry([cell1, cell2])
model.settings.run_mode = 'fixed source'
model.settings.source = openmc.IndependentSource(
space=openmc.stats.Point(),
energy=openmc.stats.Discrete([5.0e6], [1.0]),
particle='photon',
)
model.settings.particles = 10
model.settings.batches = 1
tally = openmc.Tally()
tally.filters = [openmc.CellFilter([cell2])]
tally.scores = ['heating']
model.tallies = openmc.Tallies([tally])
model.run(apply_tally_results=True)
assert (tally.mean > 0.0).all(), "No heating detected"