Add distributed cell densities (#3546)

Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Kevin Sawatzky 2025-09-18 23:11:06 -05:00 committed by GitHub
parent afd9d06074
commit 607f6babe5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 607 additions and 52 deletions

View file

@ -84,6 +84,17 @@ Functions
:return: Return status (negative if an error occurred)
:rtype: int
.. c:function:: int openmc_cell_get_density(int32_t index, const int32_t* instance, double* density)
Get the density of a cell
:param int32_t index: Index in the cells array
:param int32_t* instance: Which instance of the cell. If a null pointer is passed, the density
multiplier of the first instance is returned.
:param double* density: Density of the cell in [g/cm3]
:return: Return status (negative if an error occurred)
:rtype: int
.. c:function:: int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices)
Set the fill for a cell
@ -113,8 +124,22 @@ Functions
:param double T: Temperature in Kelvin
:param instance: Which instance of the cell. To set the temperature for all
instances, pass a null pointer.
:param set_contained: If the cell is not filled by a material, whether to set the temperatures
of all filled cells
:param bool set_contained: If the cell is not filled by a material, whether
to set the temperatures of all filled cells
:type instance: const int32_t*
:return: Return status (negative if an error occurred)
:rtype: int
.. c:function:: int openmc_cell_set_density(index index, double density, const int32_t* instance, bool set_contained)
Set the density of a cell.
:param int32_t index: Index in the cells array
:param double density: Density of the cell in [g/cm3]
:param instance: Which instance of the cell. To set the density multiplier for all
instances, pass a null pointer.
:param bool set_contained: If the cell is not filled by a material, whether
to set the density multiplier of all filled cells
:type instance: const int32_t*
:return: Return status (negative if an error occurred)
:rtype: int

View file

@ -4,7 +4,7 @@
Properties File Format
======================
The current version of the properties file format is 1.0.
The current version of the properties file format is 1.1.
**/**
@ -25,6 +25,7 @@ The current version of the properties file format is 1.0.
**/geometry/cells/cell <uid>/**
:Datasets: - **temperature** (*double[]*) -- Temperature of the cell in [K].
- **density** (*double[]*) -- Density of the cell in [g/cm3].
**/materials/**

View file

@ -4,7 +4,7 @@
Summary File Format
===================
The current version of the summary file format is 6.0.
The current version of the summary file format is 6.1.
**/**
@ -38,6 +38,7 @@ The current version of the summary file format is 6.0.
is an array if the cell uses distributed materials, otherwise it is
a scalar.
- **temperature** (*double[]*) -- Temperature of the cell in Kelvin.
- **density** (*double[]*) -- Density of the cell in [g/cm3].
- **translation** (*double[3]*) -- Translation applied to the fill
universe. This dataset is present only if fill_type is set to
'universe'.

View file

