Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/openmc/mesh.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,9 @@ class Mesh {
virtual std::string get_mesh_type() const = 0;

//! Determine volume of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
Comment thread
pshriwise marked this conversation as resolved.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand All@@ -264,6 +267,9 @@ class Mesh {
int32_t* materials, double* volumes) const;

//! Determine volume and bounding boxes of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand Down
5 changes: 4 additions & 1 deletion openmc/lib/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.0

Expand Down
5 changes: 4 additions & 1 deletion openmc/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,7 +451,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.1

Expand Down
13 changes: 8 additions & 5 deletions src/dagmc.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,12 +857,15 @@ std::pair<double, int32_t> DAGCell::distance(
dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
} else if (!dagmc_ptr_->is_implicit_complement(vol) ||
is_root_universe(dag_univ->id_)) {
// surface boundary conditions are ignored for projection plotting, meaning
// Surface boundary conditions are ignored for projection plotting, meaning
// that the particle may move through the graveyard (bounding) volume and
// into the implicit complement on the other side where no intersection will
// be found. Treating this as a lost particle is problematic when plotting.
// Instead, the infinite distance and invalid surface index are returned.
if (settings::run_mode == RunMode::PLOTTING)
// into the implicit complement on the other side where no intersection
// will be found. A no-hit result is also expected when querying root cells
// for the next boundary from undefined space, when no containing cell is
// assigned. In both cases, return an infinite distance and invalid surface
// index rather than marking a particle as lost.
if (settings::run_mode == RunMode::PLOTTING ||
p->lowest_coord().cell() == C_NONE)
Comment thread
pshriwise marked this conversation as resolved.
return {INFTY, -1};

// the particle should be marked as lost immediately if an intersection
Expand Down
10 changes: 10 additions & 0 deletions src/geometry.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -281,6 +281,16 @@ bool find_cell_inner(
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
{

#ifdef OPENMC_DAGMC_ENABLED
Comment thread
pshriwise marked this conversation as resolved.
// A CSG crossing can move the particle into another instance of the same
// DAGMC universe, where the previous facet history is no longer valid.
if (p.surface() != SURFACE_NONE) {
const auto& surf = model::surfaces[p.surface_index()];
if (surf->geom_type() == GeometryType::CSG)
p.history().reset();
}
#endif

// Reset all the deeper coordinate levels.
for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
p.coord(i).reset();
Expand Down
222 changes: 152 additions & 70 deletions src/mesh.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -482,9 +482,6 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
width.y = (ny > 0) ? width.y / ny : 0.0;
width.z = (nz > 0) ? width.z / nz : 0.0;

// Set flag for mesh being contained within model
bool out_of_model = false;

#pragma omp parallel
{
// Preallocate vector for mesh indices and length fractions and particle
Expand All@@ -496,6 +493,32 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
site.E = 1.0;
site.particle = ParticleType::neutron();

bool verbose = settings::verbosity >= 10;

// Save the cells occupied immediately before a boundary crossing.
auto save_cell_state = [&p]() {
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
};

// Initialize cell history after locating a ray inside the model.
auto initialize_cell_state = [&p, &save_cell_state]() {
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();

save_cell_state();
};

// Reset a failed coordinate search while preserving position and direction.
auto reset_geometry_state = [&p]() {
Position r = p.r();
Direction u = p.u();
p.init_from_r_u(r, u);
p.coord(0).universe() = model::root_universe;
};

for (int axis = 0; axis < 3; ++axis) {
// Set starting position and direction
site.r = {0.0, 0.0, 0.0};
Expand DownExpand Up@@ -524,6 +547,50 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int i1_start = mpi::rank * min_work + std::min(mpi::rank, remainder);
int i1_end = i1_start + n1_local;

// Add the contribution from a ray segment. The positions used here are
// kept separate from the particle position because the latter is moved a
// tiny distance across each surface for robust geometry searches.
auto add_segment = [&](const Position& r0, const Position& r1,
Comment thread
pshriwise marked this conversation as resolved.
int i_material) {
double distance = r1[axis] - r0[axis];
if (distance <= 0.0)
return;

bins.clear();
length_fractions.clear();
this->bins_crossed(r0, r1, site.u, bins, length_fractions);

double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];

Position contrib_min = site.r;
Position contrib_max = site.r;

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;

result.add_volume(mesh_index, i_material, volume, &contrib_bbox);
} else {
result.add_volume(mesh_index, i_material, volume);
}
}
};

// Loop over rays on face of bounding box
#pragma omp for collapse(2)
for (int i1 = i1_start; i1 < i1_end; ++i1) {
Expand All@@ -533,98 +600,115 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,

p.from_source(&site);

// Set the physical endpoint of this ray at the far mesh face.
Position r_mesh_end = site.r;
r_mesh_end[axis] = bbox.max[axis];

// Determine particle's location
if (!exhaustive_find_cell(p)) {
out_of_model = true;
continue;
bool inside_model = exhaustive_find_cell(p, verbose);

if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial descent into nested universes before searching
// for the first root-universe boundary from undefined space.
reset_geometry_state();
}

// Set birth cell attribute
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();
// Physical position through which volume has been accumulated. This
// differs by TINY_BIT from p.r() after crossing a surface.
Position r_scored = site.r;

while (r_scored[axis] < r_mesh_end[axis]) {
if (!inside_model) {
// The ray is outside the model. Advance to the next surface of
// any cell in the root universe, as is done for ray-traced
// plots. Undefined space traversed along the way is void.
Position r0 = p.r();
p.advance_to_boundary_from_void();

// If no model surface lies before the mesh edge, score the
// remaining exterior interval as void and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - r0[axis];
if (p.boundary().surface() == SURFACE_NONE ||
p.boundary().distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, MATERIAL_VOID);
break;
}

// Initialize last cells from current cell
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Determine the physical position of the model boundary.
Position r_boundary = r0 + p.boundary().distance() * p.u();

while (true) {
// Ray trace from r_start to r_end
Position r0 = p.r();
double max_distance = bbox.max[axis] - r0[axis];
// Score the exterior interval and record its physical endpoint.
add_segment(r_scored, r_boundary, MATERIAL_VOID);
r_scored = r_boundary;

// Check whether advancing through the surface entered the model.
inside_model = exhaustive_find_cell(p, verbose);
if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial coordinate search before looking for the
// next surface from undefined space.
reset_geometry_state();
}
continue;
}

// Find the distance to the nearest boundary
BoundaryInfo boundary = distance_to_boundary(p);

// Advance particle forward
double distance = std::min(boundary.distance(), max_distance);
p.move_distance(distance);

// Determine what mesh elements were crossed by particle
bins.clear();
length_fractions.clear();
this->bins_crossed(r0, p.r(), p.u(), bins, length_fractions);

// Add volumes to any mesh elements that were crossed
// Convert the material index to a user-facing ID
int i_material = p.material();
if (i_material != C_NONE) {
i_material = model::materials[i_material]->id();
}
double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];
// If no model boundary lies before the mesh edge, score the
// remaining material interval and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - p.r()[axis];
if (boundary.distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, i_material);
break;
}

Position contrib_min = site.r;
Position contrib_max = site.r;
// Determine the physical position of the model boundary.
Position r_boundary = p.r() + boundary.distance() * p.u();

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);
// Score the material interval and record its physical endpoint.
add_segment(r_scored, r_boundary, i_material);
r_scored = r_boundary;

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;
// Cross the next geometric surface. The small forward movement
// and neighbor-list search mirror Ray::trace, allowing a failed
// search to mean that the ray has left the model rather than that
// a transport particle has been lost.
save_cell_state();

result.add_volume(
mesh_index, i_material, volume, &contrib_bbox);
} else {
// Add volume to result
result.add_volume(mesh_index, i_material, volume);
}
}

if (distance == max_distance)
break;

// cross next geometric surface
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Move just beyond the surface to make the next search robust.
p.move_distance(boundary.distance() + TINY_BIT);

// Set surface that particle is on and adjust coordinate levels
p.surface() = boundary.surface();
p.n_coord() = boundary.coord_level();

// Update the geometry state according to the boundary type.
if (boundary.lattice_translation()[0] != 0 ||
boundary.lattice_translation()[1] != 0 ||
boundary.lattice_translation()[2] != 0) {
// Particle crosses lattice boundary
cross_lattice(p, boundary);
cross_lattice(p, boundary, verbose);
inside_model = true;
} else {
// Particle crosses surface
const auto& surf {model::surfaces[p.surface_index()].get()};
p.cross_surface(*surf);
// Search for the cell on the opposite side of a surface.
inside_model = neighbor_list_find_cell(p, verbose);
}

// Treat a failed cell search as a transition to exterior void.
if (!inside_model) {
// Reset the geometry state so the next iteration can search for
// another disjoint portion of the model.
reset_geometry_state();
}
}
}
Expand All@@ -633,9 +717,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
}

// Check for errors
if (out_of_model) {
throw std::runtime_error("Mesh not fully contained in geometry.");
} else if (result.table_full()) {
if (result.table_full()) {
throw std::runtime_error("Maximum number of materials for mesh material "
"volume calculation insufficient.");
}
Expand Down
6 changes: 0 additions & 6 deletions src/particle.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,12 +663,6 @@ void Particle::cross_surface(const Surface& surf)
write_message(1, " Crossing surface {}", surf.id_);
}

// if we're crossing a CSG surface, make sure the DAG history is reset
#ifdef OPENMC_DAGMC_ENABLED
if (surf.geom_type() == GeometryType::CSG)
history().reset();
#endif

