Write surface source files per batch (#3124)

Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Zoe Prieto 2024-10-03 19:32:03 -03:00 committed by GitHub
parent b54de4d761
commit 9686851e7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 167 additions and 48 deletions

View file

@ -923,6 +923,15 @@ attributes/sub-elements:
*Default*: None
:max_source_files:
An integer value indicating the number of surface source files to be written
containing the maximum number of particles each. The surface source bank
will be cleared in simulation memory each time a surface source file is
written. By default a ``surface_source.h5`` file will be created when the
maximum number of saved particles is reached.
*Default*: 1
:mcpl:
An optional boolean which indicates if the banked particles should be
written to a file in the MCPL_-format instead of the native HDF5-based

View file

@ -336,6 +336,17 @@ or particles going to a cell::
.. note:: The ``cell``, ``cellfrom`` and ``cellto`` attributes cannot be
used simultaneously.
To generate more than one surface source files when the maximum number of stored
particles is reached, ``max_source_files`` is available. The surface source bank
will be cleared in simulation memory each time a surface source file is written.
As an example, to write a maximum of three surface source files:::
settings.surf_source_write = {
'surfaces_ids': [1, 2, 3],
'max_particles': 10000,
'max_source_files': 3
}
.. _compiled_source:
Compiled Sources

View file

@ -129,14 +129,17 @@ extern std::unordered_set<int>
statepoint_batch; //!< Batches when state should be written
extern std::unordered_set<int>
source_write_surf_id; //!< Surface ids where sources will be written
extern int
max_history_splits; //!< maximum number of particle splits for weight windows
extern int64_t max_surface_particles; //!< maximum number of particles to be
//!< banked on surfaces per process
extern int64_t ssw_cell_id; //!< Cell id for the surface source
//!< write setting
extern SSWCellType ssw_cell_type; //!< Type of option for the cell
//!< argument of surface source write
extern int64_t ssw_max_particles; //!< maximum number of particles to be
//!< banked on surfaces per process
extern int64_t ssw_max_files; //!< maximum number of surface source files
//!< to be created
extern int64_t ssw_cell_id; //!< Cell id for the surface source
//!< write setting
extern SSWCellType ssw_cell_type; //!< Type of option for the cell
//!< argument of surface source write
extern TemperatureMethod
temperature_method; //!< method for choosing temperatures
extern double

View file

@ -37,6 +37,7 @@ extern "C" int n_lost_particles; //!< cumulative number of lost particles
extern "C" bool need_depletion_rx; //!< need to calculate depletion rx?
extern "C" int restart_batch; //!< batch at which a restart job resumed
extern "C" bool satisfy_triggers; //!< have tally triggers been satisfied?
extern int ssw_current_file; //!< current surface source file
extern "C" int total_gen; //!< total number of generations simulated
extern double total_weight; //!< Total source weight in a batch
extern int64_t work_per_rank; //!< number of particles per MPI rank

View file

@ -2,6 +2,7 @@
#define OPENMC_STATE_POINT_H
#include <cstdint>
#include <string>
#include <gsl/gsl-lite.hpp>
@ -33,8 +34,11 @@ void load_state_point();
// values on each rank, used to create global indexing. This vector
// can be created by calling calculate_parallel_index_vector on
// source_bank.size() if such a vector is not already available.
void write_source_point(const char* filename, gsl::span<SourceSite> source_bank,
const vector<int64_t>& bank_index);
void write_h5_source_point(const char* filename,
gsl::span<SourceSite> source_bank, const vector<int64_t>& bank_index);
void write_source_point(std::string, gsl::span<SourceSite> source_bank,
const vector<int64_t>& bank_index, bool use_mcpl);
// This appends a source bank specification to an HDF5 file
// that's already open. It is used internally by write_source_point.

View file

@ -211,6 +211,7 @@ class Settings:
banked (int)
:max_particles: Maximum number of particles to be banked on surfaces per
process (int)
:max_source_files: Maximum number of surface source files to be created (int)
:mcpl: Output in the form of an MCPL-file (bool)
:cell: Cell ID used to determine if particles crossing identified
surfaces are to be banked. Particles coming from or going to this
@ -718,7 +719,7 @@ class Settings:
cv.check_value(
"surface source writing key",
key,
("surface_ids", "max_particles", "mcpl", "cell", "cellfrom", "cellto"),
("surface_ids", "max_particles", "max_source_files", "mcpl", "cell", "cellfrom", "cellto"),
)
if key == "surface_ids":
cv.check_type(
@ -726,11 +727,13 @@ class Settings:
)
for surf_id in value:
cv.check_greater_than("surface id for source banking", surf_id, 0)
elif key == "mcpl":
cv.check_type("write to an MCPL-format file", value, bool)
elif key in ("max_particles", "cell", "cellfrom", "cellto"):
elif key in ("max_particles", "max_source_files", "cell", "cellfrom", "cellto"):
name = {
"max_particles": "maximum particle banks on surfaces per process",
"max_source_files": "maximun surface source files to be written",
"cell": "Cell ID for source banking (from or to)",
"cellfrom": "Cell ID for source banking (from only)",
"cellto": "Cell ID for source banking (to only)",
@ -1251,7 +1254,7 @@ class Settings:
if "mcpl" in self._surf_source_write:
subelement = ET.SubElement(element, "mcpl")
subelement.text = str(self._surf_source_write["mcpl"]).lower()
for key in ("max_particles", "cell", "cellfrom", "cellto"):
for key in ("max_particles", "max_source_files", "cell", "cellfrom", "cellto"):
if key in self._surf_source_write:
subelement = ET.SubElement(element, key)
subelement.text = str(self._surf_source_write[key])
@ -1650,14 +1653,14 @@ class Settings:
elem = root.find('surf_source_write')
if elem is None:
return
for key in ('surface_ids', 'max_particles', 'mcpl', 'cell', 'cellto', 'cellfrom'):
for key in ('surface_ids', 'max_particles', 'max_source_files', 'mcpl', 'cell', 'cellto', 'cellfrom'):
value = get_text(elem, key)
if value is not None:
if key == 'surface_ids':
value = [int(x) for x in value.split()]
elif key == 'mcpl':
value = value in ('true', '1')
elif key in ('max_particles', 'cell', 'cellfrom', 'cellto'):
elif key in ('max_particles', 'max_source_files', 'cell', 'cellfrom', 'cellto'):
value = int(value)
self.surf_source_write[key] = value

View file

@ -120,6 +120,10 @@ int openmc_finalize()
settings::source_latest = false;
settings::source_separate = false;
settings::source_write = true;
settings::ssw_cell_id = C_NONE;
settings::ssw_cell_type = SSWCellType::None;
settings::ssw_max_particles = 0;
settings::ssw_max_files = 1;
settings::survival_biasing = false;
settings::temperature_default = 293.6;
settings::temperature_method = TemperatureMethod::NEAREST;
@ -141,6 +145,7 @@ int openmc_finalize()
simulation::keff = 1.0;
simulation::need_depletion_rx = false;
simulation::ssw_current_file = 1;
simulation::total_gen = 0;
simulation::entropy_mesh = nullptr;

View file

@ -118,7 +118,8 @@ SolverType solver_type {SolverType::MONTE_CARLO};
std::unordered_set<int> sourcepoint_batch;
std::unordered_set<int> statepoint_batch;
std::unordered_set<int> source_write_surf_id;
int64_t max_surface_particles;
int64_t ssw_max_particles;
int64_t ssw_max_files;
int64_t ssw_cell_id {C_NONE};
SSWCellType ssw_cell_type {SSWCellType::None};
TemperatureMethod temperature_method {TemperatureMethod::NEAREST};
@ -799,14 +800,20 @@ void read_settings_xml(pugi::xml_node root)
// Get maximum number of particles to be banked per surface
if (check_for_node(node_ssw, "max_particles")) {
max_surface_particles =
std::stoll(get_node_value(node_ssw, "max_particles"));
ssw_max_particles = std::stoll(get_node_value(node_ssw, "max_particles"));
} else {
fatal_error("A maximum number of particles needs to be specified "
"using the 'max_particles' parameter to store surface "
"source points.");
}
// Get maximum number of surface source files to be created
if (check_for_node(node_ssw, "max_source_files")) {
ssw_max_files = std::stoll(get_node_value(node_ssw, "max_source_files"));
} else {
ssw_max_files = 1;
}
if (check_for_node(node_ssw, "mcpl")) {
surf_mcpl_write = get_node_value_bool(node_ssw, "mcpl");

View file

@ -117,6 +117,7 @@ int openmc_simulation_init()
// Reset global variables -- this is done before loading state point (as that
// will potentially populate k_generation and entropy)
simulation::current_batch = 0;
simulation::ssw_current_file = 1;
simulation::k_generation.clear();
simulation::entropy.clear();
openmc_reset();
@ -308,6 +309,7 @@ int n_lost_particles {0};
bool need_depletion_rx {false};
int restart_batch;
bool satisfy_triggers {false};
int ssw_current_file;
int total_gen {0};
double total_weight;
int64_t work_per_rank;
@ -337,7 +339,7 @@ void allocate_banks()
if (settings::surf_source_write) {
// Allocate surface source bank
simulation::surf_source_bank.reserve(settings::max_surface_particles);
simulation::surf_source_bank.reserve(settings::ssw_max_particles);
}
}
@ -345,7 +347,6 @@ void initialize_batch()
{
// Increment current batch
++simulation::current_batch;
if (settings::run_mode == RunMode::FIXED_SOURCE) {
if (settings::solver_type == SolverType::RANDOM_RAY &&
simulation::current_batch < settings::n_inactive + 1) {
@ -439,42 +440,49 @@ void finalize_batch()
std::string source_point_filename = fmt::format("{0}source.{1:0{2}}",
settings::path_output, simulation::current_batch, w);
gsl::span<SourceSite> bankspan(simulation::source_bank);
if (settings::source_mcpl_write) {
write_mcpl_source_point(
source_point_filename.c_str(), bankspan, simulation::work_index);
} else {
write_source_point(
source_point_filename.c_str(), bankspan, simulation::work_index);
}
write_source_point(source_point_filename, bankspan,
simulation::work_index, settings::source_mcpl_write);
}
// Write a continously-overwritten source point if requested.
if (settings::source_latest) {
// note: correct file extension appended automatically
auto filename = settings::path_output + "source";
gsl::span<SourceSite> bankspan(simulation::source_bank);
if (settings::source_mcpl_write) {
write_mcpl_source_point(
filename.c_str(), bankspan, simulation::work_index);
} else {
write_source_point(filename.c_str(), bankspan, simulation::work_index);
}
write_source_point(filename.c_str(), bankspan, simulation::work_index,
settings::source_mcpl_write);
}
}
// Write out surface source if requested.
if (settings::surf_source_write &&
simulation::current_batch == settings::n_batches) {
auto filename = settings::path_output + "surface_source";
auto surf_work_index =
mpi::calculate_parallel_index_vector(simulation::surf_source_bank.size());
gsl::span<SourceSite> surfbankspan(simulation::surf_source_bank.begin(),
simulation::surf_source_bank.size());
if (settings::surf_mcpl_write) {
write_mcpl_source_point(filename.c_str(), surfbankspan, surf_work_index);
} else {
write_source_point(filename.c_str(), surfbankspan, surf_work_index);
simulation::ssw_current_file <= settings::ssw_max_files) {
bool last_batch = (simulation::current_batch == settings::n_batches);
if (simulation::surf_source_bank.full() || last_batch) {
// Determine appropriate filename
auto filename = fmt::format("{}surface_source.{}", settings::path_output,
simulation::current_batch);
if (settings::ssw_max_files == 1 ||
(simulation::ssw_current_file == 1 && last_batch)) {
filename = settings::path_output + "surface_source";
}
// Get span of source bank and calculate parallel index vector
auto surf_work_index = mpi::calculate_parallel_index_vector(
simulation::surf_source_bank.size());
gsl::span<SourceSite> surfbankspan(simulation::surf_source_bank.begin(),
simulation::surf_source_bank.size());
// Write surface source file
write_source_point(
filename, surfbankspan, surf_work_index, settings::surf_mcpl_write);
// Reset surface source bank and increment counter
simulation::surf_source_bank.clear();
if (!last_batch && settings::ssw_max_files >= 1) {
simulation::surf_source_bank.reserve(settings::ssw_max_particles);
}
++simulation::ssw_current_file;
}
}
}

View file

@ -15,6 +15,7 @@
#include "openmc/error.h"
#include "openmc/file_utils.h"
#include "openmc/hdf5_interface.h"
#include "openmc/mcpl_interface.h"
#include "openmc/mesh.h"
#include "openmc/message_passing.h"
#include "openmc/mgxs_interface.h"
@ -568,8 +569,23 @@ hid_t h5banktype()
return banktype;
}
void write_source_point(const char* filename, gsl::span<SourceSite> source_bank,
const vector<int64_t>& bank_index)
void write_source_point(std::string filename, gsl::span<SourceSite> source_bank,
const vector<int64_t>& bank_index, bool use_mcpl)
{
std::string ext = use_mcpl ? "mcpl" : "h5";
write_message("Creating source file {}.{} with {} particles ...", filename,
ext, source_bank.size(), 5);
// Dispatch to appropriate function based on file type
if (use_mcpl) {
write_mcpl_source_point(filename.c_str(), source_bank, bank_index);
} else {
write_h5_source_point(filename.c_str(), source_bank, bank_index);
}
}
void write_h5_source_point(const char* filename,
gsl::span<SourceSite> source_bank, const vector<int64_t>& bank_index)
{
// When using parallel HDF5, the file is written to collectively by all
// processes. With MPI-only, the file is opened and written by the master

View file

@ -104,5 +104,4 @@ def test_surf_source(model):
def test_dagmc(model):
harness = PyAPITestHarness('statepoint.5.h5', model)
harness.main()
harness.main()

View file

@ -132,4 +132,4 @@ def test_surface_source_read(model):
harness = SurfaceSourceTestHarness('statepoint.10.h5',
model,
'inputs_true_read.dat')
harness.main()
harness.main()

View file

@ -1129,4 +1129,4 @@ def test_surface_source_cell_dagmc(
harness = SurfaceSourceWriteTestHarness(
"statepoint.5.h5", model=model, workdir=folder
)
harness.main()
harness.main()

View file

@ -32,6 +32,7 @@ def geometry():
{"max_particles": 200, "surface_ids": [2], "cell": 1},
{"max_particles": 200, "surface_ids": [2], "cellto": 1},
{"max_particles": 200, "surface_ids": [2], "cellfrom": 1},
{"max_particles": 200, "surface_ids": [2], "max_source_files": 1},
],
)
def test_xml_serialization(parameter, run_in_tmpdir):
@ -44,6 +45,58 @@ def test_xml_serialization(parameter, run_in_tmpdir):
assert read_settings.surf_source_write == parameter
@pytest.fixture(scope="module")
def model():
"""Simple hydrogen sphere geometry"""
openmc.reset_auto_ids()
model = openmc.Model()
# Material
h1 = openmc.Material(name="H1")
h1.add_nuclide("H1", 1.0)
h1.set_density('g/cm3', 1e-7)
# Geometry
radius = 1.0
sphere = openmc.Sphere(r=radius, boundary_type="vacuum")
cell = openmc.Cell(region=-sphere, fill=h1)
model.geometry = openmc.Geometry([cell])
# Settings
model.settings = openmc.Settings()
model.settings.run_mode = "fixed source"
model.settings.particles = 100
model.settings.batches = 3
model.settings.seed = 1
distribution = openmc.stats.Point()
model.settings.source = openmc.IndependentSource(space=distribution)
return model
@pytest.mark.parametrize(
"max_particles, max_source_files",
[
(100, 2),
(100, 3),
(100, 1),
],
)
def test_number_surface_source_file_created(max_particles, max_source_files,
run_in_tmpdir, model):
"""Check the number of surface source files written."""
model.settings.surf_source_write = {
"max_particles": max_particles,
"max_source_files": max_source_files
}
model.run()
should_be_numbered = max_source_files > 1
for i in range(1, max_source_files + 1):
if should_be_numbered:
assert Path(f"surface_source.{i}.h5").exists()
if not should_be_numbered:
assert Path("surface_source.h5").exists()
ERROR_MSG_1 = (
"A maximum number of particles needs to be specified "
"using the 'max_particles' parameter to store surface "