Skip to content

Commit a4b6b81

Browse files
jtrammpaulromano
authored and
Grego01
committed
Random Ray Point Source Locator (openmc-dev#3360)
Co-authored-by: Paul Romano <[email protected]>
1 parent 6433399 commit a4b6b81

File tree

9 files changed

+435
-33
lines changed

9 files changed

+435
-33
lines changed

Diff for: docs/source/methods/random_ray.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,8 @@ random ray and Monte Carlo, however.
10521052
regions. Thus, in the OpenMC implementation of random ray, particle sources
10531053
are restricted to being volumetric and isotropic, although different energy
10541054
spectrums are supported. Fixed sources can be applied to specific materials,
1055-
cells, or universes.
1055+
cells, or universes. Point sources are "smeared" to fill the volume of the
1056+
source region that contains the point source coordinate.
10561057

10571058
- **Inactive batches:** In Monte Carlo, use of a fixed source implies that all
10581059
batches are active batches, as there is no longer a need to develop a fission

Diff for: docs/source/usersguide/random_ray.rst

+6-3
Original file line numberDiff line numberDiff line change
@@ -919,9 +919,12 @@ Monte Carlo solver.
919919
Currently, all of the following conditions must be met for the particle source
920920
to be valid in random ray mode:
921921

922-
- One or more domain ids must be specified that indicate which cells, universes,
923-
or materials the source applies to. This implicitly limits the source type to
924-
being volumetric. This is specified via the ``domains`` constraint placed on the
922+
- Either a point source must be used, or a domain constraint must be specified
923+
that indicates which cells, universes, or materials the source applies to. In
924+
either case, this implicitly limits the source type to being volumetric, as
925+
even in the point source case the source will be "smeared" throughout the
926+
source region that contains the point source coordinate. A source domain is
927+
specified via the ``domains`` constraint placed on the
925928
:class:`openmc.IndependentSource` Python class.
926929
- The source must be isotropic (default for a source)
927930
- The source must use a discrete (i.e., multigroup) energy distribution. The

Diff for: include/openmc/random_ray/flat_source_domain.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ class FlatSourceDomain {
131131
std::unordered_map<SourceRegionKey, int64_t, SourceRegionKey::HashFunctor>
132132
source_region_map_;
133133

134+
// Map that relates a SourceRegionKey to the external source index. This map
135+
// is used to check if there are any point sources within a subdivided source
136+
// region at the time it is discovered.
137+
std::unordered_map<SourceRegionKey, int64_t, SourceRegionKey::HashFunctor>
138+
point_source_map_;
139+
134140
// If transport corrected MGXS data is being used, there may be negative
135141
// in-group scattering cross sections that can result in instability in MOC
136142
// and random ray if used naively. This flag enables a stabilization
@@ -141,7 +147,7 @@ class FlatSourceDomain {
141147
//----------------------------------------------------------------------------
142148
// Methods
143149
void apply_external_source_to_source_region(
144-
Discrete* discrete, double strength_factor, int64_t sr);
150+
Discrete* discrete, double strength_factor, SourceRegionHandle& srh);
145151
void apply_external_source_to_cell_instances(int32_t i_cell,
146152
Discrete* discrete, double strength_factor, int target_material_id,
147153
const vector<int32_t>& instances);

Diff for: src/random_ray/flat_source_domain.cpp

+96-23
Original file line numberDiff line numberDiff line change
@@ -946,17 +946,16 @@ void FlatSourceDomain::output_to_vtk() const
946946
}
947947

948948
void FlatSourceDomain::apply_external_source_to_source_region(
949-
Discrete* discrete, double strength_factor, int64_t sr)
949+
Discrete* discrete, double strength_factor, SourceRegionHandle& srh)
950950
{
951-
source_regions_.external_source_present(sr) = 1;
951+
srh.external_source_present() = 1;
952952

953953
const auto& discrete_energies = discrete->x();
954954
const auto& discrete_probs = discrete->prob();
955955

956956
for (int i = 0; i < discrete_energies.size(); i++) {
957957
int g = data::mg.get_group_index(discrete_energies[i]);
958-
source_regions_.external_source(sr, g) +=
959-
discrete_probs[i] * strength_factor;
958+
srh.external_source(g) += discrete_probs[i] * strength_factor;
960959
}
961960
}
962961