// Handle any applicable boundary conditions.
if (surf.bc_ && settings::run_mode != RunMode::PLOTTING &&
settings::run_mode != RunMode::VOLUME) {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Allow mesh material volume calculations outside model geometry by paulromano · Pull Request #4028 · openmc-dev/openmc · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/openmc/mesh.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,9 @@ class Mesh {
virtual std::string get_mesh_type() const = 0;

//! Determine volume of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
Comment thread
pshriwise marked this conversation as resolved.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand All@@ -264,6 +267,9 @@ class Mesh {
int32_t* materials, double* volumes) const;

//! Determine volume and bounding boxes of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand Down
5 changes: 4 additions & 1 deletion openmc/lib/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.0

Expand Down
5 changes: 4 additions & 1 deletion openmc/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,7 +451,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.1

Expand Down
13 changes: 8 additions & 5 deletions src/dagmc.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,12 +857,15 @@ std::pair<double, int32_t> DAGCell::distance(
dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
} else if (!dagmc_ptr_->is_implicit_complement(vol) ||
is_root_universe(dag_univ->id_)) {
// surface boundary conditions are ignored for projection plotting, meaning
// Surface boundary conditions are ignored for projection plotting, meaning
// that the particle may move through the graveyard (bounding) volume and
// into the implicit complement on the other side where no intersection will
// be found. Treating this as a lost particle is problematic when plotting.
// Instead, the infinite distance and invalid surface index are returned.
if (settings::run_mode == RunMode::PLOTTING)
// into the implicit complement on the other side where no intersection
// will be found. A no-hit result is also expected when querying root cells
// for the next boundary from undefined space, when no containing cell is
// assigned. In both cases, return an infinite distance and invalid surface
// index rather than marking a particle as lost.
if (settings::run_mode == RunMode::PLOTTING ||
p->lowest_coord().cell() == C_NONE)
Comment thread
pshriwise marked this conversation as resolved.
return {INFTY, -1};

// the particle should be marked as lost immediately if an intersection
Expand Down
10 changes: 10 additions & 0 deletions src/geometry.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -281,6 +281,16 @@ bool find_cell_inner(
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
{

#ifdef OPENMC_DAGMC_ENABLED
Comment thread
pshriwise marked this conversation as resolved.
// A CSG crossing can move the particle into another instance of the same
// DAGMC universe, where the previous facet history is no longer valid.
if (p.surface() != SURFACE_NONE) {
const auto& surf = model::surfaces[p.surface_index()];
if (surf->geom_type() == GeometryType::CSG)
p.history().reset();
}
#endif

// Reset all the deeper coordinate levels.
for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
p.coord(i).reset();
Expand Down
222 changes: 152 additions & 70 deletions src/mesh.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -482,9 +482,6 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
width.y = (ny > 0) ? width.y / ny : 0.0;
width.z = (nz > 0) ? width.z / nz : 0.0;

// Set flag for mesh being contained within model
bool out_of_model = false;

#pragma omp parallel
{
// Preallocate vector for mesh indices and length fractions and particle
Expand All@@ -496,6 +493,32 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
site.E = 1.0;
site.particle = ParticleType::neutron();

bool verbose = settings::verbosity >= 10;

// Save the cells occupied immediately before a boundary crossing.
auto save_cell_state = [&p]() {
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
};

// Initialize cell history after locating a ray inside the model.
auto initialize_cell_state = [&p, &save_cell_state]() {
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();

save_cell_state();
};

// Reset a failed coordinate search while preserving position and direction.
auto reset_geometry_state = [&p]() {
Position r = p.r();
Direction u = p.u();
p.init_from_r_u(r, u);
p.coord(0).universe() = model::root_universe;
};

for (int axis = 0; axis < 3; ++axis) {
// Set starting position and direction
site.r = {0.0, 0.0, 0.0};
Expand DownExpand Up@@ -524,6 +547,50 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int i1_start = mpi::rank * min_work + std::min(mpi::rank, remainder);
int i1_end = i1_start + n1_local;

// Add the contribution from a ray segment. The positions used here are
// kept separate from the particle position because the latter is moved a
// tiny distance across each surface for robust geometry searches.
auto add_segment = [&](const Position& r0, const Position& r1,
Comment thread
pshriwise marked this conversation as resolved.
int i_material) {
double distance = r1[axis] - r0[axis];
if (distance <= 0.0)
return;

bins.clear();
length_fractions.clear();
this->bins_crossed(r0, r1, site.u, bins, length_fractions);

double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];

Position contrib_min = site.r;
Position contrib_max = site.r;

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;

result.add_volume(mesh_index, i_material, volume, &contrib_bbox);
} else {
result.add_volume(mesh_index, i_material, volume);
}
}
};

// Loop over rays on face of bounding box
#pragma omp for collapse(2)
for (int i1 = i1_start; i1 < i1_end; ++i1) {
Expand All@@ -533,98 +600,115 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,

p.from_source(&site);

// Set the physical endpoint of this ray at the far mesh face.
Position r_mesh_end = site.r;
r_mesh_end[axis] = bbox.max[axis];

// Determine particle's location
if (!exhaustive_find_cell(p)) {
out_of_model = true;
continue;
bool inside_model = exhaustive_find_cell(p, verbose);

if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial descent into nested universes before searching
// for the first root-universe boundary from undefined space.
reset_geometry_state();
}

// Set birth cell attribute
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();
// Physical position through which volume has been accumulated. This
// differs by TINY_BIT from p.r() after crossing a surface.
Position r_scored = site.r;

while (r_scored[axis] < r_mesh_end[axis]) {
if (!inside_model) {
// The ray is outside the model. Advance to the next surface of
// any cell in the root universe, as is done for ray-traced
// plots. Undefined space traversed along the way is void.
Position r0 = p.r();
p.advance_to_boundary_from_void();

// If no model surface lies before the mesh edge, score the
// remaining exterior interval as void and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - r0[axis];
if (p.boundary().surface() == SURFACE_NONE ||
p.boundary().distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, MATERIAL_VOID);
break;
}

// Initialize last cells from current cell
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Determine the physical position of the model boundary.
Position r_boundary = r0 + p.boundary().distance() * p.u();

while (true) {
// Ray trace from r_start to r_end
Position r0 = p.r();
double max_distance = bbox.max[axis] - r0[axis];
// Score the exterior interval and record its physical endpoint.
add_segment(r_scored, r_boundary, MATERIAL_VOID);
r_scored = r_boundary;

// Check whether advancing through the surface entered the model.
inside_model = exhaustive_find_cell(p, verbose);
if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial coordinate search before looking for the
// next surface from undefined space.
reset_geometry_state();
}
continue;
}

// Find the distance to the nearest boundary
BoundaryInfo boundary = distance_to_boundary(p);

// Advance particle forward
double distance = std::min(boundary.distance(), max_distance);
p.move_distance(distance);

// Determine what mesh elements were crossed by particle
bins.clear();
length_fractions.clear();
this->bins_crossed(r0, p.r(), p.u(), bins, length_fractions);

// Add volumes to any mesh elements that were crossed
// Convert the material index to a user-facing ID
int i_material = p.material();
if (i_material != C_NONE) {
i_material = model::materials[i_material]->id();
}
double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];
// If no model boundary lies before the mesh edge, score the
// remaining material interval and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - p.r()[axis];
if (boundary.distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, i_material);
break;
}

Position contrib_min = site.r;
Position contrib_max = site.r;
// Determine the physical position of the model boundary.
Position r_boundary = p.r() + boundary.distance() * p.u();

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);
// Score the material interval and record its physical endpoint.
add_segment(r_scored, r_boundary, i_material);
r_scored = r_boundary;

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;
// Cross the next geometric surface. The small forward movement
// and neighbor-list search mirror Ray::trace, allowing a failed
// search to mean that the ray has left the model rather than that
// a transport particle has been lost.
save_cell_state();

result.add_volume(
mesh_index, i_material, volume, &contrib_bbox);
} else {
// Add volume to result
result.add_volume(mesh_index, i_material, volume);
}
}

if (distance == max_distance)
break;

// cross next geometric surface
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Move just beyond the surface to make the next search robust.
p.move_distance(boundary.distance() + TINY_BIT);

// Set surface that particle is on and adjust coordinate levels
p.surface() = boundary.surface();
p.n_coord() = boundary.coord_level();

// Update the geometry state according to the boundary type.
if (boundary.lattice_translation()[0] != 0 ||
boundary.lattice_translation()[1] != 0 ||
boundary.lattice_translation()[2] != 0) {
// Particle crosses lattice boundary
cross_lattice(p, boundary);
cross_lattice(p, boundary, verbose);
inside_model = true;
} else {
// Particle crosses surface
const auto& surf {model::surfaces[p.surface_index()].get()};
p.cross_surface(*surf);
// Search for the cell on the opposite side of a surface.
inside_model = neighbor_list_find_cell(p, verbose);
}

// Treat a failed cell search as a transition to exterior void.
if (!inside_model) {
// Reset the geometry state so the next iteration can search for
// another disjoint portion of the model.
reset_geometry_state();
}
}
}
Expand All@@ -633,9 +717,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
}

// Check for errors
if (out_of_model) {
throw std::runtime_error("Mesh not fully contained in geometry.");
} else if (result.table_full()) {
if (result.table_full()) {
throw std::runtime_error("Maximum number of materials for mesh material "
"volume calculation insufficient.");
}
Expand Down
6 changes: 0 additions & 6 deletions src/particle.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,12 +663,6 @@ void Particle::cross_surface(const Surface& surf)
write_message(1, " Crossing surface {}", surf.id_);
}

// if we're crossing a CSG surface, make sure the DAG history is reset
#ifdef OPENMC_DAGMC_ENABLED
if (surf.geom_type() == GeometryType::CSG)
history().reset();
#endif