@ -17,6 +17,8 @@ int openmc_cell_get_fill(
int openmc_cell_get_id(int32_t index, int32_t* id);
int openmc_cell_get_temperature(
int32_t index, const int32_t* instance, double* T);
int openmc_cell_get_density(
int32_t index, const int32_t* instance, double* rho);
int openmc_cell_get_translation(int32_t index, double xyz[]);
int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n);
int openmc_cell_get_name(int32_t index, const char** name);
@ -27,6 +29,8 @@ int openmc_cell_set_fill(
int openmc_cell_set_id(int32_t index, int32_t id);
int openmc_cell_set_temperature(
int32_t index, double T, const int32_t* instance, bool set_contained = false);
int openmc_cell_set_density(int32_t index, double rho, const int32_t* instance,
bool set_contained = false);
int openmc_cell_set_translation(int32_t index, const double xyz[]);
int openmc_cell_set_rotation(int32_t index, const double rot[], size_t rot_len);
int openmc_dagmc_universe_get_cell_ids(

View file

@ -216,6 +216,18 @@ public:
//! \return Temperature in [K]
double temperature(int32_t instance = -1) const;
//! Get the density multiplier of a cell instance
//! \param[in] instance Instance index. If -1 is given, the density multiplier
//! for the first instance is returned.
//! \return Density multiplier
double density_mult(int32_t instance = -1) const;
//! Get the density of a cell instance in g/cm3
//! \param[in] instance Instance index. If -1 is given, the density
//! for the first instance is returned.
//! \return Density in [g/cm3]
double density(int32_t instance = -1) const;
//! 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
@ -226,6 +238,16 @@ public:
void set_temperature(
double T, int32_t instance = -1, bool set_contained = false);
//! Set the density of a cell instance
//! \param[in] density Density [g/cm3]
//! \param[in] instance Instance index. If -1 is given, the density
//! for all instances is set.
//! \param[in] set_contained If this cell is not filled with a material,
//! collect all contained cells with material fills and set their
//! densities.
void set_density(
double density, int32_t instance = -1, bool set_contained = false);
int32_t n_instances() const;
//! Set the rotation matrix of a cell instance
@ -334,6 +356,9 @@ public:
//! T. The units are sqrt(eV).
vector<double> sqrtkT_;
//! \brief Unitless density multiplier(s) within this cell.
vector<double> density_mult_;
//! \brief Neighboring cells in the same universe.
NeighborList neighbors_;

View file

@ -28,11 +28,11 @@ constexpr int HDF5_VERSION[] {3, 0};
constexpr array<int, 2> VERSION_STATEPOINT {18, 1};
constexpr array<int, 2> VERSION_PARTICLE_RESTART {2, 0};
constexpr array<int, 2> VERSION_TRACK {3, 0};
constexpr array<int, 2> VERSION_SUMMARY {6, 0};
constexpr array<int, 2> VERSION_SUMMARY {6, 1};
constexpr array<int, 2> VERSION_VOLUME {1, 0};
constexpr array<int, 2> VERSION_VOXEL {2, 0};
constexpr array<int, 2> VERSION_MGXS_LIBRARY {1, 0};
constexpr array<int, 2> VERSION_PROPERTIES {1, 0};
constexpr array<int, 2> VERSION_PROPERTIES {1, 1};
constexpr array<int, 2> VERSION_WEIGHT_WINDOWS {1, 0};
// ============================================================================

View file

@ -37,6 +37,12 @@ void adjust_indices();
void assign_temperatures();
//==============================================================================
//! Finalize densities (compute density multipliers).
//==============================================================================
void finalize_cell_densities();
//==============================================================================
//! \brief Obtain a list of temperatures that each nuclide/thermal scattering
//! table appears at in the model. Later, this list is used to determine the

View file

@ -99,6 +99,13 @@ public:
//----------------------------------------------------------------------------
// Accessors
//! Get the atom density in [atom/b-cm]
//! \return Density in [atom/b-cm]
double atom_density(int32_t i, double rho_multiplier = 1.0) const
{
return atom_density_(i) * rho_multiplier;
}
//! Get density in [atom/b-cm]
//! \return Density in [atom/b-cm]
double density() const { return density_; }

View file

@ -389,6 +389,11 @@ public:
const double& sqrtkT() const { return sqrtkT_; }
double& sqrtkT_last() { return sqrtkT_last_; }
// density multiplier of the current and last cell
double& density_mult() { return density_mult_; }
const double& density_mult() const { return density_mult_; }
double& density_mult_last() { return density_mult_last_; }
private:
int64_t id_ {-1}; //!< Unique ID
@ -417,6 +422,9 @@ private:
double sqrtkT_ {-1.0}; //!< sqrt(k_Boltzmann * temperature) in eV
double sqrtkT_last_ {0.0}; //!< last temperature
double density_mult_ {1.0}; //!< density multiplier
double density_mult_last_ {1.0}; //!< last density multiplier
#ifdef OPENMC_DAGMC_ENABLED
moab::DagMC::RayHistory history_;
Direction last_dir_;

View file

@ -72,6 +72,10 @@ class Cell(IDManagerMixin):
temperature : float or iterable of float
Temperature of the cell in Kelvin. Multiple temperatures can be given
to give each distributed cell instance a unique temperature.
density : float or iterable of float
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.
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.
@ -109,6 +113,7 @@ class Cell(IDManagerMixin):
self._rotation = None
self._rotation_matrix = None
self._temperature = None
self._density = None
self._translation = None
self._paths = None
self._num_instances = None
@ -141,6 +146,7 @@ class Cell(IDManagerMixin):
if self.fill_type == 'material':
string += '\t{0: <15}=\t{1}\n'.format('Temperature',
self.temperature)
string += '\t{0: <15}=\t{1}\n'.format('Density', self.density)
string += '{: <16}=\t{}\n'.format('\tTranslation', self.translation)
string += '{: <16}=\t{}\n'.format('\tVolume', self.volume)
@ -257,6 +263,30 @@ class Cell(IDManagerMixin):
else:
self._temperature = temperature
@property
def density(self):
return self._density
@density.setter
def density(self, density):
# Make sure densities are greater than zero
cv.check_type('cell density', density, (Iterable, Real), none_ok=True)
if isinstance(density, Iterable):
cv.check_type('cell density', density, Iterable, Real)
for rho in density:
cv.check_greater_than('cell density', rho, 0.0, True)
elif isinstance(density, Real):
cv.check_greater_than('cell density', density, 0.0, True)
# If this cell is filled with a universe or lattice, propagate
# densities to all cells contained. Otherwise, simply assign it.
if self.fill_type in ('universe', 'lattice'):
for c in self.get_all_cells().values():
if c.fill_type == 'material':
c._density = density
else:
self._density = density
@property
def translation(self):
return self._translation
@ -525,6 +555,8 @@ class Cell(IDManagerMixin):
clone.volume = self.volume
if self.temperature is not None:
clone.temperature = self.temperature
if self.density is not None:
clone.density = self.density
if self.translation is not None:
clone.translation = self.translation
if self.rotation is not None:
@ -650,6 +682,12 @@ class Cell(IDManagerMixin):
else:
element.set("temperature", str(self.temperature))
if self.density is not None:
if isinstance(self.density, Iterable):
element.set("density", ' '.join(str(t) for t in self.density))
else:
element.set("density", str(self.density))
if self.translation is not None:
element.set("translation", ' '.join(map(str, self.translation)))
@ -711,10 +749,13 @@ class Cell(IDManagerMixin):
c.temperature = temperature
else:
c.temperature = temperature[0]
density = get_elem_list(elem, 'density', float)
if density is not None:
c.density = density if len(density) > 1 else density[0]
v = get_text(elem, 'volume')
if v is not None:
c.volume = float(v)
for key in ('temperature', 'rotation', 'translation'):
for key in ('temperature', 'density', 'rotation', 'translation'):
values = get_elem_list(elem, key, float)
if values is not None:
if key == 'rotation' and len(values) == 9:

View file

@ -34,6 +34,10 @@ _dll.openmc_cell_get_temperature.argtypes = [
c_int32, POINTER(c_int32), POINTER(c_double)]
_dll.openmc_cell_get_temperature.restype = c_int
_dll.openmc_cell_get_temperature.errcheck = _error_handler
_dll.openmc_cell_get_density.argtypes = [
c_int32, POINTER(c_int32), POINTER(c_double)]
_dll.openmc_cell_get_density.restype = c_int
_dll.openmc_cell_get_density.errcheck = _error_handler
_dll.openmc_cell_get_name.argtypes = [c_int32, POINTER(c_char_p)]
_dll.openmc_cell_get_name.restype = c_int
_dll.openmc_cell_get_name.errcheck = _error_handler
@ -58,6 +62,10 @@ _dll.openmc_cell_set_temperature.argtypes = [
c_int32, c_double, POINTER(c_int32), c_bool]
_dll.openmc_cell_set_temperature.restype = c_int
_dll.openmc_cell_set_temperature.errcheck = _error_handler
_dll.openmc_cell_set_density.argtypes = [
c_int32, c_double, POINTER(c_int32), c_bool]
_dll.openmc_cell_set_density.restype = c_int
_dll.openmc_cell_set_density.errcheck = _error_handler
_dll.openmc_cell_set_translation.argtypes = [c_int32, POINTER(c_double)]
_dll.openmc_cell_set_translation.restype = c_int
_dll.openmc_cell_set_translation.errcheck = _error_handler
@ -236,6 +244,44 @@ class Cell(_FortranObjectWithID):
_dll.openmc_cell_set_temperature(self._index, T, instance, set_contained)
def get_density(self, instance: int | None = None):
"""Get the density of a cell in [g/cm3]
Parameters
----------
instance : int or None
Which instance of the cell
"""
if instance is not None:
instance = c_int32(instance)
rho = c_double()
_dll.openmc_cell_get_density(self._index, instance, rho)
return rho.value
def set_density(self, rho: float, instance: int | None = None,
set_contained: bool = False):
"""Set the density of a cell
Parameters
----------
rho : float
Density of the cell in [g/cm3]
instance : int or None
Which instance of the cell
set_contained : bool
If cell is not filled by a material, whether to set the density
of all filled cells
"""
if instance is not None:
instance = c_int32(instance)
_dll.openmc_cell_set_density(self._index, rho, instance, set_contained)
@property
def translation(self):
translation = np.zeros(3)

View file

@ -633,7 +633,7 @@ class Model:
raise ValueError("Number of cells in properties file doesn't "
"match current model.")
# Update temperatures for cells filled with materials
# Update temperatures and densities for cells filled with materials
for name, group in cells_group.items():
cell_id = int(name.split()[1])
cell = cells[cell_id]
@ -648,6 +648,20 @@ class Model:
else:
lib_cell.set_temperature(temperature[0])
if group['density']:
density = group['density'][()]
if density.size > 1:
cell.density = [rho for rho in density]
else:
cell.density = density
if self.is_initialized:
lib_cell = openmc.lib.cells[cell_id]
if density.size > 1:
for i, rho in enumerate(density):
lib_cell.set_density(rho, i)
else:
lib_cell.set_density(density[0])
# Make sure number of materials matches
mats_group = fh['materials']
n_cells = mats_group.attrs['n_materials']

View file

@ -101,6 +101,25 @@ double Cell::temperature(int32_t instance) const
}
}
double Cell::density_mult(int32_t instance) const
{
if (instance >= 0) {
return density_mult_.size() == 1 ? density_mult_.at(0)
: density_mult_.at(instance);
} else {
return density_mult_[0];
}
}
double Cell::density(int32_t instance) const
{
const int32_t mat_index = material(instance);
if (mat_index == MATERIAL_VOID)
return 0.0;
return density_mult(instance) * model::materials[mat_index]->density_gpcc();
}
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
{
if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
@ -151,6 +170,47 @@ void Cell::set_temperature(double T, int32_t instance, bool set_contained)
}
}
void Cell::set_density(double density, int32_t instance, bool set_contained)
{
if (type_ != Fill::MATERIAL && !set_contained) {
fatal_error(
fmt::format("Attempted to set the density multiplier of cell {} "
"which is not filled by a material.",
id_));
}
if (type_ == Fill::MATERIAL) {
const int32_t mat_index = material(instance);
if (mat_index == MATERIAL_VOID)
return;
if (instance >= 0) {
// If density multiplier vector is not big enough, resize it first
if (density_mult_.size() != n_instances())
density_mult_.resize(n_instances(), density_mult_[0]);
// Set density multiplier for the corresponding instance
density_mult_.at(instance) =
density / model::materials[mat_index]->density_gpcc();
} else {
// Set density multiplier for all instances
for (auto& x : density_mult_) {
x = density / model::materials[mat_index]->density_gpcc();
}
}
} else {
auto contained_cells = this->get_contained_cells(instance);
for (const auto& entry : contained_cells) {
auto& cell = model::cells[entry.first];
assert(cell->type_ == Fill::MATERIAL);
auto& instances = entry.second;
for (auto instance : instances) {
cell->set_density(density, instance);
}
}
}
}
void Cell::export_properties_hdf5(hid_t group) const
{
// Create a group for this cell.
@ -162,6 +222,15 @@ void Cell::export_properties_hdf5(hid_t group) const
temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
write_dataset(cell_group, "temperature", temps);
// Write density for one or more cell instances
if (type_ == Fill::MATERIAL && material_.size() > 0) {
vector<double> density;
for (int32_t i = 0; i < density_mult_.size(); ++i)
density.push_back(this->density(i));
write_dataset(cell_group, "density", density);
}
close_group(cell_group);
}
@ -176,7 +245,7 @@ void Cell::import_properties_hdf5(hid_t group)
// Ensure number of temperatures makes sense
auto n_temps = temps.size();
if (n_temps > 1 && n_temps != n_instances()) {
throw std::runtime_error(fmt::format(
fatal_error(fmt::format(
"Number of temperatures for cell {} doesn't match number of instances",
id_));
}
@ -188,6 +257,25 @@ void Cell::import_properties_hdf5(hid_t group)
this->set_temperature(temps[i], i);
}
// Read densities
if (object_exists(cell_group, "density")) {
vector<double> density;
read_dataset(cell_group, "density", density);
// Ensure number of densities makes sense
auto n_density = density.size();
if (n_density > 1 && n_density != n_instances()) {
fatal_error(fmt::format("Number of densities for cell {} "
"doesn't match number of instances",
id_));
}
// Set densities.
for (int32_t i = 0; i < n_density; ++i) {
this->set_density(density[i], i);
}
}
close_group(cell_group);
}
@ -227,6 +315,8 @@ void Cell::to_hdf5(hid_t cell_group) const
temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
write_dataset(group, "temperature", temps);
write_dataset(group, "density_mult", density_mult_);
} else if (type_ == Fill::UNIVERSE) {
write_dataset(group, "fill_type", "universe");
write_dataset(group, "fill", model::universes[fill_]->id_);
@ -344,6 +434,44 @@ CSGCell::CSGCell(pugi::xml_node cell_node)
}
}
// Read the density element which can be distributed similar to temperature.
// These get assigned to the density multiplier, requiring a division by
// the material density.
// Note: calculating the actual density multiplier is deferred until materials
// are finalized. density_mult_ contains the true density in the meantime.
if (check_for_node(cell_node, "density")) {
density_mult_ = get_node_array<double>(cell_node, "density");
density_mult_.shrink_to_fit();
// Make sure this is a material-filled cell.
if (material_.size() == 0) {
fatal_error(fmt::format(
"Cell {} was specified with a density but no material. Density"
"specification is only valid for cells filled with a material.",
id_));
}
// Make sure this is a non-void material.
for (auto mat_id : material_) {
if (mat_id == MATERIAL_VOID) {
fatal_error(fmt::format(
"Cell {} was specified with a density, but contains a void "
"material. Density specification is only valid for cells "
"filled with a non-void material.",
id_));
}
}
// Make sure all densities are non-negative and greater than zero.
for (auto rho : density_mult_) {
if (rho <= 0) {
fatal_error(fmt::format(
"Cell {} was specified with a density less than or equal to zero",
id_));
}
}
}
// Read the region specification.
std::string region_spec;
if (check_for_node(cell_node, "region")) {
@ -1129,6 +1257,24 @@ extern "C" int openmc_cell_set_temperature(
return 0;
}
extern "C" int openmc_cell_set_density(
int32_t index, double density, const int32_t* instance, bool set_contained)
{
if (index < 0 || index >= model::cells.size()) {
strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
int32_t instance_index = instance ? *instance : -1;
try {
model::cells[index]->set_density(density, instance_index, set_contained);
} catch (const std::exception& e) {
set_errmsg(e.what());
return OPENMC_E_UNASSIGNED;
}
return 0;
}
extern "C" int openmc_cell_get_temperature(
int32_t index, const int32_t* instance, double* T)
{
@ -1147,6 +1293,36 @@ extern "C" int openmc_cell_get_temperature(
return 0;
}
extern "C" int openmc_cell_get_density(
int32_t index, const int32_t* instance, double* density)
{
if (index < 0 || index >= model::cells.size()) {
strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
return OPENMC_E_OUT_OF_BOUNDS;
}
int32_t instance_index = instance ? *instance : -1;
try {
if (model::cells[index]->type_ != Fill::MATERIAL) {
fatal_error(
fmt::format("Cell {}, instance {} is not filled with a material.",
model::cells[index]->id_, instance_index));
}
int32_t mat_index = model::cells[index]->material(instance_index);
if (mat_index == MATERIAL_VOID) {
*density = 0.0;
} else {
*density = model::cells[index]->density_mult(instance_index) *
model::materials[mat_index]->density_gpcc();
}
} catch (const std::exception& e) {
set_errmsg(e.what());
return OPENMC_E_UNASSIGNED;
}
return 0;
}
//! Get the bounding box of a cell
extern "C" int openmc_cell_bounding_box(
const int32_t index, double* llc, double* urc)

View file

@ -172,11 +172,13 @@ bool find_cell_inner(
p.cell_instance() = cell_instance_at_level(p, p.n_coord() - 1);
}
// Set the material and temperature.
// Set the material, temperature and density multiplier.
p.material_last() = p.material();
p.material() = c.material(p.cell_instance());
p.sqrtkT_last() = p.sqrtkT();
p.sqrtkT() = c.sqrtkT(p.cell_instance());
p.density_mult_last() = p.density_mult();
p.density_mult() = c.density_mult(p.cell_instance());
return true;

View file

@ -195,6 +195,24 @@ void assign_temperatures()
//==============================================================================
void finalize_cell_densities()
{
for (auto& c : model::cells) {
// Convert to density multipliers.
if (!c->density_mult_.empty()) {
for (int32_t instance = 0; instance < c->density_mult_.size();
++instance) {
c->density_mult_[instance] /=
model::materials[c->material(instance)]->density_gpcc();
}
} else {
c->density_mult_ = {1.0};
}
}
}
//==============================================================================
void get_temperatures(
vector<vector<double>>& nuc_temps, vector<vector<double>>& thermal_temps)
{
@ -362,6 +380,17 @@ void prepare_distribcell(const std::vector<int32_t>* user_distribcells)
c.id_, c.sqrtkT_.size(), c.n_instances()));
}
}
if (c.density_mult_.size() > 1) {
if (c.density_mult_.size() != c.n_instances()) {
fatal_error(fmt::format("Cell {} was specified with {} density "
"multipliers but has {} distributed "
"instances. The number of density multipliers "
"must equal one or the number "
"of instances.",
c.id_, c.density_mult_.size(), c.n_instances()));
}
}
}
// Search through universes for material cells and assign each one a

