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.
This commit is contained in:
Dan Short 2020-08-03 14:24:17 +01:00
parent 1de3d9ddf9
commit 8bb10563c1
11 changed files with 84 additions and 92 deletions

View file

@ -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`.

View file

@ -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

View file

@ -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.

View file

@ -1,14 +1,5 @@
import openmc
# Define the serialized source
serialized_source = """<Source>
<Radius>1.5</Radius>
<Energy>1e3</Energy>
</Source>
"""
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()

View file

@ -1,9 +1,9 @@
#include <cmath> // for M_PI
#include <unordered_map>
#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<std::string, std::string> 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

View file

@ -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:

View file

@ -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<sample_t>(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<sample_t>(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());
}
}

View file

@ -19,5 +19,5 @@
<particles>1000</particles>
<batches>10</batches>
<inactive>0</inactive>
<source library="build/libserialized_source.so" serialization="serialized_source.xml" strength="1.0" />
<source library="build/libserialized_source.so" parameters="energy=1e3" strength="1.0" />
</settings>

View file

@ -1,3 +0,0 @@
<Source>
<Energy>1e3</Energy>
</Source>

View file

@ -1,3 +1,5 @@
#include <unordered_map>
#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<std::string, std::string> 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

View file

@ -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