// Handle any applicable boundary conditions.
if (surf.bc_ && settings::run_mode != RunMode::PLOTTING &&
settings::run_mode != RunMode::VOLUME) {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow mesh material volume calculations outside model geometry by paulromano · Pull Request #4028 · openmc-dev/openmc · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/openmc/mesh.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,9 @@ class Mesh {
virtual std::string get_mesh_type() const = 0;

//! Determine volume of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
Comment thread
pshriwise marked this conversation as resolved.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand All@@ -264,6 +267,9 @@ class Mesh {
int32_t* materials, double* volumes) const;

//! Determine volume and bounding boxes of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand Down
5 changes: 4 additions & 1 deletion openmc/lib/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.0

Expand Down
5 changes: 4 additions & 1 deletion openmc/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,7 +451,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.1

Expand Down
13 changes: 8 additions & 5 deletions src/dagmc.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,12 +857,15 @@ std::pair<double, int32_t> DAGCell::distance(
dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
} else if (!dagmc_ptr_->is_implicit_complement(vol) ||
is_root_universe(dag_univ->id_)) {
// surface boundary conditions are ignored for projection plotting, meaning
// Surface boundary conditions are ignored for projection plotting, meaning
// that the particle may move through the graveyard (bounding) volume and
// into the implicit complement on the other side where no intersection will
// be found. Treating this as a lost particle is problematic when plotting.
// Instead, the infinite distance and invalid surface index are returned.
if (settings::run_mode == RunMode::PLOTTING)
// into the implicit complement on the other side where no intersection
// will be found. A no-hit result is also expected when querying root cells
// for the next boundary from undefined space, when no containing cell is
// assigned. In both cases, return an infinite distance and invalid surface
// index rather than marking a particle as lost.
if (settings::run_mode == RunMode::PLOTTING ||
p->lowest_coord().cell() == C_NONE)
Comment thread
pshriwise marked this conversation as resolved.
return {INFTY, -1};

// the particle should be marked as lost immediately if an intersection
Expand Down
10 changes: 10 additions & 0 deletions src/geometry.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -281,6 +281,16 @@ bool find_cell_inner(
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
{

#ifdef OPENMC_DAGMC_ENABLED
Comment thread
pshriwise marked this conversation as resolved.
// A CSG crossing can move the particle into another instance of the same
// DAGMC universe, where the previous facet history is no longer valid.
if (p.surface() != SURFACE_NONE) {
const auto& surf = model::surfaces[p.surface_index()];
if (surf->geom_type() == GeometryType::CSG)
p.history().reset();
}
#endif

// Reset all the deeper coordinate levels.
for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
p.coord(i).reset();
Expand Down
222 changes: 152 additions & 70 deletions src/mesh.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -482,9 +482,6 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
width.y = (ny > 0) ? width.y / ny : 0.0;
width.z = (nz > 0) ? width.z / nz : 0.0;

// Set flag for mesh being contained within model
bool out_of_model = false;

#pragma omp parallel
{
// Preallocate vector for mesh indices and length fractions and particle
Expand All@@ -496,6 +493,32 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
site.E = 1.0;
site.particle = ParticleType::neutron();

bool verbose = settings::verbosity >= 10;

// Save the cells occupied immediately before a boundary crossing.
auto save_cell_state = [&p]() {
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
};

// Initialize cell history after locating a ray inside the model.
auto initialize_cell_state = [&p, &save_cell_state]() {
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();

save_cell_state();
};

// Reset a failed coordinate search while preserving position and direction.
auto reset_geometry_state = [&p]() {
Position r = p.r();
Direction u = p.u();
p.init_from_r_u(r, u);
p.coord(0).universe() = model::root_universe;
};

for (int axis = 0; axis < 3; ++axis) {
// Set starting position and direction
site.r = {0.0, 0.0, 0.0};
Expand DownExpand Up@@ -524,6 +547,50 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int i1_start = mpi::rank * min_work + std::min(mpi::rank, remainder);
int i1_end = i1_start + n1_local;

// Add the contribution from a ray segment. The positions used here are
// kept separate from the particle position because the latter is moved a
// tiny distance across each surface for robust geometry searches.
auto add_segment = [&](const Position& r0, const Position& r1,
Comment thread
pshriwise marked this conversation as resolved.
int i_material) {
double distance = r1[axis] - r0[axis];
if (distance <= 0.0)
return;

bins.clear();
length_fractions.clear();
this->bins_crossed(r0, r1, site.u, bins, length_fractions);

double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];

Position contrib_min = site.r;
Position contrib_max = site.r;

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;

result.add_volume(mesh_index, i_material, volume, &contrib_bbox);
} else {
result.add_volume(mesh_index, i_material, volume);
}
}
};

// Loop over rays on face of bounding box
#pragma omp for collapse(2)
for (int i1 = i1_start; i1 < i1_end; ++i1) {
Expand All@@ -533,98 +600,115 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,

p.from_source(&site);

// Set the physical endpoint of this ray at the far mesh face.
Position r_mesh_end = site.r;
r_mesh_end[axis] = bbox.max[axis];

// Determine particle's location
if (!exhaustive_find_cell(p)) {
out_of_model = true;
continue;
bool inside_model = exhaustive_find_cell(p, verbose);

if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial descent into nested universes before searching
// for the first root-universe boundary from undefined space.
reset_geometry_state();
}

// Set birth cell attribute
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();
// Physical position through which volume has been accumulated. This
// differs by TINY_BIT from p.r() after crossing a surface.
Position r_scored = site.r;

while (r_scored[axis] < r_mesh_end[axis]) {
if (!inside_model) {
// The ray is outside the model. Advance to the next surface of
// any cell in the root universe, as is done for ray-traced
// plots. Undefined space traversed along the way is void.
Position r0 = p.r();
p.advance_to_boundary_from_void();

// If no model surface lies before the mesh edge, score the
// remaining exterior interval as void and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - r0[axis];
if (p.boundary().surface() == SURFACE_NONE ||
p.boundary().distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, MATERIAL_VOID);
break;
}

// Initialize last cells from current cell
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Determine the physical position of the model boundary.
Position r_boundary = r0 + p.boundary().distance() * p.u();

while (true) {
// Ray trace from r_start to r_end
Position r0 = p.r();
double max_distance = bbox.max[axis] - r0[axis];
// Score the exterior interval and record its physical endpoint.
add_segment(r_scored, r_boundary, MATERIAL_VOID);
r_scored = r_boundary;

// Check whether advancing through the surface entered the model.
inside_model = exhaustive_find_cell(p, verbose);
if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial coordinate search before looking for the
// next surface from undefined space.
reset_geometry_state();
}
continue;
}

// Find the distance to the nearest boundary
BoundaryInfo boundary = distance_to_boundary(p);

// Advance particle forward
double distance = std::min(boundary.distance(), max_distance);
p.move_distance(distance);

// Determine what mesh elements were crossed by particle
bins.clear();
length_fractions.clear();
this->bins_crossed(r0, p.r(), p.u(), bins, length_fractions);

// Add volumes to any mesh elements that were crossed
// Convert the material index to a user-facing ID
int i_material = p.material();
if (i_material != C_NONE) {
i_material = model::materials[i_material]->id();
}
double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];
// If no model boundary lies before the mesh edge, score the
// remaining material interval and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - p.r()[axis];
if (boundary.distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, i_material);
break;
}

Position contrib_min = site.r;
Position contrib_max = site.r;
// Determine the physical position of the model boundary.
Position r_boundary = p.r() + boundary.distance() * p.u();

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);
// Score the material interval and record its physical endpoint.
add_segment(r_scored, r_boundary, i_material);
r_scored = r_boundary;

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;
// Cross the next geometric surface. The small forward movement
// and neighbor-list search mirror Ray::trace, allowing a failed
// search to mean that the ray has left the model rather than that
// a transport particle has been lost.
save_cell_state();

result.add_volume(
mesh_index, i_material, volume, &contrib_bbox);
} else {
// Add volume to result
result.add_volume(mesh_index, i_material, volume);
}
}

if (distance == max_distance)
break;

// cross next geometric surface
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Move just beyond the surface to make the next search robust.
p.move_distance(boundary.distance() + TINY_BIT);

// Set surface that particle is on and adjust coordinate levels
p.surface() = boundary.surface();
p.n_coord() = boundary.coord_level();

// Update the geometry state according to the boundary type.
if (boundary.lattice_translation()[0] != 0 ||
boundary.lattice_translation()[1] != 0 ||
boundary.lattice_translation()[2] != 0) {
// Particle crosses lattice boundary
cross_lattice(p, boundary);
cross_lattice(p, boundary, verbose);
inside_model = true;
} else {
// Particle crosses surface
const auto& surf {model::surfaces[p.surface_index()].get()};
p.cross_surface(*surf);
// Search for the cell on the opposite side of a surface.
inside_model = neighbor_list_find_cell(p, verbose);
}

// Treat a failed cell search as a transition to exterior void.
if (!inside_model) {
// Reset the geometry state so the next iteration can search for
// another disjoint portion of the model.
reset_geometry_state();
}
}
}
Expand All@@ -633,9 +717,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
}

// Check for errors
if (out_of_model) {
throw std::runtime_error("Mesh not fully contained in geometry.");
} else if (result.table_full()) {
if (result.table_full()) {
throw std::runtime_error("Maximum number of materials for mesh material "
"volume calculation insufficient.");
}
Expand Down
6 changes: 0 additions & 6 deletions src/particle.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,12 +663,6 @@ void Particle::cross_surface(const Surface& surf)
write_message(1, " Crossing surface {}", surf.id_);
}

// if we're crossing a CSG surface, make sure the DAG history is reset
#ifdef OPENMC_DAGMC_ENABLED
if (surf.geom_type() == GeometryType::CSG)
history().reset();
#endif

