From 61cf4f71a900ab52bc2d083c9244cceca1a1f78c Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 10:26:33 +0100 Subject: [PATCH 01/24] Add a serialization attribute to Source Adds serialization as an optional attribute to Source objects. This attribute represents the path to the file containing the serialized representation of the source. --- openmc/source.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/openmc/source.py b/openmc/source.py index cc749c324..8ede50581 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -22,6 +22,8 @@ class Source: Source file from which sites should be sampled library : str Path to a custom source library + serialization : str + Path to the serialized representation of the custom source .. versionadded:: 0.12 strength : float @@ -41,6 +43,8 @@ class Source: Source file from which sites should be sampled library : str or None Path to a custom source library + serialization : str + Path to the serialized representation of the custom source strength : float Strength of the source particle : {'neutron', 'photon'} @@ -49,12 +53,13 @@ class Source: """ def __init__(self, space=None, angle=None, energy=None, filename=None, - library=None, strength=1.0, particle='neutron'): + library=None, serialization=None, strength=1.0, particle='neutron'): self._space = None self._angle = None self._energy = None self._file = None self._library = None + self._serialization = None if space is not None: self.space = space @@ -66,6 +71,8 @@ class Source: self.file = filename if library is not None: self.library = library + if serialization is not None: + self.serialization = serialization self.strength = strength self.particle = particle @@ -77,6 +84,10 @@ class Source: def library(self): return self._library + @property + def serialization(self): + return self._serialization + @property def space(self): return self._space @@ -107,6 +118,11 @@ class Source: cv.check_type('library', library_name, str) self._library = library_name + @serialization.setter + def serialization(self, serialization_path): + cv.check_type('serialization', serialization_path, str) + self._serialization = serialization_path + @space.setter def space(self, space): cv.check_type('spatial distribution', space, Spatial) @@ -150,6 +166,8 @@ class Source: element.set("file", self.file) if self.library is not None: element.set("library", self.library) + if self.serialization is not None: + element.set("serialization", self.serialization) if self.space is not None: element.append(self.space.to_xml_element()) if self.angle is not None: @@ -191,6 +209,10 @@ class Source: if library is not None: source.library = library + serialization = get_text(elem, 'serialization') + if serialization is not None: + source.serialization = serialization + space = elem.find('space') if space is not None: source.space = Spatial.from_xml_element(space) From 1b60181ddbb86e9c68672dc70759b85adbe92a42 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 10:29:23 +0100 Subject: [PATCH 02/24] Update build_xml to use serialization File is largely based on the existing custom_source example. Uses the serialization attribute on the source to point to an XML file containing the serialized representation of the source. Also builds the serialized_source.xml file with some default values. Uses a new name for the library containing the serializable source. --- .../serialized_custom_source/build_xml.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 examples/serialized_custom_source/build_xml.py diff --git a/examples/serialized_custom_source/build_xml.py b/examples/serialized_custom_source/build_xml.py new file mode 100644 index 000000000..fba545959 --- /dev/null +++ b/examples/serialized_custom_source/build_xml.py @@ -0,0 +1,46 @@ +import openmc + +# Define the serialised source +serialized_source = """ + 1.5 + 1e3 + +""" +with open('serialized_source.xml', 'w') as f: + f.write(serialized_source) + +# Create a single material +iron = openmc.Material() +iron.set_density('g/cm3', 5.0) +iron.add_element('Fe', 1.0) +mats = openmc.Materials([iron]) +mats.export_to_xml() + +# Create a 5 cm x 5 cm box filled with iron +box = openmc.model.rectangular_prism(10.0, 10.0, boundary_type='vacuum') +cell = openmc.Cell(fill=iron, region=box) +geometry = openmc.Geometry([cell]) +geometry.export_to_xml() + +# Tell OpenMC we're going to use our custom source +settings = openmc.Settings() +settings.run_mode = 'fixed source' +settings.batches = 10 +settings.particles = 1000 +source = openmc.Source() +source.library = 'build/libserialized_source.so' +source.serialization = 'serialized_source.xml' +settings.source = source +settings.export_to_xml() + +# Finally, define a mesh tally so that we can see the resulting flux +mesh = openmc.RegularMesh() +mesh.lower_left = (-5.0, -5.0) +mesh.upper_right = (5.0, 5.0) +mesh.dimension = (50, 50) + +tally = openmc.Tally() +tally.filters = [openmc.MeshFilter(mesh)] +tally.scores = ['flux'] +tallies = openmc.Tallies([tally]) +tallies.export_to_xml() From 6b193882c3f40b3590675f6342c411043f62ebfa Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 11:41:43 +0100 Subject: [PATCH 03/24] Class and sampling function of serialized source Generates a source ring in a similar manner to the existing custom_source example. Allows the radius and energy to be defined via a serialized representation of the source. Builds using CMake. --- .../serialized_custom_source/CMakeLists.txt | 8 +++ .../serialized_source_ring.cpp | 58 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 examples/serialized_custom_source/CMakeLists.txt create mode 100644 examples/serialized_custom_source/serialized_source_ring.cpp diff --git a/examples/serialized_custom_source/CMakeLists.txt b/examples/serialized_custom_source/CMakeLists.txt new file mode 100644 index 000000000..9d76718e5 --- /dev/null +++ b/examples/serialized_custom_source/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) +project(openmc_sources CXX) +add_library(serialized_source SHARED serialized_source_ring.cpp) +find_package(OpenMC REQUIRED) +if (OpenMC_FOUND) + message(STATUS "Found OpenMC: ${OpenMC_DIR}") +endif() +target_link_libraries(serialized_source OpenMC::libopenmc) diff --git a/examples/serialized_custom_source/serialized_source_ring.cpp b/examples/serialized_custom_source/serialized_source_ring.cpp new file mode 100644 index 000000000..560a99345 --- /dev/null +++ b/examples/serialized_custom_source/serialized_source_ring.cpp @@ -0,0 +1,58 @@ +#include // for M_PI + +#include "openmc/random_lcg.h" +#include "openmc/source.h" +#include "openmc/particle.h" +#include "pugixml.hpp" + +class SerialisedSource { + protected: + double radius_; + double energy_; + + // Protect the constructor so that the class can only be created by serialisation. + SerialisedSource(double radius, double energy) { + radius_ = radius; + energy_ = energy; + } + + public: + // Getters for the values that we want to use in sampling. + double radius() { return radius_; } + double energy() { return energy_; } + + // The deserialisation routine populates the constructor from well defined elements + // in the input XML document. + static SerialisedSource from_xml(char* serialised_source) { + pugi::xml_document doc; + doc.load_file(serialised_source); + pugi::xml_node root_node = doc.root().child("Source"); + double radius = root_node.child("Radius").text().as_double(); + double energy = root_node.child("Energy").text().as_double(); + return SerialisedSource(radius, energy); + } +}; + +// you must have external C linkage here otherwise +// dlopen will not find the file +extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, char* serialised_source) +{ + SerialisedSource source = SerialisedSource::from_xml(serialised_source); + + 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; +} From 67e846c4d53e8ee3d350421a68674415ce349c0e Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 11:48:00 +0100 Subject: [PATCH 04/24] Load new serializable form of source function Updates the SourceDistribution to optionally look for a function template accepting char* as the second argument. If serialization is defined as an attribute on the source element provided in the settings.xml then the new form with serialization will be used. Otherwise the existing form to load the custom source as-is from the library will be used. --- src/source.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 3f31541db..86dac6b3f 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -46,6 +46,9 @@ namespace { using sample_t = Particle::Bank (*)(uint64_t* seed); sample_t custom_source_function; +std::string serialization; +using serialized_sample_t = Particle::Bank (*)(uint64_t* seed, const char* serialization); +serialized_sample_t custom_serialized_source_function; void* custom_source_library; } @@ -94,6 +97,10 @@ SourceDistribution::SourceDistribution(pugi::xml_node node) fatal_error(fmt::format("Source library '{}' does not exist.", settings::path_source_library)); } + + if (check_for_node(node, "serialization")) { + serialization = get_node_value(node, "serialization", false, true); + } } else { // Spatial distribution for external source @@ -367,9 +374,15 @@ void load_custom_source_library() // reset errors dlerror(); - // 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")); + if (serialization.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 from the library using the provided serialization + using sample_t = Particle::Bank (*)(uint64_t* seed, const char* serialization); + custom_serialized_source_function = reinterpret_cast(dlsym(custom_source_library, "sample_source")); + } // check for any dlsym errors auto dlsym_error = dlerror(); @@ -396,7 +409,11 @@ void close_custom_source_library() Particle::Bank sample_custom_source_library(uint64_t* seed) { - return custom_source_function(seed); + if (serialization.empty()) { + return custom_source_function(seed); + } else { + return custom_serialized_source_function(seed, serialization.c_str()); + } } void fill_source_bank_custom_source() From 62e194e0d669e44b4e83f6109ded5a34045ac9b2 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 11:48:32 +0100 Subject: [PATCH 05/24] Add README to describe usage of serialized source --- examples/serialized_custom_source/README.md | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 examples/serialized_custom_source/README.md diff --git a/examples/serialized_custom_source/README.md b/examples/serialized_custom_source/README.md new file mode 100644 index 000000000..52c003074 --- /dev/null +++ b/examples/serialized_custom_source/README.md @@ -0,0 +1,22 @@ +# Building a Serialised Custom Source + +To run this example, you first need to compile the custom source library, which +requires headers from OpenMC. A CMakeLists.txt file has been set up for you that +will search for OpenMC and build the custom library. To build the source +library, you can run: + + mkdir build && cd build + OPENMC_ROOT= cmake .. + make + +After this, you can build the model by running `python build_xml.py`. In the XML +files that are created, you should see a reference to build/libserialised_source.so, +the custom source library that was built by CMake. The model is also set up with a +mesh tally of the flux, so once you run `openmc`, you will get a statepoint file +with the tally results in it. Running `python show_flux.py` will pull in the +results from the statepoint file and display them. If all worked well, you +should see a ring "imprint" as well as a higher flux to the right side (since +the custom source has all particles moving in the positive x direction). + +Once built, you can edit the serialised_source.xml file to change the radius of the +sampled ring or the energy of the sampled particles. From 59594fea70ae73eecab172b656e695d958d9a5dd Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 11:48:53 +0100 Subject: [PATCH 06/24] Add copy of existing show_flux.py --- examples/serialized_custom_source/show_flux.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/serialized_custom_source/show_flux.py diff --git a/examples/serialized_custom_source/show_flux.py b/examples/serialized_custom_source/show_flux.py new file mode 100644 index 000000000..6f5494301 --- /dev/null +++ b/examples/serialized_custom_source/show_flux.py @@ -0,0 +1,14 @@ +import matplotlib.pyplot as plt +import openmc + +# Get the flux from the statepoint +with openmc.StatePoint('statepoint.10.h5') as sp: + flux = sp.tallies[1].mean + flux.shape = (50, 50) + +# Plot the flux +fig, ax = plt.subplots() +ax.imshow(flux, origin='lower', extent=(-5.0, 5.0, -5.0, 5.0)) +ax.set_xlabel('x [cm]') +ax.set_ylabel('y [cm]') +plt.show() From d4e7fa5d87050eea456121faa3e7ad078ff2d3a4 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 13:22:29 +0100 Subject: [PATCH 07/24] Add description of serialized XML file to README --- examples/serialized_custom_source/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/serialized_custom_source/README.md b/examples/serialized_custom_source/README.md index 52c003074..2ebc1ecc2 100644 --- a/examples/serialized_custom_source/README.md +++ b/examples/serialized_custom_source/README.md @@ -11,7 +11,8 @@ library, you can run: After this, you can build the model by running `python build_xml.py`. In the XML files that are created, you should see a reference to build/libserialised_source.so, -the custom source library that was built by CMake. The model is also set up with a +the custom source library that was built by CMake, and the path to the serialized +representation of the source in serialized_source.xml. The model is also set up with a mesh tally of the flux, so once you run `openmc`, you will get a statepoint file with the tally results in it. Running `python show_flux.py` will pull in the results from the statepoint file and display them. If all worked well, you From 175d57c1266a37f01fae7ae64370eb75e2e12566 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 13:25:46 +0100 Subject: [PATCH 08/24] Load the serialized representation from file once The previous implementation had each sampling run reading from the serialized file. This introduced a large I/O overhead and the performance was much slower than the equivalent run with the unserialized source. --- .../serialized_custom_source/serialized_source_ring.cpp | 4 +++- src/source.cpp | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/serialized_custom_source/serialized_source_ring.cpp b/examples/serialized_custom_source/serialized_source_ring.cpp index 560a99345..7e19bc579 100644 --- a/examples/serialized_custom_source/serialized_source_ring.cpp +++ b/examples/serialized_custom_source/serialized_source_ring.cpp @@ -23,9 +23,11 @@ class SerialisedSource { // The deserialisation routine populates the constructor from well defined elements // in the input XML document. + // Note that the source will have already been read from file, so what will be passed + // in here is a string-like serialized value (not the path to the serialized value). static SerialisedSource from_xml(char* serialised_source) { pugi::xml_document doc; - doc.load_file(serialised_source); + doc.load_string(serialised_source); pugi::xml_node root_node = doc.root().child("Source"); double radius = root_node.child("Radius").text().as_double(); double energy = root_node.child("Energy").text().as_double(); diff --git a/src/source.cpp b/src/source.cpp index 86dac6b3f..d7bcf726b 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -99,7 +99,13 @@ SourceDistribution::SourceDistribution(pugi::xml_node node) } if (check_for_node(node, "serialization")) { - serialization = get_node_value(node, "serialization", false, true); + // If the source is serialized then make sure we only load it from file once, otherwise there will + // be a significant I/O overhead. + pugi::xml_document doc; + doc.load_file(get_node_value(node, "serialization", false, true).c_str()); + std::stringstream ss; + doc.print(ss); + serialization = ss.str(); } } else { From 7770010cf1375d39c04baa839cea153f4b3c405d Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 16:55:00 +0100 Subject: [PATCH 09/24] Formatting updates Use American English consistently. A small tweak to a brace in the SerializedSource. --- examples/serialized_custom_source/README.md | 6 +++--- examples/serialized_custom_source/build_xml.py | 2 +- .../serialized_source_ring.cpp | 15 +++++++-------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/examples/serialized_custom_source/README.md b/examples/serialized_custom_source/README.md index 2ebc1ecc2..d7c1e0542 100644 --- a/examples/serialized_custom_source/README.md +++ b/examples/serialized_custom_source/README.md @@ -1,4 +1,4 @@ -# Building a Serialised Custom Source +# Building a Serialized Custom Source To run this example, you first need to compile the custom source library, which requires headers from OpenMC. A CMakeLists.txt file has been set up for you that @@ -10,7 +10,7 @@ library, you can run: make After this, you can build the model by running `python build_xml.py`. In the XML -files that are created, you should see a reference to build/libserialised_source.so, +files that are created, you should see a reference to build/libserialized_source.so, the custom source library that was built by CMake, and the path to the serialized representation of the source in serialized_source.xml. The model is also set up with a mesh tally of the flux, so once you run `openmc`, you will get a statepoint file @@ -19,5 +19,5 @@ results from the statepoint file and display them. If all worked well, you should see a ring "imprint" as well as a higher flux to the right side (since the custom source has all particles moving in the positive x direction). -Once built, you can edit the serialised_source.xml file to change the radius of the +Once built, you can edit the serialized_source.xml file to change the radius of the sampled ring or the energy of the sampled particles. diff --git a/examples/serialized_custom_source/build_xml.py b/examples/serialized_custom_source/build_xml.py index fba545959..7c6d5f61d 100644 --- a/examples/serialized_custom_source/build_xml.py +++ b/examples/serialized_custom_source/build_xml.py @@ -1,6 +1,6 @@ import openmc -# Define the serialised source +# Define the serialized source serialized_source = """ 1.5 1e3 diff --git a/examples/serialized_custom_source/serialized_source_ring.cpp b/examples/serialized_custom_source/serialized_source_ring.cpp index 7e19bc579..bf398ce83 100644 --- a/examples/serialized_custom_source/serialized_source_ring.cpp +++ b/examples/serialized_custom_source/serialized_source_ring.cpp @@ -5,13 +5,13 @@ #include "openmc/particle.h" #include "pugixml.hpp" -class SerialisedSource { +class SerializedSource { protected: double radius_; double energy_; // Protect the constructor so that the class can only be created by serialisation. - SerialisedSource(double radius, double energy) { + SerializedSource(double radius, double energy) { radius_ = radius; energy_ = energy; } @@ -25,21 +25,20 @@ class SerialisedSource { // in the input XML document. // Note that the source will have already been read from file, so what will be passed // in here is a string-like serialized value (not the path to the serialized value). - static SerialisedSource from_xml(char* serialised_source) { + static SerializedSource from_xml(char* serialized_source) { pugi::xml_document doc; - doc.load_string(serialised_source); + doc.load_string(serialized_source); pugi::xml_node root_node = doc.root().child("Source"); double radius = root_node.child("Radius").text().as_double(); double energy = root_node.child("Energy").text().as_double(); - return SerialisedSource(radius, energy); + return SerializedSource(radius, energy); } }; // you must have external C linkage here otherwise // dlopen will not find the file -extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, char* serialised_source) -{ - SerialisedSource source = SerialisedSource::from_xml(serialised_source); +extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, char* serialized_source) { + SerializedSource source = SerializedSource::from_xml(serialized_source); openmc::Particle::Bank particle; // wgt From b5de1fce2d71617cbfa0308ad85a327272c0fcd3 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 17:10:32 +0100 Subject: [PATCH 10/24] Add documentation This describes the general concept of serialization and gives an example of how to write a source_sampling function that deserializes the input and uses values set via the serialized form. --- docs/source/io_formats/settings.rst | 13 ++++++++ docs/source/usersguide/settings.rst | 50 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 52b52f68c..71a6a4f25 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -468,6 +468,19 @@ attributes/sub-elements: *Default*: None + :serialization: + If this attribute is given, it indicates that the source is to be + instantiated from an externally compiled source function, with parameters + defined by a serialized form of the source. The serialized source will be + read from a file in the location provided by 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 the + provided file. 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`. + + *Default*: None + :space: An element specifying the spatial distribution of source sites. This element has the following attributes: diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index a879fe6d1..ea7d7298a 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -235,6 +235,56 @@ file in your build directory. Setting the :attr:`openmc.Source.library` attribute to the path of this shared library will indicate that it should be used for sampling source particles at runtime. +.. _serialized_custom_source: + +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 (XML, JSON, +etc.) 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 serialized form of the source: + +.. code-block:: c++ + + // you must have external C linkage here + extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, char* serialized_source) { + // function to deserialize the source + SerializedSource source = SerializedSource::from_xml(serialized_source); + + openmc::Particle::Bank particle; + // wgt + particle.particle = openmc::Particle::Type::neutron; + particle.wgt = 1.0; + // position + double angle = 2. * M_PI * openmc::prn(seed); + + // 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}; + + // get the energy from the serialized form of the source + particle.E = source.energy(); + particle.delayed_group = 0; + + return particle; + } + +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.serialization` attribute, along with the +custom source library location in :attr:`openmc.Source.library`. + +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``. + --------------- Shannon Entropy --------------- From 1de3d9ddf9abf395f8b192c723b2af472eb45043 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 31 Jul 2020 17:32:50 +0100 Subject: [PATCH 11/24] Write a test for a serialized source Heavily based off of the existing custom source test. Creates a simple class that can be deserialized from XML as use a serialized XML representation to provide the energy value in the source. Builds the source and runs the model in the test routine. --- .../source_serialized_dlopen/__init__.py | 0 .../source_serialized_dlopen/inputs_true.dat | 23 ++++++ .../source_serialized_dlopen/results_true.dat | 0 .../serialized_source.xml | 3 + .../serialized_source_sampling.cpp | 47 ++++++++++++ .../source_serialized_dlopen/test.py | 75 +++++++++++++++++++ 6 files changed, 148 insertions(+) create mode 100644 tests/regression_tests/source_serialized_dlopen/__init__.py create mode 100644 tests/regression_tests/source_serialized_dlopen/inputs_true.dat create mode 100644 tests/regression_tests/source_serialized_dlopen/results_true.dat create mode 100644 tests/regression_tests/source_serialized_dlopen/serialized_source.xml create mode 100644 tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp create mode 100644 tests/regression_tests/source_serialized_dlopen/test.py diff --git a/tests/regression_tests/source_serialized_dlopen/__init__.py b/tests/regression_tests/source_serialized_dlopen/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/source_serialized_dlopen/inputs_true.dat b/tests/regression_tests/source_serialized_dlopen/inputs_true.dat new file mode 100644 index 000000000..a60d90759 --- /dev/null +++ b/tests/regression_tests/source_serialized_dlopen/inputs_true.dat @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + 0 + + diff --git a/tests/regression_tests/source_serialized_dlopen/results_true.dat b/tests/regression_tests/source_serialized_dlopen/results_true.dat new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/source_serialized_dlopen/serialized_source.xml b/tests/regression_tests/source_serialized_dlopen/serialized_source.xml new file mode 100644 index 000000000..ec5b2c8c0 --- /dev/null +++ b/tests/regression_tests/source_serialized_dlopen/serialized_source.xml @@ -0,0 +1,3 @@ + + 1e3 + diff --git a/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp b/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp new file mode 100644 index 000000000..1b1c31184 --- /dev/null +++ b/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp @@ -0,0 +1,47 @@ +#include "openmc/random_lcg.h" +#include "openmc/source.h" +#include "openmc/particle.h" +#include "pugixml.hpp" + +class SerializedSource { + protected: + double energy_; + + // Protect the constructor so that the class can only be created by serialisation. + SerializedSource(double energy) { + energy_ = energy; + } + + public: + // Getters for the values that we want to use in sampling. + double energy() { return energy_; } + + static SerializedSource from_xml(char* serialized_source) { + pugi::xml_document doc; + doc.load_string(serialized_source); + pugi::xml_node root_node = doc.root().child("Source"); + double energy = root_node.child("Energy").text().as_double(); + return SerializedSource(energy); + } +}; + +// you must have external C linkage here otherwise +// dlopen will not find the file +extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, char* serialized_source) { + SerializedSource source = SerializedSource::from_xml(serialized_source); + + 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; +} diff --git a/tests/regression_tests/source_serialized_dlopen/test.py b/tests/regression_tests/source_serialized_dlopen/test.py new file mode 100644 index 000000000..194e00c84 --- /dev/null +++ b/tests/regression_tests/source_serialized_dlopen/test.py @@ -0,0 +1,75 @@ +from pathlib import Path +import os +import shutil +import subprocess +import textwrap + +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def compile_source(request): + """Compile the external source""" + + # Get build directory and write CMakeLists.txt file + openmc_dir = Path(str(request.config.rootdir)) / 'build' + with open('CMakeLists.txt', 'w') as f: + f.write(textwrap.dedent(""" + cmake_minimum_required(VERSION 3.3 FATAL_ERROR) + project(openmc_sources CXX) + add_library(serialized_source SHARED serialized_source_sampling.cpp) + find_package(OpenMC REQUIRED HINTS {}) + target_link_libraries(serialized_source OpenMC::libopenmc) + """.format(openmc_dir))) + + # Create temporary build directory and change to there + local_builddir = Path('build') + local_builddir.mkdir(exist_ok=True) + os.chdir(str(local_builddir)) + + # Run cmake/make to build the shared libary + subprocess.run(['cmake', os.path.pardir], check=True) + subprocess.run(['make'], check=True) + os.chdir(os.path.pardir) + + yield + + # Remove local build directory when test is complete + shutil.rmtree('build') + os.remove('CMakeLists.txt') + + +@pytest.fixture +def model(): + model = openmc.model.Model() + natural_lead = openmc.Material(name="natural_lead") + natural_lead.add_element('Pb', 1.0) + natural_lead.set_density('g/cm3', 11.34) + model.materials.append(natural_lead) + + # geometry + surface_sph1 = openmc.Sphere(r=100, boundary_type='vacuum') + cell_1 = openmc.Cell(fill=natural_lead, region=-surface_sph1) + model.geometry = openmc.Geometry([cell_1]) + + # settings + model.settings.batches = 10 + model.settings.inactive = 0 + model.settings.particles = 1000 + model.settings.run_mode = 'fixed source' + + # custom source from shared library + source = openmc.Source() + source.library = 'build/libserialized_source.so' + source.serialization = 'serialized_source.xml' + model.settings.source = source + + return model + + +def test_dlopen_source(compile_source, model): + harness = PyAPITestHarness('statepoint.10.h5', model) + harness.main() From 8bb10563c1efd8e39a14980e73dd09f8df4ebf54 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Mon, 3 Aug 2020 14:24:17 +0100 Subject: [PATCH 12/24] Pass serialization as parameter attribute Changes the implementation of the serialization to be on an attribute of the source XML element within settings.xml. This removes the need for a new file containing the serialization. Parameters are provided as a key-value string, separated by a comma and a space, although the implementation can change this is required. Change example values to align with existing custom_source to make comparisons easier. Update documentation to be consistent. --- docs/source/io_formats/settings.rst | 11 +++--- docs/source/usersguide/settings.rst | 14 +++---- examples/serialized_custom_source/README.md | 18 ++++----- .../serialized_custom_source/build_xml.py | 11 +----- .../serialized_source_ring.cpp | 30 ++++++++------- openmc/source.py | 38 +++++++++---------- src/source.cpp | 22 ++++------- .../source_serialized_dlopen/inputs_true.dat | 2 +- .../serialized_source.xml | 3 -- .../serialized_source_sampling.cpp | 25 ++++++++---- .../source_serialized_dlopen/test.py | 2 +- 11 files changed, 84 insertions(+), 92 deletions(-) delete mode 100644 tests/regression_tests/source_serialized_dlopen/serialized_source.xml diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 71a6a4f25..7a356ffa1 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -468,14 +468,13 @@ attributes/sub-elements: *Default*: None - :serialization: + :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 a serialized form of the source. The serialized source will be - read from a file in the location provided by 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 the - provided file. If the library attribute is not provided then this attribute + 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 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 ea7d7298a..ebab37122 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -241,17 +241,17 @@ 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 (XML, JSON, -etc.) 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 serialized form of the source: +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: .. code-block:: c++ // you must have external C linkage here - extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, char* serialized_source) { + extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, const char* parameters) { // function to deserialize the source - SerializedSource source = SerializedSource::from_xml(serialized_source); + SerializedSource source = SerializedSource::from_string(parameters); openmc::Particle::Bank particle; // wgt @@ -278,7 +278,7 @@ parameter that receives the serialized form of the source: 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.serialization` attribute, along with the +provided via the :attr:`openmc.Source.parameters` attribute, along with the custom source library location in :attr:`openmc.Source.library`. When defining a class to be implemented via this deserialization approach, care diff --git a/examples/serialized_custom_source/README.md b/examples/serialized_custom_source/README.md index d7c1e0542..d5c5ee120 100644 --- a/examples/serialized_custom_source/README.md +++ b/examples/serialized_custom_source/README.md @@ -11,13 +11,13 @@ library, you can run: After this, you can build the model by running `python build_xml.py`. In the XML files that are created, you should see a reference to build/libserialized_source.so, -the custom source library that was built by CMake, and the path to the serialized -representation of the source in serialized_source.xml. The model is also set up with a -mesh tally of the flux, so once you run `openmc`, you will get a statepoint file -with the tally results in it. Running `python show_flux.py` will pull in the -results from the statepoint file and display them. If all worked well, you -should see a ring "imprint" as well as a higher flux to the right side (since -the custom source has all particles moving in the positive x direction). +the custom source library that was built by CMake, and the serialized representation +of the source in the parameters attribute. The model is also set up with a mesh tally +of the flux, so once you run `openmc`, you will get a statepoint file with the tally +results in it. Running `python show_flux.py` will pull in the results from the +statepoint file and display them. If all worked well, you should see a ring "imprint" +as well as a higher flux to the right side (since the custom source has all particles +moving in the positive x direction). -Once built, you can edit the serialized_source.xml file to change the radius of the -sampled ring or the energy of the sampled particles. +Once built, you can edit the parameters attribute on the source to change the radius of +the sampled ring or the energy of the sampled particles. diff --git a/examples/serialized_custom_source/build_xml.py b/examples/serialized_custom_source/build_xml.py index 7c6d5f61d..31ba2ac3e 100644 --- a/examples/serialized_custom_source/build_xml.py +++ b/examples/serialized_custom_source/build_xml.py @@ -1,14 +1,5 @@ import openmc -# Define the serialized source -serialized_source = """ - 1.5 - 1e3 - -""" -with open('serialized_source.xml', 'w') as f: - f.write(serialized_source) - # Create a single material iron = openmc.Material() iron.set_density('g/cm3', 5.0) @@ -29,7 +20,7 @@ settings.batches = 10 settings.particles = 1000 source = openmc.Source() source.library = 'build/libserialized_source.so' -source.serialization = 'serialized_source.xml' +source.parameters = 'radius=3.0, energy=14.08e6' settings.source = source settings.export_to_xml() diff --git a/examples/serialized_custom_source/serialized_source_ring.cpp b/examples/serialized_custom_source/serialized_source_ring.cpp index bf398ce83..4b93d2c76 100644 --- a/examples/serialized_custom_source/serialized_source_ring.cpp +++ b/examples/serialized_custom_source/serialized_source_ring.cpp @@ -1,9 +1,9 @@ #include // for M_PI +#include #include "openmc/random_lcg.h" #include "openmc/source.h" #include "openmc/particle.h" -#include "pugixml.hpp" class SerializedSource { protected: @@ -21,24 +21,26 @@ class SerializedSource { double radius() { return radius_; } double energy() { return energy_; } - // The deserialisation routine populates the constructor from well defined elements - // in the input XML document. - // Note that the source will have already been read from file, so what will be passed - // in here is a string-like serialized value (not the path to the serialized value). - static SerializedSource from_xml(char* serialized_source) { - pugi::xml_document doc; - doc.load_string(serialized_source); - pugi::xml_node root_node = doc.root().child("Source"); - double radius = root_node.child("Radius").text().as_double(); - double energy = root_node.child("Energy").text().as_double(); - return SerializedSource(radius, energy); + static SerializedSource from_string(const char* parameters) { + std::unordered_map parameter_mapping; + + 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 SerializedSource(std::stod(parameter_mapping["radius"]), std::stod(parameter_mapping["energy"])); } }; // you must have external C linkage here otherwise // dlopen will not find the file -extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, char* serialized_source) { - SerializedSource source = SerializedSource::from_xml(serialized_source); +extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, const char* parameters) { + SerializedSource source = SerializedSource::from_string(parameters); openmc::Particle::Bank particle; // wgt diff --git a/openmc/source.py b/openmc/source.py index 8ede50581..78ca9dad0 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -22,8 +22,8 @@ class Source: Source file from which sites should be sampled library : str Path to a custom source library - serialization : str - Path to the serialized representation of the custom source + parameters : str + Parameters to be provided to the custom source .. versionadded:: 0.12 strength : float @@ -43,8 +43,8 @@ class Source: Source file from which sites should be sampled library : str or None Path to a custom source library - serialization : str - Path to the serialized representation of the custom source + parameters : str + Parameters to be provided to the custom source strength : float Strength of the source particle : {'neutron', 'photon'} @@ -53,13 +53,13 @@ class Source: """ def __init__(self, space=None, angle=None, energy=None, filename=None, - library=None, serialization=None, strength=1.0, particle='neutron'): + library=None, parameters=None, strength=1.0, particle='neutron'): self._space = None self._angle = None self._energy = None self._file = None self._library = None - self._serialization = None + self._parameters = None if space is not None: self.space = space @@ -71,8 +71,8 @@ class Source: self.file = filename if library is not None: self.library = library - if serialization is not None: - self.serialization = serialization + if parameters is not None: + self.parameters = parameters self.strength = strength self.particle = particle @@ -85,8 +85,8 @@ class Source: return self._library @property - def serialization(self): - return self._serialization + def parameters(self): + return self._parameters @property def space(self): @@ -118,10 +118,10 @@ class Source: cv.check_type('library', library_name, str) self._library = library_name - @serialization.setter - def serialization(self, serialization_path): - cv.check_type('serialization', serialization_path, str) - self._serialization = serialization_path + @parameters.setter + def parameters(self, parameters_path): + cv.check_type('parameters', parameters_path, str) + self._parameters = parameters_path @space.setter def space(self, space): @@ -166,8 +166,8 @@ class Source: element.set("file", self.file) if self.library is not None: element.set("library", self.library) - if self.serialization is not None: - element.set("serialization", self.serialization) + if self.parameters is not None: + element.set("parameters", self.parameters) if self.space is not None: element.append(self.space.to_xml_element()) if self.angle is not None: @@ -209,9 +209,9 @@ class Source: if library is not None: source.library = library - serialization = get_text(elem, 'serialization') - if serialization is not None: - source.serialization = serialization + parameters = get_text(elem, 'parameters') + if parameters is not None: + source.parameters = parameters space = elem.find('space') if space is not None: diff --git a/src/source.cpp b/src/source.cpp index d7bcf726b..0650b5737 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -46,8 +46,8 @@ namespace { using sample_t = Particle::Bank (*)(uint64_t* seed); sample_t custom_source_function; -std::string serialization; -using serialized_sample_t = Particle::Bank (*)(uint64_t* seed, const char* serialization); +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; @@ -98,14 +98,8 @@ SourceDistribution::SourceDistribution(pugi::xml_node node) settings::path_source_library)); } - if (check_for_node(node, "serialization")) { - // If the source is serialized then make sure we only load it from file once, otherwise there will - // be a significant I/O overhead. - pugi::xml_document doc; - doc.load_file(get_node_value(node, "serialization", false, true).c_str()); - std::stringstream ss; - doc.print(ss); - serialization = ss.str(); + if (check_for_node(node, "parameters")) { + custom_source_parameters = get_node_value(node, "parameters", false, true); } } else { @@ -380,13 +374,13 @@ void load_custom_source_library() // reset errors dlerror(); - if (serialization.empty()) { + 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 from the library using the provided serialization - using sample_t = Particle::Bank (*)(uint64_t* seed, const char* serialization); + using sample_t = Particle::Bank (*)(uint64_t* seed, const char* parameters); custom_serialized_source_function = reinterpret_cast(dlsym(custom_source_library, "sample_source")); } @@ -415,10 +409,10 @@ void close_custom_source_library() Particle::Bank sample_custom_source_library(uint64_t* seed) { - if (serialization.empty()) { + if (custom_source_parameters.empty()) { return custom_source_function(seed); } else { - return custom_serialized_source_function(seed, serialization.c_str()); + return custom_serialized_source_function(seed, custom_source_parameters.c_str()); } } diff --git a/tests/regression_tests/source_serialized_dlopen/inputs_true.dat b/tests/regression_tests/source_serialized_dlopen/inputs_true.dat index a60d90759..c7765d20a 100644 --- a/tests/regression_tests/source_serialized_dlopen/inputs_true.dat +++ b/tests/regression_tests/source_serialized_dlopen/inputs_true.dat @@ -19,5 +19,5 @@ 1000 10 0 - + diff --git a/tests/regression_tests/source_serialized_dlopen/serialized_source.xml b/tests/regression_tests/source_serialized_dlopen/serialized_source.xml deleted file mode 100644 index ec5b2c8c0..000000000 --- a/tests/regression_tests/source_serialized_dlopen/serialized_source.xml +++ /dev/null @@ -1,3 +0,0 @@ - - 1e3 - 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 1b1c31184..d4c5d8024 100644 --- a/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp +++ b/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp @@ -1,3 +1,5 @@ +#include + #include "openmc/random_lcg.h" #include "openmc/source.h" #include "openmc/particle.h" @@ -16,19 +18,26 @@ class SerializedSource { // Getters for the values that we want to use in sampling. double energy() { return energy_; } - static SerializedSource from_xml(char* serialized_source) { - pugi::xml_document doc; - doc.load_string(serialized_source); - pugi::xml_node root_node = doc.root().child("Source"); - double energy = root_node.child("Energy").text().as_double(); - return SerializedSource(energy); + static SerializedSource from_string(const char* parameters) { + std::unordered_map parameter_mapping; + + 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 SerializedSource(std::stod(parameter_mapping["energy"])); } }; // you must have external C linkage here otherwise // dlopen will not find the file -extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, char* serialized_source) { - SerializedSource source = SerializedSource::from_xml(serialized_source); +extern "C" openmc::Particle::Bank sample_source(uint64_t* seed, const char* parameters) { + SerializedSource source = SerializedSource::from_string(parameters); openmc::Particle::Bank particle; // wgt diff --git a/tests/regression_tests/source_serialized_dlopen/test.py b/tests/regression_tests/source_serialized_dlopen/test.py index 194e00c84..09ed11be3 100644 --- a/tests/regression_tests/source_serialized_dlopen/test.py +++ b/tests/regression_tests/source_serialized_dlopen/test.py @@ -64,7 +64,7 @@ def model(): # custom source from shared library source = openmc.Source() source.library = 'build/libserialized_source.so' - source.serialization = 'serialized_source.xml' + source.parameters = 'energy=1e3' model.settings.source = source return model From 9c1b318e785b4ecbdbba9d58b50222e076cb50ec Mon Sep 17 00:00:00 2001 From: Dan Short Date: Mon, 3 Aug 2020 17:38:12 +0100 Subject: [PATCH 13/24] 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; } From 64a4c96a946230ef435c3aa44386404bea44f15a Mon Sep 17 00:00:00 2001 From: Dan Short Date: Wed, 5 Aug 2020 15:53:48 +0100 Subject: [PATCH 14/24] Rename serialized -> parameterized Updates to examples and tests to refer to parameterized sources rather than serialized sources. Small update to source.h to remove reference to serialized parameters. --- .../CMakeLists.txt | 4 ++-- .../parameterized_custom_source/README.md | 23 +++++++++++++++++++ .../build_xml.py | 2 +- .../parameterized_source_ring.cpp} | 14 +++++------ .../show_flux.py | 0 examples/serialized_custom_source/README.md | 23 ------------------- include/openmc/source.h | 2 +- .../__init__.py | 0 .../inputs_true.dat | 2 +- .../parameterized_source_sampling.cpp} | 14 +++++------ .../results_true.dat | 0 .../test.py | 6 ++--- 12 files changed, 45 insertions(+), 45 deletions(-) rename examples/{serialized_custom_source => parameterized_custom_source}/CMakeLists.txt (57%) create mode 100644 examples/parameterized_custom_source/README.md rename examples/{serialized_custom_source => parameterized_custom_source}/build_xml.py (95%) rename examples/{serialized_custom_source/serialized_source_ring.cpp => parameterized_custom_source/parameterized_source_ring.cpp} (81%) rename examples/{serialized_custom_source => parameterized_custom_source}/show_flux.py (100%) delete mode 100644 examples/serialized_custom_source/README.md rename tests/regression_tests/{source_serialized_dlopen => source_parameterized_dlopen}/__init__.py (100%) rename tests/regression_tests/{source_serialized_dlopen => source_parameterized_dlopen}/inputs_true.dat (87%) rename tests/regression_tests/{source_serialized_dlopen/serialized_source_sampling.cpp => source_parameterized_dlopen/parameterized_source_sampling.cpp} (79%) rename tests/regression_tests/{source_serialized_dlopen => source_parameterized_dlopen}/results_true.dat (100%) rename tests/regression_tests/{source_serialized_dlopen => source_parameterized_dlopen}/test.py (90%) diff --git a/examples/serialized_custom_source/CMakeLists.txt b/examples/parameterized_custom_source/CMakeLists.txt similarity index 57% rename from examples/serialized_custom_source/CMakeLists.txt rename to examples/parameterized_custom_source/CMakeLists.txt index 9d76718e5..3024e90cf 100644 --- a/examples/serialized_custom_source/CMakeLists.txt +++ b/examples/parameterized_custom_source/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.3 FATAL_ERROR) project(openmc_sources CXX) -add_library(serialized_source SHARED serialized_source_ring.cpp) +add_library(parameterized_source SHARED parameterized_source_ring.cpp) find_package(OpenMC REQUIRED) if (OpenMC_FOUND) message(STATUS "Found OpenMC: ${OpenMC_DIR}") endif() -target_link_libraries(serialized_source OpenMC::libopenmc) +target_link_libraries(parameterized_source OpenMC::libopenmc) diff --git a/examples/parameterized_custom_source/README.md b/examples/parameterized_custom_source/README.md new file mode 100644 index 000000000..9116fadea --- /dev/null +++ b/examples/parameterized_custom_source/README.md @@ -0,0 +1,23 @@ +# Building a Parameterized Custom Source + +To run this example, you first need to compile the custom source library, which +requires headers from OpenMC. A CMakeLists.txt file has been set up for you that +will search for OpenMC and build the custom library. To build the source +library, you can run: + + mkdir build && cd build + OPENMC_ROOT= cmake .. + make + +After this, you can build the model by running `python build_xml.py`. In the XML +files that are created, you should see a reference to build/libparameterized_source.so, +the custom source library that was built by CMake, and values in the parameters +attribute. The model is also set up with a mesh tally of the flux, so once you run +`openmc`, you will get a statepoint file with the tally results in it. Running +`python show_flux.py` will pull in the results from the statepoint file and display +them. If all worked well, you should see a ring "imprint" as well as a higher flux to +the right side (since the custom source has all particles moving in the positive x +direction). + +Once built, you can edit the parameters attribute on the source to change the radius of +the sampled ring or the energy of the sampled particles. diff --git a/examples/serialized_custom_source/build_xml.py b/examples/parameterized_custom_source/build_xml.py similarity index 95% rename from examples/serialized_custom_source/build_xml.py rename to examples/parameterized_custom_source/build_xml.py index 31ba2ac3e..5edb204df 100644 --- a/examples/serialized_custom_source/build_xml.py +++ b/examples/parameterized_custom_source/build_xml.py @@ -19,7 +19,7 @@ settings.run_mode = 'fixed source' settings.batches = 10 settings.particles = 1000 source = openmc.Source() -source.library = 'build/libserialized_source.so' +source.library = 'build/libparameterized_source.so' source.parameters = 'radius=3.0, energy=14.08e6' settings.source = source settings.export_to_xml() diff --git a/examples/serialized_custom_source/serialized_source_ring.cpp b/examples/parameterized_custom_source/parameterized_source_ring.cpp similarity index 81% rename from examples/serialized_custom_source/serialized_source_ring.cpp rename to examples/parameterized_custom_source/parameterized_source_ring.cpp index 7584b152c..7b0089090 100644 --- a/examples/serialized_custom_source/serialized_source_ring.cpp +++ b/examples/parameterized_custom_source/parameterized_source_ring.cpp @@ -5,13 +5,13 @@ #include "openmc/source.h" #include "openmc/particle.h" -class SerializedSource : public openmc::CustomSource { +class ParameterizedSource : public openmc::CustomSource { protected: double radius_; double energy_; // Protect the constructor so that the class can only be created by serialisation. - SerializedSource(double radius, double energy) { + ParameterizedSource(double radius, double energy) { radius_ = radius; energy_ = energy; } @@ -23,7 +23,7 @@ class SerializedSource : public openmc::CustomSource { // 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) { + static ParameterizedSource* from_string(const char* parameters) { std::unordered_map parameter_mapping; std::stringstream ss(parameters); @@ -35,7 +35,7 @@ class SerializedSource : public openmc::CustomSource { parameter_mapping[key] = value; } - return new SerializedSource(std::stod(parameter_mapping["radius"]), std::stod(parameter_mapping["energy"])); + return new ParameterizedSource(std::stod(parameter_mapping["radius"]), std::stod(parameter_mapping["energy"])); } // Samples from an instance of this class. @@ -62,13 +62,13 @@ class SerializedSource : public openmc::CustomSource { // 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); +extern "C" ParameterizedSource* create(const char* parameters) { + return ParameterizedSource::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) { +extern "C" void destroy(ParameterizedSource* source) { delete source; } diff --git a/examples/serialized_custom_source/show_flux.py b/examples/parameterized_custom_source/show_flux.py similarity index 100% rename from examples/serialized_custom_source/show_flux.py rename to examples/parameterized_custom_source/show_flux.py diff --git a/examples/serialized_custom_source/README.md b/examples/serialized_custom_source/README.md deleted file mode 100644 index d5c5ee120..000000000 --- a/examples/serialized_custom_source/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Building a Serialized Custom Source - -To run this example, you first need to compile the custom source library, which -requires headers from OpenMC. A CMakeLists.txt file has been set up for you that -will search for OpenMC and build the custom library. To build the source -library, you can run: - - mkdir build && cd build - OPENMC_ROOT= cmake .. - make - -After this, you can build the model by running `python build_xml.py`. In the XML -files that are created, you should see a reference to build/libserialized_source.so, -the custom source library that was built by CMake, and the serialized representation -of the source in the parameters attribute. The model is also set up with a mesh tally -of the flux, so once you run `openmc`, you will get a statepoint file with the tally -results in it. Running `python show_flux.py` will pull in the results from the -statepoint file and display them. If all worked well, you should see a ring "imprint" -as well as a higher flux to the right side (since the custom source has all particles -moving in the positive x direction). - -Once built, you can edit the parameters attribute on the source to change the radius of -the sampled ring or the energy of the sampled particles. diff --git a/include/openmc/source.h b/include/openmc/source.h index bd24f093f..2492cb2b7 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* serialized_source); +typedef CustomSource* create_custom_source_t(const char* parameters); typedef void destroy_custom_source_t(CustomSource*); //============================================================================== diff --git a/tests/regression_tests/source_serialized_dlopen/__init__.py b/tests/regression_tests/source_parameterized_dlopen/__init__.py similarity index 100% rename from tests/regression_tests/source_serialized_dlopen/__init__.py rename to tests/regression_tests/source_parameterized_dlopen/__init__.py diff --git a/tests/regression_tests/source_serialized_dlopen/inputs_true.dat b/tests/regression_tests/source_parameterized_dlopen/inputs_true.dat similarity index 87% rename from tests/regression_tests/source_serialized_dlopen/inputs_true.dat rename to tests/regression_tests/source_parameterized_dlopen/inputs_true.dat index c7765d20a..a7462ae7f 100644 --- a/tests/regression_tests/source_serialized_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_serialized_dlopen/serialized_source_sampling.cpp b/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp similarity index 79% rename from tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp rename to tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp index a951ef3cd..b50c9d284 100644 --- a/tests/regression_tests/source_serialized_dlopen/serialized_source_sampling.cpp +++ b/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp @@ -4,12 +4,12 @@ #include "openmc/source.h" #include "openmc/particle.h" -class SerializedSource : public openmc::CustomSource { +class ParameterizedSource : public openmc::CustomSource { protected: double energy_; // Protect the constructor so that the class can only be created by serialisation. - SerializedSource(double energy) { + ParameterizedSource(double energy) { energy_ = energy; } @@ -19,7 +19,7 @@ class SerializedSource : public openmc::CustomSource { // 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) { + static ParameterizedSource* from_string(const char* parameters) { std::unordered_map parameter_mapping; std::stringstream ss(parameters); @@ -31,7 +31,7 @@ class SerializedSource : public openmc::CustomSource { parameter_mapping[key] = value; } - return new SerializedSource(std::stod(parameter_mapping["energy"])); + return new ParameterizedSource(std::stod(parameter_mapping["energy"])); } // Samples from an instance of this class. @@ -55,12 +55,12 @@ class SerializedSource : public openmc::CustomSource { // 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); +extern "C" ParameterizedSource* create(const char* parameters) { + return ParameterizedSource::from_string(parameters); } // you must have external C linkage here otherwise // dlopen will not find the file -extern "C" void destroy(SerializedSource* source) { +extern "C" void destroy(ParameterizedSource* source) { delete source; } diff --git a/tests/regression_tests/source_serialized_dlopen/results_true.dat b/tests/regression_tests/source_parameterized_dlopen/results_true.dat similarity index 100% rename from tests/regression_tests/source_serialized_dlopen/results_true.dat rename to tests/regression_tests/source_parameterized_dlopen/results_true.dat diff --git a/tests/regression_tests/source_serialized_dlopen/test.py b/tests/regression_tests/source_parameterized_dlopen/test.py similarity index 90% rename from tests/regression_tests/source_serialized_dlopen/test.py rename to tests/regression_tests/source_parameterized_dlopen/test.py index 09ed11be3..6c88c37c2 100644 --- a/tests/regression_tests/source_serialized_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(serialized_source SHARED serialized_source_sampling.cpp) + add_library(parameterized_source SHARED parameterized_source_sampling.cpp) find_package(OpenMC REQUIRED HINTS {}) - target_link_libraries(serialized_source OpenMC::libopenmc) + target_link_libraries(parameterized_source OpenMC::libopenmc) """.format(openmc_dir))) # Create temporary build directory and change to there @@ -63,7 +63,7 @@ def model(): # custom source from shared library source = openmc.Source() - source.library = 'build/libserialized_source.so' + source.library = 'build/libparameterized_source.so' source.parameters = 'energy=1e3' model.settings.source = source From 651c8825f3fc4a8901cb81894b3b36314ad27eac Mon Sep 17 00:00:00 2001 From: Dan Short Date: Wed, 5 Aug 2020 15:55:18 +0100 Subject: [PATCH 15/24] Updates to documentation Accounts for serialized -> parameterized change. Simplifies some of the examples to be more appropriate for documentation. Removes use of external destroy method. --- docs/source/io_formats/settings.rst | 16 +++---- docs/source/usersguide/settings.rst | 74 ++++++++--------------------- 2 files changed, 27 insertions(+), 63 deletions(-) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 511140290..6bd5c3bc1 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -474,15 +474,13 @@ attributes/sub-elements: 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 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`. + ``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`. *Default*: None diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index 064803694..7bb8875be 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -235,55 +235,28 @@ file in your build directory. Setting the :attr:`openmc.Source.library` attribute to the path of this shared library will indicate that it should be used for sampling source particles at runtime. -.. _serialized_custom_source: +.. _parameterized_custom_source: -Custom Serialized Sources -------------------------- +Custom Parameterized 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 -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: +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: .. code-block:: c++ - #include - - #include "openmc/random_lcg.h" #include "openmc/source.h" #include "openmc/particle.h" - class SerializedSource : public openmc::CustomSource { - protected: - double energy_; - - // Protect the constructor so that the class can only be created by serialisation. - SerializedSource(double energy) { - energy_ = energy; - } - + class ParameterizedSource : public openmc::CustomSource { public: - // Getters for the values that we want to use in sampling. - 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 SerializedSource* from_string(const char* parameters) { - std::unordered_map parameter_mapping; - - 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"])); + double energy; + + ParameterizedSource(double energy) { + this->energy = energy; } // Samples from an instance of this class. @@ -298,32 +271,25 @@ the parameters provided via the settings.xml file in the particle.r.z = 0.0; // angle particle.u = {1.0, 0.0, 0.0}; - particle.E = this->energy(); + 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"``: +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" 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; + extern "C" ParameterizedSource* openmc_create_source(const char* parameterized_source) { + return new ParameterizedSource(std::stod(parameters)); } As with the basic custom source functionality, the custom source library From fae474869c563e27e3624ae56cfab4b5f16994c7 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Wed, 5 Aug 2020 16:00:49 +0100 Subject: [PATCH 16/24] Remove external destroy method OpenMC will call delete directly on the CustomSource. --- .../parameterized_source_ring.cpp | 7 ------- include/openmc/source.h | 1 - src/source.cpp | 8 +++----- .../parameterized_source_sampling.cpp | 6 ------ 4 files changed, 3 insertions(+), 19 deletions(-) diff --git a/examples/parameterized_custom_source/parameterized_source_ring.cpp b/examples/parameterized_custom_source/parameterized_source_ring.cpp index 7b0089090..bd7bb1f6f 100644 --- a/examples/parameterized_custom_source/parameterized_source_ring.cpp +++ b/examples/parameterized_custom_source/parameterized_source_ring.cpp @@ -65,10 +65,3 @@ class ParameterizedSource : public openmc::CustomSource { extern "C" ParameterizedSource* create(const char* parameters) { return ParameterizedSource::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(ParameterizedSource* source) { - delete source; -} diff --git a/include/openmc/source.h b/include/openmc/source.h index 2492cb2b7..5bc2901b4 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -67,7 +67,6 @@ class CustomSource { }; typedef CustomSource* create_custom_source_t(const char* parameters); -typedef void destroy_custom_source_t(CustomSource*); //============================================================================== // Functions diff --git a/src/source.cpp b/src/source.cpp index fc184dc4e..4ddd3eb2a 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -50,7 +50,6 @@ sample_t custom_source_function; std::string custom_source_parameters; CustomSource* custom_source; -destroy_custom_source_t* destroy_custom_source; } @@ -380,9 +379,8 @@ 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 functions to create and destroy the CustomSource from the library + // 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, "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()); @@ -404,8 +402,8 @@ void load_custom_source_library() void close_custom_source_library() { if (custom_source) { - // destroy the CustomSource if it exists - destroy_custom_source(custom_source); + // delete the CustomSource if it exists + delete custom_source; } #ifdef HAS_DYNAMIC_LINKING 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 b50c9d284..3eda071e8 100644 --- a/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp +++ b/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp @@ -58,9 +58,3 @@ class ParameterizedSource : public openmc::CustomSource { extern "C" ParameterizedSource* create(const char* parameters) { return ParameterizedSource::from_string(parameters); } - -// you must have external C linkage here otherwise -// dlopen will not find the file -extern "C" void destroy(ParameterizedSource* source) { - delete source; -} From 0bb5030ee093c69b90b396c694181bf689314839 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Wed, 5 Aug 2020 16:02:52 +0100 Subject: [PATCH 17/24] Rename create -> openmc_create_source --- .../parameterized_custom_source/parameterized_source_ring.cpp | 2 +- src/source.cpp | 2 +- .../parameterized_source_sampling.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/parameterized_custom_source/parameterized_source_ring.cpp b/examples/parameterized_custom_source/parameterized_source_ring.cpp index bd7bb1f6f..51218e1b3 100644 --- a/examples/parameterized_custom_source/parameterized_source_ring.cpp +++ b/examples/parameterized_custom_source/parameterized_source_ring.cpp @@ -62,6 +62,6 @@ class ParameterizedSource : public openmc::CustomSource { // 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" ParameterizedSource* create(const char* parameters) { +extern "C" ParameterizedSource* openmc_create_source(const char* parameters) { return ParameterizedSource::from_string(parameters); } diff --git a/src/source.cpp b/src/source.cpp index 4ddd3eb2a..673ee8804 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -380,7 +380,7 @@ void load_custom_source_library() 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, "create"); + 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()); 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 3eda071e8..9fcb841d0 100644 --- a/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp +++ b/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp @@ -55,6 +55,6 @@ class ParameterizedSource : public openmc::CustomSource { // you must have external C linkage here otherwise // dlopen will not find the file -extern "C" ParameterizedSource* create(const char* parameters) { +extern "C" ParameterizedSource* openmc_create_source(const char* parameters) { return ParameterizedSource::from_string(parameters); } From 475656e6e030cc7e9a77ecab8a255f5986a8a8ec Mon Sep 17 00:00:00 2001 From: Dan Short Date: Wed, 5 Aug 2020 16:10:04 +0100 Subject: [PATCH 18/24] Simplify test and fix typos in documentation --- docs/source/usersguide/settings.rst | 4 +- .../parameterized_source_sampling.cpp | 37 +++---------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index 7bb8875be..30bf71132 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -288,8 +288,8 @@ the custom source, based on the value supplied in the // you must have external C linkage here otherwise // dlopen will not find the file - extern "C" ParameterizedSource* openmc_create_source(const char* parameterized_source) { - return new ParameterizedSource(std::stod(parameters)); + extern "C" ParameterizedSource* openmc_create_source(const char* parameter) { + return new ParameterizedSource(atof(parameter)); } As with the basic custom source functionality, the custom source library 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 9fcb841d0..b3e9ca9cd 100644 --- a/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp +++ b/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp @@ -1,37 +1,12 @@ -#include - -#include "openmc/random_lcg.h" #include "openmc/source.h" #include "openmc/particle.h" class ParameterizedSource : public openmc::CustomSource { - protected: - double energy_; - - // Protect the constructor so that the class can only be created by serialisation. - ParameterizedSource(double energy) { - energy_ = energy; - } - public: - // Getters for the values that we want to use in sampling. - double energy() { return energy_; } + double 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) { - std::unordered_map parameter_mapping; - - 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 ParameterizedSource(std::stod(parameter_mapping["energy"])); + ParameterizedSource(double energy) { + this->energy = energy; } // Samples from an instance of this class. @@ -46,7 +21,7 @@ class ParameterizedSource : public openmc::CustomSource { particle.r.z = 0.0; // angle particle.u = {1.0, 0.0, 0.0}; - particle.E = this->energy(); + particle.E = this->energy; particle.delayed_group = 0; return particle; @@ -55,6 +30,6 @@ 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* parameters) { - return ParameterizedSource::from_string(parameters); +extern "C" ParameterizedSource* openmc_create_source(const char* parameter) { + return new ParameterizedSource(atof(parameter)); } From 821e7d98251e484aec94856b38e78f5bfac8c3ba Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 21 Aug 2020 12:05:43 +0100 Subject: [PATCH 19/24] Remove old source_sampling custom source method Custom sources are now created only through the new class-based method, which supports parameterization. New method also slightly adjusted to create the source as managed by a unique_ptr. Examples and tests updated to align with this approach. Documentation updated. --- docs/source/io_formats/settings.rst | 28 ++-- docs/source/usersguide/settings.rst | 137 +++++++++--------- examples/custom_source/source_ring.cpp | 46 +++--- .../parameterized_source_ring.cpp | 32 ++-- include/openmc/source.h | 2 +- src/source.cpp | 36 ++--- .../source_dlopen/source_sampling.cpp | 18 ++- .../inputs_true.dat | 2 +- .../parameterized_source_sampling.cpp | 19 ++- .../source_parameterized_dlopen/test.py | 8 +- 10 files changed, 177 insertions(+), 151 deletions(-) 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 From 798b66a52766de2274aaf8d776e74f01f0fe3b99 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 28 Aug 2020 08:17:55 +0100 Subject: [PATCH 20/24] Fix typo in settings.rst create_openmc_source -> openmc_create_source --- docs/source/io_formats/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 33674ca32..0e7cacadd 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -467,7 +467,7 @@ attributes/sub-elements: * 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 + * There must be an ``openmc_create_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. From cf656032f7c1864fe7f33e851056324d2f6104f2 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 28 Aug 2020 09:37:28 +0100 Subject: [PATCH 21/24] Simplify sampling function name in doc sample_source -> sample --- docs/source/io_formats/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 0e7cacadd..09d7db123 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -466,7 +466,7 @@ attributes/sub-elements: has a few basic requirements: * It must contain a class that inherits from ``openmc::CustomSource``; - * The class must implement a function called ``sample_source()``; + * The class must implement a function called ``sample()``; * There must be an ``openmc_create_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. From 94350201979e6c4a313666da05ce8bcaac7bd0f2 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 28 Aug 2020 09:38:38 +0100 Subject: [PATCH 22/24] Update docstring custom source -> custom source library --- openmc/source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/source.py b/openmc/source.py index 78ca9dad0..22952b4a5 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -23,7 +23,7 @@ class Source: library : str Path to a custom source library parameters : str - Parameters to be provided to the custom source + Parameters to be provided to the custom source library .. versionadded:: 0.12 strength : float @@ -44,7 +44,7 @@ class Source: library : str or None Path to a custom source library parameters : str - Parameters to be provided to the custom source + Parameters to be provided to the custom source library strength : float Strength of the source particle : {'neutron', 'photon'} From 21e8d91e9074538f743c8cd9ee3000065af89525 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 28 Aug 2020 09:49:36 +0100 Subject: [PATCH 23/24] Requested updates to source Rename sample_source -> sample. Use std::string as type of argument to openmc_create_source. Use reinterpret_cast when accessing dlsym. Also moves check for dlerror to be before the attempt to create the source (avoids null reference if fails) and capture error message before dlclose, so that the error message is still available. Formatting updates and some refactoring for examples and tests. --- examples/custom_source/source_ring.cpp | 6 ++-- .../parameterized_source_ring.cpp | 35 +++++++------------ include/openmc/source.h | 4 +-- src/source.cpp | 16 +++++---- .../source_dlopen/source_sampling.cpp | 6 ++-- .../parameterized_source_sampling.cpp | 22 ++++++------ 6 files changed, 40 insertions(+), 49 deletions(-) diff --git a/examples/custom_source/source_ring.cpp b/examples/custom_source/source_ring.cpp index 5b7bb837c..bc8a8a7f8 100644 --- a/examples/custom_source/source_ring.cpp +++ b/examples/custom_source/source_ring.cpp @@ -7,7 +7,7 @@ class Source : public openmc::CustomSource { - openmc::Particle::Bank sample_source(uint64_t* seed) + openmc::Particle::Bank sample(uint64_t* seed) { openmc::Particle::Bank particle; // wgt @@ -30,7 +30,7 @@ class Source : public openmc::CustomSource // 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() +extern "C" std::unique_ptr openmc_create_source(std::string parameters) { - return std::unique_ptr (new Source()); + return std::make_unique(); } diff --git a/examples/parameterized_custom_source/parameterized_source_ring.cpp b/examples/parameterized_custom_source/parameterized_source_ring.cpp index abcaa975a..c88333e15 100644 --- a/examples/parameterized_custom_source/parameterized_source_ring.cpp +++ b/examples/parameterized_custom_source/parameterized_source_ring.cpp @@ -8,25 +8,12 @@ class Source : public openmc::CustomSource { - protected: - 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; - } - public: - // Getters for the values that we want to use in sampling. - double radius() { return radius_; } - double energy() { return energy_; } + Source(double radius, double energy) : radius_(radius), energy_(energy) { } // 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) + static std::unique_ptr from_string(std::string parameters) { std::unordered_map parameter_mapping; @@ -39,13 +26,13 @@ class Source : public openmc::CustomSource parameter_mapping[key] = value; } - return std::unique_ptr ( - new Source(std::stod(parameter_mapping["radius"]), std::stod(parameter_mapping["energy"])) - ); + double radius = std::stod(parameter_mapping["radius"]); + double energy = std::stod(parameter_mapping["energy"]); + return std::make_unique(radius, energy); } // Samples from an instance of this class. - openmc::Particle::Bank sample_source(uint64_t* seed) + openmc::Particle::Bank sample(uint64_t* seed) { openmc::Particle::Bank particle; // wgt @@ -53,23 +40,27 @@ class Source : public openmc::CustomSource particle.wgt = 1.0; // position double angle = 2.0 * M_PI * openmc::prn(seed); - double radius = this->radius(); + 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.E = this->energy_; particle.delayed_group = 0; return particle; } + + private: + double radius_; + double energy_; }; // 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* parameters) +extern "C" std::unique_ptr openmc_create_source(std::string parameters) { return Source::from_string(parameters); } diff --git a/include/openmc/source.h b/include/openmc/source.h index 460e7eca3..529cb1836 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -63,10 +63,10 @@ class CustomSource { public: virtual ~CustomSource() {} - virtual Particle::Bank sample_source(uint64_t* seed) = 0; + virtual Particle::Bank sample(uint64_t* seed) = 0; }; -typedef std::unique_ptr create_custom_source_t(const char* parameters); +typedef std::unique_ptr create_custom_source_t(std::string parameters); //============================================================================== // Functions diff --git a/src/source.cpp b/src/source.cpp index 7c40147ce..b80a2ce7a 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -46,7 +46,7 @@ std::vector external_sources; namespace { void* custom_source_library; -std::string custom_source_parameters = ""; +std::string custom_source_parameters; std::unique_ptr custom_source; } @@ -373,18 +373,20 @@ void load_custom_source_library() dlerror(); // 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()); + auto create_custom_source = reinterpret_cast( + dlsym(custom_source_library, "openmc_create_source")); // check for any dlsym errors auto dlsym_error = dlerror(); if (dlsym_error) { + std::string error_msg = fmt::format("Couldn't open the openmc_create_source symbol: {}", dlsym_error); dlclose(custom_source_library); - fatal_error(fmt::format("Couldn't open the sample_source symbol: {}", dlsym_error)); + fatal_error(error_msg); } + // create a pointer to an instance of the CustomSource + custom_source = create_custom_source(custom_source_parameters); + #else fatal_error("Custom source libraries have not yet been implemented for " "non-POSIX systems"); @@ -409,7 +411,7 @@ void close_custom_source_library() Particle::Bank sample_custom_source_library(uint64_t* seed) { // sample from the instance of the CustomSource - return custom_source->sample_source(seed); + return custom_source->sample(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 529e3a547..ea74afdd9 100644 --- a/tests/regression_tests/source_dlopen/source_sampling.cpp +++ b/tests/regression_tests/source_dlopen/source_sampling.cpp @@ -7,7 +7,7 @@ class Source : openmc::CustomSource { - openmc::Particle::Bank sample_source(uint64_t *seed) + openmc::Particle::Bank sample(uint64_t *seed) { openmc::Particle::Bank particle; // wgt @@ -29,7 +29,7 @@ class Source : openmc::CustomSource // 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() +extern "C" std::unique_ptr openmc_create_source(std::string parameters) { - return std::unique_ptr (new Source()); + return std::make_unique(); } 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 162164f01..3b0875bb0 100644 --- a/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp +++ b/tests/regression_tests/source_parameterized_dlopen/parameterized_source_sampling.cpp @@ -1,18 +1,12 @@ #include "openmc/source.h" #include "openmc/particle.h" -class Source : public openmc::CustomSource -{ +class Source : public openmc::CustomSource { public: - double energy; - - Source(double energy) - { - this->energy = energy; - } + Source(double energy) : energy_(energy) { } // Samples from an instance of this class. - openmc::Particle::Bank sample_source(uint64_t* seed) + openmc::Particle::Bank sample(uint64_t* seed) { openmc::Particle::Bank particle; // wgt @@ -24,17 +18,21 @@ class Source : public openmc::CustomSource particle.r.z = 0.0; // angle particle.u = {1.0, 0.0, 0.0}; - particle.E = this->energy; + particle.E = this->energy_; particle.delayed_group = 0; return particle; } + + private: + double energy_; }; // 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) +extern "C" std::unique_ptr openmc_create_source(std::string parameter) { - return std::unique_ptr (new Source(atof(parameter))); + double energy = std::stod(parameter); + return std::make_unique(energy); } From 0245d3356206dc512ff8c1c543ae56b6cd6ce583 Mon Sep 17 00:00:00 2001 From: Dan Short Date: Fri, 28 Aug 2020 09:50:05 +0100 Subject: [PATCH 24/24] Update doc to align with latest changes --- docs/source/usersguide/settings.rst | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index b992ed2ae..340d4af00 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -196,7 +196,7 @@ below. class Source : public openmc::CustomSource { - openmc::Particle::Bank sample_source(uint64_t* seed) + openmc::Particle::Bank sample(uint64_t* seed) { openmc::Particle::Bank particle; // weight @@ -216,8 +216,9 @@ below. } }; - extern "C" std::unique_ptr openmc_create_source() { - return std::unique_ptr (new Source()); + extern "C" std::unique_ptr openmc_create_source(std::string parameters) + { + return std::make_unique(); } The above source creates monodirectional 14.08 MeV neutrons that are distributed @@ -225,7 +226,7 @@ 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 source class must inherit from ``openmc::CustomSource`` and - implement a ``sample_source()`` function. + implement a ``sample()`` function. .. note:: The ``openmc_create_source()`` function signature must be declared ``extern "C"``. @@ -254,7 +255,7 @@ Custom Parameterized Sources ---------------------------- Some custom sources may have values (parameters) that can be changed between -runs. This is supported by using the ``create_custom_source()`` function to +runs. This is supported by using the ``openmc_create_source()`` function to pass parameters defined in the :attr:`openmc.Source.parameters` attribute to the source class when it is created: @@ -265,17 +266,12 @@ the source class when it is created: #include "openmc/source.h" #include "openmc/particle.h" - class Source : public openmc::CustomSource - { - double energy; - - Source(double energy) - { - this->energy = energy; - } + class Source : public openmc::CustomSource { + public: + Source(double energy) : energy_{energy} { } // Samples from an instance of this class. - openmc::Particle::Bank sample_source(uint64_t* seed) + openmc::Particle::Bank sample(uint64_t* seed) { openmc::Particle::Bank particle; // weight @@ -287,15 +283,19 @@ the source class when it is created: particle.r.z = 0.0; // angle particle.u = {1.0, 0.0, 0.0}; - particle.E = this->energy; + particle.E = this->energy_; particle.delayed_group = 0; return particle; } + + private: + double energy_; }; - extern "C" std::unique_ptr openmc_create_source(const char* parameter) { - return std::unique_ptr (new Source(atof(parameter))); + extern "C" std::unique_ptr openmc_create_source(std::string parameter) { + double energy = std::stod(parameter); + return std::make_unique(energy); } As with the basic custom source functionality, the custom source library