@@ -980,8 +979,9 @@ void FlatSourceDomain::apply_external_source_to_cell_instances(int32_t i_cell,
980979
if (target_material_id == C_NONE ||
981980
cell_material_id == target_material_id) {
982981
int64_t source_region = source_region_offsets_[i_cell] + j;
983-
apply_external_source_to_source_region(
984-
discrete, strength_factor, source_region);
982+
SourceRegionHandle srh =
983+
source_regions_.get_source_region_handle(source_region);
984+
apply_external_source_to_source_region(discrete, strength_factor, srh);
985985
}
986986
}
987987
}
@@ -1023,34 +1023,88 @@ void FlatSourceDomain::convert_external_sources()
10231023
{
10241024
// Loop over external sources
10251025
for (int es = 0; es < model::external_sources.size(); es++) {
1026+
1027+
// Extract source information
10261028
Source* s = model::external_sources[es].get();
10271029
IndependentSource* is = dynamic_cast<IndependentSource*>(s);
10281030
Discrete* energy = dynamic_cast<Discrete*>(is->energy());
10291031
const std::unordered_set<int32_t>& domain_ids = is->domain_ids();
1030-
10311032
double strength_factor = is->strength();
10321033

1033-
if (is->domain_type() == Source::DomainType::MATERIAL) {
1034-
for (int32_t material_id : domain_ids) {
1035-
for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) {
1036-
apply_external_source_to_cell_and_children(
1037-
i_cell, energy, strength_factor, material_id);
1038-
}
1034+
// If there is no domain constraint specified, then this must be a point
1035+
// source. In this case, we need to find the source region that contains the
1036+
// point source and apply or relate it to the external source.
1037+
if (is->domain_ids().size() == 0) {
1038+
1039+
// Extract the point source coordinate and find the base source region at
1040+
// that point
1041+
auto sp = dynamic_cast<SpatialPoint*>(is->space());
1042+
GeometryState gs;
1043+
gs.r() = sp->r();
1044+
gs.r_last() = sp->r();
1045+
gs.u() = {1.0, 0.0, 0.0};
1046+
bool found = exhaustive_find_cell(gs);
1047+
if (!found) {
1048+
fatal_error(fmt::format("Could not find cell containing external "
1049+
"point source at {}",
1050+
sp->r()));
10391051
}
1040-
} else if (is->domain_type() == Source::DomainType::CELL) {
1041-
for (int32_t cell_id : domain_ids) {
1042-
int32_t i_cell = model::cell_map[cell_id];
1043-
apply_external_source_to_cell_and_children(
1044-
i_cell, energy, strength_factor, C_NONE);
1052+
int i_cell = gs.lowest_coord().cell;
1053+
int64_t sr = source_region_offsets_[i_cell] + gs.cell_instance();
1054+
1055+
if (RandomRay::mesh_subdivision_enabled_) {
1056+
// If mesh subdivision is enabled, we need to determine which subdivided
1057+
// mesh bin the point source coordinate is in as well
1058+
int mesh_idx = source_regions_.mesh(sr);
1059+
int mesh_bin;
1060+
if (mesh_idx == C_NONE) {
1061+
mesh_bin = 0;
1062+
} else {
1063+
mesh_bin = model::meshes[mesh_idx]->get_bin(gs.r());
1064+
}
1065+
// With the source region and mesh bin known, we can use the
1066+
// accompanying SourceRegionKey as a key into a map that stores the
1067+
// corresponding external source index for the point source. Notably, we
1068+
// do not actually apply the external source to any source regions here,
1069+
// as if mesh subdivision is enabled, they haven't actually been
1070+
// discovered & initilized yet. When discovered, they will read from the
1071+
// point_source_map to determine if there are any point source terms
1072+
// that should be applied.
1073+
SourceRegionKey key {sr, mesh_bin};
1074+
point_source_map_[key] = es;
1075+
} else {
1076+
// If we are not using mesh subdivision, we can apply the external
1077+
// source directly to the source region as we do for volumetric domain
1078+
// constraint sources.
1079+
SourceRegionHandle srh = source_regions_.get_source_region_handle(sr);
1080+
apply_external_source_to_source_region(energy, strength_factor, srh);
10451081
}
1046-
} else if (is->domain_type() == Source::DomainType::UNIVERSE) {
1047-
for (int32_t universe_id : domain_ids) {
1048-
int32_t i_universe = model::universe_map[universe_id];
1049-
Universe& universe = *model::universes[i_universe];
1050-
for (int32_t i_cell : universe.cells_) {
1082+
1083+
} else {
1084+
// If not a point source, then use the volumetric domain constraints to
1085+
// determine which source regions to apply the external source to.
1086+
if (is->domain_type() == Source::DomainType::MATERIAL) {
1087+
for (int32_t material_id : domain_ids) {
1088+
for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) {
1089+
apply_external_source_to_cell_and_children(
1090+
i_cell, energy, strength_factor, material_id);
1091+
}
1092+
}
1093+
} else if (is->domain_type() == Source::DomainType::CELL) {
1094+
for (int32_t cell_id : domain_ids) {
1095+
int32_t i_cell = model::cell_map[cell_id];
10511096
apply_external_source_to_cell_and_children(
10521097
i_cell, energy, strength_factor, C_NONE);
10531098
}
1099+
} else if (is->domain_type() == Source::DomainType::UNIVERSE) {
1100+
for (int32_t universe_id : domain_ids) {
1101+
int32_t i_universe = model::universe_map[universe_id];
1102+
Universe& universe = *model::universes[i_universe];
1103+
for (int32_t i_cell : universe.cells_) {
1104+
apply_external_source_to_cell_and_children(
1105+
i_cell, energy, strength_factor, C_NONE);
1106+
}
1107+
}
10541108
}
10551109
}
10561110
} // End loop over external sources
@@ -1399,6 +1453,25 @@ SourceRegionHandle FlatSourceDomain::get_subdivided_source_region_handle(
13991453
sr_key, {base_source_regions_.get_source_region_handle(sr), sr});
14001454
discovered_source_regions_.unlock(sr_key);
14011455
SourceRegionHandle handle {*sr_ptr};
1456+
1457+
// Check if the new source region contains a point source and apply it if so
1458+
auto it2 = point_source_map_.find(sr_key);
1459+
if (it2 != point_source_map_.end()) {
1460+
int es = it2->second;
1461+
auto s = model::external_sources[es].get();
1462+
auto is = dynamic_cast<IndependentSource*>(s);
1463+
auto energy = dynamic_cast<Discrete*>(is->energy());
1464+
double strength_factor = is->strength();
1465+
apply_external_source_to_source_region(energy, strength_factor, handle);
1466+
int material = handle.material();
1467+
if (material != MATERIAL_VOID) {
1468+
for (int g = 0; g < negroups_; g++) {
1469+
double sigma_t = sigma_t_[material * negroups_ + g];
1470+
handle.external_source(g) /= sigma_t;
1471+
}
1472+
}
1473+
}
1474+
14021475
return handle;
14031476
}
14041477

Diff for: src/random_ray/random_ray_simulation.cpp

+18-5
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,21 @@ void validate_random_ray_inputs()
281281
"allowed in random ray mode.");
282282
}
283283

284-
// Validate that a domain ID was specified
285-
if (is->domain_ids().size() == 0) {
286-
fatal_error("Fixed sources must be specified by domain "
287-
"id (cell, material, or universe) in random ray mode.");
284+
// Validate that a domain ID was specified OR that it is a point source
285+
auto sp = dynamic_cast<SpatialPoint*>(is->space());
286+
if (is->domain_ids().size() == 0 && !sp) {
287+
fatal_error("Fixed sources must be point source or spatially "
288+
"constrained by domain id (cell, material, or universe) in "
289+
"random ray mode.");
290+
} else if (is->domain_ids().size() > 0 && sp) {
291+
// If both a domain constraint and a non-default point source location
292+
// are specified, notify user that domain constraint takes precedence.
293+
if (sp->r().x == 0.0 && sp->r().y == 0.0 && sp->r().z == 0.0) {
294+
warning("Fixed source has both a domain constraint and a point "
295+
"type spatial distribution. The domain constraint takes "
296+
"precedence in random ray mode -- point source coordinate "
297+
"will be ignored.");
298+
}
288299
}
289300

290301
// Check that a discrete energy distribution was used
@@ -393,12 +404,12 @@ RandomRaySimulation::RandomRaySimulation()
393404

394405
void RandomRaySimulation::apply_fixed_sources_and_mesh_domains()
395406
{
407+
domain_->apply_meshes();
396408
if (settings::run_mode == RunMode::FIXED_SOURCE) {
397409
// Transfer external source user inputs onto random ray source regions
398410
domain_->convert_external_sources();
399411
domain_->count_external_source_regions();
400412
}
401-
domain_->apply_meshes();
402413
}
403414

404415
void RandomRaySimulation::prepare_fixed_sources_adjoint(
@@ -517,6 +528,8 @@ void RandomRaySimulation::simulate()
517528
finalize_generation();
518529
finalize_batch();
519530
} // End random ray power iteration loop
531+
532+
domain_->count_external_source_regions();
520533
}
521534

522535
void RandomRaySimulation::output_simulation_results() const

Diff for: tests/regression_tests/random_ray_point_source_locator/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)