// Handle any applicable boundary conditions.
if (surf.bc_ && settings::run_mode != RunMode::PLOTTING &&
settings::run_mode != RunMode::VOLUME) {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow mesh material volume calculations outside model geometry by paulromano · Pull Request #4028 · openmc-dev/openmc · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/openmc/mesh.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,9 @@ class Mesh {
virtual std::string get_mesh_type() const = 0;

//! Determine volume of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
Comment thread
pshriwise marked this conversation as resolved.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand All@@ -264,6 +267,9 @@ class Mesh {
int32_t* materials, double* volumes) const;

//! Determine volume and bounding boxes of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand Down
5 changes: 4 additions & 1 deletion openmc/lib/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.0

Expand Down
5 changes: 4 additions & 1 deletion openmc/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,7 +451,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.1

Expand Down
13 changes: 8 additions & 5 deletions src/dagmc.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,12 +857,15 @@ std::pair<double, int32_t> DAGCell::distance(
dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
} else if (!dagmc_ptr_->is_implicit_complement(vol) ||
is_root_universe(dag_univ->id_)) {
// surface boundary conditions are ignored for projection plotting, meaning
// Surface boundary conditions are ignored for projection plotting, meaning
// that the particle may move through the graveyard (bounding) volume and
// into the implicit complement on the other side where no intersection will
// be found. Treating this as a lost particle is problematic when plotting.
// Instead, the infinite distance and invalid surface index are returned.
if (settings::run_mode == RunMode::PLOTTING)
// into the implicit complement on the other side where no intersection
// will be found. A no-hit result is also expected when querying root cells
// for the next boundary from undefined space, when no containing cell is
// assigned. In both cases, return an infinite distance and invalid surface
// index rather than marking a particle as lost.
if (settings::run_mode == RunMode::PLOTTING ||
p->lowest_coord().cell() == C_NONE)
Comment thread
pshriwise marked this conversation as resolved.
return {INFTY, -1};

// the particle should be marked as lost immediately if an intersection
Expand Down
10 changes: 10 additions & 0 deletions src/geometry.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -281,6 +281,16 @@ bool find_cell_inner(
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
{

#ifdef OPENMC_DAGMC_ENABLED
Comment thread
pshriwise marked this conversation as resolved.
// A CSG crossing can move the particle into another instance of the same
// DAGMC universe, where the previous facet history is no longer valid.
if (p.surface() != SURFACE_NONE) {
const auto& surf = model::surfaces[p.surface_index()];
if (surf->geom_type() == GeometryType::CSG)
p.history().reset();
}
#endif

// Reset all the deeper coordinate levels.
for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
p.coord(i).reset();
Expand Down
222 changes: 152 additions & 70 deletions src/mesh.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -482,9 +482,6 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
width.y = (ny > 0) ? width.y / ny : 0.0;
width.z = (nz > 0) ? width.z / nz : 0.0;

// Set flag for mesh being contained within model
bool out_of_model = false;

#pragma omp parallel
{
// Preallocate vector for mesh indices and length fractions and particle
Expand All@@ -496,6 +493,32 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
site.E = 1.0;
site.particle = ParticleType::neutron();

bool verbose = settings::verbosity >= 10;

// Save the cells occupied immediately before a boundary crossing.
auto save_cell_state = [&p]() {
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
};

// Initialize cell history after locating a ray inside the model.
auto initialize_cell_state = [&p, &save_cell_state]() {
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();

save_cell_state();
};

// Reset a failed coordinate search while preserving position and direction.
auto reset_geometry_state = [&p]() {
Position r = p.r();
Direction u = p.u();
p.init_from_r_u(r, u);
p.coord(0).universe() = model::root_universe;
};

for (int axis = 0; axis < 3; ++axis) {
// Set starting position and direction
site.r = {0.0, 0.0, 0.0};
Expand DownExpand Up@@ -524,6 +547,50 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int i1_start = mpi::rank * min_work + std::min(mpi::rank, remainder);
int i1_end = i1_start + n1_local;

// Add the contribution from a ray segment. The positions used here are
// kept separate from the particle position because the latter is moved a
// tiny distance across each surface for robust geometry searches.
auto add_segment = [&](const Position& r0, const Position& r1,
Comment thread
pshriwise marked this conversation as resolved.
int i_material) {
double distance = r1[axis] - r0[axis];
if (distance <= 0.0)
return;

bins.clear();
length_fractions.clear();
this->bins_crossed(r0, r1, site.u, bins, length_fractions);

double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];

Position contrib_min = site.r;
Position contrib_max = site.r;

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;

result.add_volume(mesh_index, i_material, volume, &contrib_bbox);
} else {
result.add_volume(mesh_index, i_material, volume);
}
}
};

// Loop over rays on face of bounding box
#pragma omp for collapse(2)
for (int i1 = i1_start; i1 < i1_end; ++i1) {
Expand All@@ -533,98 +600,115 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,

p.from_source(&site);

// Set the physical endpoint of this ray at the far mesh face.
Position r_mesh_end = site.r;
r_mesh_end[axis] = bbox.max[axis];

// Determine particle's location
if (!exhaustive_find_cell(p)) {
out_of_model = true;
continue;
bool inside_model = exhaustive_find_cell(p, verbose);

if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial descent into nested universes before searching
// for the first root-universe boundary from undefined space.
reset_geometry_state();
}

// Set birth cell attribute
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();
// Physical position through which volume has been accumulated. This
// differs by TINY_BIT from p.r() after crossing a surface.
Position r_scored = site.r;

while (r_scored[axis] < r_mesh_end[axis]) {
if (!inside_model) {
// The ray is outside the model. Advance to the next surface of
// any cell in the root universe, as is done for ray-traced
// plots. Undefined space traversed along the way is void.
Position r0 = p.r();
p.advance_to_boundary_from_void();

// If no model surface lies before the mesh edge, score the
// remaining exterior interval as void and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - r0[axis];
if (p.boundary().surface() == SURFACE_NONE ||
p.boundary().distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, MATERIAL_VOID);
break;
}

// Initialize last cells from current cell
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Determine the physical position of the model boundary.
Position r_boundary = r0 + p.boundary().distance() * p.u();

while (true) {
// Ray trace from r_start to r_end
Position r0 = p.r();
double max_distance = bbox.max[axis] - r0[axis];
// Score the exterior interval and record its physical endpoint.
add_segment(r_scored, r_boundary, MATERIAL_VOID);
r_scored = r_boundary;

// Check whether advancing through the surface entered the model.
inside_model = exhaustive_find_cell(p, verbose);
if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial coordinate search before looking for the
// next surface from undefined space.
reset_geometry_state();
}
continue;
}

// Find the distance to the nearest boundary
BoundaryInfo boundary = distance_to_boundary(p);

// Advance particle forward
double distance = std::min(boundary.distance(), max_distance);
p.move_distance(distance);

// Determine what mesh elements were crossed by particle
bins.clear();
length_fractions.clear();
this->bins_crossed(r0, p.r(), p.u(), bins, length_fractions);

// Add volumes to any mesh elements that were crossed
// Convert the material index to a user-facing ID
int i_material = p.material();
if (i_material != C_NONE) {
i_material = model::materials[i_material]->id();
}
double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];
// If no model boundary lies before the mesh edge, score the
// remaining material interval and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - p.r()[axis];
if (boundary.distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, i_material);
break;
}

Position contrib_min = site.r;
Position contrib_max = site.r;
// Determine the physical position of the model boundary.
Position r_boundary = p.r() + boundary.distance() * p.u();

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);
// Score the material interval and record its physical endpoint.
add_segment(r_scored, r_boundary, i_material);
r_scored = r_boundary;

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;
// Cross the next geometric surface. The small forward movement
// and neighbor-list search mirror Ray::trace, allowing a failed
// search to mean that the ray has left the model rather than that
// a transport particle has been lost.
save_cell_state();

result.add_volume(
mesh_index, i_material, volume, &contrib_bbox);
} else {
// Add volume to result
result.add_volume(mesh_index, i_material, volume);
}
}

if (distance == max_distance)
break;

// cross next geometric surface
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Move just beyond the surface to make the next search robust.
p.move_distance(boundary.distance() + TINY_BIT);

// Set surface that particle is on and adjust coordinate levels
p.surface() = boundary.surface();
p.n_coord() = boundary.coord_level();

// Update the geometry state according to the boundary type.
if (boundary.lattice_translation()[0] != 0 ||
boundary.lattice_translation()[1] != 0 ||
boundary.lattice_translation()[2] != 0) {
// Particle crosses lattice boundary
cross_lattice(p, boundary);
cross_lattice(p, boundary, verbose);
inside_model = true;
} else {
// Particle crosses surface
const auto& surf {model::surfaces[p.surface_index()].get()};
p.cross_surface(*surf);
// Search for the cell on the opposite side of a surface.
inside_model = neighbor_list_find_cell(p, verbose);
}

// Treat a failed cell search as a transition to exterior void.
if (!inside_model) {
// Reset the geometry state so the next iteration can search for
// another disjoint portion of the model.
reset_geometry_state();
}
}
}
Expand All@@ -633,9 +717,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
}

// Check for errors
if (out_of_model) {
throw std::runtime_error("Mesh not fully contained in geometry.");
} else if (result.table_full()) {
if (result.table_full()) {
throw std::runtime_error("Maximum number of materials for mesh material "
"volume calculation insufficient.");
}
Expand Down
6 changes: 0 additions & 6 deletions src/particle.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,12 +663,6 @@ void Particle::cross_surface(const Surface& surf)
write_message(1, " Crossing surface {}", surf.id_);
}

// if we're crossing a CSG surface, make sure the DAG history is reset
#ifdef OPENMC_DAGMC_ENABLED
if (surf.geom_type() == GeometryType::CSG)
history().reset();
#endif

