Skip to content

Commit ced8929

Browse files
tkittelpaulromano
andauthored
NCrystal becomes runtime rather than buildtime dependency (#3328)
Co-authored-by: Paul Romano <[email protected]>
1 parent 239f7fe commit ced8929

File tree

18 files changed

+331
-180
lines changed

18 files changed

+331
-180
lines changed

.github/workflows/ci.yml

+1-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ jobs:
2929
mpi: [n, y]
3030
omp: [n, y]
3131
dagmc: [n]
32-
ncrystal: [n]
3332
libmesh: [n]
3433
event: [n]
3534
vectfit: [n]
@@ -45,10 +44,6 @@ jobs:
4544
python-version: "3.11"
4645
mpi: y
4746
omp: y
48-
- ncrystal: y
49-
python-version: "3.11"
50-
mpi: n
51-
omp: n
5247
- libmesh: y
5348
python-version: "3.11"
5449
mpi: y
@@ -66,7 +61,7 @@ jobs:
6661
omp: n
6762
mpi: y
6863
name: "Python ${{ matrix.python-version }} (omp=${{ matrix.omp }},
69-
mpi=${{ matrix.mpi }}, dagmc=${{ matrix.dagmc }}, ncrystal=${{ matrix.ncrystal }},
64+
mpi=${{ matrix.mpi }}, dagmc=${{ matrix.dagmc }},
7065
libmesh=${{ matrix.libmesh }}, event=${{ matrix.event }}
7166
vectfit=${{ matrix.vectfit }})"
7267

@@ -75,7 +70,6 @@ jobs:
7570
PHDF5: ${{ matrix.mpi }}
7671
OMP: ${{ matrix.omp }}
7772
DAGMC: ${{ matrix.dagmc }}
78-
NCRYSTAL: ${{ matrix.ncrystal }}
7973
EVENT: ${{ matrix.event }}
8074
VECTFIT: ${{ matrix.vectfit }}
8175
LIBMESH: ${{ matrix.libmesh }}

CMakeLists.txt

