mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-28 14:15:42 -04:00
Make MCPL a Runtime Optional Dependency (#3429)
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
parent
24f78e6e92
commit
5318ea6e2b
9 changed files with 438 additions and 203 deletions
|
|
@ -36,7 +36,6 @@ option(OPENMC_ENABLE_COVERAGE "Compile with coverage analysis flags"
|
|||
option(OPENMC_USE_DAGMC "Enable support for DAGMC (CAD) geometry" OFF)
|
||||
option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tallies" OFF)
|
||||
option(OPENMC_USE_MPI "Enable MPI" OFF)
|
||||
option(OPENMC_USE_MCPL "Enable MCPL" OFF)
|
||||
option(OPENMC_USE_UWUW "Enable UWUW" OFF)
|
||||
option(OPENMC_FORCE_VENDORED_LIBS "Explicitly use submodules defined in 'vendor'" OFF)
|
||||
|
||||
|
|
@ -47,7 +46,6 @@ message(STATUS "OPENMC_ENABLE_COVERAGE ${OPENMC_ENABLE_COVERAGE}")
|
|||
message(STATUS "OPENMC_USE_DAGMC ${OPENMC_USE_DAGMC}")
|
||||
message(STATUS "OPENMC_USE_LIBMESH ${OPENMC_USE_LIBMESH}")
|
||||
message(STATUS "OPENMC_USE_MPI ${OPENMC_USE_MPI}")
|
||||
message(STATUS "OPENMC_USE_MCPL ${OPENMC_USE_MCPL}")
|
||||
message(STATUS "OPENMC_USE_UWUW ${OPENMC_USE_UWUW}")
|
||||
message(STATUS "OPENMC_FORCE_VENDORED_LIBS ${OPENMC_FORCE_VENDORED_LIBS}")
|
||||
|
||||
|
|
@ -191,22 +189,6 @@ if(${HDF5_VERSION} VERSION_GREATER_EQUAL 1.12.0)
|
|||
list(APPEND cxxflags -DH5Oget_info_by_idx_vers=1 -DH5O_info_t_vers=1)
|
||||
endif()
|
||||
|
||||
#===============================================================================
|
||||
# MCPL
|
||||
#===============================================================================
|
||||
|
||||
if (OPENMC_USE_MCPL)
|
||||
if (NOT DEFINED MCPL_DIR)
|
||||
execute_process(
|
||||
COMMAND mcpl-config --show cmakedir
|
||||
OUTPUT_VARIABLE MCPL_DIR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
find_package(MCPL REQUIRED)
|
||||
message(STATUS "Found MCPL: ${MCPL_DIR} (found version \"${MCPL_VERSION}\")")
|
||||
endif()
|
||||
|
||||
#===============================================================================
|
||||
# Set compile/link flags based on which compiler is being used
|
||||
#===============================================================================
|
||||
|
|
@ -557,11 +539,6 @@ if (OPENMC_BUILD_TESTS)
|
|||
add_subdirectory(tests/cpp_unit_tests)
|
||||
endif()
|
||||
|
||||
if (OPENMC_USE_MCPL)
|
||||
target_compile_definitions(libopenmc PUBLIC OPENMC_MCPL)
|
||||
target_link_libraries(libopenmc MCPL::mcpl)
|
||||
endif()
|
||||
|
||||
#===============================================================================
|
||||
# Log build info that this executable can report later
|
||||
#===============================================================================
|
||||
|
|
|
|||
|
|
@ -29,10 +29,6 @@ if(@OPENMC_USE_OPENMP@)
|
|||
find_package(OpenMP REQUIRED)
|
||||
endif()
|
||||
|
||||
if(@OPENMC_USE_MCPL@)
|
||||
find_package(MCPL REQUIRED HINTS @MCPL_DIR@)
|
||||
endif()
|
||||
|
||||
if(@OPENMC_USE_UWUW@ AND NOT ${DAGMC_BUILD_UWUW})
|
||||
message(FATAL_ERROR "UWUW is enabled in OpenMC but the DAGMC installation discovered was not configured with UWUW.")
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -375,10 +375,6 @@ OPENMC_USE_DAGMC
|
|||
should also be defined as `DAGMC_ROOT` in the CMake configuration command.
|
||||
(Default: off)
|
||||
|
||||
OPENMC_USE_MCPL
|
||||
Turns on support for reading MCPL_ source files and writing MCPL source points
|
||||
and surface sources. (Default: off)
|
||||
|
||||
OPENMC_USE_LIBMESH
|
||||
Enables the use of unstructured mesh tallies with libMesh_. (Default: off)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,6 @@
|
|||
|
||||
namespace openmc {
|
||||
|
||||
//==============================================================================
|
||||
// Constants
|
||||
//==============================================================================
|
||||
|
||||
extern "C" const bool MCPL_ENABLED;
|
||||
|
||||
//==============================================================================
|
||||
// Functions
|
||||
//==============================================================================
|
||||
|
|
@ -29,14 +23,18 @@ vector<SourceSite> mcpl_source_sites(std::string path);
|
|||
//
|
||||
//! \param[in] filename Path to MCPL file
|
||||
//! \param[in] source_bank Vector of SourceSites to write to file for this
|
||||
//! MPI rank. Note that this can't be const due to
|
||||
//! it being used as work space by MPI.
|
||||
//! \param[in] bank_indx Pointer to vector of site index ranges over all
|
||||
//! MPI ranks. This can be computed by calling
|
||||
//! calculate_parallel_index_vector on
|
||||
//! source_bank.size().
|
||||
//! MPI rank.
|
||||
//! \param[in] bank_index Pointer to vector of site index ranges over all
|
||||
//! MPI ranks.
|
||||
void write_mcpl_source_point(const char* filename, span<SourceSite> source_bank,
|
||||
const vector<int64_t>& bank_index);
|
||||
|
||||
//! Check if MCPL functionality is available
|
||||
bool is_mcpl_interface_available();
|
||||
|
||||
//! Initialize the MCPL interface
|
||||
void initialize_mcpl_interface_if_needed();
|
||||
|
||||
} // namespace openmc
|
||||
|
||||
#endif // OPENMC_MCPL_INTERFACE_H
|
||||
|
|
|
|||
|
|
@ -46,9 +46,6 @@ def _coord_levels():
|
|||
def _libmesh_enabled():
|
||||
return c_bool.in_dll(_dll, "LIBMESH_ENABLED").value
|
||||
|
||||
def _mcpl_enabled():
|
||||
return c_bool.in_dll(_dll, "MCPL_ENABLED").value
|
||||
|
||||
def _uwuw_enabled():
|
||||
return c_bool.in_dll(_dll, "UWUW_ENABLED").value
|
||||
|
||||
|
|
|
|||
|
|
@ -11,32 +11,292 @@
|
|||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#ifdef OPENMC_MCPL
|
||||
#include <mcpl.h>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
// WARNING: These declarations MUST EXACTLY MATCH the structure and function
|
||||
// signatures of the libmcpl being loaded at runtime. Any discrepancy will
|
||||
// likely lead to crashes or incorrect behavior. This is a maintenance risk.
|
||||
// MCPL 2.2.0
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct mcpl_particle_repr_t {
|
||||
double ekin;
|
||||
double polarisation[3];
|
||||
double position[3];
|
||||
double direction[3];
|
||||
double time;
|
||||
double weight;
|
||||
int32_t pdgcode;
|
||||
uint32_t userflags;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Opaque struct definitions replicating the MCPL C-API to ensure ABI
|
||||
// compatibility without including mcpl.h. These must be kept in sync.
|
||||
struct mcpl_file_t {
|
||||
void* internal;
|
||||
};
|
||||
struct mcpl_outfile_t {
|
||||
void* internal;
|
||||
};
|
||||
|
||||
// Function pointer types for the dynamically loaded MCPL library
|
||||
using mcpl_open_file_fpt = mcpl_file_t* (*)(const char* filename);
|
||||
using mcpl_hdr_nparticles_fpt = uint64_t (*)(mcpl_file_t* file_handle);
|
||||
using mcpl_read_fpt = const mcpl_particle_repr_t* (*)(mcpl_file_t* file_handle);
|
||||
using mcpl_close_file_fpt = void (*)(mcpl_file_t* file_handle);
|
||||
|
||||
using mcpl_create_outfile_fpt = mcpl_outfile_t* (*)(const char* filename);
|
||||
using mcpl_hdr_set_srcname_fpt = void (*)(
|
||||
mcpl_outfile_t* outfile_handle, const char* srcname);
|
||||
using mcpl_add_particle_fpt = void (*)(
|
||||
mcpl_outfile_t* outfile_handle, const mcpl_particle_repr_t* particle);
|
||||
using mcpl_close_outfile_fpt = void (*)(mcpl_outfile_t* outfile_handle);
|
||||
|
||||
namespace openmc {
|
||||
|
||||
//==============================================================================
|
||||
// Constants
|
||||
//==============================================================================
|
||||
|
||||
#ifdef OPENMC_MCPL
|
||||
const bool MCPL_ENABLED = true;
|
||||
#ifdef _WIN32
|
||||
using LibraryHandleType = HMODULE;
|
||||
#else
|
||||
const bool MCPL_ENABLED = false;
|
||||
using LibraryHandleType = void*;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
// Functions
|
||||
//==============================================================================
|
||||
std::string get_last_library_error()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD error_code = GetLastError();
|
||||
if (error_code == 0)
|
||||
return "No error reported by system."; // More accurate than "No error."
|
||||
LPSTR message_buffer = nullptr;
|
||||
size_t size =
|
||||
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPSTR)&message_buffer, 0, NULL);
|
||||
std::string message(message_buffer, size);
|
||||
LocalFree(message_buffer);
|
||||
while (
|
||||
!message.empty() && (message.back() == '\n' || message.back() == '\r')) {
|
||||
message.pop_back();
|
||||
}
|
||||
return message;
|
||||
#else
|
||||
const char* err = dlerror();
|
||||
return err ? std::string(err) : "No error reported by dlerror.";
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef OPENMC_MCPL
|
||||
SourceSite mcpl_particle_to_site(const mcpl_particle_t* particle)
|
||||
struct McplApi {
|
||||
mcpl_open_file_fpt open_file;
|
||||
mcpl_hdr_nparticles_fpt hdr_nparticles;
|
||||
mcpl_read_fpt read;
|
||||
mcpl_close_file_fpt close_file;
|
||||
mcpl_create_outfile_fpt create_outfile;
|
||||
mcpl_hdr_set_srcname_fpt hdr_set_srcname;
|
||||
mcpl_add_particle_fpt add_particle;
|
||||
mcpl_close_outfile_fpt close_outfile;
|
||||
|
||||
explicit McplApi(LibraryHandleType lib_handle)
|
||||
{
|
||||
if (!lib_handle)
|
||||
throw std::runtime_error(
|
||||
"MCPL library handle is null during API binding.");
|
||||
|
||||
auto load_symbol_platform = [lib_handle](const char* name) {
|
||||
void* sym = nullptr;
|
||||
#ifdef _WIN32
|
||||
sym = (void*)GetProcAddress(lib_handle, name);
|
||||
#else
|
||||
sym = dlsym(lib_handle, name);
|
||||
#endif
|
||||
if (!sym) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Failed to load MCPL symbol '{}': {}", name,
|
||||
get_last_library_error()));
|
||||
}
|
||||
return sym;
|
||||
};
|
||||
|
||||
open_file = reinterpret_cast<mcpl_open_file_fpt>(
|
||||
load_symbol_platform("mcpl_open_file"));
|
||||
hdr_nparticles = reinterpret_cast<mcpl_hdr_nparticles_fpt>(
|
||||
load_symbol_platform("mcpl_hdr_nparticles"));
|
||||
read = reinterpret_cast<mcpl_read_fpt>(load_symbol_platform("mcpl_read"));
|
||||
close_file = reinterpret_cast<mcpl_close_file_fpt>(
|
||||
load_symbol_platform("mcpl_close_file"));
|
||||
create_outfile = reinterpret_cast<mcpl_create_outfile_fpt>(
|
||||
load_symbol_platform("mcpl_create_outfile"));
|
||||
hdr_set_srcname = reinterpret_cast<mcpl_hdr_set_srcname_fpt>(
|
||||
load_symbol_platform("mcpl_hdr_set_srcname"));
|
||||
add_particle = reinterpret_cast<mcpl_add_particle_fpt>(
|
||||
load_symbol_platform("mcpl_add_particle"));
|
||||
close_outfile = reinterpret_cast<mcpl_close_outfile_fpt>(
|
||||
load_symbol_platform("mcpl_close_outfile"));
|
||||
}
|
||||
};
|
||||
|
||||
static LibraryHandleType g_mcpl_lib_handle = nullptr;
|
||||
static std::unique_ptr<McplApi> g_mcpl_api;
|
||||
static bool g_mcpl_init_attempted = false;
|
||||
static bool g_mcpl_successfully_loaded = false;
|
||||
static std::string g_mcpl_load_error_msg;
|
||||
static std::once_flag g_mcpl_init_flag;
|
||||
|
||||
void append_error(std::string& existing_msg, const std::string& new_error)
|
||||
{
|
||||
if (!existing_msg.empty()) {
|
||||
existing_msg += "; ";
|
||||
}
|
||||
existing_msg += new_error;
|
||||
}
|
||||
|
||||
void initialize_mcpl_interface_impl()
|
||||
{
|
||||
g_mcpl_init_attempted = true;
|
||||
g_mcpl_load_error_msg.clear();
|
||||
|
||||
// Try mcpl-config
|
||||
if (!g_mcpl_lib_handle) {
|
||||
FILE* pipe = nullptr;
|
||||
#ifdef _WIN32
|
||||
pipe = _popen("mcpl-config --show libpath", "r");
|
||||
#else
|
||||
pipe = popen("mcpl-config --show libpath 2>/dev/null", "r");
|
||||
#endif
|
||||
if (pipe) {
|
||||
char buffer[512];
|
||||
if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||
std::string shlibpath = buffer;
|
||||
// Remove trailing whitespace
|
||||
while (!shlibpath.empty() &&
|
||||
std::isspace(static_cast<unsigned char>(shlibpath.back()))) {
|
||||
shlibpath.pop_back();
|
||||
}
|
||||
|
||||
if (!shlibpath.empty()) {
|
||||
#ifdef _WIN32
|
||||
g_mcpl_lib_handle = LoadLibraryA(shlibpath.c_str());
|
||||
#else
|
||||
g_mcpl_lib_handle = dlopen(shlibpath.c_str(), RTLD_LAZY);
|
||||
#endif
|
||||
if (!g_mcpl_lib_handle) {
|
||||
append_error(
|
||||
g_mcpl_load_error_msg, fmt::format("From mcpl-config ({}): {}",
|
||||
shlibpath, get_last_library_error()));
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
_pclose(pipe);
|
||||
#else
|
||||
pclose(pipe);
|
||||
#endif
|
||||
} else { // pipe failed to open
|
||||
append_error(g_mcpl_load_error_msg,
|
||||
"mcpl-config command not found or failed to execute");
|
||||
}
|
||||
}
|
||||
|
||||
// Try standard library names
|
||||
if (!g_mcpl_lib_handle) {
|
||||
#ifdef _WIN32
|
||||
const char* standard_names[] = {"mcpl.dll", "libmcpl.dll"};
|
||||
#else
|
||||
const char* standard_names[] = {"libmcpl.so", "libmcpl.dylib"};
|
||||
#endif
|
||||
for (const char* name : standard_names) {
|
||||
#ifdef _WIN32
|
||||
g_mcpl_lib_handle = LoadLibraryA(name);
|
||||
#else
|
||||
g_mcpl_lib_handle = dlopen(name, RTLD_LAZY);
|
||||
#endif
|
||||
if (g_mcpl_lib_handle)
|
||||
break;
|
||||
}
|
||||
if (!g_mcpl_lib_handle) {
|
||||
append_error(
|
||||
g_mcpl_load_error_msg, fmt::format("Using standard names (e.g. {}): {}",
|
||||
standard_names[0], get_last_library_error()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_mcpl_lib_handle) {
|
||||
if (mpi::master) {
|
||||
warning(fmt::format("MCPL library could not be loaded. MCPL-dependent "
|
||||
"features will be unavailable. Load attempts: {}",
|
||||
g_mcpl_load_error_msg.empty()
|
||||
? "No specific error during load attempts."
|
||||
: g_mcpl_load_error_msg));
|
||||
}
|
||||
g_mcpl_successfully_loaded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
g_mcpl_api = std::make_unique<McplApi>(g_mcpl_lib_handle);
|
||||
g_mcpl_successfully_loaded = true;
|
||||
// Do not call dlclose/FreeLibrary at exit. Leaking the handle is safer
|
||||
// and standard practice for libraries used for the application's lifetime.
|
||||
} catch (const std::runtime_error& e) {
|
||||
append_error(g_mcpl_load_error_msg,
|
||||
fmt::format(
|
||||
"MCPL library loaded, but failed to bind symbols: {}", e.what()));
|
||||
if (mpi::master) {
|
||||
warning(g_mcpl_load_error_msg);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(g_mcpl_lib_handle);
|
||||
#else
|
||||
dlclose(g_mcpl_lib_handle);
|
||||
#endif
|
||||
g_mcpl_lib_handle = nullptr;
|
||||
g_mcpl_successfully_loaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
void initialize_mcpl_interface_if_needed()
|
||||
{
|
||||
std::call_once(g_mcpl_init_flag, initialize_mcpl_interface_impl);
|
||||
}
|
||||
|
||||
bool is_mcpl_interface_available()
|
||||
{
|
||||
initialize_mcpl_interface_if_needed();
|
||||
return g_mcpl_successfully_loaded;
|
||||
}
|
||||
|
||||
inline void ensure_mcpl_ready_or_fatal()
|
||||
{
|
||||
initialize_mcpl_interface_if_needed();
|
||||
if (!g_mcpl_successfully_loaded) {
|
||||
fatal_error("MCPL functionality is required, but the MCPL library is not "
|
||||
"available or failed to initialize. Please ensure MCPL is "
|
||||
"installed and its library can be found (e.g., via PATH on "
|
||||
"Windows, LD_LIBRARY_PATH on Linux, or DYLD_LIBRARY_PATH on "
|
||||
"macOS). You can often install MCPL with 'pip install mcpl' or "
|
||||
"'conda install mcpl'.");
|
||||
}
|
||||
}
|
||||
|
||||
SourceSite mcpl_particle_to_site(const mcpl_particle_repr_t* particle_repr)
|
||||
{
|
||||
SourceSite site;
|
||||
|
||||
switch (particle->pdgcode) {
|
||||
switch (particle_repr->pdgcode) {
|
||||
case 2112:
|
||||
site.particle = ParticleType::neutron;
|
||||
break;
|
||||
|
|
@ -49,179 +309,204 @@ SourceSite mcpl_particle_to_site(const mcpl_particle_t* particle)
|
|||
case -11:
|
||||
site.particle = ParticleType::positron;
|
||||
break;
|
||||
default:
|
||||
fatal_error(fmt::format(
|
||||
"MCPL: Encountered unexpected PDG code {} when converting to SourceSite.",
|
||||
particle_repr->pdgcode));
|
||||
break;
|
||||
}
|
||||
|
||||
// Copy position and direction
|
||||
site.r.x = particle->position[0];
|
||||
site.r.y = particle->position[1];
|
||||
site.r.z = particle->position[2];
|
||||
site.u.x = particle->direction[0];
|
||||
site.u.y = particle->direction[1];
|
||||
site.u.z = particle->direction[2];
|
||||
|
||||
site.r.x = particle_repr->position[0];
|
||||
site.r.y = particle_repr->position[1];
|
||||
site.r.z = particle_repr->position[2];
|
||||
site.u.x = particle_repr->direction[0];
|
||||
site.u.y = particle_repr->direction[1];
|
||||
site.u.z = particle_repr->direction[2];
|
||||
// MCPL stores kinetic energy in [MeV], time in [ms]
|
||||
site.E = particle->ekin * 1e6;
|
||||
site.time = particle->time * 1e-3;
|
||||
site.wgt = particle->weight;
|
||||
|
||||
site.E = particle_repr->ekin * 1e6;
|
||||
site.time = particle_repr->time * 1e-3;
|
||||
site.wgt = particle_repr->weight;
|
||||
return site;
|
||||
}
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
|
||||
vector<SourceSite> mcpl_source_sites(std::string path)
|
||||
{
|
||||
ensure_mcpl_ready_or_fatal();
|
||||
vector<SourceSite> sites;
|
||||
|
||||
#ifdef OPENMC_MCPL
|
||||
// Open MCPL file and determine number of particles
|
||||
auto mcpl_file = mcpl_open_file(path.c_str());
|
||||
size_t n_sites = mcpl_hdr_nparticles(mcpl_file);
|
||||
mcpl_file_t* mcpl_file = g_mcpl_api->open_file(path.c_str());
|
||||
if (!mcpl_file) {
|
||||
fatal_error(fmt::format("MCPL: Could not open file '{}'. It might be "
|
||||
"missing, inaccessible, or not a valid MCPL file.",
|
||||
path));
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_sites; i++) {
|
||||
// Extract particle from mcpl-file, checking if it is a neutron, photon,
|
||||
// electron, or positron. Otherwise skip.
|
||||
const mcpl_particle_t* particle;
|
||||
int pdg = 0;
|
||||
while (pdg != 2112 && pdg != 22 && pdg != 11 && pdg != -11) {
|
||||
particle = mcpl_read(mcpl_file);
|
||||
pdg = particle->pdgcode;
|
||||
size_t n_particles_in_file = g_mcpl_api->hdr_nparticles(mcpl_file);
|
||||
size_t n_skipped = 0;
|
||||
if (n_particles_in_file > 0) {
|
||||
sites.reserve(n_particles_in_file);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < n_particles_in_file; ++i) {
|
||||
const mcpl_particle_repr_t* p_repr = g_mcpl_api->read(mcpl_file);
|
||||
if (!p_repr) {
|
||||
warning(fmt::format("MCPL: Read error or unexpected end of file '{}' "
|
||||
"after reading {} of {} expected particles.",
|
||||
path, sites.size(), n_particles_in_file));
|
||||
break;
|
||||
}
|
||||
if (p_repr->pdgcode == 2112 || p_repr->pdgcode == 22 ||
|
||||
p_repr->pdgcode == 11 || p_repr->pdgcode == -11) {
|
||||
sites.push_back(mcpl_particle_to_site(p_repr));
|
||||
} else {
|
||||
n_skipped++;
|
||||
}
|
||||
|
||||
// Convert to source site and add to vector
|
||||
sites.push_back(mcpl_particle_to_site(particle));
|
||||
}
|
||||
|
||||
// Check that some sites were read
|
||||
g_mcpl_api->close_file(mcpl_file);
|
||||
|
||||
if (n_skipped > 0 && n_particles_in_file > 0) {
|
||||
double percent_skipped =
|
||||
100.0 * static_cast<double>(n_skipped) / n_particles_in_file;
|
||||
warning(fmt::format(
|
||||
"MCPL: Skipped {} of {} total particles ({:.1f}%) in file '{}' because "
|
||||
"their type is not supported by OpenMC.",
|
||||
n_skipped, n_particles_in_file, percent_skipped, path));
|
||||
}
|
||||
|
||||
if (sites.empty()) {
|
||||
fatal_error("MCPL file contained no neutron, photon, electron, or positron "
|
||||
"source particles.");
|
||||
if (n_particles_in_file > 0) {
|
||||
fatal_error(fmt::format(
|
||||
"MCPL file '{}' contained {} particles, but none were of the supported "
|
||||
"types (neutron, photon, electron, positron). OpenMC cannot proceed "
|
||||
"without source particles.",
|
||||
path, n_particles_in_file));
|
||||
} else {
|
||||
fatal_error(fmt::format(
|
||||
"MCPL file '{}' is empty or contains no particle data.", path));
|
||||
}
|
||||
}
|
||||
|
||||
mcpl_close_file(mcpl_file);
|
||||
#else
|
||||
fatal_error(
|
||||
"Your build of OpenMC does not support reading MCPL source files.");
|
||||
#endif
|
||||
|
||||
return sites;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
||||
#ifdef OPENMC_MCPL
|
||||
void write_mcpl_source_bank(mcpl_outfile_t file_id,
|
||||
span<SourceSite> source_bank, const vector<int64_t>& bank_index)
|
||||
void write_mcpl_source_bank_internal(mcpl_outfile_t* file_id,
|
||||
span<SourceSite> local_source_bank,
|
||||
const vector<int64_t>& bank_index_all_ranks)
|
||||
{
|
||||
int64_t dims_size = settings::n_particles;
|
||||
int64_t count_size = simulation::work_per_rank;
|
||||
|
||||
if (mpi::master) {
|
||||
// Particles are writeen to disk from the master node only
|
||||
if (!file_id) {
|
||||
fatal_error("MCPL: Internal error - master rank called "
|
||||
"write_mcpl_source_bank_internal with null file_id.");
|
||||
}
|
||||
vector<SourceSite> receive_buffer;
|
||||
|
||||
// Save source bank sites since the array is overwritten below
|
||||
for (int rank_idx = 0; rank_idx < mpi::n_procs; ++rank_idx) {
|
||||
size_t num_sites_on_rank = static_cast<size_t>(
|
||||
bank_index_all_ranks[rank_idx + 1] - bank_index_all_ranks[rank_idx]);
|
||||
if (num_sites_on_rank == 0)
|
||||
continue;
|
||||
|
||||
span<const SourceSite> sites_to_write;
|
||||
#ifdef OPENMC_MPI
|
||||
vector<SourceSite> temp_source {source_bank.begin(), source_bank.end()};
|
||||
if (rank_idx == mpi::rank) {
|
||||
sites_to_write = openmc::span<const SourceSite>(
|
||||
local_source_bank.data(), num_sites_on_rank);
|
||||
} else {
|
||||
if (receive_buffer.size() < num_sites_on_rank) {
|
||||
receive_buffer.resize(num_sites_on_rank);
|
||||
}
|
||||
MPI_Recv(receive_buffer.data(), num_sites_on_rank, mpi::source_site,
|
||||
rank_idx, rank_idx, mpi::intracomm, MPI_STATUS_IGNORE);
|
||||
sites_to_write = openmc::span<const SourceSite>(
|
||||
receive_buffer.data(), num_sites_on_rank);
|
||||
}
|
||||
#else
|
||||
sites_to_write = openmc::span<const SourceSite>(
|
||||
local_source_bank.data(), num_sites_on_rank);
|
||||
#endif
|
||||
|
||||
// loop over the other nodes and receive data - then write those.
|
||||
for (int i = 0; i < mpi::n_procs; ++i) {
|
||||
// number of particles for node node i
|
||||
size_t count[] {static_cast<size_t>(bank_index[i + 1] - bank_index[i])};
|
||||
|
||||
#ifdef OPENMC_MPI
|
||||
if (i > 0)
|
||||
MPI_Recv(source_bank.data(), count[0], mpi::source_site, i, i,
|
||||
mpi::intracomm, MPI_STATUS_IGNORE);
|
||||
#endif
|
||||
// now write the source_bank data again.
|
||||
for (const auto& site : source_bank) {
|
||||
// particle is now at the iterator
|
||||
// write it to the mcpl-file
|
||||
mcpl_particle_t p;
|
||||
p.position[0] = site.r.x;
|
||||
p.position[1] = site.r.y;
|
||||
p.position[2] = site.r.z;
|
||||
|
||||
// mcpl requires that the direction vector is unit length
|
||||
// which is also the case in openmc
|
||||
p.direction[0] = site.u.x;
|
||||
p.direction[1] = site.u.y;
|
||||
p.direction[2] = site.u.z;
|
||||
|
||||
// MCPL stores kinetic energy in [MeV], time in [ms]
|
||||
p.ekin = site.E * 1e-6;
|
||||
p.time = site.time * 1e3;
|
||||
p.weight = site.wgt;
|
||||
|
||||
for (const auto& site : sites_to_write) {
|
||||
mcpl_particle_repr_t p_repr {};
|
||||
p_repr.position[0] = site.r.x;
|
||||
p_repr.position[1] = site.r.y;
|
||||
p_repr.position[2] = site.r.z;
|
||||
p_repr.direction[0] = site.u.x;
|
||||
p_repr.direction[1] = site.u.y;
|
||||
p_repr.direction[2] = site.u.z;
|
||||
p_repr.ekin = site.E * 1e-6;
|
||||
p_repr.time = site.time * 1e3;
|
||||
p_repr.weight = site.wgt;
|
||||
switch (site.particle) {
|
||||
case ParticleType::neutron:
|
||||
p.pdgcode = 2112;
|
||||
p_repr.pdgcode = 2112;
|
||||
break;
|
||||
case ParticleType::photon:
|
||||
p.pdgcode = 22;
|
||||
p_repr.pdgcode = 22;
|
||||
break;
|
||||
case ParticleType::electron:
|
||||
p.pdgcode = 11;
|
||||
p_repr.pdgcode = 11;
|
||||
break;
|
||||
case ParticleType::positron:
|
||||
p.pdgcode = -11;
|
||||
p_repr.pdgcode = -11;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
mcpl_add_particle(file_id, &p);
|
||||
g_mcpl_api->add_particle(file_id, &p_repr);
|
||||
}
|
||||
}
|
||||
#ifdef OPENMC_MPI
|
||||
// Restore state of source bank
|
||||
std::copy(temp_source.begin(), temp_source.end(), source_bank.begin());
|
||||
#endif
|
||||
} else {
|
||||
#ifdef OPENMC_MPI
|
||||
MPI_Send(source_bank.data(), count_size, mpi::source_site, 0, mpi::rank,
|
||||
mpi::intracomm);
|
||||
if (!local_source_bank.empty()) {
|
||||
MPI_Send(local_source_bank.data(), local_source_bank.size(),
|
||||
mpi::source_site, 0, mpi::rank, mpi::intracomm);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
|
||||
void write_mcpl_source_point(const char* filename, span<SourceSite> source_bank,
|
||||
const vector<int64_t>& bank_index)
|
||||
{
|
||||
ensure_mcpl_ready_or_fatal();
|
||||
|
||||
std::string filename_(filename);
|
||||
const auto extension = get_file_extension(filename_);
|
||||
if (extension == "") {
|
||||
if (extension.empty()) {
|
||||
filename_.append(".mcpl");
|
||||
} else if (extension != "mcpl") {
|
||||
warning("write_mcpl_source_point was passed a file extension differing "
|
||||
"from .mcpl, but an mcpl file will be written.");
|
||||
warning(fmt::format("Specified filename '{}' has an extension '.{}', but "
|
||||
"an MCPL file (.mcpl) will be written using this name.",
|
||||
filename, extension));
|
||||
}
|
||||
|
||||
#ifdef OPENMC_MCPL
|
||||
mcpl_outfile_t file_id;
|
||||
mcpl_outfile_t* file_id = nullptr;
|
||||
|
||||
std::string line;
|
||||
if (mpi::master) {
|
||||
file_id = mcpl_create_outfile(filename_.c_str());
|
||||
file_id = g_mcpl_api->create_outfile(filename_.c_str());
|
||||
if (!file_id) {
|
||||
fatal_error(fmt::format(
|
||||
"MCPL: Failed to create output file '{}'. Check permissions and path.",
|
||||
filename_));
|
||||
}
|
||||
std::string src_line;
|
||||
if (VERSION_DEV) {
|
||||
line = fmt::format("OpenMC {0}.{1}.{2}-dev{3}", VERSION_MAJOR,
|
||||
src_line = fmt::format("OpenMC {}.{}.{}-dev{}", VERSION_MAJOR,
|
||||
VERSION_MINOR, VERSION_RELEASE, VERSION_COMMIT_COUNT);
|
||||
} else {
|
||||
line = fmt::format(
|
||||
"OpenMC {0}.{1}.{2}", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE);
|
||||
src_line = fmt::format(
|
||||
"OpenMC {}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE);
|
||||
}
|
||||
mcpl_hdr_set_srcname(file_id, line.c_str());
|
||||
g_mcpl_api->hdr_set_srcname(file_id, src_line.c_str());
|
||||
}
|
||||
|
||||
write_mcpl_source_bank(file_id, source_bank, bank_index);
|
||||
write_mcpl_source_bank_internal(file_id, source_bank, bank_index);
|
||||
|
||||
if (mpi::master) {
|
||||
mcpl_close_outfile(file_id);
|
||||
if (file_id) {
|
||||
g_mcpl_api->close_outfile(file_id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace openmc
|
||||
|
|
|
|||
|
|
@ -844,12 +844,6 @@ void read_settings_xml(pugi::xml_node root)
|
|||
}
|
||||
if (check_for_node(node_sp, "mcpl")) {
|
||||
source_mcpl_write = get_node_value_bool(node_sp, "mcpl");
|
||||
|
||||
// Make sure MCPL support is enabled
|
||||
if (source_mcpl_write && !MCPL_ENABLED) {
|
||||
fatal_error(
|
||||
"Your build of OpenMC does not support writing MCPL source files.");
|
||||
}
|
||||
}
|
||||
if (check_for_node(node_sp, "overwrite_latest")) {
|
||||
source_latest = get_node_value_bool(node_sp, "overwrite_latest");
|
||||
|
|
@ -902,12 +896,6 @@ void read_settings_xml(pugi::xml_node root)
|
|||
|
||||
if (check_for_node(node_ssw, "mcpl")) {
|
||||
surf_mcpl_write = get_node_value_bool(node_ssw, "mcpl");
|
||||
|
||||
// Make sure MCPL support is enabled
|
||||
if (surf_mcpl_write && !MCPL_ENABLED) {
|
||||
fatal_error("Your build of OpenMC does not support writing MCPL "
|
||||
"surface source files.");
|
||||
}
|
||||
}
|
||||
// Get cell information
|
||||
if (check_for_node(node_ssw, "cell")) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
<?xml version="1.0"?>
|
||||
<settings>
|
||||
<run_mode>eigenvalue</run_mode>
|
||||
<state_point batches="10" />
|
||||
<source_point mcpl="true" separate="true" />
|
||||
<eigenvalue>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
<particles>1000</particles>
|
||||
</eigenvalue>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
<particles>1000</particles>
|
||||
<source>
|
||||
<space type="box">
|
||||
<parameters>-4 -4 -4 4 4 4</parameters>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
#!/usr/bin/env python
|
||||
import openmc.lib
|
||||
import pytest
|
||||
import glob
|
||||
import os
|
||||
|
||||
import shutil
|
||||
from tests.testing_harness import *
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not openmc.lib._mcpl_enabled(),
|
||||
reason="MCPL is not enabled.")
|
||||
shutil.which("mcpl-config") is None,
|
||||
reason="mcpl-config command not found in PATH; MCPL is likely not available."
|
||||
)
|
||||
|
||||
settings1="""<?xml version="1.0"?>
|
||||
<settings>
|
||||
<run_mode>eigenvalue</run_mode>
|
||||
<state_point batches="10" />
|
||||
<source_point mcpl="true" separate="true" />
|
||||
<eigenvalue>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
<particles>1000</particles>
|
||||
</eigenvalue>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
<particles>1000</particles>
|
||||
<source>
|
||||
<space type="box">
|
||||
<parameters>-4 -4 -4 4 4 4</parameters>
|
||||
|
|
@ -28,11 +28,10 @@ settings1="""<?xml version="1.0"?>
|
|||
|
||||
settings2 = """<?xml version="1.0"?>
|
||||
<settings>
|
||||
<eigenvalue>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
<particles>1000</particles>
|
||||
</eigenvalue>
|
||||
<run_mode>eigenvalue</run_mode>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
<particles>1000</particles>
|
||||
<source>
|
||||
<file>source.10.{}</file>
|
||||
</source>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue