diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 6bd5c3bc1..33674ca32 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -462,25 +462,23 @@ attributes/sub-elements: :library: If this attribute is given, it indicates that the source is to be instantiated from an externally compiled source function. This source can be - as complex as is required to define the source for your problem. The only - requirement is that there is a function called ``sample_source()``. More - documentation on how to build sources can be found in :ref:`custom_source`. + as complex as is required to define the source for your problem. The library + has a few basic requirements: + + * It must contain a class that inherits from ``openmc::CustomSource``; + * The class must implement a function called ``sample_source()``; + * There must be a ``create_openmc_source()`` function that creates the source + as a unique pointer. This function can be used to pass parameters through to + the source from the XML, if needed. + + More documentation on how to build sources can be found in :ref:`custom_source`. *Default*: None :parameters: - If this attribute is given, it indicates that the source is to be - instantiated from an externally compiled source function, with parameters - defined by the string provided in this attribute. In this case, the - custom source library must define a class that inherits from the - ``openmc::CustomSource`` abstract class. This class must implement a - ``sample_source()`` function, which takes an unsigned integer pointer as an - argument. The custom source library must also contain an - ``openmc_create_source`` method, which takes the value provided to the - parameters attribute as an argument and returns a pointer to an instance of - the custom source. If the library attribute is not provided then this - attribute will be ignored. More documentation on how to build parametrized - sources can be found in :ref:`parameterized_custom_source`. + If this attribute is given, it provides the parameters to pass through to the + class generated using the ``library`` parameter . More documentation on how to + build parametrized sources can be found in :ref:`parameterized_custom_source`. *Default*: None diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index 30bf71132..b992ed2ae 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -182,42 +182,55 @@ Custom Sources It is often the case that one may wish to simulate a complex source distribution that is not possible to represent with the classes described above. For these -situations, it is possible to define a complex source with an externally defined -source function that is loaded at runtime. A simple example source is shown +situations, it is possible to define a complex source class containing an externally +defined source function that is loaded at runtime. A simple example source is shown below. .. code-block:: c++ - #include "openmc/random_lcg.h" - #include "openmc/source.h" - #include "openmc/particle.h" + #include // for unique_ptr - // you must have external C linkage here - extern "C" openmc::Particle::Bank sample_source(uint64_t* seed) { - openmc::Particle::Bank particle; - // weight - particle.particle = openmc::Particle::Type::neutron; - particle.wgt = 1.0; - // position - double angle = 2.0 * M_PI * openmc::prn(seed); - double radius = 3.0; - particle.r.x = radius * std::cos(angle); - particle.r.y = radius * std::sin(angle); - particle.r.z = 0.0; - // angle - particle.u = {1.0, 0.0, 0.0}; - particle.E = 14.08e6; - particle.delayed_group = 0; - return particle; + #include "openmc/random_lcg.h" + #include "openmc/source.h" + #include "openmc/particle.h" + + class Source : public openmc::CustomSource + { + openmc::Particle::Bank sample_source(uint64_t* seed) + { + openmc::Particle::Bank particle; + // weight + particle.particle = openmc::Particle::Type::neutron; + particle.wgt = 1.0; + // position + double angle = 2.0 * M_PI * openmc::prn(seed); + double radius = 3.0; + particle.r.x = radius * std::cos(angle); + particle.r.y = radius * std::sin(angle); + particle.r.z = 0.0; + // angle + particle.u = {1.0, 0.0, 0.0}; + particle.E = 14.08e6; + particle.delayed_group = 0; + return particle; + } + }; + + extern "C" std::unique_ptr openmc_create_source() { + return std::unique_ptr (new Source()); } The above source creates monodirectional 14.08 MeV neutrons that are distributed in a ring with a 3 cm radius. This routine is not particularly complex, but should serve as an example upon which to build more complicated sources. - .. note:: The function signature must be declared ``extern "C"``. + .. note:: The source class must inherit from ``openmc::CustomSource`` and + implement a ``sample_source()`` function. - .. note:: You should only use the openmc::prn() random number generator + .. note:: The ``openmc_create_source()`` function signature must be declared + ``extern "C"``. + + .. note:: You should only use the ``openmc::prn()`` random number generator. In order to build your external source, you will need to link it against the OpenMC shared library. This can be done by writing a CMakeLists.txt file: @@ -240,61 +253,53 @@ used for sampling source particles at runtime. Custom Parameterized Sources ---------------------------- -If the custom source may be used with parameters at a variety of values then it -may be necessary to represent those parameters as a string in order to avoid -recompiling the source library for each run. This is supported by defining a -class inheriting from ``openmc::CustomSource`` that implements a -``sample_source`` function: +Some custom sources may have values (parameters) that can be changed between +runs. This is supported by using the ``create_custom_source()`` function to +pass parameters defined in the :attr:`openmc.Source.parameters` attribute to +the source class when it is created: .. code-block:: c++ + #include // for unique_ptr + #include "openmc/source.h" #include "openmc/particle.h" - class ParameterizedSource : public openmc::CustomSource { - public: - double energy; - - ParameterizedSource(double energy) { - this->energy = energy; - } + class Source : public openmc::CustomSource + { + double energy; - // Samples from an instance of this class. - openmc::Particle::Bank sample_source(uint64_t* seed) { - openmc::Particle::Bank particle; - // wgt - particle.particle = openmc::Particle::Type::neutron; - particle.wgt = 1.0; - // position - particle.r.x = 0.0; - particle.r.y = 0.0; - particle.r.z = 0.0; - // angle - particle.u = {1.0, 0.0, 0.0}; - particle.E = this->energy; - particle.delayed_group = 0; + Source(double energy) + { + this->energy = energy; + } - return particle; - } + // Samples from an instance of this class. + openmc::Particle::Bank sample_source(uint64_t* seed) + { + openmc::Particle::Bank particle; + // weight + particle.particle = openmc::Particle::Type::neutron; + particle.wgt = 1.0; + // position + particle.r.x = 0.0; + particle.r.y = 0.0; + particle.r.z = 0.0; + // angle + particle.u = {1.0, 0.0, 0.0}; + particle.E = this->energy; + particle.delayed_group = 0; + + return particle; + } }; -The custom source library function in this case must also define an -``openmc_create_source`` method, which will be used to generate an instance of -the custom source, based on the value supplied in the -:attr:``openmc.Source.parameters`` attribute. The -``openmc_create_source`` method must be defined with ``extern "C"``: - -.. code-block:: c++ - - // you must have external C linkage here otherwise - // dlopen will not find the file - extern "C" ParameterizedSource* openmc_create_source(const char* parameter) { - return new ParameterizedSource(atof(parameter)); + extern "C" std::unique_ptr openmc_create_source(const char* parameter) { + return std::unique_ptr (new Source(atof(parameter))); } As with the basic custom source functionality, the custom source library -location must also be provided in the :attr:`openmc.Source.library` -attribute. +location must be provided in the :attr:`openmc.Source.library` attribute. --------------- Shannon Entropy diff --git a/examples/custom_source/source_ring.cpp b/examples/custom_source/source_ring.cpp index d68122dd7..5b7bb837c 100644 --- a/examples/custom_source/source_ring.cpp +++ b/examples/custom_source/source_ring.cpp @@ -1,26 +1,36 @@ #include // for M_PI +#include // for unique_ptr #include "openmc/random_lcg.h" #include "openmc/source.h" #include "openmc/particle.h" -// you must have external C linkage here otherwise -// dlopen will not find the file -extern "C" openmc::Particle::Bank sample_source(uint64_t* seed) +class Source : public openmc::CustomSource { - openmc::Particle::Bank particle; - // wgt - particle.particle = openmc::Particle::Type::neutron; - particle.wgt = 1.0; - // position - double angle = 2. * M_PI * openmc::prn(seed); - double radius = 3.0; - particle.r.x = radius * std::cos(angle); - particle.r.y = radius * std::sin(angle); - particle.r.z = 0.0; - // angle - particle.u = {1.0, 0.0, 0.0}; - particle.E = 14.08e6; - particle.delayed_group = 0; - return particle; + openmc::Particle::Bank sample_source(uint64_t* seed) + { + openmc::Particle::Bank particle; + // wgt + particle.particle = openmc::Particle::Type::neutron; + particle.wgt = 1.0; + // position + double angle = 2.0 * M_PI * openmc::prn(seed); + double radius = 3.0; + particle.r.x = radius * std::cos(angle); + particle.r.y = radius * std::sin(angle); + particle.r.z = 0.0; + // angle + particle.u = {1.0, 0.0, 0.0}; + particle.E = 14.08e6; + particle.delayed_group = 0; + return particle; + } +}; + +// A function to create a unique pointer to an instance of this class when generated +// via a plugin call using dlopen/dlsym. +// You must have external C linkage here otherwise dlopen will not find the file +extern "C" std::unique_ptr openmc_create_source() +{ + return std::unique_ptr (new Source()); } diff --git a/examples/parameterized_custom_source/parameterized_source_ring.cpp b/examples/parameterized_custom_source/parameterized_source_ring.cpp index 51218e1b3..abcaa975a 100644 --- a/examples/parameterized_custom_source/parameterized_source_ring.cpp +++ b/examples/parameterized_custom_source/parameterized_source_ring.cpp @@ -1,17 +1,20 @@ #include // for M_PI +#include // for unique_ptr #include #include "openmc/random_lcg.h" #include "openmc/source.h" #include "openmc/particle.h" -class ParameterizedSource : public openmc::CustomSource { +class Source : public openmc::CustomSource +{ protected: double radius_; double energy_; - // Protect the constructor so that the class can only be created by serialisation. - ParameterizedSource(double radius, double energy) { + // Protect the constructor as we only want the class to be created by the from_string method. + Source(double radius, double energy) + { radius_ = radius; energy_ = energy; } @@ -21,9 +24,10 @@ class ParameterizedSource : public openmc::CustomSource { double radius() { return radius_; } double energy() { return energy_; } - // Defines a function that can create a pointer to a new instance of this class - // by deserializing from the provided string. - static ParameterizedSource* from_string(const char* parameters) { + // Defines a function that can create a unique pointer to a new instance of this class + // by extracting the parameters from the provided string. + static std::unique_ptr from_string(const char* parameters) + { std::unordered_map parameter_mapping; std::stringstream ss(parameters); @@ -35,17 +39,20 @@ class ParameterizedSource : public openmc::CustomSource { parameter_mapping[key] = value; } - return new ParameterizedSource(std::stod(parameter_mapping["radius"]), std::stod(parameter_mapping["energy"])); + return std::unique_ptr ( + new Source(std::stod(parameter_mapping["radius"]), std::stod(parameter_mapping["energy"])) + ); } // Samples from an instance of this class. - openmc::Particle::Bank sample_source(uint64_t* seed) { + openmc::Particle::Bank sample_source(uint64_t* seed) + { openmc::Particle::Bank particle; // wgt particle.particle = openmc::Particle::Type::neutron; particle.wgt = 1.0; // position - double angle = 2. * M_PI * openmc::prn(seed); + double angle = 2.0 * M_PI * openmc::prn(seed); double radius = this->radius(); particle.r.x = radius * std::cos(angle); particle.r.y = radius * std::sin(angle); @@ -59,9 +66,10 @@ class ParameterizedSource : public openmc::CustomSource { } }; -// A function to create a pointer to an instance of this class when generated +// A function to create a unique pointer to an instance of this class when generated // via a plugin call using dlopen/dlsym. // You must have external C linkage here otherwise dlopen will not find the file -extern "C" ParameterizedSource* openmc_create_source(const char* parameters) { - return ParameterizedSource::from_string(parameters); +extern "C" std::unique_ptr openmc_create_source(const char* parameters) +{ + return Source::from_string(parameters); } diff --git a/include/openmc/source.h b/include/openmc/source.h index 5bc2901b4..460e7eca3 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -66,7 +66,7 @@ class CustomSource { virtual Particle::Bank sample_source(uint64_t* seed) = 0; }; -typedef CustomSource* create_custom_source_t(const char* parameters); +typedef std::unique_ptr create_custom_source_t(const char* parameters); //============================================================================== // Functions diff --git a/src/source.cpp b/src/source.cpp index 673ee8804..7c40147ce 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -5,6 +5,7 @@ #endif #include // for move +#include // for unique_ptr #ifdef HAS_DYNAMIC_LINKING #include // for dlopen, dlsym, dlclose, dlerror @@ -45,11 +46,8 @@ std::vector external_sources; namespace { void* custom_source_library; -using sample_t = Particle::Bank (*)(uint64_t* seed); -sample_t custom_source_function; - -std::string custom_source_parameters; -CustomSource* custom_source; +std::string custom_source_parameters = ""; +std::unique_ptr custom_source; } @@ -374,17 +372,11 @@ void load_custom_source_library() // reset errors dlerror(); - if (custom_source_parameters.empty()) { - // get the function from the library - using sample_t = Particle::Bank (*)(uint64_t* seed); - custom_source_function = reinterpret_cast(dlsym(custom_source_library, "sample_source")); - } else { - // get the function to create the CustomSource from the library - create_custom_source_t* create_custom_source = (create_custom_source_t*) dlsym(custom_source_library, "openmc_create_source"); + // get the function to create the CustomSource from the library + create_custom_source_t* create_custom_source = (create_custom_source_t*) dlsym(custom_source_library, "openmc_create_source"); - // create a pointer to an instance of the CustomSource - custom_source = create_custom_source(custom_source_parameters.c_str()); - } + // create a pointer to an instance of the CustomSource + custom_source = create_custom_source(custom_source_parameters.c_str()); // check for any dlsym errors auto dlsym_error = dlerror(); @@ -401,9 +393,9 @@ void load_custom_source_library() void close_custom_source_library() { - if (custom_source) { - // delete the CustomSource if it exists - delete custom_source; + if (custom_source.get()) { + // Make sure the custom source is destroyed before we close it's libary. + custom_source.reset(); } #ifdef HAS_DYNAMIC_LINKING @@ -416,12 +408,8 @@ void close_custom_source_library() Particle::Bank sample_custom_source_library(uint64_t* seed) { - if (custom_source_parameters.empty()) { - return custom_source_function(seed); - } else { - // sample from the instance of the CustomSource - return custom_source->sample_source(seed); - } + // sample from the instance of the CustomSource + return custom_source->sample_source(seed); } void fill_source_bank_custom_source() diff --git a/tests/regression_tests/source_dlopen/source_sampling.cpp b/tests/regression_tests/source_dlopen/source_sampling.cpp index eaf8b74d9..529e3a547 100644 --- a/tests/regression_tests/source_dlopen/source_sampling.cpp +++ b/tests/regression_tests/source_dlopen/source_sampling.cpp @@ -1,11 +1,14 @@ #include +#include + #include "openmc/random_lcg.h" #include "openmc/source.h" #include "openmc/particle.h" -// you must have external C linkage here otherwise -// dlopen will not find the file -extern "C" openmc::Particle::Bank sample_source(uint64_t *seed) { +class Source : openmc::CustomSource +{ + openmc::Particle::Bank sample_source(uint64_t *seed) + { openmc::Particle::Bank particle; // wgt particle.particle = openmc::Particle::Type::neutron; @@ -20,4 +23,13 @@ extern "C" openmc::Particle::Bank sample_source(uint64_t *seed) { particle.E = 14.08e6; particle.delayed_group = 0; return particle; + } +}; + +// A function to create a unique pointer to an instance of this class when generated +// via a plugin call using dlopen/dlsym. +// You must have external C linkage here otherwise dlopen will not find the file +extern "C" std::unique_ptr openmc_create_source() +{ + return std::unique_ptr (new Source()); } diff --git a/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat b/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat index a7462ae7f..f4a0eba73 100644 --- a/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat +++ b/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat @@ -19,5 +19,5 @@ 1000 10 0 - + diff --git a/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp b/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp index b3e9ca9cd..162164f01 100644 --- a/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp +++ b/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp @@ -1,16 +1,19 @@ #include "openmc/source.h" #include "openmc/particle.h" -class ParameterizedSource : public openmc::CustomSource { +class Source : public openmc::CustomSource +{ public: double energy; - ParameterizedSource(double energy) { + Source(double energy) + { this->energy = energy; } // Samples from an instance of this class. - openmc::Particle::Bank sample_source(uint64_t* seed) { + openmc::Particle::Bank sample_source(uint64_t* seed) + { openmc::Particle::Bank particle; // wgt particle.particle = openmc::Particle::Type::neutron; @@ -28,8 +31,10 @@ class ParameterizedSource : public openmc::CustomSource { } }; -// you must have external C linkage here otherwise -// dlopen will not find the file -extern "C" ParameterizedSource* openmc_create_source(const char* parameter) { - return new ParameterizedSource(atof(parameter)); +// A function to create a unique pointer to an instance of this class when generated +// via a plugin call using dlopen/dlsym. +// You must have external C linkage here otherwise dlopen will not find the file +extern "C" std::unique_ptr openmc_create_source(const char* parameter) +{ + return std::unique_ptr (new Source(atof(parameter))); } diff --git a/tests/regression_tests/source_parameterized_dlopen/test.py b/tests/regression_tests/source_parameterized_dlopen/test.py index 6c88c37c2..c613cde4b 100644 --- a/tests/regression_tests/source_parameterized_dlopen/test.py +++ b/tests/regression_tests/source_parameterized_dlopen/test.py @@ -20,9 +20,9 @@ def compile_source(request): f.write(textwrap.dedent(""" cmake_minimum_required(VERSION 3.3 FATAL_ERROR) project(openmc_sources CXX) - add_library(parameterized_source SHARED parameterized_source_sampling.cpp) + add_library(source SHARED parameterized_source_sampling.cpp) find_package(OpenMC REQUIRED HINTS {}) - target_link_libraries(parameterized_source OpenMC::libopenmc) + target_link_libraries(source OpenMC::libopenmc) """.format(openmc_dir))) # Create temporary build directory and change to there @@ -63,8 +63,8 @@ def model(): # custom source from shared library source = openmc.Source() - source.library = 'build/libparameterized_source.so' - source.parameters = 'energy=1e3' + source.library = 'build/libsource.so' + source.parameters = '1e3' model.settings.source = source return model