View file

@ -401,6 +401,10 @@ bool read_model_xml()
// Finalize cross sections having assigned temperatures
finalize_cross_sections();
// Compute cell density multipliers now that material densities
// have been finalized (from geometry_aux.h)
finalize_cell_densities();
if (check_for_node(root, "tallies"))
read_tallies_xml(root.child("tallies"));
@ -441,6 +445,11 @@ void read_separate_xml_files()
// Finalize cross sections having assigned temperatures
finalize_cross_sections();
// Compute cell density multipliers now that material densities
// have been finalized (from geometry_aux.h)
finalize_cell_densities();
read_tallies_xml();
// Initialize distribcell_filters

View file

@ -890,7 +890,7 @@ void Material::calculate_neutron_xs(Particle& p) const
// ADD TO MACROSCOPIC CROSS SECTION
// Copy atom density of nuclide in material
double atom_density = atom_density_(i);
double atom_density = this->atom_density(i, p.density_mult());
// Add contributions to cross sections
p.macro_xs().total += atom_density * micro.total;
@ -925,7 +925,7 @@ void Material::calculate_photon_xs(Particle& p) const
// ADD TO MACROSCOPIC CROSS SECTION
// Copy atom density of nuclide in material
double atom_density = atom_density_(i);
double atom_density = this->atom_density(i, p.density_mult());
// Add contributions to material macroscopic cross sections
p.macro_xs().total += atom_density * micro.total;