// Handle any applicable boundary conditions.
if (surf.bc_ && settings::run_mode != RunMode::PLOTTING &&
settings::run_mode != RunMode::VOLUME) {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Allow mesh material volume calculations outside model geometry by paulromano · Pull Request #4028 · openmc-dev/openmc · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/openmc/mesh.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,9 @@ class Mesh {
virtual std::string get_mesh_type() const = 0;

//! Determine volume of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
Comment thread
pshriwise marked this conversation as resolved.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand All@@ -264,6 +267,9 @@ class Mesh {
int32_t* materials, double* volumes) const;

//! Determine volume and bounding boxes of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand Down
5 changes: 4 additions & 1 deletion openmc/lib/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.0

Expand Down
5 changes: 4 additions & 1 deletion openmc/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,7 +451,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.1

Expand Down
13 changes: 8 additions & 5 deletions src/dagmc.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,12 +857,15 @@ std::pair<double, int32_t> DAGCell::distance(
dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
} else if (!dagmc_ptr_->is_implicit_complement(vol) ||
is_root_universe(dag_univ->id_)) {
// surface boundary conditions are ignored for projection plotting, meaning
// Surface boundary conditions are ignored for projection plotting, meaning
// that the particle may move through the graveyard (bounding) volume and
// into the implicit complement on the other side where no intersection will
// be found. Treating this as a lost particle is problematic when plotting.
// Instead, the infinite distance and invalid surface index are returned.
if (settings::run_mode == RunMode::PLOTTING)
// into the implicit complement on the other side where no intersection
// will be found. A no-hit result is also expected when querying root cells
// for the next boundary from undefined space, when no containing cell is
// assigned. In both cases, return an infinite distance and invalid surface
// index rather than marking a particle as lost.
if (settings::run_mode == RunMode::PLOTTING ||
p->lowest_coord().cell() == C_NONE)
Comment thread
pshriwise marked this conversation as resolved.
return {INFTY, -1};

// the particle should be marked as lost immediately if an intersection
Expand Down
10 changes: 10 additions & 0 deletions src/geometry.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -281,6 +281,16 @@ bool find_cell_inner(
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
{

#ifdef OPENMC_DAGMC_ENABLED
Comment thread
pshriwise marked this conversation as resolved.
// A CSG crossing can move the particle into another instance of the same
// DAGMC universe, where the previous facet history is no longer valid.
if (p.surface() != SURFACE_NONE) {
const auto& surf = model::surfaces[p.surface_index()];
if (surf->geom_type() == GeometryType::CSG)
p.history().reset();
}
#endif

// Reset all the deeper coordinate levels.
for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
p.coord(i).reset();
Expand Down
222 changes: 152 additions & 70 deletions src/mesh.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -482,9 +482,6 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
width.y = (ny > 0) ? width.y / ny : 0.0;
width.z = (nz > 0) ? width.z / nz : 0.0;

// Set flag for mesh being contained within model
bool out_of_model = false;

#pragma omp parallel
{
// Preallocate vector for mesh indices and length fractions and particle
Expand All@@ -496,6 +493,32 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
site.E = 1.0;
site.particle = ParticleType::neutron();

bool verbose = settings::verbosity >= 10;

// Save the cells occupied immediately before a boundary crossing.
auto save_cell_state = [&p]() {
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
};

// Initialize cell history after locating a ray inside the model.
auto initialize_cell_state = [&p, &save_cell_state]() {
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();

save_cell_state();
};

// Reset a failed coordinate search while preserving position and direction.
auto reset_geometry_state = [&p]() {
Position r = p.r();
Direction u = p.u();
p.init_from_r_u(r, u);
p.coord(0).universe() = model::root_universe;
};

for (int axis = 0; axis < 3; ++axis) {
// Set starting position and direction
site.r = {0.0, 0.0, 0.0};
Expand DownExpand Up@@ -524,6 +547,50 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int i1_start = mpi::rank * min_work + std::min(mpi::rank, remainder);
int i1_end = i1_start + n1_local;

// Add the contribution from a ray segment. The positions used here are
// kept separate from the particle position because the latter is moved a
// tiny distance across each surface for robust geometry searches.
auto add_segment = [&](const Position& r0, const Position& r1,
Comment thread
pshriwise marked this conversation as resolved.
int i_material) {
double distance = r1[axis] - r0[axis];
if (distance <= 0.0)
return;

bins.clear();
length_fractions.clear();
this->bins_crossed(r0, r1, site.u, bins, length_fractions);

double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];

Position contrib_min = site.r;
Position contrib_max = site.r;

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;

result.add_volume(mesh_index, i_material, volume, &contrib_bbox);
} else {
result.add_volume(mesh_index, i_material, volume);
}
}
};

// Loop over rays on face of bounding box
#pragma omp for collapse(2)
for (int i1 = i1_start; i1 < i1_end; ++i1) {
Expand All@@ -533,98 +600,115 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,

p.from_source(&site);

// Set the physical endpoint of this ray at the far mesh face.
Position r_mesh_end = site.r;
r_mesh_end[axis] = bbox.max[axis];

// Determine particle's location
if (!exhaustive_find_cell(p)) {
out_of_model = true;
continue;
bool inside_model = exhaustive_find_cell(p, verbose);

if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial descent into nested universes before searching
// for the first root-universe boundary from undefined space.
reset_geometry_state();
}

// Set birth cell attribute
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();
// Physical position through which volume has been accumulated. This
// differs by TINY_BIT from p.r() after crossing a surface.
Position r_scored = site.r;

while (r_scored[axis] < r_mesh_end[axis]) {
if (!inside_model) {
// The ray is outside the model. Advance to the next surface of
// any cell in the root universe, as is done for ray-traced
// plots. Undefined space traversed along the way is void.
Position r0 = p.r();
p.advance_to_boundary_from_void();

// If no model surface lies before the mesh edge, score the
// remaining exterior interval as void and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - r0[axis];
if (p.boundary().surface() == SURFACE_NONE ||
p.boundary().distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, MATERIAL_VOID);
break;
}

// Initialize last cells from current cell
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Determine the physical position of the model boundary.
Position r_boundary = r0 + p.boundary().distance() * p.u();

while (true) {
// Ray trace from r_start to r_end
Position r0 = p.r();
double max_distance = bbox.max[axis] - r0[axis];
// Score the exterior interval and record its physical endpoint.
add_segment(r_scored, r_boundary, MATERIAL_VOID);
r_scored = r_boundary;

// Check whether advancing through the surface entered the model.
inside_model = exhaustive_find_cell(p, verbose);
if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial coordinate search before looking for the
// next surface from undefined space.
reset_geometry_state();
}
continue;
}

// Find the distance to the nearest boundary
BoundaryInfo boundary = distance_to_boundary(p);

// Advance particle forward
double distance = std::min(boundary.distance(), max_distance);
p.move_distance(distance);

// Determine what mesh elements were crossed by particle
bins.clear();
length_fractions.clear();
this->bins_crossed(r0, p.r(), p.u(), bins, length_fractions);

// Add volumes to any mesh elements that were crossed
// Convert the material index to a user-facing ID
int i_material = p.material();
if (i_material != C_NONE) {
i_material = model::materials[i_material]->id();
}
double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];
// If no model boundary lies before the mesh edge, score the
// remaining material interval and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - p.r()[axis];
if (boundary.distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, i_material);
break;
}

Position contrib_min = site.r;
Position contrib_max = site.r;
// Determine the physical position of the model boundary.
Position r_boundary = p.r() + boundary.distance() * p.u();

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);
// Score the material interval and record its physical endpoint.
add_segment(r_scored, r_boundary, i_material);
r_scored = r_boundary;

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;
// Cross the next geometric surface. The small forward movement
// and neighbor-list search mirror Ray::trace, allowing a failed
// search to mean that the ray has left the model rather than that
// a transport particle has been lost.
save_cell_state();

result.add_volume(
mesh_index, i_material, volume, &contrib_bbox);
} else {
// Add volume to result
result.add_volume(mesh_index, i_material, volume);
}
}

if (distance == max_distance)
break;

// cross next geometric surface
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Move just beyond the surface to make the next search robust.
p.move_distance(boundary.distance() + TINY_BIT);

// Set surface that particle is on and adjust coordinate levels
p.surface() = boundary.surface();
p.n_coord() = boundary.coord_level();

// Update the geometry state according to the boundary type.
if (boundary.lattice_translation()[0] != 0 ||
boundary.lattice_translation()[1] != 0 ||
boundary.lattice_translation()[2] != 0) {
// Particle crosses lattice boundary
cross_lattice(p, boundary);
cross_lattice(p, boundary, verbose);
inside_model = true;
} else {
// Particle crosses surface
const auto& surf {model::surfaces[p.surface_index()].get()};
p.cross_surface(*surf);
// Search for the cell on the opposite side of a surface.
inside_model = neighbor_list_find_cell(p, verbose);
}

// Treat a failed cell search as a transition to exterior void.
if (!inside_model) {
// Reset the geometry state so the next iteration can search for
// another disjoint portion of the model.
reset_geometry_state();
}
}
}
Expand All@@ -633,9 +717,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
}

// Check for errors
if (out_of_model) {
throw std::runtime_error("Mesh not fully contained in geometry.");
} else if (result.table_full()) {
if (result.table_full()) {
throw std::runtime_error("Maximum number of materials for mesh material "
"volume calculation insufficient.");
}
Expand Down
6 changes: 0 additions & 6 deletions src/particle.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,12 +663,6 @@ void Particle::cross_surface(const Surface& surf)
write_message(1, " Crossing surface {}", surf.id_);
}

// if we're crossing a CSG surface, make sure the DAG history is reset
#ifdef OPENMC_DAGMC_ENABLED
if (surf.geom_type() == GeometryType::CSG)
history().reset();
#endif

