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