View file

@ -617,10 +617,12 @@ void Mgxs::calculate_xs(Particle& p)
}
int temperature = p.mg_xs_cache().t;
int angle = p.mg_xs_cache().a;
p.macro_xs().total = xs[temperature].total(angle, p.g());
p.macro_xs().absorption = xs[temperature].absorption(angle, p.g());
p.macro_xs().total = xs[temperature].total(angle, p.g()) * p.density_mult();
p.macro_xs().absorption =
xs[temperature].absorption(angle, p.g()) * p.density_mult();
p.macro_xs().nu_fission =
fissionable ? xs[temperature].nu_fission(angle, p.g()) : 0.;
fissionable ? xs[temperature].nu_fission(angle, p.g()) * p.density_mult()
: 0.;
}
//==============================================================================

View file

@ -200,7 +200,8 @@ void Particle::event_calculate_xs()
// Calculate microscopic and macroscopic cross sections
if (material() != MATERIAL_VOID) {
if (settings::run_CE) {
if (material() != material_last() || sqrtkT() != sqrtkT_last()) {
if (material() != material_last() || sqrtkT() != sqrtkT_last() ||
density_mult() != density_mult_last()) {
// If the material is the same as the last material and the
// temperature hasn't changed, we don't need to lookup cross
// sections again.
@ -564,9 +565,10 @@ void Particle::cross_surface(const Surface& surf)
int32_t i_cell = next_cell(surface_index(), cell_last(n_coord() - 1),
lowest_coord().universe()) -
1;
// save material and temp
// save material, temperature, and density multiplier
material_last() = material();
sqrtkT_last() = sqrtkT();
density_mult_last() = density_mult();
// set new cell value
lowest_coord().cell() = i_cell;
auto& cell = model::cells[i_cell];
@ -577,6 +579,7 @@ void Particle::cross_surface(const Surface& surf)
material() = cell->material(cell_instance());
sqrtkT() = cell->sqrtkT(cell_instance());
density_mult() = cell->density_mult(cell_instance());
return;
}
#endif

View file

@ -496,7 +496,7 @@ int sample_nuclide(Particle& p)
for (int i = 0; i < n; ++i) {
// Get atom density
int i_nuclide = mat->nuclide_[i];
double atom_density = mat->atom_density_[i];
double atom_density = mat->atom_density(i, p.density_mult());
// Increment probability to compare to cutoff
prob += atom_density * p.neutron_xs(i_nuclide).total;
@ -521,7 +521,7 @@ int sample_element(Particle& p)
for (int i = 0; i < mat->element_.size(); ++i) {
// Find atom density
int i_element = mat->element_[i];
double atom_density = mat->atom_density_[i];
double atom_density = mat->atom_density(i, p.density_mult());
// Determine microscopic cross section
double sigma = atom_density * p.photon_xs(i_element).total;

View file

@ -233,7 +233,7 @@ double score_fission_q(const Particle& p, int score_bin, const Tally& tally,
double score {0.0};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
const Nuclide& nuc {*data::nuclides[j_nuclide]};
score += get_nuc_fission_q(nuc, p, score_bin) * atom_density *
p.neutron_xs(j_nuclide).fission;
@ -696,7 +696,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
score += p.neutron_xs(j_nuclide).fission *
data::nuclides[j_nuclide]->nu(
E, ReactionProduct::EmissionMode::prompt) *
@ -743,7 +743,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
// Tally each delayed group bin individually
for (auto d_bin = 0; d_bin < filt.n_bins(); ++d_bin) {
auto d = filt.groups()[d_bin];
@ -763,7 +763,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
score += p.neutron_xs(j_nuclide).fission *
data::nuclides[j_nuclide]->nu(
E, ReactionProduct::EmissionMode::delayed) *
@ -824,7 +824,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
const auto& nuc {*data::nuclides[j_nuclide]};
if (nuc.fissionable_) {
const auto& rxn {*nuc.fission_rx_[0]};
@ -849,7 +849,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
const auto& nuc {*data::nuclides[j_nuclide]};
if (nuc.fissionable_) {
const auto& rxn {*nuc.fission_rx_[0]};
@ -893,7 +893,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
const auto& nuc {*data::nuclides[j_nuclide]};
if (nuc.fissionable_) {
const auto& rxn {*nuc.fission_rx_[0]};
@ -924,7 +924,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
if (p.neutron_xs(j_nuclide).elastic == CACHE_INVALID)
data::nuclides[j_nuclide]->calculate_elastic_xs(p);
score += p.neutron_xs(j_nuclide).elastic * atom_density * flux;
@ -1025,7 +1025,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
score += p.neutron_xs(j_nuclide).reaction[m] * atom_density * flux;
}
}
@ -1079,7 +1079,7 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index,
const Material& material {*model::materials[p.material()]};
for (auto i = 0; i < material.nuclide_.size(); ++i) {
auto j_nuclide = material.nuclide_[i];
auto atom_density = material.atom_density_(i);
auto atom_density = material.atom_density(i, p.density_mult());
score +=
get_nuclide_xs(p, j_nuclide, score_bin) * atom_density * flux;
}
@ -2383,7 +2383,8 @@ void score_analog_tally_mg(Particle& p)
model::materials[p.material()]->mat_nuclide_index_[i_nuclide];
if (j == C_NONE)
continue;
atom_density = model::materials[p.material()]->atom_density_(j);
atom_density =
model::materials[p.material()]->atom_density(j, p.density_mult());
}
score_general_mg(p, i_tally, i * tally.scores_.size(), filter_index,
@ -2449,8 +2450,9 @@ void score_tracklength_tally_general(
atom_density = 1.0;
}
} else {
atom_density =
tally.multiply_density() ? mat->atom_density_(j) : 1.0;
atom_density = tally.multiply_density()
? mat->atom_density(j, p.density_mult())
: 1.0;
}
}
}
@ -2579,8 +2581,9 @@ void score_collision_tally(Particle& p)
atom_density = 1.0;
}
} else {
atom_density =
tally.multiply_density() ? mat->atom_density_(j) : 1.0;
atom_density = tally.multiply_density()
? mat->atom_density(j, p.density_mult())
: 1.0;
}
}

View file

@ -15,14 +15,16 @@
using namespace openmc;
int main(int argc, char** argv) {
int main(int argc, char** argv)
{
#ifdef OPENMC_MPI
MPI_Comm world {MPI_COMM_WORLD};
int err = openmc_init(argc, argv, &world);
#else
int err = openmc_init(argc, argv, nullptr);
#endif
if (err) fatal_error(openmc_err_msg);
if (err)
fatal_error(openmc_err_msg);
// create a new cell filter
auto cell_filter = Filter::create<CellFilter>();
@ -30,7 +32,7 @@ int main(int argc, char** argv) {
// add all cells to the cell filter
std::vector<int32_t> cell_indices;
for (auto& entry : openmc::model::cell_map) {
cell_indices.push_back(entry.second);
cell_indices.push_back(entry.second);
}
// enable distribcells offsets for all cells
prepare_distribcell(&cell_indices);
@ -39,7 +41,6 @@ int main(int argc, char** argv) {
std::sort(cell_indices.begin(), cell_indices.end());
cell_filter->set_cells(cell_indices);
// create a new tally
auto tally = Tally::create();
std::vector<Filter*> filters = {cell_filter};
@ -60,14 +61,19 @@ int main(int argc, char** argv) {
}
}
// set a higher temperature for only one of the lattice cells (ID is 4 in the model)
// set a higher temperature for only one of the lattice cells (ID is 4 in the
// model)
model::cells[model::cell_map[4]]->set_temperature(400.0, 3, true);
// set the density of another lattice cell to 2
model::cells[model::cell_map[4]]->set_density(2.0, 2, true);
// the summary file will be used to check that
// temperatures were set correctly so clear
// error output can be provided
#ifdef OPENMC_MPI
if (openmc::mpi::master) openmc::write_summary();
if (openmc::mpi::master)
openmc::write_summary();
#else
openmc::write_summary();
#endif

View file

@ -1,13 +1,13 @@
k-combined:
1.933305E+00 1.300360E-02
1.953962E+00 1.828426E-02
tally 1:
9.552846E+01
1.019358E+03
2.887973E+01
9.308509E+01
9.732441E+01
1.059022E+03
2.217326E+02
5.486892E+03
2.217326E+02
5.486892E+03
9.607953E+01
1.031898E+03
2.853683E+01
9.085469E+01
9.745011E+01
1.058928E+03
2.220665E+02
5.496813E+03
2.220665E+02
5.496813E+03

View file

@ -100,6 +100,9 @@ int main(int argc, char* argv[])
}
}
// Finalize cell densities
openmc::finalize_cell_densities();
// Run OpenMC
openmc_err = openmc_run();
if (openmc_err)

View file

@ -0,0 +1,40 @@
<?xml version='1.0' encoding='utf-8'?>
<model>
<materials>
<material id="1" name="UO2" depletable="true">
<density value="10.0" units="g/cm3"/>
<nuclide name="U235" ao="1.0"/>
<nuclide name="O16" ao="2.0"/>
</material>
<material id="2" name="light water">
<density value="1.0" units="g/cm3"/>
<nuclide name="H1" ao="2.0"/>
<nuclide name="O16" ao="1.0"/>
<sab name="c_H_in_H2O"/>
</material>
</materials>
<geometry>
<cell id="1" material="1" region="-1" density="10.0 20.0 10.0 20.0" universe="1"/>
<cell id="2" material="2" region="1" universe="1"/>
<cell id="3" fill="2" region="2 -3 4 -5" universe="3"/>
<lattice id="2">
<pitch>1.0 1.0</pitch>
<dimension>2 2</dimension>
<lower_left>-1.0 -1.0</lower_left>
<universes>
1 1
1 1 </universes>
</lattice>
<surface id="1" type="z-cylinder" coeffs="0.0 0.0 0.4"/>
<surface id="2" name="minimum x" type="x-plane" boundary="reflective" coeffs="-1.0"/>
<surface id="3" name="maximum x" type="x-plane" boundary="reflective" coeffs="1.0"/>
<surface id="4" name="minimum y" type="y-plane" boundary="reflective" coeffs="-1.0"/>
<surface id="5" name="maximum y" type="y-plane" boundary="reflective" coeffs="1.0"/>
</geometry>
<settings>
<run_mode>eigenvalue</run_mode>
<particles>1000</particles>
<batches>10</batches>
<inactive>5</inactive>
</settings>
</model>

View file

@ -0,0 +1,2 @@
k-combined:
1.904471E+00 5.255549E-03

View file

@ -0,0 +1,51 @@
import openmc
import pytest
from tests.testing_harness import PyAPITestHarness
@pytest.fixture
def model():
model = openmc.Model()
uo2 = openmc.Material(name='UO2')
uo2.set_density('g/cm3', 10.0)
uo2.add_nuclide('U235', 1.0)
uo2.add_nuclide('O16', 2.0)
water = openmc.Material(name='light water')
water.add_nuclide('H1', 2.0)
water.add_nuclide('O16', 1.0)
water.set_density('g/cm3', 1.0)
water.add_s_alpha_beta('c_H_in_H2O')
model.materials.extend([uo2, water])
cyl = openmc.ZCylinder(r=0.4)
pin = openmc.model.pin([cyl], [uo2, water])
d = 1.0
lattice = openmc.RectLattice()
lattice.lower_left = (-d, -d)
lattice.pitch = (d, d)
lattice.universes = [[pin, pin],
[pin, pin]]
box = openmc.model.RectangularPrism(
2.0 * d, 2.0 * d,
origin=(0.0, 0.0),
boundary_type='reflective'
)
pin.cells[1].density = [10.0, 20.0, 10.0, 20.0]
model.geometry = openmc.Geometry([openmc.Cell(fill=lattice, region=-box)])
model.geometry.merge_surfaces = True
model.settings.batches = 10
model.settings.inactive = 5
model.settings.particles = 1000
return model
def test_lattice_checkerboard(model):
harness = PyAPITestHarness('statepoint.10.h5', model)
harness.main()

View file

@ -38,5 +38,6 @@ Cell
Region = -1
Rotation = None
Temperature = [500. 700. 0. 800.]
Density = None
Translation = None
Volume = None

View file

@ -126,6 +126,29 @@ def test_temperature(cell_with_lattice):
c.temperature = (300., 600., 900.)
def test_densities(cell_with_lattice):
# Make sure density propagates through universes
m = openmc.Material()
s = openmc.XPlane()
c1 = openmc.Cell(fill=m, region=+s)
c2 = openmc.Cell(fill=m, region=-s)
u1 = openmc.Universe(cells=[c1, c2])
c = openmc.Cell(fill=u1)
c.density = 1.
assert c1.density == 1.
assert c2.density == 1.
with pytest.raises(ValueError):
c.density = -1.
c.density = None
assert c1.density == None
assert c2.density == None
# distributed density
cells, _, _, _ = cell_with_lattice
c = cells[0]
c.density = (1., 2., 3.)
def test_rotation():
u = openmc.Universe()
c = openmc.Cell(fill=u)

View file

@ -159,6 +159,29 @@ def test_properties_temperature(lib_init):
assert cell.get_temperature() == pytest.approx(200.0)
def test_cell_density(lib_init):
cell = openmc.lib.cells[1]
cell.set_density(1.5, 0)
assert cell.get_density(0) == pytest.approx(1.5)
cell.set_density(2.0)
assert cell.get_density() == pytest.approx(2.0)
def test_properties_cell_density(lib_init):
# Cell density should be 2.0 from above test
cell = openmc.lib.cells[1]
assert cell.get_density() == pytest.approx(2.0)
# Export properties and change density
openmc.lib.export_properties('properties.h5')
cell.set_density(3.0)
assert cell.get_density() == pytest.approx(3.0)
# Import properties and check that density is restored
openmc.lib.import_properties('properties.h5')
assert cell.get_density() == pytest.approx(2.0)
def test_new_cell(lib_init):
with pytest.raises(exc.AllocationError):
openmc.lib.Cell(1)

View file

@ -251,10 +251,11 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm):
model = openmc.examples.pwr_pin_cell()
model.init_lib(output=False, intracomm=mpi_intracomm)
# Change fuel temperature and density and export properties
# Change cell fuel temperature, density, material density and export properties
cell = openmc.lib.cells[1]
cell.set_temperature(600.0)
cell.fill.set_density(5.0, 'g/cm3')
cell.set_density(10.0)
openmc.lib.export_properties(output=False)
# Import properties to existing model
@ -264,9 +265,11 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm):
# First python
cell = model.geometry.get_all_cells()[1]
assert cell.temperature == [600.0]
assert cell.density == [pytest.approx(10.0, 1e-5)]
assert cell.fill.get_mass_density() == pytest.approx(5.0)
# Now C
assert openmc.lib.cells[1].get_temperature() == 600.
assert openmc.lib.cells[1].get_density() == pytest.approx(10.0, 1e-5)
assert openmc.lib.materials[1].get_density('g/cm3') == pytest.approx(5.0)
# Clear the C API
@ -283,6 +286,7 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm):
)
cell = model_with_properties.geometry.get_all_cells()[1]
assert cell.temperature == [600.0]
assert cell.density == [pytest.approx(10.0, 1e-5)]
assert cell.fill.get_mass_density() == pytest.approx(5.0)