NCrystal becomes runtime rather than buildtime dependency (#3328)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Thomas Kittelmann 2025-03-05 22:45:27 +01:00 committed by GitHub
parent 239f7fed5e
commit ced8929128
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 331 additions and 180 deletions

View file

@ -29,7 +29,6 @@ jobs:
mpi: [n, y] mpi: [n, y]
omp: [n, y] omp: [n, y]
dagmc: [n] dagmc: [n]
ncrystal: [n]
libmesh: [n] libmesh: [n]
event: [n] event: [n]
vectfit: [n] vectfit: [n]
@ -45,10 +44,6 @@ jobs:
python-version: "3.11" python-version: "3.11"
mpi: y mpi: y
omp: y omp: y
- ncrystal: y
python-version: "3.11"
mpi: n
omp: n
- libmesh: y - libmesh: y
python-version: "3.11" python-version: "3.11"
mpi: y mpi: y
@ -66,7 +61,7 @@ jobs:
omp: n omp: n
mpi: y mpi: y
name: "Python ${{ matrix.python-version }} (omp=${{ matrix.omp }}, name: "Python ${{ matrix.python-version }} (omp=${{ matrix.omp }},
mpi=${{ matrix.mpi }}, dagmc=${{ matrix.dagmc }}, ncrystal=${{ matrix.ncrystal }}, mpi=${{ matrix.mpi }}, dagmc=${{ matrix.dagmc }},
libmesh=${{ matrix.libmesh }}, event=${{ matrix.event }} libmesh=${{ matrix.libmesh }}, event=${{ matrix.event }}
vectfit=${{ matrix.vectfit }})" vectfit=${{ matrix.vectfit }})"
@ -75,7 +70,6 @@ jobs:
PHDF5: ${{ matrix.mpi }} PHDF5: ${{ matrix.mpi }}
OMP: ${{ matrix.omp }} OMP: ${{ matrix.omp }}
DAGMC: ${{ matrix.dagmc }} DAGMC: ${{ matrix.dagmc }}
NCRYSTAL: ${{ matrix.ncrystal }}
EVENT: ${{ matrix.event }} EVENT: ${{ matrix.event }}
VECTFIT: ${{ matrix.vectfit }} VECTFIT: ${{ matrix.vectfit }}
LIBMESH: ${{ matrix.libmesh }} LIBMESH: ${{ matrix.libmesh }}

View file

@ -37,7 +37,6 @@ option(OPENMC_USE_DAGMC "Enable support for DAGMC (CAD) geometry"
option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tallies" OFF) option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tallies" OFF)
option(OPENMC_USE_MPI "Enable MPI" OFF) option(OPENMC_USE_MPI "Enable MPI" OFF)
option(OPENMC_USE_MCPL "Enable MCPL" OFF) option(OPENMC_USE_MCPL "Enable MCPL" OFF)
option(OPENMC_USE_NCRYSTAL "Enable support for NCrystal scattering" OFF)
option(OPENMC_USE_UWUW "Enable UWUW" OFF) option(OPENMC_USE_UWUW "Enable UWUW" OFF)
message(STATUS "OPENMC_USE_OPENMP ${OPENMC_USE_OPENMP}") message(STATUS "OPENMC_USE_OPENMP ${OPENMC_USE_OPENMP}")
@ -48,7 +47,6 @@ message(STATUS "OPENMC_USE_DAGMC ${OPENMC_USE_DAGMC}")
message(STATUS "OPENMC_USE_LIBMESH ${OPENMC_USE_LIBMESH}") message(STATUS "OPENMC_USE_LIBMESH ${OPENMC_USE_LIBMESH}")
message(STATUS "OPENMC_USE_MPI ${OPENMC_USE_MPI}") message(STATUS "OPENMC_USE_MPI ${OPENMC_USE_MPI}")
message(STATUS "OPENMC_USE_MCPL ${OPENMC_USE_MCPL}") message(STATUS "OPENMC_USE_MCPL ${OPENMC_USE_MCPL}")
message(STATUS "OPENMC_USE_NCRYSTAL ${OPENMC_USE_NCRYSTAL}")
message(STATUS "OPENMC_USE_UWUW ${OPENMC_USE_UWUW}") message(STATUS "OPENMC_USE_UWUW ${OPENMC_USE_UWUW}")
# Warnings for deprecated options # Warnings for deprecated options
@ -120,23 +118,6 @@ macro(find_package_write_status pkg)
endif() endif()
endmacro() endmacro()
#===============================================================================
# NCrystal Scattering Support
#===============================================================================
if(OPENMC_USE_NCRYSTAL)
if(NOT DEFINED "NCrystal_DIR")
#Invocation of "ncrystal-config --show cmakedir" is needed to find NCrystal
#when it is installed from Python wheels:
execute_process(
COMMAND "ncrystal-config" "--show" "cmakedir"
OUTPUT_VARIABLE "NCrystal_DIR" OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
find_package(NCrystal 3.8.0 REQUIRED)
message(STATUS "Found NCrystal: ${NCrystal_DIR} (version ${NCrystal_VERSION})")
endif()
#=============================================================================== #===============================================================================
# DAGMC Geometry Support - need DAGMC/MOAB # DAGMC Geometry Support - need DAGMC/MOAB
#=============================================================================== #===============================================================================
@ -372,6 +353,7 @@ list(APPEND libopenmc_SOURCES
src/mgxs.cpp src/mgxs.cpp
src/mgxs_interface.cpp src/mgxs_interface.cpp
src/ncrystal_interface.cpp src/ncrystal_interface.cpp
src/ncrystal_load.cpp
src/nuclide.cpp src/nuclide.cpp
src/output.cpp src/output.cpp
src/particle.cpp src/particle.cpp
@ -548,11 +530,6 @@ if (OPENMC_USE_MCPL)
target_link_libraries(libopenmc MCPL::mcpl) target_link_libraries(libopenmc MCPL::mcpl)
endif() endif()
if(OPENMC_USE_NCRYSTAL)
target_compile_definitions(libopenmc PRIVATE NCRYSTAL)
target_link_libraries(libopenmc NCrystal::NCrystal)
endif()
#=============================================================================== #===============================================================================
# Log build info that this executable can report later # Log build info that this executable can report later
#=============================================================================== #===============================================================================

View file

@ -60,7 +60,8 @@ Dockerfile @shimwell
src/random_ray/ @jtramm src/random_ray/ @jtramm
# NCrystal interface # NCrystal interface
src/ncrystal_interface.cpp @marquezj src/ncrystal_interface.cpp @marquezj @tkittel
src/ncrystal_load.cpp @marquezj @tkittel
# MCPL interface # MCPL interface
src/mcpl_interface.cpp @ebknudsen src/mcpl_interface.cpp @ebknudsen

View file

@ -8,19 +8,6 @@ if(@OPENMC_USE_DAGMC@)
find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@) find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@)
endif() endif()
if(@OPENMC_USE_NCRYSTAL@)
if(NOT DEFINED "NCrystal_DIR")
#Invocation of "ncrystal-config --show cmakedir" is needed to find NCrystal
#when it is installed from Python wheels:
execute_process(
COMMAND "ncrystal-config" "--show" "cmakedir"
OUTPUT_VARIABLE "NCrystal_DIR" OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
find_package(NCrystal REQUIRED)
message(STATUS "Found NCrystal: ${NCrystal_DIR} (version ${NCrystal_VERSION})")
endif()
if(@OPENMC_USE_LIBMESH@) if(@OPENMC_USE_LIBMESH@)
include(FindPkgConfig) include(FindPkgConfig)
list(APPEND CMAKE_PREFIX_PATH @LIBMESH_PREFIX@) list(APPEND CMAKE_PREFIX_PATH @LIBMESH_PREFIX@)

View file

@ -284,13 +284,13 @@ Prerequisites
* NCrystal_ library for defining materials with enhanced thermal neutron transport * NCrystal_ library for defining materials with enhanced thermal neutron transport
Adding this option allows the creation of materials from NCrystal, which OpenMC supports the creation of materials from NCrystal, which replaces
replaces the scattering kernel treatment of ACE files with a modular, the scattering kernel treatment of ACE files with a modular, on-the-fly
on-the-fly approach. To use it `install approach. OpenMC does not need any particular build option to use this,
<https://github.com/mctools/ncrystal/wiki/Get-NCrystal>`_ NCrystal and but NCrystal must be installed on the system. Refer to `NCrystal
turn on the option in the CMake configuration step:: documentation
<https://github.com/mctools/ncrystal/wiki/Get-NCrystal>`_ for how this is
cmake -DOPENMC_USE_NCRYSTAL=on .. achieved.
* libMesh_ mesh library framework for numerical simulations of partial differential equations * libMesh_ mesh library framework for numerical simulations of partial differential equations
@ -393,12 +393,6 @@ OPENMC_USE_MCPL
Turns on support for reading MCPL_ source files and writing MCPL source points Turns on support for reading MCPL_ source files and writing MCPL source points
and surface sources. (Default: off) and surface sources. (Default: off)
OPENMC_USE_NCRYSTAL
Turns on support for NCrystal materials. NCrystal must be `installed
<https://github.com/mctools/ncrystal/wiki/Get-NCrystal>`_ and `initialized
<https://github.com/mctools/ncrystal/wiki/Using-NCrystal#setting-up>`_.
(Default: off)
OPENMC_USE_LIBMESH OPENMC_USE_LIBMESH
Enables the use of unstructured mesh tallies with libMesh_. (Default: off) Enables the use of unstructured mesh tallies with libMesh_. (Default: off)

View file

@ -1,10 +1,7 @@
#ifndef OPENMC_NCRYSTAL_INTERFACE_H #ifndef OPENMC_NCRYSTAL_INTERFACE_H
#define OPENMC_NCRYSTAL_INTERFACE_H #define OPENMC_NCRYSTAL_INTERFACE_H
#ifdef NCRYSTAL #include "openmc/ncrystal_load.h"
#include "NCrystal/NCrystal.hh"
#endif
#include "openmc/particle.h" #include "openmc/particle.h"
#include <cstdint> // for uint64_t #include <cstdint> // for uint64_t
@ -17,28 +14,25 @@ namespace openmc {
// Constants // Constants
//============================================================================== //==============================================================================
extern "C" const bool NCRYSTAL_ENABLED;
//! Energy in [eV] to switch between NCrystal and ENDF //! Energy in [eV] to switch between NCrystal and ENDF
constexpr double NCRYSTAL_MAX_ENERGY {5.0}; constexpr double NCRYSTAL_MAX_ENERGY {5.0};
//============================================================================== //==============================================================================
// Wrapper class an NCrystal material // Wrapper class for an NCrystal material
//============================================================================== //==============================================================================
class NCrystalMat { class NCrystalMat {
public: public:
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// Constructors // Constructors
NCrystalMat() = default; NCrystalMat() = default; // empty object
explicit NCrystalMat(const std::string& cfg); explicit NCrystalMat(const std::string& cfg);
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// Methods // Methods
#ifdef NCRYSTAL //! Return configuration string:
//! Return configuration string const std::string& cfg() const { return cfg_; }
std::string cfg() const;
//! Get cross section from NCrystal material //! Get cross section from NCrystal material
// //
@ -52,25 +46,21 @@ public:
void scatter(Particle& p) const; void scatter(Particle& p) const;
//! Whether the object holds a valid NCrystal material //! Whether the object holds a valid NCrystal material
operator bool() const; operator bool() const { return !cfg_.empty(); }
#else
//---------------------------------------------------------------------------- NCrystalMat clone() const
// Trivial methods when compiling without NCRYSTAL {
std::string cfg() const { return ""; } NCrystalMat c;
double xs(const Particle& p) const { return -1.0; } c.cfg_ = cfg_;
void scatter(Particle& p) const {} c.proc_ = proc_.clone();
operator bool() const { return false; } return c;
#endif }
private: private:
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// Data members (only present when compiling with NCrystal support) // Data members (only present when compiling with NCrystal support)
#ifdef NCRYSTAL
std::string cfg_; //!< NCrystal configuration string std::string cfg_; //!< NCrystal configuration string
std::shared_ptr<const NCrystal::ProcImpl::Process> NCrystalScatProc proc_; //!< NCrystal scatter process
ptr_; //!< Pointer to NCrystal material object
#endif
}; };
//============================================================================== //==============================================================================

View file

@ -0,0 +1,127 @@
//! \file ncrystal_load.h
//! \brief Helper class taking care of loading NCrystal at runtime.
#ifndef OPENMC_NCRYSTAL_LOAD_H
#define OPENMC_NCRYSTAL_LOAD_H
#include <algorithm> // for swap
#include <functional> // for function
#include <memory> // for shared_ptr
#include <utility> // for move
namespace NCrystalVirtualAPI {
// NOTICE: Do NOT make ANY changes in the NCrystalVirtualAPI::VirtAPI_Type1_v1
// class, it is required to stay exactly constant over time and compatible with
// the same definition used to compile the NCrystal library! But changes to
// white space, comments, and formatting is of course allowed. This API was
// introduced in NCrystal 4.1.0.
//! Abstract base class for NCrystal interface which must be declared exactly as
// it is in NCrystal itself.
class VirtAPI_Type1_v1 {
public:
// Note: neutron must be an array of length 4 with values {ekin,ux,uy,uz}
class ScatterProcess;
virtual const ScatterProcess* createScatter(const char* cfgstr) const = 0;
virtual const ScatterProcess* cloneScatter(const ScatterProcess*) const = 0;
virtual void deallocateScatter(const ScatterProcess*) const = 0;
virtual double crossSectionUncached(
const ScatterProcess&, const double* neutron) const = 0;
virtual void sampleScatterUncached(const ScatterProcess&,
std::function<double()>& rng, double* neutron) const = 0;
// Plumbing:
static constexpr unsigned interface_id = 1001;
virtual ~VirtAPI_Type1_v1() = default;
VirtAPI_Type1_v1() = default;
VirtAPI_Type1_v1(const VirtAPI_Type1_v1&) = delete;
VirtAPI_Type1_v1& operator=(const VirtAPI_Type1_v1&) = delete;
VirtAPI_Type1_v1(VirtAPI_Type1_v1&&) = delete;
VirtAPI_Type1_v1& operator=(VirtAPI_Type1_v1&&) = delete;
};
} // namespace NCrystalVirtualAPI
namespace openmc {
using NCrystalAPI = NCrystalVirtualAPI::VirtAPI_Type1_v1;
//! Function which locates and loads NCrystal at runtime using the virtual API
std::shared_ptr<const NCrystalAPI> load_ncrystal_api();
//! Class encapsulating exactly the parts of NCrystal needed by OpenMC
class NCrystalScatProc final {
public:
//! Empty constructor which does not load NCrystal
NCrystalScatProc() {}
//! Load NCrystal and instantiate a scattering process
//! \param cfgstr NCrystal cfg-string defining the material.
NCrystalScatProc(const char* cfgstr)
: api_(load_ncrystal_api()), p_(api_->createScatter(cfgstr))
{}
// Note: Neutron state array is {ekin,ux,uy,uz}
//! Returns total scattering cross section in units of barns per atom.
//! \param neutron_state array {ekin,ux,uy,uz} with ekin (eV) and direction.
double cross_section(const double* neutron_state) const
{
return api_->crossSectionUncached(*p_, neutron_state);
}
//! Returns total scattering cross section in units of barns per atom.
//! \param rng function returning random numbers in the unit interval
//! \param neutron_state array {ekin,ux,uy,uz} with ekin (eV) and direction.
void scatter(std::function<double()>& rng, double* neutron_state) const
{
api_->sampleScatterUncached(*p_, rng, neutron_state);
}
//! Clones the object which is otherwise move-only
NCrystalScatProc clone() const
{
NCrystalScatProc c;
if (p_) {
c.api_ = api_;
c.p_ = api_->cloneScatter(p_);
}
return c;
}
// Plumbing (move-only semantics, but supports explicit clone):
NCrystalScatProc(const NCrystalScatProc&) = delete;
NCrystalScatProc& operator=(const NCrystalScatProc&) = delete;
NCrystalScatProc(NCrystalScatProc&& o) : api_(std::move(o.api_)), p_(nullptr)
{
std::swap(p_, o.p_);
}
NCrystalScatProc& operator=(NCrystalScatProc&& o)
{
if (p_) {
api_->deallocateScatter(p_);
p_ = nullptr;
}
std::swap(api_, o.api_);
std::swap(p_, o.p_);
return *this;
}
~NCrystalScatProc()
{
if (p_)
api_->deallocateScatter(p_);
}
private:
std::shared_ptr<const NCrystalAPI> api_;
const NCrystalAPI::ScatterProcess* p_ = nullptr;
};
} // namespace openmc
#endif

View file

@ -40,9 +40,6 @@ else:
def _dagmc_enabled(): def _dagmc_enabled():
return c_bool.in_dll(_dll, "DAGMC_ENABLED").value return c_bool.in_dll(_dll, "DAGMC_ENABLED").value
def _ncrystal_enabled():
return c_bool.in_dll(_dll, "NCRYSTAL_ENABLED").value
def _coord_levels(): def _coord_levels():
return c_int.in_dll(_dll, "n_coord_levels").value return c_int.in_dll(_dll, "n_coord_levels").value

View file

@ -429,7 +429,11 @@ class Material(IDManagerMixin):
""" """
try:
import NCrystal import NCrystal
except ModuleNotFoundError as e:
raise RuntimeError('The .from_ncrystal method requires'
' NCrystal to be installed.') from e
nc_mat = NCrystal.createInfo(cfg) nc_mat = NCrystal.createInfo(cfg)
def openmc_natabund(Z): def openmc_natabund(Z):

View file

@ -368,7 +368,7 @@ Material& Material::clone()
mat->name_ = name_; mat->name_ = name_;
mat->nuclide_ = nuclide_; mat->nuclide_ = nuclide_;
mat->element_ = element_; mat->element_ = element_;
mat->ncrystal_mat_ = ncrystal_mat_; mat->ncrystal_mat_ = ncrystal_mat_.clone();
mat->atom_density_ = atom_density_; mat->atom_density_ = atom_density_;
mat->density_ = density_; mat->density_ = density_;
mat->density_gpcc_ = density_gpcc_; mat->density_gpcc_ = density_gpcc_;

View file

@ -7,89 +7,34 @@
namespace openmc { namespace openmc {
//============================================================================== //==============================================================================
// Constants // NCrystalMat implementation
//============================================================================== //==============================================================================
#ifdef NCRYSTAL NCrystalMat::NCrystalMat(const std::string& cfg) : cfg_(cfg), proc_(cfg.c_str())
const bool NCRYSTAL_ENABLED = true; {}
#else
const bool NCRYSTAL_ENABLED = false;
#endif
//==============================================================================
// NCrystal wrapper class for the OpenMC random number generator
//==============================================================================
#ifdef NCRYSTAL
class NCrystalRNGWrapper : public NCrystal::RNGStream {
public:
constexpr NCrystalRNGWrapper(uint64_t* seed) noexcept : openmc_seed_(seed) {}
protected:
double actualGenerate() override
{
return std::max<double>(
std::numeric_limits<double>::min(), prn(openmc_seed_));
}
private:
uint64_t* openmc_seed_;
};
#endif
//==============================================================================
// NCrystal implementation
//==============================================================================
NCrystalMat::NCrystalMat(const std::string& cfg)
{
#ifdef NCRYSTAL
cfg_ = cfg;
ptr_ = NCrystal::FactImpl::createScatter(cfg);
#else
fatal_error("Your build of OpenMC does not support NCrystal materials.");
#endif
}
#ifdef NCRYSTAL
std::string NCrystalMat::cfg() const
{
return cfg_;
}
double NCrystalMat::xs(const Particle& p) const double NCrystalMat::xs(const Particle& p) const
{ {
// Calculate scattering XS per atom with NCrystal, only once per material // Calculate scattering XS per atom with NCrystal, only once per material
NCrystal::CachePtr dummy_cache; double neutron_state[4] = {p.E(), p.u().x, p.u().y, p.u().z};
auto nc_energy = NCrystal::NeutronEnergy {p.E()}; return proc_.cross_section(neutron_state);
return ptr_->crossSection(dummy_cache, nc_energy, {p.u().x, p.u().y, p.u().z})
.get();
} }
void NCrystalMat::scatter(Particle& p) const void NCrystalMat::scatter(Particle& p) const
{ {
NCrystalRNGWrapper rng(p.current_seed()); // Initialize RNG // Scatter with NCrystal, using the OpenMC RNG stream:
// create a cache pointer for multi thread physics uint64_t* seed = p.current_seed();
NCrystal::CachePtr dummy_cache; std::function<double()> rng = [&seed]() { return prn(seed); };
auto nc_energy = NCrystal::NeutronEnergy {p.E()}; double neutron_state[4] = {p.E(), p.u().x, p.u().y, p.u().z};
auto outcome = ptr_->sampleScatter( proc_.scatter(rng, neutron_state);
dummy_cache, rng, nc_energy, {p.u().x, p.u().y, p.u().z});
// Modify attributes of particle // Modify attributes of particle
p.E() = outcome.ekin.get(); p.E() = neutron_state[0];
Direction u_old {p.u()}; Direction u_old {p.u()};
p.u() = p.u() = Direction(neutron_state[1], neutron_state[2], neutron_state[3]);
Direction(outcome.direction[0], outcome.direction[1], outcome.direction[2]);
p.mu() = u_old.dot(p.u()); p.mu() = u_old.dot(p.u());
p.event_mt() = ELASTIC; p.event_mt() = ELASTIC;
} }
NCrystalMat::operator bool() const
{
return ptr_.get();
}
#endif
//============================================================================== //==============================================================================
// Functions // Functions
//============================================================================== //==============================================================================

151
src/ncrystal_load.cpp Normal file
View file

@ -0,0 +1,151 @@
#include "openmc/ncrystal_load.h"
#include <cctype> // for isspace
#include <cstdlib> // for strtoul
#include <memory> // for shared_ptr
#include <mutex> // for mutex, lock_guard
#include <string>
#include <fmt/core.h>
#include <stdio.h> // for popen, pclose
#include "openmc/error.h"
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h> // for LoadLibrary, GetProcAddress
#else
#include <dlfcn.h> // for dlopen, dlsym, dlerror
#endif
namespace openmc {
namespace {
struct NCrystalConfig {
std::string shlibpath;
unsigned long intversion = 0;
std::string symbol_namespace;
};
NCrystalConfig query_ncrystal_config()
{
#ifdef _WIN32
FILE* pipe = _popen("ncrystal-config --show "
"intversion shlibpath namespace",
"r");
#else
FILE* pipe = popen("ncrystal-config --show "
"intversion shlibpath namespace 2>/dev/null",
"r");
#endif
if (!pipe)
return {}; // failure
auto readLine = [pipe](std::string& tgt) -> bool {
// Read line and discard trailing whitespace (including newline chars).
char buffer[4096];
if (fgets(buffer, sizeof(buffer), pipe) == NULL)
return false;
tgt = buffer;
while (!tgt.empty() && std::isspace(tgt.back()))
tgt.pop_back();
return true;
};
auto parseIntVersion = [](const std::string& s) {
char* str_end = nullptr;
unsigned long v = std::strtoul(s.c_str(), &str_end, 10);
return (v >= 2002000 && v < 999999999 && str_end == s.c_str() + s.size())
? v
: 0;
};
NCrystalConfig res;
if (!readLine(res.shlibpath) ||
!(res.intversion = parseIntVersion(res.shlibpath)) ||
!readLine(res.shlibpath) || res.shlibpath.empty() ||
!readLine(res.symbol_namespace)) {
res.intversion = 0; // failure
}
#ifdef _WIN32
auto returnCode = _pclose(pipe);
#else
auto returnCode = pclose(pipe);
#endif
if (returnCode == 0 && res.intversion >= 2002000)
return res;
return {}; // failure
}
struct NCrystalAPIDB {
std::mutex mtx;
std::shared_ptr<const NCrystalAPI> api;
using FctSignature = void* (*)(int);
FctSignature ncrystal_access_virtapi_fct = nullptr;
};
void* load_virtapi_raw(unsigned interface_id, NCrystalAPIDB& db)
{
if (!db.ncrystal_access_virtapi_fct) {
auto cfg = query_ncrystal_config();
if (!(cfg.intversion >= 4001000)) {
// This is the most likely error message people will see:
fatal_error("Could not locate a functioning and recent enough"
" NCrystal installation (required since geometry"
" contains NCrystal materials).");
}
#ifdef _WIN32
auto handle = LoadLibrary(cfg.shlibpath.c_str());
#else
dlerror(); // clear previous errors
void* handle = dlopen(cfg.shlibpath.c_str(), RTLD_LOCAL | RTLD_LAZY);
#endif
if (!handle)
fatal_error("Loading of the NCrystal library failed");
std::string symbol =
fmt::format("ncrystal{}_access_virtual_api", cfg.symbol_namespace);
#ifdef _WIN32
void* addr = (void*)(intptr_t)GetProcAddress(handle, symbol.c_str());
if (!addr)
fatal_error("GetProcAddress("
"ncrystal_access_virtual_api) failed");
#else
dlerror(); // clear previous errors
void* addr = dlsym(handle, symbol.c_str());
if (!addr)
fatal_error("dlsym(ncrystal_access_virtual_api) failed");
#endif
db.ncrystal_access_virtapi_fct =
reinterpret_cast<NCrystalAPIDB::FctSignature>(addr);
}
void* result = (*db.ncrystal_access_virtapi_fct)(interface_id);
if (!result)
fatal_error("NCrystal installation does not support required interface.");
return result;
}
NCrystalAPIDB& get_ncrystal_api_db()
{
static NCrystalAPIDB db;
return db;
}
} // namespace
std::shared_ptr<const NCrystalAPI> load_ncrystal_api()
{
auto& db = get_ncrystal_api_db();
std::lock_guard<std::mutex> lock(db.mtx);
if (!db.api) {
void* raw_api = load_virtapi_raw(NCrystalAPI::interface_id, db);
if (!raw_api)
fatal_error("Problems loading NCrystal.");
db.api = *reinterpret_cast<std::shared_ptr<const NCrystalAPI>*>(raw_api);
}
return db.api;
}
} // namespace openmc

View file

@ -314,7 +314,6 @@ void print_build_info()
std::string profiling(n); std::string profiling(n);
std::string coverage(n); std::string coverage(n);
std::string mcpl(n); std::string mcpl(n);
std::string ncrystal(n);
std::string uwuw(n); std::string uwuw(n);
#ifdef PHDF5 #ifdef PHDF5
@ -332,9 +331,6 @@ void print_build_info()
#ifdef OPENMC_MCPL #ifdef OPENMC_MCPL
mcpl = y; mcpl = y;
#endif #endif
#ifdef NCRYSTAL
ncrystal = y;
#endif
#ifdef USE_LIBPNG #ifdef USE_LIBPNG
png = y; png = y;
#endif #endif
@ -362,7 +358,6 @@ void print_build_info()
fmt::print("DAGMC support: {}\n", dagmc); fmt::print("DAGMC support: {}\n", dagmc);
fmt::print("libMesh support: {}\n", libmesh); fmt::print("libMesh support: {}\n", libmesh);
fmt::print("MCPL support: {}\n", mcpl); fmt::print("MCPL support: {}\n", mcpl);
fmt::print("NCrystal support: {}\n", ncrystal);
fmt::print("Coverage testing: {}\n", coverage); fmt::print("Coverage testing: {}\n", coverage);
fmt::print("Profiling flags: {}\n", profiling); fmt::print("Profiling flags: {}\n", profiling);
fmt::print("UWUW support: {}\n", uwuw); fmt::print("UWUW support: {}\n", uwuw);

View file

@ -4,7 +4,7 @@
#define HAS_DYNAMIC_LINKING #define HAS_DYNAMIC_LINKING
#endif #endif
#include <algorithm> // for move #include <utility> // for move
#ifdef HAS_DYNAMIC_LINKING #ifdef HAS_DYNAMIC_LINKING
#include <dlfcn.h> // for dlopen, dlsym, dlclose, dlerror #include <dlfcn.h> // for dlopen, dlsym, dlclose, dlerror

View file

@ -6,12 +6,13 @@ import numpy as np
import openmc import openmc
import openmc.lib import openmc.lib
import pytest import pytest
import shutil
from tests.testing_harness import PyAPITestHarness from tests.testing_harness import PyAPITestHarness
pytestmark = pytest.mark.skipif( pytestmark = pytest.mark.skipif(
not openmc.lib._ncrystal_enabled(), not shutil.which('ncrystal-config'),
reason="NCrystal materials are not enabled.") reason="NCrystal is not installed.")
def pencil_beam_model(cfg, E0, N): def pencil_beam_model(cfg, E0, N):

View file

@ -3,7 +3,7 @@ import shutil
import subprocess import subprocess
def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False, ncrystal=False): def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False):
# Create build directory and change to it # Create build directory and change to it
shutil.rmtree('build', ignore_errors=True) shutil.rmtree('build', ignore_errors=True)
os.mkdir('build') os.mkdir('build')
@ -40,9 +40,6 @@ def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False, ncrys
libmesh_path = os.environ.get('HOME') + '/LIBMESH' libmesh_path = os.environ.get('HOME') + '/LIBMESH'
cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + libmesh_path) cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + libmesh_path)
if ncrystal:
cmake_cmd.append('-DOPENMC_USE_NCRYSTAL=ON')
# Build in coverage mode for coverage testing # Build in coverage mode for coverage testing
cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on') cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on')
@ -59,11 +56,10 @@ def main():
mpi = (os.environ.get('MPI') == 'y') mpi = (os.environ.get('MPI') == 'y')
phdf5 = (os.environ.get('PHDF5') == 'y') phdf5 = (os.environ.get('PHDF5') == 'y')
dagmc = (os.environ.get('DAGMC') == 'y') dagmc = (os.environ.get('DAGMC') == 'y')
ncrystal = (os.environ.get('NCRYSTAL') == 'y')
libmesh = (os.environ.get('LIBMESH') == 'y') libmesh = (os.environ.get('LIBMESH') == 'y')
# Build and install # Build and install
install(omp, mpi, phdf5, dagmc, libmesh, ncrystal) install(omp, mpi, phdf5, dagmc, libmesh)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View file

@ -14,12 +14,9 @@ if [[ $DAGMC = 'y' ]]; then
./tools/ci/gha-install-dagmc.sh ./tools/ci/gha-install-dagmc.sh
fi fi
# Install NCrystal if needed # Install NCrystal and verify installation
if [[ $NCRYSTAL = 'y' ]]; then pip install 'ncrystal>=4.1.0'
pip install 'ncrystal>=4.0.0' nctool --test
#Basic quick verification:
nctool --test
fi
# Install vectfit for WMP generation if needed # Install vectfit for WMP generation if needed
if [[ $VECTFIT = 'y' ]]; then if [[ $VECTFIT = 'y' ]]; then

View file

@ -14,10 +14,5 @@ if [[ $EVENT == 'y' ]]; then
args="${args} --event " args="${args} --event "
fi fi
# Check NCrystal installation
if [[ $NCRYSTAL = 'y' ]]; then
nctool --test
fi
# Run regression and unit tests # Run regression and unit tests
pytest --cov=openmc -v $args tests pytest --cov=openmc -v $args tests