// Handle any applicable boundary conditions.
if (surf.bc_ && settings::run_mode != RunMode::PLOTTING &&
settings::run_mode != RunMode::VOLUME) {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow mesh material volume calculations outside model geometry by paulromano · Pull Request #4028 · openmc-dev/openmc · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/openmc/mesh.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,9 @@ class Mesh {
virtual std::string get_mesh_type() const = 0;

//! Determine volume of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
Comment thread
pshriwise marked this conversation as resolved.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand All@@ -264,6 +267,9 @@ class Mesh {
int32_t* materials, double* volumes) const;

//! Determine volume and bounding boxes of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand Down
5 changes: 4 additions & 1 deletion openmc/lib/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.0

Expand Down
5 changes: 4 additions & 1 deletion openmc/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,7 +451,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.1

Expand Down
13 changes: 8 additions & 5 deletions src/dagmc.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,12 +857,15 @@ std::pair<double, int32_t> DAGCell::distance(
dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
} else if (!dagmc_ptr_->is_implicit_complement(vol) ||
is_root_universe(dag_univ->id_)) {
// surface boundary conditions are ignored for projection plotting, meaning
// Surface boundary conditions are ignored for projection plotting, meaning
// that the particle may move through the graveyard (bounding) volume and
// into the implicit complement on the other side where no intersection will
// be found. Treating this as a lost particle is problematic when plotting.
// Instead, the infinite distance and invalid surface index are returned.
if (settings::run_mode == RunMode::PLOTTING)
// into the implicit complement on the other side where no intersection
// will be found. A no-hit result is also expected when querying root cells
// for the next boundary from undefined space, when no containing cell is
// assigned. In both cases, return an infinite distance and invalid surface
// index rather than marking a particle as lost.
if (settings::run_mode == RunMode::PLOTTING ||
p->lowest_coord().cell() == C_NONE)
Comment thread
pshriwise marked this conversation as resolved.
return {INFTY, -1};

// the particle should be marked as lost immediately if an intersection
Expand Down
10 changes: 10 additions & 0 deletions src/geometry.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -281,6 +281,16 @@ bool find_cell_inner(
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
{

#ifdef OPENMC_DAGMC_ENABLED
Comment thread
pshriwise marked this conversation as resolved.
// A CSG crossing can move the particle into another instance of the same
// DAGMC universe, where the previous facet history is no longer valid.
if (p.surface() != SURFACE_NONE) {
const auto& surf = model::surfaces[p.surface_index()];
if (surf->geom_type() == GeometryType::CSG)
p.history().reset();
}
#endif

// Reset all the deeper coordinate levels.
for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
p.coord(i).reset();
Expand Down
222 changes: 152 additions & 70 deletions src/mesh.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -482,9 +482,6 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
width.y = (ny > 0) ? width.y / ny : 0.0;
width.z = (nz > 0) ? width.z / nz : 0.0;

// Set flag for mesh being contained within model
bool out_of_model = false;

#pragma omp parallel
{
// Preallocate vector for mesh indices and length fractions and particle
Expand All@@ -496,6 +493,32 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
site.E = 1.0;
site.particle = ParticleType::neutron();

bool verbose = settings::verbosity >= 10;

// Save the cells occupied immediately before a boundary crossing.
auto save_cell_state = [&p]() {
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
};

// Initialize cell history after locating a ray inside the model.
auto initialize_cell_state = [&p, &save_cell_state]() {
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();

save_cell_state();
};

// Reset a failed coordinate search while preserving position and direction.
auto reset_geometry_state = [&p]() {
Position r = p.r();
Direction u = p.u();
p.init_from_r_u(r, u);
p.coord(0).universe() = model::root_universe;
};

for (int axis = 0; axis < 3; ++axis) {
// Set starting position and direction
site.r = {0.0, 0.0, 0.0};
Expand DownExpand Up@@ -524,6 +547,50 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int i1_start = mpi::rank * min_work + std::min(mpi::rank, remainder);
int i1_end = i1_start + n1_local;

// Add the contribution from a ray segment. The positions used here are
// kept separate from the particle position because the latter is moved a
// tiny distance across each surface for robust geometry searches.
auto add_segment = [&](const Position& r0, const Position& r1,
Comment thread
pshriwise marked this conversation as resolved.
int i_material) {
double distance = r1[axis] - r0[axis];
if (distance <= 0.0)
return;

bins.clear();
length_fractions.clear();
this->bins_crossed(r0, r1, site.u, bins, length_fractions);

double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];

Position contrib_min = site.r;
Position contrib_max = site.r;

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;

result.add_volume(mesh_index, i_material, volume, &contrib_bbox);
} else {
result.add_volume(mesh_index, i_material, volume);
}
}
};

// Loop over rays on face of bounding box
#pragma omp for collapse(2)
for (int i1 = i1_start; i1 < i1_end; ++i1) {
Expand All@@ -533,98 +600,115 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,

p.from_source(&site);

// Set the physical endpoint of this ray at the far mesh face.
Position r_mesh_end = site.r;
r_mesh_end[axis] = bbox.max[axis];

// Determine particle's location
if (!exhaustive_find_cell(p)) {
out_of_model = true;
continue;
bool inside_model = exhaustive_find_cell(p, verbose);

if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial descent into nested universes before searching
// for the first root-universe boundary from undefined space.
reset_geometry_state();
}

// Set birth cell attribute
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();
// Physical position through which volume has been accumulated. This
// differs by TINY_BIT from p.r() after crossing a surface.
Position r_scored = site.r;

while (r_scored[axis] < r_mesh_end[axis]) {
if (!inside_model) {
// The ray is outside the model. Advance to the next surface of
// any cell in the root universe, as is done for ray-traced
// plots. Undefined space traversed along the way is void.
Position r0 = p.r();
p.advance_to_boundary_from_void();

// If no model surface lies before the mesh edge, score the
// remaining exterior interval as void and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - r0[axis];
if (p.boundary().surface() == SURFACE_NONE ||
p.boundary().distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, MATERIAL_VOID);
break;
}

// Initialize last cells from current cell
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Determine the physical position of the model boundary.
Position r_boundary = r0 + p.boundary().distance() * p.u();

while (true) {
// Ray trace from r_start to r_end
Position r0 = p.r();
double max_distance = bbox.max[axis] - r0[axis];
// Score the exterior interval and record its physical endpoint.
add_segment(r_scored, r_boundary, MATERIAL_VOID);
r_scored = r_boundary;

// Check whether advancing through the surface entered the model.
inside_model = exhaustive_find_cell(p, verbose);
if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial coordinate search before looking for the
// next surface from undefined space.
reset_geometry_state();
}
continue;
}

// Find the distance to the nearest boundary
BoundaryInfo boundary = distance_to_boundary(p);

// Advance particle forward
double distance = std::min(boundary.distance(), max_distance);
p.move_distance(distance);

// Determine what mesh elements were crossed by particle
bins.clear();
length_fractions.clear();
this->bins_crossed(r0, p.r(), p.u(), bins, length_fractions);

// Add volumes to any mesh elements that were crossed
// Convert the material index to a user-facing ID
int i_material = p.material();
if (i_material != C_NONE) {
i_material = model::materials[i_material]->id();
}
double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];
// If no model boundary lies before the mesh edge, score the
// remaining material interval and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - p.r()[axis];
if (boundary.distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, i_material);
break;
}

Position contrib_min = site.r;
Position contrib_max = site.r;
// Determine the physical position of the model boundary.
Position r_boundary = p.r() + boundary.distance() * p.u();

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);
// Score the material interval and record its physical endpoint.
add_segment(r_scored, r_boundary, i_material);
r_scored = r_boundary;

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;
// Cross the next geometric surface. The small forward movement
// and neighbor-list search mirror Ray::trace, allowing a failed
// search to mean that the ray has left the model rather than that
// a transport particle has been lost.
save_cell_state();

result.add_volume(
mesh_index, i_material, volume, &contrib_bbox);
} else {
// Add volume to result
result.add_volume(mesh_index, i_material, volume);
}
}

if (distance == max_distance)
break;

// cross next geometric surface
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Move just beyond the surface to make the next search robust.
p.move_distance(boundary.distance() + TINY_BIT);

// Set surface that particle is on and adjust coordinate levels
p.surface() = boundary.surface();
p.n_coord() = boundary.coord_level();

// Update the geometry state according to the boundary type.
if (boundary.lattice_translation()[0] != 0 ||
boundary.lattice_translation()[1] != 0 ||
boundary.lattice_translation()[2] != 0) {
// Particle crosses lattice boundary
cross_lattice(p, boundary);
cross_lattice(p, boundary, verbose);
inside_model = true;
} else {
// Particle crosses surface
const auto& surf {model::surfaces[p.surface_index()].get()};
p.cross_surface(*surf);
// Search for the cell on the opposite side of a surface.
inside_model = neighbor_list_find_cell(p, verbose);
}

// Treat a failed cell search as a transition to exterior void.
if (!inside_model) {
// Reset the geometry state so the next iteration can search for
// another disjoint portion of the model.
reset_geometry_state();
}
}
}
Expand All@@ -633,9 +717,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
}

// Check for errors
if (out_of_model) {
throw std::runtime_error("Mesh not fully contained in geometry.");
} else if (result.table_full()) {
if (result.table_full()) {
throw std::runtime_error("Maximum number of materials for mesh material "
"volume calculation insufficient.");
}
Expand Down
6 changes: 0 additions & 6 deletions src/particle.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,12 +663,6 @@ void Particle::cross_surface(const Surface& surf)
write_message(1, " Crossing surface {}", surf.id_);
}

// if we're crossing a CSG surface, make sure the DAG history is reset
#ifdef OPENMC_DAGMC_ENABLED
if (surf.geom_type() == GeometryType::CSG)
history().reset();
#endif

// Handle any applicable boundary conditions.
if (surf.bc_ && settings::run_mode != RunMode::PLOTTING &&
settings::run_mode != RunMode::VOLUME) {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow mesh material volume calculations outside model geometry by paulromano · Pull Request #4028 · openmc-dev/openmc · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/openmc/mesh.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,9 @@ class Mesh {
virtual std::string get_mesh_type() const = 0;

//! Determine volume of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
Comment thread
pshriwise marked this conversation as resolved.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand All@@ -264,6 +267,9 @@ class Mesh {
int32_t* materials, double* volumes) const;

//! Determine volume and bounding boxes of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand Down
5 changes: 4 additions & 1 deletion openmc/lib/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.0

Expand Down
5 changes: 4 additions & 1 deletion openmc/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,7 +451,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.1

Expand Down
13 changes: 8 additions & 5 deletions src/dagmc.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,12 +857,15 @@ std::pair<double, int32_t> DAGCell::distance(
dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
} else if (!dagmc_ptr_->is_implicit_complement(vol) ||
is_root_universe(dag_univ->id_)) {
// surface boundary conditions are ignored for projection plotting, meaning
// Surface boundary conditions are ignored for projection plotting, meaning
// that the particle may move through the graveyard (bounding) volume and
// into the implicit complement on the other side where no intersection will
// be found. Treating this as a lost particle is problematic when plotting.
// Instead, the infinite distance and invalid surface index are returned.
if (settings::run_mode == RunMode::PLOTTING)
// into the implicit complement on the other side where no intersection
// will be found. A no-hit result is also expected when querying root cells
// for the next boundary from undefined space, when no containing cell is
// assigned. In both cases, return an infinite distance and invalid surface
// index rather than marking a particle as lost.
if (settings::run_mode == RunMode::PLOTTING ||
p->lowest_coord().cell() == C_NONE)
Comment thread
pshriwise marked this conversation as resolved.
return {INFTY, -1};

// the particle should be marked as lost immediately if an intersection
Expand Down
10 changes: 10 additions & 0 deletions src/geometry.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -281,6 +281,16 @@ bool find_cell_inner(
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
{

#ifdef OPENMC_DAGMC_ENABLED
Comment thread
pshriwise marked this conversation as resolved.
// A CSG crossing can move the particle into another instance of the same
// DAGMC universe, where the previous facet history is no longer valid.
if (p.surface() != SURFACE_NONE) {
const auto& surf = model::surfaces[p.surface_index()];
if (surf->geom_type() == GeometryType::CSG)
p.history().reset();
}
#endif

// Reset all the deeper coordinate levels.
for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
p.coord(i).reset();
Expand Down
222 changes: 152 additions & 70 deletions src/mesh.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -482,9 +482,6 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
width.y = (ny > 0) ? width.y / ny : 0.0;
width.z = (nz > 0) ? width.z / nz : 0.0;

// Set flag for mesh being contained within model
bool out_of_model = false;

#pragma omp parallel
{
// Preallocate vector for mesh indices and length fractions and particle
Expand All@@ -496,6 +493,32 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
site.E = 1.0;
site.particle = ParticleType::neutron();

bool verbose = settings::verbosity >= 10;

// Save the cells occupied immediately before a boundary crossing.
auto save_cell_state = [&p]() {
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
};

// Initialize cell history after locating a ray inside the model.
auto initialize_cell_state = [&p, &save_cell_state]() {
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();

save_cell_state();
};

// Reset a failed coordinate search while preserving position and direction.
auto reset_geometry_state = [&p]() {
Position r = p.r();
Direction u = p.u();
p.init_from_r_u(r, u);
p.coord(0).universe() = model::root_universe;
};

for (int axis = 0; axis < 3; ++axis) {
// Set starting position and direction
site.r = {0.0, 0.0, 0.0};
Expand DownExpand Up@@ -524,6 +547,50 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int i1_start = mpi::rank * min_work + std::min(mpi::rank, remainder);
int i1_end = i1_start + n1_local;

// Add the contribution from a ray segment. The positions used here are
// kept separate from the particle position because the latter is moved a
// tiny distance across each surface for robust geometry searches.
auto add_segment = [&](const Position& r0, const Position& r1,
Comment thread
pshriwise marked this conversation as resolved.
int i_material) {
double distance = r1[axis] - r0[axis];
if (distance <= 0.0)
return;

bins.clear();
length_fractions.clear();
this->bins_crossed(r0, r1, site.u, bins, length_fractions);

double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];

Position contrib_min = site.r;
Position contrib_max = site.r;

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;

result.add_volume(mesh_index, i_material, volume, &contrib_bbox);
} else {
result.add_volume(mesh_index, i_material, volume);
}
}
};

// Loop over rays on face of bounding box
#pragma omp for collapse(2)
for (int i1 = i1_start; i1 < i1_end; ++i1) {
Expand All@@ -533,98 +600,115 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,

p.from_source(&site);

// Set the physical endpoint of this ray at the far mesh face.
Position r_mesh_end = site.r;
r_mesh_end[axis] = bbox.max[axis];

// Determine particle's location
if (!exhaustive_find_cell(p)) {
out_of_model = true;
continue;
bool inside_model = exhaustive_find_cell(p, verbose);

if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial descent into nested universes before searching
// for the first root-universe boundary from undefined space.
reset_geometry_state();
}

// Set birth cell attribute
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();
// Physical position through which volume has been accumulated. This
// differs by TINY_BIT from p.r() after crossing a surface.
Position r_scored = site.r;

while (r_scored[axis] < r_mesh_end[axis]) {
if (!inside_model) {
// The ray is outside the model. Advance to the next surface of
// any cell in the root universe, as is done for ray-traced
// plots. Undefined space traversed along the way is void.
Position r0 = p.r();
p.advance_to_boundary_from_void();

// If no model surface lies before the mesh edge, score the
// remaining exterior interval as void and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - r0[axis];
if (p.boundary().surface() == SURFACE_NONE ||
p.boundary().distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, MATERIAL_VOID);
break;
}

// Initialize last cells from current cell
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Determine the physical position of the model boundary.
Position r_boundary = r0 + p.boundary().distance() * p.u();

while (true) {
// Ray trace from r_start to r_end
Position r0 = p.r();
double max_distance = bbox.max[axis] - r0[axis];
// Score the exterior interval and record its physical endpoint.
add_segment(r_scored, r_boundary, MATERIAL_VOID);
r_scored = r_boundary;

// Check whether advancing through the surface entered the model.
inside_model = exhaustive_find_cell(p, verbose);
if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial coordinate search before looking for the
// next surface from undefined space.
reset_geometry_state();
}
continue;
}

// Find the distance to the nearest boundary
BoundaryInfo boundary = distance_to_boundary(p);

// Advance particle forward
double distance = std::min(boundary.distance(), max_distance);
p.move_distance(distance);

// Determine what mesh elements were crossed by particle
bins.clear();
length_fractions.clear();
this->bins_crossed(r0, p.r(), p.u(), bins, length_fractions);

// Add volumes to any mesh elements that were crossed
// Convert the material index to a user-facing ID
int i_material = p.material();
if (i_material != C_NONE) {
i_material = model::materials[i_material]->id();
}
double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];
// If no model boundary lies before the mesh edge, score the
// remaining material interval and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - p.r()[axis];
if (boundary.distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, i_material);
break;
}

Position contrib_min = site.r;
Position contrib_max = site.r;
// Determine the physical position of the model boundary.
Position r_boundary = p.r() + boundary.distance() * p.u();

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);
// Score the material interval and record its physical endpoint.
add_segment(r_scored, r_boundary, i_material);
r_scored = r_boundary;

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;
// Cross the next geometric surface. The small forward movement
// and neighbor-list search mirror Ray::trace, allowing a failed
// search to mean that the ray has left the model rather than that
// a transport particle has been lost.
save_cell_state();

result.add_volume(
mesh_index, i_material, volume, &contrib_bbox);
} else {
// Add volume to result
result.add_volume(mesh_index, i_material, volume);
}
}

if (distance == max_distance)
break;

// cross next geometric surface
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Move just beyond the surface to make the next search robust.
p.move_distance(boundary.distance() + TINY_BIT);

// Set surface that particle is on and adjust coordinate levels
p.surface() = boundary.surface();
p.n_coord() = boundary.coord_level();

// Update the geometry state according to the boundary type.
if (boundary.lattice_translation()[0] != 0 ||
boundary.lattice_translation()[1] != 0 ||
boundary.lattice_translation()[2] != 0) {
// Particle crosses lattice boundary
cross_lattice(p, boundary);
cross_lattice(p, boundary, verbose);
inside_model = true;
} else {
// Particle crosses surface
const auto& surf {model::surfaces[p.surface_index()].get()};
p.cross_surface(*surf);
// Search for the cell on the opposite side of a surface.
inside_model = neighbor_list_find_cell(p, verbose);
}

// Treat a failed cell search as a transition to exterior void.
if (!inside_model) {
// Reset the geometry state so the next iteration can search for
// another disjoint portion of the model.
reset_geometry_state();
}
}
}
Expand All@@ -633,9 +717,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
}

// Check for errors
if (out_of_model) {
throw std::runtime_error("Mesh not fully contained in geometry.");
} else if (result.table_full()) {
if (result.table_full()) {
throw std::runtime_error("Maximum number of materials for mesh material "
"volume calculation insufficient.");
}
Expand Down
6 changes: 0 additions & 6 deletions src/particle.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,12 +663,6 @@ void Particle::cross_surface(const Surface& surf)
write_message(1, " Crossing surface {}", surf.id_);
}

// if we're crossing a CSG surface, make sure the DAG history is reset
#ifdef OPENMC_DAGMC_ENABLED
if (surf.geom_type() == GeometryType::CSG)
history().reset();
#endif

