From 9c1b318e785b4ecbdbba9d58b50222e076cb50ec Mon Sep 17 00:00:00 2001 From: Dan Short Date: Mon, 3 Aug 2020 17:38:12 +0100 Subject: [PATCH] Only instantiate custom source once In the existing custom_source implementation, the source will only be created once. This is much more efficient than the custom serialized source, where each sampling will create a new instance of the class. As there could be many samples, this introduces an overhead, particularly if the operations to instantiate the class are not trivial. This implementation defines an abstract class, which is then used by the custom classes to allow the custom serialized class to be created based on the plugin, sampled from, and then destroyed. Update documentation and test to reflect this. --- docs/source/io_formats/settings.rst | 12 +- docs/source/usersguide/settings.rst | 109 +++++++++++++----- .../serialized_source_ring.cpp | 61 ++++++---- include/openmc/source.h | 10 ++ src/source.cpp | 24 ++-- .../serialized_source_sampling.cpp | 52 +++++---- 6 files changed, 181 insertions(+), 87 deletions(-) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 7a356ffa1..511140290 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -472,9 +472,15 @@ attributes/sub-elements: 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 - ``sample_source()`` function must take as input an additional character - array containing the serialization that OpenMC will have read from this - attribute. If the library attribute is not provided then this attribute + 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 array of integers as an + argument. The custom source library must also contain a ``create`` method, + which takes a serialized form of the source (as provided to the parameters + attribute) as an argument and returns a pointer to an instance of the custom + source, and a ``destroy`` method, which takes a pointer to an instance of + the custom source as an argument and deletes the memory allocated to the + custom source. If the library attribute is not provided then this attribute will be ignored. More documentation on how to build serialized sources can be found in :ref:`serialized_custom_source`. diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index ebab37122..064803694 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -243,47 +243,92 @@ Custom Serialized Sources If the custom source may be used with parameters at a variety of values then it may be necessary to serialize the source to an appropriate format in order to avoid recompiling the source library for each run. This is supported by defining -the ``source_sampling`` function with an additional parameter that receives the -parameters used to build the source: +a class inheriting from ``openmc::CustomSource`` that implements a +``sample_source`` function. The class should also have logic to deserialise +the parameters provided via the settings.xml file in the +:attr:``openmc.Source.parameters`` attribute: .. code-block:: c++ - // you must have external C linkage here - extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, const char* parameters) { - // function to deserialize the source - SerializedSource source = SerializedSource::from_string(parameters); + #include - openmc::Particle::Bank particle; - // wgt - particle.particle = openmc::Particle::Type::neutron; - particle.wgt = 1.0; - // position - double angle = 2. * M_PI * openmc::prn(seed); + #include "openmc/random_lcg.h" + #include "openmc/source.h" + #include "openmc/particle.h" - // get the radius from the serialized form of the source - double radius = source.radius(); - 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}; + class SerializedSource : public openmc::CustomSource { + protected: + double energy_; - // get the energy from the serialized form of the source - particle.E = source.energy(); - particle.delayed_group = 0; + // Protect the constructor so that the class can only be created by serialisation. + SerializedSource(double energy) { + energy_ = energy; + } - return particle; - } + public: + // Getters for the values that we want to use in sampling. + double energy() { return energy_; } -The details of the serialization routine, in particular the schema of the source -are to be defined by the implementation of the serializable source class. The -location of the serialized representation of the source to be used must be -provided via the :attr:`openmc.Source.parameters` attribute, along with the -custom source library location in :attr:`openmc.Source.library`. + // Defines a function that can create a pointer to a new instance of this class + // by deserializing from the provided string. + static SerializedSource* from_string(const char* parameters) { + std::unordered_map parameter_mapping; -When defining a class to be implemented via this deserialization approach, care -must be taken to ensure that unique symbols in the resulting binary are -discoverable when the ``sample_source`` function is loaded via ``dlsym``. + std::stringstream ss(parameters); + std::string parameter; + while (std::getline(ss, parameter, ',')) { + parameter.erase(0, parameter.find_first_not_of(' ')); + std::string key = parameter.substr(0, parameter.find_first_of('=')); + std::string value = parameter.substr(parameter.find_first_of('=') + 1, parameter.length()); + parameter_mapping[key] = value; + } + + return new SerializedSource(std::stod(parameter_mapping["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; + + return particle; + } + }; + +The custom source library function in this case must also define a ``create`` +method and a ``destroy`` method. The ``create`` method will be used to +generate an instance of the custom source, based on the value supplied in +the :attr:``openmc.Source.parameters`` attribute. The ``destroy`` method +will be used to destroy that instance once the sampling has completed. Both +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" SerializedSource* create(const char* serialized_source) { + return SerializedSource::from_string(serialized_source); + } + + // you must have external C linkage here otherwise + // dlopen will not find the file + extern "C" void destroy(SerializedSource* source) { + delete source; + } + +As with the basic custom source functionality, the custom source library +location must also be provided in the :attr:`openmc.Source.library` +attribute. --------------- Shannon Entropy diff --git a/examples/serialized_custom_source/serialized_source_ring.cpp b/examples/serialized_custom_source/serialized_source_ring.cpp index 4b93d2c76..7584b152c 100644 --- a/examples/serialized_custom_source/serialized_source_ring.cpp +++ b/examples/serialized_custom_source/serialized_source_ring.cpp @@ -5,7 +5,7 @@ #include "openmc/source.h" #include "openmc/particle.h" -class SerializedSource { +class SerializedSource : public openmc::CustomSource { protected: double radius_; double energy_; @@ -21,7 +21,9 @@ class SerializedSource { double radius() { return radius_; } double energy() { return energy_; } - static SerializedSource from_string(const char* parameters) { + // Defines a function that can create a pointer to a new instance of this class + // by deserializing from the provided string. + static SerializedSource* from_string(const char* parameters) { std::unordered_map parameter_mapping; std::stringstream ss(parameters); @@ -33,29 +35,40 @@ class SerializedSource { parameter_mapping[key] = value; } - return SerializedSource(std::stod(parameter_mapping["radius"]), std::stod(parameter_mapping["energy"])); + return new SerializedSource(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 particle; + // wgt + particle.particle = openmc::Particle::Type::neutron; + particle.wgt = 1.0; + // position + double angle = 2. * M_PI * openmc::prn(seed); + double radius = this->radius(); + 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 = this->energy(); + particle.delayed_group = 0; + + return particle; } }; -// you must have external C linkage here otherwise -// dlopen will not find the file -extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, const char* parameters) { - SerializedSource source = SerializedSource::from_string(parameters); - - 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 = source.radius(); - 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 = source.energy(); - particle.delayed_group = 0; - - return particle; +// A function to create a 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" SerializedSource* create(const char* parameters) { + return SerializedSource::from_string(parameters); +} + +// A function to destroy a 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" void destroy(SerializedSource* source) { + delete source; } diff --git a/include/openmc/source.h b/include/openmc/source.h index 78e4043b9..bd24f093f 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -59,6 +59,16 @@ private: UPtrDist energy_; //!< Energy distribution }; +class CustomSource { + public: + virtual ~CustomSource() {} + + virtual Particle::Bank sample_source(uint64_t* seed) = 0; +}; + +typedef CustomSource* create_custom_source_t(const char* serialized_source); +typedef void destroy_custom_source_t(CustomSource*); + //============================================================================== // Functions //============================================================================== diff --git a/src/source.cpp b/src/source.cpp index 0650b5737..fc184dc4e 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -44,12 +44,13 @@ 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; -using serialized_sample_t = Particle::Bank (*)(uint64_t* seed, const char* parameters); -serialized_sample_t custom_serialized_source_function; -void* custom_source_library; +CustomSource* custom_source; +destroy_custom_source_t* destroy_custom_source; } @@ -379,9 +380,12 @@ void load_custom_source_library() using sample_t = Particle::Bank (*)(uint64_t* seed); custom_source_function = reinterpret_cast(dlsym(custom_source_library, "sample_source")); } else { - // get the function from the library using the provided serialization - using sample_t = Particle::Bank (*)(uint64_t* seed, const char* parameters); - custom_serialized_source_function = reinterpret_cast(dlsym(custom_source_library, "sample_source")); + // get the functions to create and destroy the CustomSource from the library + create_custom_source_t* create_custom_source = (create_custom_source_t*) dlsym(custom_source_library, "create"); + destroy_custom_source = (destroy_custom_source_t*) dlsym(custom_source_library, "destroy"); + + // create a pointer to an instance of the CustomSource + custom_source = create_custom_source(custom_source_parameters.c_str()); } // check for any dlsym errors @@ -399,6 +403,11 @@ void load_custom_source_library() void close_custom_source_library() { + if (custom_source) { + // destroy the CustomSource if it exists + destroy_custom_source(custom_source); + } + #ifdef HAS_DYNAMIC_LINKING dlclose(custom_source_library); #else @@ -412,7 +421,8 @@ Particle::Bank sample_custom_source_library(uint64_t* seed) if (custom_source_parameters.empty()) { return custom_source_function(seed); } else { - return custom_serialized_source_function(seed, custom_source_parameters.c_str()); + // sample from the instance of the CustomSource + return custom_source->sample_source(seed); } } diff --git a/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp b/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp index d4c5d8024..a951ef3cd 100644 --- a/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp +++ b/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp @@ -3,9 +3,8 @@ #include "openmc/random_lcg.h" #include "openmc/source.h" #include "openmc/particle.h" -#include "pugixml.hpp" -class SerializedSource { +class SerializedSource : public openmc::CustomSource { protected: double energy_; @@ -18,7 +17,9 @@ class SerializedSource { // Getters for the values that we want to use in sampling. double energy() { return energy_; } - static SerializedSource from_string(const char* parameters) { + // Defines a function that can create a pointer to a new instance of this class + // by deserializing from the provided string. + static SerializedSource* from_string(const char* parameters) { std::unordered_map parameter_mapping; std::stringstream ss(parameters); @@ -30,27 +31,36 @@ class SerializedSource { parameter_mapping[key] = value; } - return SerializedSource(std::stod(parameter_mapping["energy"])); + return new SerializedSource(std::stod(parameter_mapping["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; + + return particle; } }; // you must have external C linkage here otherwise // dlopen will not find the file -extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, const char* parameters) { - SerializedSource source = SerializedSource::from_string(parameters); - - 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 = source.energy(); - particle.delayed_group = 0; - - return particle; +extern "C" SerializedSource* create(const char* serialized_source) { + return SerializedSource::from_string(serialized_source); +} + +// you must have external C linkage here otherwise +// dlopen will not find the file +extern "C" void destroy(SerializedSource* source) { + delete source; }