+1-24
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ option(OPENMC_USE_DAGMC "Enable support for DAGMC (CAD) geometry"
3737
option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tallies" OFF)
3838
option(OPENMC_USE_MPI "Enable MPI" OFF)
3939
option(OPENMC_USE_MCPL "Enable MCPL" OFF)
40-
option(OPENMC_USE_NCRYSTAL "Enable support for NCrystal scattering" OFF)
4140
option(OPENMC_USE_UWUW "Enable UWUW" OFF)
4241

4342
message(STATUS "OPENMC_USE_OPENMP ${OPENMC_USE_OPENMP}")
@@ -48,7 +47,6 @@ message(STATUS "OPENMC_USE_DAGMC ${OPENMC_USE_DAGMC}")
4847
message(STATUS "OPENMC_USE_LIBMESH ${OPENMC_USE_LIBMESH}")
4948
message(STATUS "OPENMC_USE_MPI ${OPENMC_USE_MPI}")
5049
message(STATUS "OPENMC_USE_MCPL ${OPENMC_USE_MCPL}")
51-
message(STATUS "OPENMC_USE_NCRYSTAL ${OPENMC_USE_NCRYSTAL}")
5250
message(STATUS "OPENMC_USE_UWUW ${OPENMC_USE_UWUW}")
5351

5452
# Warnings for deprecated options
@@ -120,23 +118,6 @@ macro(find_package_write_status pkg)
120118
endif()
121119
endmacro()
122120

123-
#===============================================================================
124-
# NCrystal Scattering Support
125-
#===============================================================================
126-
127-
if(OPENMC_USE_NCRYSTAL)
128-
if(NOT DEFINED "NCrystal_DIR")
129-
#Invocation of "ncrystal-config --show cmakedir" is needed to find NCrystal
130-
#when it is installed from Python wheels:
131-
execute_process(
132-
COMMAND "ncrystal-config" "--show" "cmakedir"
133-
OUTPUT_VARIABLE "NCrystal_DIR" OUTPUT_STRIP_TRAILING_WHITESPACE
134-
)
135-
endif()
136-
find_package(NCrystal 3.8.0 REQUIRED)
137-
message(STATUS "Found NCrystal: ${NCrystal_DIR} (version ${NCrystal_VERSION})")
138-
endif()
139-
140121
#===============================================================================
141122
# DAGMC Geometry Support - need DAGMC/MOAB
142123
#===============================================================================
@@ -372,6 +353,7 @@ list(APPEND libopenmc_SOURCES
372353
src/mgxs.cpp
373354
src/mgxs_interface.cpp
374355
src/ncrystal_interface.cpp
356+
src/ncrystal_load.cpp
375357
src/nuclide.cpp
376358
src/output.cpp
377359
src/particle.cpp
@@ -548,11 +530,6 @@ if (OPENMC_USE_MCPL)
548530
target_link_libraries(libopenmc MCPL::mcpl)
549531
endif()
550532

551-
if(OPENMC_USE_NCRYSTAL)
552-
target_compile_definitions(libopenmc PRIVATE NCRYSTAL)
553-
target_link_libraries(libopenmc NCrystal::NCrystal)
554-
endif()
555-
556533
#===============================================================================
557534
# Log build info that this executable can report later
558535
#===============================================================================

CODEOWNERS

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ Dockerfile @shimwell
6060
src/random_ray/ @jtramm
6161

6262
# NCrystal interface
63-
src/ncrystal_interface.cpp @marquezj
63+
src/ncrystal_interface.cpp @marquezj @tkittel
64+
src/ncrystal_load.cpp @marquezj @tkittel
6465

6566
# MCPL interface
6667
src/mcpl_interface.cpp @ebknudsen

cmake/OpenMCConfig.cmake.in

-13
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@ if(@OPENMC_USE_DAGMC@)
88
find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@)
99
endif()
1010

11-
if(@OPENMC_USE_NCRYSTAL@)
12-
if(NOT DEFINED "NCrystal_DIR")
13-
#Invocation of "ncrystal-config --show cmakedir" is needed to find NCrystal
14-
#when it is installed from Python wheels:
15-
execute_process(
16-
COMMAND "ncrystal-config" "--show" "cmakedir"
17-
OUTPUT_VARIABLE "NCrystal_DIR" OUTPUT_STRIP_TRAILING_WHITESPACE
18-
)
19-
endif()
20-
find_package(NCrystal REQUIRED)
21-
message(STATUS "Found NCrystal: ${NCrystal_DIR} (version ${NCrystal_VERSION})")
22-
endif()
23-
2411
if(@OPENMC_USE_LIBMESH@)
2512
include(FindPkgConfig)
2613
list(APPEND CMAKE_PREFIX_PATH @LIBMESH_PREFIX@)

docs/source/usersguide/install.rst

+7-13
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,13 @@ Prerequisites
284284

285285
* NCrystal_ library for defining materials with enhanced thermal neutron transport
286286

287-
Adding this option allows the creation of materials from NCrystal, which
288-
replaces the scattering kernel treatment of ACE files with a modular,
289-
on-the-fly approach. To use it `install
290-
<https://github.com/mctools/ncrystal/wiki/Get-NCrystal>`_ NCrystal and
291-
turn on the option in the CMake configuration step::
292-
293-
cmake -DOPENMC_USE_NCRYSTAL=on ..
287+
OpenMC supports the creation of materials from NCrystal, which replaces
288+
the scattering kernel treatment of ACE files with a modular, on-the-fly
289+
approach. OpenMC does not need any particular build option to use this,
290+
but NCrystal must be installed on the system. Refer to `NCrystal
291+
documentation
292+
<https://github.com/mctools/ncrystal/wiki/Get-NCrystal>`_ for how this is
293+
achieved.
294294

295295
* libMesh_ mesh library framework for numerical simulations of partial differential equations
296296

@@ -393,12 +393,6 @@ OPENMC_USE_MCPL
393393
Turns on support for reading MCPL_ source files and writing MCPL source points
394394
and surface sources. (Default: off)
395395

396-
OPENMC_USE_NCRYSTAL
397-
Turns on support for NCrystal materials. NCrystal must be `installed
398-
<https://github.com/mctools/ncrystal/wiki/Get-NCrystal>`_ and `initialized
399-
<https://github.com/mctools/ncrystal/wiki/Using-NCrystal#setting-up>`_.
400-
(Default: off)
401-
402396
OPENMC_USE_LIBMESH
403397
Enables the use of unstructured mesh tallies with libMesh_. (Default: off)
404398

include/openmc/ncrystal_interface.h

+15-25
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
#ifndef OPENMC_NCRYSTAL_INTERFACE_H
22
#define OPENMC_NCRYSTAL_INTERFACE_H
33

4-
#ifdef NCRYSTAL
5-
#include "NCrystal/NCrystal.hh"
6-
#endif
7-
4+
#include "openmc/ncrystal_load.h"
85
#include "openmc/particle.h"
96

107
#include <cstdint> // for uint64_t
@@ -17,28 +14,25 @@ namespace openmc {
1714
// Constants
1815
//==============================================================================
1916

20-
extern "C" const bool NCRYSTAL_ENABLED;
21-
2217
//! Energy in [eV] to switch between NCrystal and ENDF
2318
constexpr double NCRYSTAL_MAX_ENERGY {5.0};
2419

2520
//==============================================================================
26-
// Wrapper class an NCrystal material
21+
// Wrapper class for an NCrystal material
2722
//==============================================================================
2823

2924
class NCrystalMat {
3025
public:
3126
//----------------------------------------------------------------------------
3227
// Constructors
33-
NCrystalMat() = default;
28+
NCrystalMat() = default; // empty object
3429
explicit NCrystalMat(const std::string& cfg);
3530

3631
//----------------------------------------------------------------------------
3732
// Methods
3833

39-
#ifdef NCRYSTAL
40-
//! Return configuration string
41-
std::string cfg() const;
34+
//! Return configuration string:
35+
const std::string& cfg() const { return cfg_; }
4236

4337
//! Get cross section from NCrystal material
4438
//
@@ -52,25 +46,21 @@ class NCrystalMat {
5246
void scatter(Particle& p) const;
5347

5448
//! Whether the object holds a valid NCrystal material
55-
operator bool() const;
56-
#else
49+
operator bool() const { return !cfg_.empty(); }
5750

58-
//----------------------------------------------------------------------------
59-
// Trivial methods when compiling without NCRYSTAL
60-
std::string cfg() const { return ""; }
61-
double xs(const Particle& p) const { return -1.0; }
62-
void scatter(Particle& p) const {}
63-
operator bool() const { return false; }
64-
#endif
51+
NCrystalMat clone() const
52+
{
53+
NCrystalMat c;
54+
c.cfg_ = cfg_;
55+
c.proc_ = proc_.clone();
56+
return c;
57+
}
6558

6659
private:
6760
//----------------------------------------------------------------------------
6861
// Data members (only present when compiling with NCrystal support)
69-
#ifdef NCRYSTAL
70-
std::string cfg_; //!< NCrystal configuration string
71-
std::shared_ptr<const NCrystal::ProcImpl::Process>
72-
ptr_; //!< Pointer to NCrystal material object
73-
#endif
62+
std::string cfg_; //!< NCrystal configuration string
63+
NCrystalScatProc proc_; //!< NCrystal scatter process
7464
};
7565

7666
//==============================================================================

include/openmc/ncrystal_load.h

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//! \file ncrystal_load.h
2+
//! \brief Helper class taking care of loading NCrystal at runtime.
3+
4+
#ifndef OPENMC_NCRYSTAL_LOAD_H
5+
#define OPENMC_NCRYSTAL_LOAD_H
6+
7+
#include <algorithm> // for swap
8+
#include <functional> // for function
9+
#include <memory> // for shared_ptr
10+
#include <utility> // for move
11+
12+
namespace NCrystalVirtualAPI {
13+
14+
// NOTICE: Do NOT make ANY changes in the NCrystalVirtualAPI::VirtAPI_Type1_v1
15+
// class, it is required to stay exactly constant over time and compatible with
16+
// the same definition used to compile the NCrystal library! But changes to
17+
// white space, comments, and formatting is of course allowed. This API was
18+
// introduced in NCrystal 4.1.0.
19+
20+
//! Abstract base class for NCrystal interface which must be declared exactly as
21+
// it is in NCrystal itself.
22+
23+
class VirtAPI_Type1_v1 {
24+
public:
25+
// Note: neutron must be an array of length 4 with values {ekin,ux,uy,uz}
26+
class ScatterProcess;
27+
virtual const ScatterProcess* createScatter(const char* cfgstr) const = 0;
28+
virtual const ScatterProcess* cloneScatter(const ScatterProcess*) const = 0;
29+
virtual void deallocateScatter(const ScatterProcess*) const = 0;
30+
virtual double crossSectionUncached(
31+
const ScatterProcess&, const double* neutron) const = 0;
32+
virtual void sampleScatterUncached(const ScatterProcess&,
33+
std::function<double()>& rng, double* neutron) const = 0;
34+
// Plumbing:
35+
static constexpr unsigned interface_id = 1001;
36+
virtual ~VirtAPI_Type1_v1() = default;
37+
VirtAPI_Type1_v1() = default;
38+
VirtAPI_Type1_v1(const VirtAPI_Type1_v1&) = delete;
39+
VirtAPI_Type1_v1& operator=(const VirtAPI_Type1_v1&) = delete;
40+
VirtAPI_Type1_v1(VirtAPI_Type1_v1&&) = delete;
41+
VirtAPI_Type1_v1& operator=(VirtAPI_Type1_v1&&) = delete;
42+
};
43+
44+
} // namespace NCrystalVirtualAPI
45+
46+
namespace openmc {
47+
48+
using NCrystalAPI = NCrystalVirtualAPI::VirtAPI_Type1_v1;
49+
50+
//! Function which locates and loads NCrystal at runtime using the virtual API
51+
std::shared_ptr<const NCrystalAPI> load_ncrystal_api();
52+
53+
//! Class encapsulating exactly the parts of NCrystal needed by OpenMC
54+
55+
class NCrystalScatProc final {
56+
public:
57+
//! Empty constructor which does not load NCrystal
58+
NCrystalScatProc() {}
59+
60+
//! Load NCrystal and instantiate a scattering process
61+
//! \param cfgstr NCrystal cfg-string defining the material.
62+
NCrystalScatProc(const char* cfgstr)
63+
: api_(load_ncrystal_api()), p_(api_->createScatter(cfgstr))
64+
{}
65+
66+
// Note: Neutron state array is {ekin,ux,uy,uz}
67+
68+
//! Returns total scattering cross section in units of barns per atom.
69+
//! \param neutron_state array {ekin,ux,uy,uz} with ekin (eV) and direction.
70+
double cross_section(const double* neutron_state) const
71+
{
72+
return api_->crossSectionUncached(*p_, neutron_state);
73+
}
74+
75+
//! Returns total scattering cross section in units of barns per atom.
76+
//! \param rng function returning random numbers in the unit interval
77+
//! \param neutron_state array {ekin,ux,uy,uz} with ekin (eV) and direction.
78+
void scatter(std::function<double()>& rng, double* neutron_state) const
79+
{
80+
api_->sampleScatterUncached(*p_, rng, neutron_state);
81+
}
82+
83+
//! Clones the object which is otherwise move-only
84+
NCrystalScatProc clone() const
85+
{
86+
NCrystalScatProc c;
87+
if (p_) {
88+
c.api_ = api_;
89+
c.p_ = api_->cloneScatter(p_);
90+
}
91+
return c;
92+
}
93+
94+
// Plumbing (move-only semantics, but supports explicit clone):
95+
NCrystalScatProc(const NCrystalScatProc&) = delete;
96+
NCrystalScatProc& operator=(const NCrystalScatProc&) = delete;
97+
98+
NCrystalScatProc(NCrystalScatProc&& o) : api_(std::move(o.api_)), p_(nullptr)
99+
{
100+
std::swap(p_, o.p_);
101+
}
102+
103+
NCrystalScatProc& operator=(NCrystalScatProc&& o)
104+
{
105+
if (p_) {
106+
api_->deallocateScatter(p_);
107+
p_ = nullptr;
108+
}
109+
std::swap(api_, o.api_);
110+
std::swap(p_, o.p_);
111+
return *this;
112+
}
113+
114+
~NCrystalScatProc()
115+
{
116+
if (p_)
117+
api_->deallocateScatter(p_);
118+
}
119+
120+
private:
121+
std::shared_ptr<const NCrystalAPI> api_;
122+
const NCrystalAPI::ScatterProcess* p_ = nullptr;
123+
};
124+
125+
} // namespace openmc
126+
127+
#endif

openmc/lib/__init__.py

-3
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@
4040
def _dagmc_enabled():
4141
return c_bool.in_dll(_dll, "DAGMC_ENABLED").value
4242

43-
def _ncrystal_enabled():
44-
return c_bool.in_dll(_dll, "NCRYSTAL_ENABLED").value
45-
4643
def _coord_levels():
4744
return c_int.in_dll(_dll, "n_coord_levels").value
4845

openmc/material.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,11 @@ def from_ncrystal(cls, cfg, **kwargs) -> Material:
429429
430430
"""
431431

432-
import NCrystal
432+
try:
433+
import NCrystal
434+
except ModuleNotFoundError as e:
435+
raise RuntimeError('The .from_ncrystal method requires'
436+
' NCrystal to be installed.') from e
433437
nc_mat = NCrystal.createInfo(cfg)
434438

435439
def openmc_natabund(Z):

0 commit comments

Comments
 (0)