// Handle any applicable boundary conditions.
if (surf.bc_ && settings::run_mode != RunMode::PLOTTING &&
settings::run_mode != RunMode::VOLUME) {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Allow mesh material volume calculations outside model geometry by paulromano · Pull Request #4028 · openmc-dev/openmc · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/openmc/mesh.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,9 @@ class Mesh {
virtual std::string get_mesh_type() const = 0;

//! Determine volume of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
Comment thread
pshriwise marked this conversation as resolved.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand All@@ -264,6 +267,9 @@ class Mesh {
int32_t* materials, double* volumes) const;

//! Determine volume and bounding boxes of materials within each mesh element
//!
//! Portions of mesh elements outside the model geometry are treated as void.
//! Universe fills within the model must still define all enclosed space.
//
//! \param[in] nx Number of samples in x direction
//! \param[in] ny Number of samples in y direction
Expand Down
5 changes: 4 additions & 1 deletion openmc/lib/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.0

Expand Down
5 changes: 4 additions & 1 deletion openmc/mesh.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,7 +451,10 @@ def material_volumes(
This method works by raytracing repeatedly through the mesh to count the
estimated volume of each material in all mesh elements. Three sets of
rays are used: one set parallel to the x-axis, one parallel to the
y-axis, and one parallel to the z-axis.
y-axis, and one parallel to the z-axis. Regions of the mesh that are
outside the model geometry are treated as void, equivalent to a cell
with no material. Universe fills within the model must still define all
enclosed space.

.. versionadded:: 0.15.1

Expand Down
13 changes: 8 additions & 5 deletions src/dagmc.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -857,12 +857,15 @@ std::pair<double, int32_t> DAGCell::distance(
dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
} else if (!dagmc_ptr_->is_implicit_complement(vol) ||
is_root_universe(dag_univ->id_)) {
// surface boundary conditions are ignored for projection plotting, meaning
// Surface boundary conditions are ignored for projection plotting, meaning
// that the particle may move through the graveyard (bounding) volume and
// into the implicit complement on the other side where no intersection will
// be found. Treating this as a lost particle is problematic when plotting.
// Instead, the infinite distance and invalid surface index are returned.
if (settings::run_mode == RunMode::PLOTTING)
// into the implicit complement on the other side where no intersection
// will be found. A no-hit result is also expected when querying root cells
// for the next boundary from undefined space, when no containing cell is
// assigned. In both cases, return an infinite distance and invalid surface
// index rather than marking a particle as lost.
if (settings::run_mode == RunMode::PLOTTING ||
p->lowest_coord().cell() == C_NONE)
Comment thread
pshriwise marked this conversation as resolved.
return {INFTY, -1};

// the particle should be marked as lost immediately if an intersection
Expand Down
10 changes: 10 additions & 0 deletions src/geometry.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -281,6 +281,16 @@ bool find_cell_inner(
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
{

#ifdef OPENMC_DAGMC_ENABLED
Comment thread
pshriwise marked this conversation as resolved.
// A CSG crossing can move the particle into another instance of the same
// DAGMC universe, where the previous facet history is no longer valid.
if (p.surface() != SURFACE_NONE) {
const auto& surf = model::surfaces[p.surface_index()];
if (surf->geom_type() == GeometryType::CSG)
p.history().reset();
}
#endif

// Reset all the deeper coordinate levels.
for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
p.coord(i).reset();
Expand Down
222 changes: 152 additions & 70 deletions src/mesh.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -482,9 +482,6 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
width.y = (ny > 0) ? width.y / ny : 0.0;
width.z = (nz > 0) ? width.z / nz : 0.0;

// Set flag for mesh being contained within model
bool out_of_model = false;

#pragma omp parallel
{
// Preallocate vector for mesh indices and length fractions and particle
Expand All@@ -496,6 +493,32 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
site.E = 1.0;
site.particle = ParticleType::neutron();

bool verbose = settings::verbosity >= 10;

// Save the cells occupied immediately before a boundary crossing.
auto save_cell_state = [&p]() {
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
};

// Initialize cell history after locating a ray inside the model.
auto initialize_cell_state = [&p, &save_cell_state]() {
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();

save_cell_state();
};

// Reset a failed coordinate search while preserving position and direction.
auto reset_geometry_state = [&p]() {
Position r = p.r();
Direction u = p.u();
p.init_from_r_u(r, u);
p.coord(0).universe() = model::root_universe;
};

for (int axis = 0; axis < 3; ++axis) {
// Set starting position and direction
site.r = {0.0, 0.0, 0.0};
Expand DownExpand Up@@ -524,6 +547,50 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int i1_start = mpi::rank * min_work + std::min(mpi::rank, remainder);
int i1_end = i1_start + n1_local;

// Add the contribution from a ray segment. The positions used here are
// kept separate from the particle position because the latter is moved a
// tiny distance across each surface for robust geometry searches.
auto add_segment = [&](const Position& r0, const Position& r1,
Comment thread
pshriwise marked this conversation as resolved.
int i_material) {
double distance = r1[axis] - r0[axis];
if (distance <= 0.0)
return;

bins.clear();
length_fractions.clear();
this->bins_crossed(r0, r1, site.u, bins, length_fractions);

double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];

Position contrib_min = site.r;
Position contrib_max = site.r;

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;

result.add_volume(mesh_index, i_material, volume, &contrib_bbox);
} else {
result.add_volume(mesh_index, i_material, volume);
}
}
};

// Loop over rays on face of bounding box
#pragma omp for collapse(2)
for (int i1 = i1_start; i1 < i1_end; ++i1) {
Expand All@@ -533,98 +600,115 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,

p.from_source(&site);

// Set the physical endpoint of this ray at the far mesh face.
Position r_mesh_end = site.r;
r_mesh_end[axis] = bbox.max[axis];

// Determine particle's location
if (!exhaustive_find_cell(p)) {
out_of_model = true;
continue;
bool inside_model = exhaustive_find_cell(p, verbose);

if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial descent into nested universes before searching
// for the first root-universe boundary from undefined space.
reset_geometry_state();
}

// Set birth cell attribute
if (p.cell_born() == C_NONE)
p.cell_born() = p.lowest_coord().cell();
// Physical position through which volume has been accumulated. This
// differs by TINY_BIT from p.r() after crossing a surface.
Position r_scored = site.r;

while (r_scored[axis] < r_mesh_end[axis]) {
if (!inside_model) {
// The ray is outside the model. Advance to the next surface of
// any cell in the root universe, as is done for ray-traced
// plots. Undefined space traversed along the way is void.
Position r0 = p.r();
p.advance_to_boundary_from_void();

// If no model surface lies before the mesh edge, score the
// remaining exterior interval as void and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - r0[axis];
if (p.boundary().surface() == SURFACE_NONE ||
p.boundary().distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, MATERIAL_VOID);
break;
}

// Initialize last cells from current cell
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Determine the physical position of the model boundary.
Position r_boundary = r0 + p.boundary().distance() * p.u();

while (true) {
// Ray trace from r_start to r_end
Position r0 = p.r();
double max_distance = bbox.max[axis] - r0[axis];
// Score the exterior interval and record its physical endpoint.
add_segment(r_scored, r_boundary, MATERIAL_VOID);
r_scored = r_boundary;

// Check whether advancing through the surface entered the model.
inside_model = exhaustive_find_cell(p, verbose);
if (inside_model) {
initialize_cell_state();
} else {
// Clear any partial coordinate search before looking for the
// next surface from undefined space.
reset_geometry_state();
}
continue;
}

// Find the distance to the nearest boundary
BoundaryInfo boundary = distance_to_boundary(p);

// Advance particle forward
double distance = std::min(boundary.distance(), max_distance);
p.move_distance(distance);

// Determine what mesh elements were crossed by particle
bins.clear();
length_fractions.clear();
this->bins_crossed(r0, p.r(), p.u(), bins, length_fractions);

// Add volumes to any mesh elements that were crossed
// Convert the material index to a user-facing ID
int i_material = p.material();
if (i_material != C_NONE) {
i_material = model::materials[i_material]->id();
}
double cumulative_frac = 0.0;
for (int i_bin = 0; i_bin < bins.size(); i_bin++) {
int mesh_index = bins[i_bin];
double length = distance * length_fractions[i_bin];
double volume = length * d1 * d2;

if (compute_bboxes) {
double axis_start = r0[axis] + distance * cumulative_frac;
double axis_end = axis_start + length;
cumulative_frac += length_fractions[i_bin];
// If no model boundary lies before the mesh edge, score the
// remaining material interval and finish the ray.
double distance_to_mesh_end = r_mesh_end[axis] - p.r()[axis];
if (boundary.distance() >= distance_to_mesh_end) {
add_segment(r_scored, r_mesh_end, i_material);
break;
}

Position contrib_min = site.r;
Position contrib_max = site.r;
// Determine the physical position of the model boundary.
Position r_boundary = p.r() + boundary.distance() * p.u();

contrib_min[ax1] = site.r[ax1] - 0.5 * d1;
contrib_max[ax1] = site.r[ax1] + 0.5 * d1;
contrib_min[ax2] = site.r[ax2] - 0.5 * d2;
contrib_max[ax2] = site.r[ax2] + 0.5 * d2;
contrib_min[axis] = std::min(axis_start, axis_end);
contrib_max[axis] = std::max(axis_start, axis_end);
// Score the material interval and record its physical endpoint.
add_segment(r_scored, r_boundary, i_material);
r_scored = r_boundary;

BoundingBox contrib_bbox {contrib_min, contrib_max};
contrib_bbox &= bbox;
// Cross the next geometric surface. The small forward movement
// and neighbor-list search mirror Ray::trace, allowing a failed
// search to mean that the ray has left the model rather than that
// a transport particle has been lost.
save_cell_state();

result.add_volume(
mesh_index, i_material, volume, &contrib_bbox);
} else {
// Add volume to result
result.add_volume(mesh_index, i_material, volume);
}
}

if (distance == max_distance)
break;

// cross next geometric surface
for (int j = 0; j < p.n_coord(); ++j) {
p.cell_last(j) = p.coord(j).cell();
}
p.n_coord_last() = p.n_coord();
// Move just beyond the surface to make the next search robust.
p.move_distance(boundary.distance() + TINY_BIT);

// Set surface that particle is on and adjust coordinate levels
p.surface() = boundary.surface();
p.n_coord() = boundary.coord_level();

// Update the geometry state according to the boundary type.
if (boundary.lattice_translation()[0] != 0 ||
boundary.lattice_translation()[1] != 0 ||
boundary.lattice_translation()[2] != 0) {
// Particle crosses lattice boundary
cross_lattice(p, boundary);
cross_lattice(p, boundary, verbose);
inside_model = true;
} else {
// Particle crosses surface
const auto& surf {model::surfaces[p.surface_index()].get()};
p.cross_surface(*surf);
// Search for the cell on the opposite side of a surface.
inside_model = neighbor_list_find_cell(p, verbose);
}

// Treat a failed cell search as a transition to exterior void.
if (!inside_model) {
// Reset the geometry state so the next iteration can search for
// another disjoint portion of the model.
reset_geometry_state();
}
}
}
Expand All@@ -633,9 +717,7 @@ void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
}

// Check for errors
if (out_of_model) {
throw std::runtime_error("Mesh not fully contained in geometry.");
} else if (result.table_full()) {
if (result.table_full()) {
throw std::runtime_error("Maximum number of materials for mesh material "
"volume calculation insufficient.");
}
Expand Down
6 changes: 0 additions & 6 deletions src/particle.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,12 +663,6 @@ void Particle::cross_surface(const Surface& surf)
write_message(1, " Crossing surface {}", surf.id_);
}

// if we're crossing a CSG surface, make sure the DAG history is reset
#ifdef OPENMC_DAGMC_ENABLED
if (surf.geom_type() == GeometryType::CSG)
history().reset();
#endif

// Handle any applicable boundary conditions.
if (surf.bc_ && settings::run_mode != RunMode::PLOTTING &&
settings::run_mode != RunMode::VOLUME) {
Expand Down
Loading
Loading