Program Listing for File XMLStateWriter.cu
↰ Return to documentation for file (src/flamegpu/io/XMLStateWriter.cu
)
#include "flamegpu/io/XMLStateWriter.h"
#include <numeric>
#include <sstream>
#include "tinyxml2/tinyxml2.h" // downloaded from https:// github.com/leethomason/tinyxml2, the list of xml parsers : http:// lars.ruoff.free.fr/xmlcpp/
#include "flamegpu/exception/FLAMEGPUException.h"
#include "flamegpu/model/AgentDescription.h"
#include "flamegpu/simulation/CUDASimulation.h"
#include "flamegpu/simulation/AgentVector.h"
#include "flamegpu/simulation/detail/EnvironmentManager.cuh"
namespace flamegpu {
namespace io {
#ifndef XMLCheckResult
#define XMLCheckResult(a_eResult) if (a_eResult != tinyxml2::XML_SUCCESS) { exception::FLAMEGPUException::setLocation(__FILE__, __LINE__);\
switch (a_eResult) { \
case tinyxml2::XML_ERROR_FILE_NOT_FOUND : \
case tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED : \
THROW exception::InvalidInputFile("TinyXML error: File could not be opened.\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_FILE_READ_ERROR : \
THROW exception::InvalidInputFile("TinyXML error: File could not be read.\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_PARSING_ELEMENT : \
case tinyxml2::XML_ERROR_PARSING_ATTRIBUTE : \
case tinyxml2::XML_ERROR_PARSING_TEXT : \
case tinyxml2::XML_ERROR_PARSING_CDATA : \
case tinyxml2::XML_ERROR_PARSING_COMMENT : \
case tinyxml2::XML_ERROR_PARSING_DECLARATION : \
case tinyxml2::XML_ERROR_PARSING_UNKNOWN : \
case tinyxml2::XML_ERROR_PARSING : \
THROW exception::TinyXMLError("TinyXML error: Error parsing file.\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_EMPTY_DOCUMENT : \
THROW exception::TinyXMLError("TinyXML error: XML_ERROR_EMPTY_DOCUMENT\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_MISMATCHED_ELEMENT : \
THROW exception::TinyXMLError("TinyXML error: XML_ERROR_MISMATCHED_ELEMENT\n Error code: %d", a_eResult); \
case tinyxml2::XML_CAN_NOT_CONVERT_TEXT : \
THROW exception::TinyXMLError("TinyXML error: XML_CAN_NOT_CONVERT_TEXT\n Error code: %d", a_eResult); \
case tinyxml2::XML_NO_TEXT_NODE : \
THROW exception::TinyXMLError("TinyXML error: XML_NO_TEXT_NODE\n Error code: %d", a_eResult); \
case tinyxml2::XML_ELEMENT_DEPTH_EXCEEDED : \
THROW exception::TinyXMLError("TinyXML error: XML_ELEMENT_DEPTH_EXCEEDED\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_COUNT : \
THROW exception::TinyXMLError("TinyXML error: XML_ERROR_COUNT\n Error code: %d", a_eResult); \
case tinyxml2::XML_NO_ATTRIBUTE: \
THROW exception::TinyXMLError("TinyXML error: XML_NO_ATTRIBUTE\n Error code: %d", a_eResult); \
case tinyxml2::XML_WRONG_ATTRIBUTE_TYPE : \
THROW exception::TinyXMLError("TinyXML error: XML_WRONG_ATTRIBUTE_TYPE\n Error code: %d", a_eResult); \
default: \
THROW exception::TinyXMLError("TinyXML error: Unrecognised error code\n Error code: %d", a_eResult); \
} \
}
#endif
XMLStateWriter::XMLStateWriter()
: StateWriter() {}
void XMLStateWriter::beginWrite(const std::string &output_file, bool pretty_print) {
this->outputPath = output_file;
this->prettyPrint = pretty_print;
if (doc || pRoot) {
THROW exception::UnknownInternalError("Writing already active, in XMLStateWriter::beginWrite()");
}
doc = std::make_unique<tinyxml2::XMLDocument>();
// Begin Json file
pRoot = doc->NewElement("states");
doc->InsertFirstChild(pRoot);
// Clear flags
this->config_written = false;
this->stats_written = false;
this->environment_written = false;
this->macro_environment_written = false;
this->agents_written = false;
}
void XMLStateWriter::endWrite() {
if (!doc || !pRoot) {
THROW exception::UnknownInternalError("Writing not active, in XMLStateWriter::endWrite()");
}
// End Json file
tinyxml2::XMLError errorId = doc->SaveFile(outputPath.c_str(), !prettyPrint);
XMLCheckResult(errorId);
pRoot = nullptr;
doc.reset();
}
void XMLStateWriter::writeConfig(const Simulation *sim_instance) {
if (!doc || !pRoot) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeConfig(), in XMLStateWriter::writeConfig()");
} else if (config_written) {
THROW exception::UnknownInternalError("writeConfig() can only be called once per write session, in XMLStateWriter::writeConfig()");
}
// Output config elements
tinyxml2::XMLElement *pElement = doc->NewElement("config");
{
// Sim config
tinyxml2::XMLElement *pSimCfg = doc->NewElement("simulation");
{
const auto &sim_cfg = sim_instance->getSimulationConfig();
tinyxml2::XMLElement *pListElement = nullptr;
// Input file
pListElement = doc->NewElement("input_file");
pListElement->SetText(sim_cfg.input_file.c_str());
pSimCfg->InsertEndChild(pListElement);
// Step log file
pListElement = doc->NewElement("step_log_file");
pListElement->SetText(sim_cfg.step_log_file.c_str());
pSimCfg->InsertEndChild(pListElement);
// Exit log file
pListElement = doc->NewElement("exit_log_file");
pListElement->SetText(sim_cfg.exit_log_file.c_str());
pSimCfg->InsertEndChild(pListElement);
// Common log file
pListElement = doc->NewElement("common_log_file");
pListElement->SetText(sim_cfg.common_log_file.c_str());
pSimCfg->InsertEndChild(pListElement);
// Truncate log files
pListElement = doc->NewElement("truncate_log_files");
pListElement->SetText(sim_cfg.truncate_log_files);
pSimCfg->InsertEndChild(pListElement);
// Random seed
pListElement = doc->NewElement("random_seed");
pListElement->SetText(sim_cfg.random_seed);
pSimCfg->InsertEndChild(pListElement);
// Steps
pListElement = doc->NewElement("steps");
pListElement->SetText(sim_cfg.steps);
pSimCfg->InsertEndChild(pListElement);
// Verbose output
pListElement = doc->NewElement("verbosity");
pListElement->SetText(static_cast<unsigned int>(sim_cfg.verbosity));
pSimCfg->InsertEndChild(pListElement);
// Timing Output
pListElement = doc->NewElement("timing");
pListElement->SetText(sim_cfg.timing);
pSimCfg->InsertEndChild(pListElement);
#ifdef FLAMEGPU_VISUALISATION
// Console Mode
pListElement = doc->NewElement("console_mode");
pListElement->SetText(sim_cfg.console_mode);
pSimCfg->InsertEndChild(pListElement);
#endif
}
pElement->InsertEndChild(pSimCfg);
// Cuda config
if (auto *cudamodel_instance = dynamic_cast<const CUDASimulation*>(sim_instance)) {
tinyxml2::XMLElement *pCUDACfg = doc->NewElement("cuda");
{
const auto &cuda_cfg = cudamodel_instance->getCUDAConfig();
tinyxml2::XMLElement *pListElement = nullptr;
// Device ID
pListElement = doc->NewElement("device_id");
pListElement->SetText(cuda_cfg.device_id);
pCUDACfg->InsertEndChild(pListElement);
// inLayerConcurrency
pListElement = doc->NewElement("inLayerConcurrency");
pListElement->SetText(cuda_cfg.inLayerConcurrency);
pCUDACfg->InsertEndChild(pListElement);
}
pElement->InsertEndChild(pCUDACfg);
}
}
pRoot->InsertEndChild(pElement);
config_written = true;
}
void XMLStateWriter::writeStats(unsigned int iterations) {
if (!doc || !pRoot) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeStats(), in XMLStateWriter::writeStats()");
} else if (stats_written) {
THROW exception::UnknownInternalError("writeStats() can only be called once per write session, in XMLStateWriter::writeStats()");
}
// Redundant for FLAMEGPU1 backwards compatibility
tinyxml2::XMLElement *pElement = doc->NewElement("itno");
pElement->SetText(iterations);
pRoot->InsertEndChild(pElement);
// Output stats elements
pElement = doc->NewElement("stats");
{
tinyxml2::XMLElement *pListElement = nullptr;
// Input file
pListElement = doc->NewElement("step_count");
pListElement->SetText(iterations);
pElement->InsertEndChild(pListElement);
}
pRoot->InsertEndChild(pElement);
stats_written = true;
}
void XMLStateWriter::writeEnvironment(const std::shared_ptr<const detail::EnvironmentManager>& env_manager) {
if (!doc || !pRoot) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeEnvironment(), in XMLStateWriter::writeEnvironment()");
} else if (environment_written) {
THROW exception::UnknownInternalError("writeEnvironment() can only be called once per write session, in XMLStateWriter::writeEnvironment()");
}
tinyxml2::XMLElement *pElement = doc->NewElement("environment");
if (env_manager) {
const char* env_buffer = reinterpret_cast<const char*>(env_manager->getHostBuffer());
// for each environment property
for (auto &a : env_manager->getPropertiesMap()) {
tinyxml2::XMLElement* pListElement = doc->NewElement(a.first.c_str());
pListElement->SetAttribute("type", a.second.type.name());
// Output properties
std::stringstream ss;
// Loop through elements, to construct csv string
for (unsigned int el = 0; el < a.second.elements; ++el) {
if (a.second.type == std::type_index(typeid(float))) {
ss << *reinterpret_cast<const float*>(env_buffer + a.second.offset + (el * sizeof(float)));
} else if (a.second.type == std::type_index(typeid(double))) {
ss << *reinterpret_cast<const double*>(env_buffer + a.second.offset + (el * sizeof(double)));
} else if (a.second.type == std::type_index(typeid(int64_t))) {
ss << *reinterpret_cast<const int64_t*>(env_buffer + a.second.offset + (el * sizeof(int64_t)));
} else if (a.second.type == std::type_index(typeid(uint64_t))) {
ss << *reinterpret_cast<const uint64_t*>(env_buffer + a.second.offset + (el * sizeof(uint64_t)));
} else if (a.second.type == std::type_index(typeid(int32_t))) {
ss << *reinterpret_cast<const int32_t*>(env_buffer + a.second.offset + (el * sizeof(int32_t)));
} else if (a.second.type == std::type_index(typeid(uint32_t))) {
ss << *reinterpret_cast<const uint32_t*>(env_buffer + a.second.offset + (el * sizeof(uint32_t)));
} else if (a.second.type == std::type_index(typeid(int16_t))) {
ss << *reinterpret_cast<const int16_t*>(env_buffer + a.second.offset + (el * sizeof(int16_t)));
} else if (a.second.type == std::type_index(typeid(uint16_t))) {
ss << *reinterpret_cast<const uint16_t*>(env_buffer + a.second.offset + (el * sizeof(uint16_t)));
} else if (a.second.type == std::type_index(typeid(int8_t))) {
ss << static_cast<int32_t>(*reinterpret_cast<const int8_t*>(env_buffer + a.second.offset + (el * sizeof(int8_t)))); // Char outputs weird if being used as an integer
} else if (a.second.type == std::type_index(typeid(uint8_t))) {
ss << static_cast<uint32_t>(*reinterpret_cast<const uint8_t*>(env_buffer + a.second.offset + (el * sizeof(uint8_t)))); // Char outputs weird if being used as an integer
} else {
THROW exception::TinyXMLError("Model contains environment property '%s' of unsupported type '%s', "
"in XMLStateWriter::writeEnvironment()\n", a.first.c_str(), a.second.type.name());
}
if (el + 1 != a.second.elements)
ss << ",";
}
pListElement->SetText(ss.str().c_str());
pElement->InsertEndChild(pListElement);
}
}
pRoot->InsertEndChild(pElement);
environment_written = true;
}
void XMLStateWriter::writeMacroEnvironment(const std::shared_ptr<const detail::CUDAMacroEnvironment>& macro_env, std::initializer_list<std::string> filter) {
if (!doc || !pRoot) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeMacroEnvironment(), in XMLStateWriter::writeMacroEnvironment()");
} else if (macro_environment_written) {
THROW exception::UnknownInternalError("writeMacroEnvironment() can only be called once per write session, in XMLStateWriter::writeMacroEnvironment()");
}
tinyxml2::XMLElement *pElement = doc->NewElement("macro_environment");
if (macro_env) {
const std::map<std::string, detail::CUDAMacroEnvironment::MacroEnvProp>& m_properties = macro_env->getPropertiesMap();
for (const auto &_filter : filter) {
if (m_properties.find(_filter) == m_properties.end()) {
THROW exception::InvalidEnvProperty("Macro property '%s' specified in filter does not exist, in XMLStateWriter::writeMacroEnvironment()", _filter.c_str());
}
}
std::set<std::string> filter_set = filter;
// Calculate largest buffer in map
size_t max_len = 0;
for (const auto& [_, prop] : m_properties) {
max_len = std::max(max_len, std::accumulate(prop.elements.begin(), prop.elements.end(), 1, std::multiplies<unsigned int>()) * prop.type_size);
}
if (max_len) {
// Allocate temp buffer
char* const t_buffer = static_cast<char*>(malloc(max_len));
// Write out each array (all are written out as 1D arrays for simplicity given variable dimensions)
for (const auto& [name, prop] : m_properties) {
if (!filter_set.empty() && filter_set.find(name) == filter_set.end())
continue;
// Copy data
const size_t element_ct = std::accumulate(prop.elements.begin(), prop.elements.end(), 1, std::multiplies<unsigned int>());
gpuErrchk(cudaMemcpy(t_buffer, prop.d_ptr, element_ct * prop.type_size, cudaMemcpyDeviceToHost));
tinyxml2::XMLElement* pListElement = doc->NewElement(name.c_str());
pListElement->SetAttribute("type", prop.type.name());
// Loop through dimensions to construct dimensions string
// Clip trailing 1 dimensions
std::stringstream ss;
size_t sum = 1;
for (size_t j = 0; j < prop.elements.size(); ++j) {
ss << prop.elements[j];
sum *= prop.elements[j];
if (sum == element_ct)
break;
ss << ",";
}
pListElement->SetAttribute("dimensions", ss.str().c_str());
ss.str("");
ss.clear();
// Output elements
// Loop through elements, to construct csv string
for (size_t i = 0; i < element_ct; ++i) {
if (prop.type == std::type_index(typeid(float))) {
ss << *reinterpret_cast<const float*>(t_buffer + i * sizeof(float));
} else if (prop.type == std::type_index(typeid(double))) {
ss << *reinterpret_cast<const double*>(t_buffer + i * sizeof(double));
} else if (prop.type == std::type_index(typeid(int64_t))) {
ss << *reinterpret_cast<const int64_t*>(t_buffer + i * sizeof(int64_t));
} else if (prop.type == std::type_index(typeid(uint64_t))) {
ss << *reinterpret_cast<const uint64_t*>(t_buffer + i * sizeof(uint64_t));
} else if (prop.type == std::type_index(typeid(int32_t))) {
ss << *reinterpret_cast<const int32_t*>(t_buffer + i * sizeof(int32_t));
} else if (prop.type == std::type_index(typeid(uint32_t))) {
ss << *reinterpret_cast<const uint32_t*>(t_buffer + i * sizeof(uint32_t));
} else if (prop.type == std::type_index(typeid(int16_t))) {
ss << *reinterpret_cast<const int16_t*>(t_buffer + i * sizeof(int16_t));
} else if (prop.type == std::type_index(typeid(uint16_t))) {
ss << *reinterpret_cast<const uint16_t*>(t_buffer + i * sizeof(uint16_t));
} else if (prop.type == std::type_index(typeid(int8_t))) {
ss << static_cast<int32_t>(*reinterpret_cast<const int8_t*>(t_buffer + i * sizeof(int8_t))); // Char outputs weird if being used as an integer
} else if (prop.type == std::type_index(typeid(uint8_t))) {
ss << static_cast<uint32_t>(*reinterpret_cast<const uint8_t*>(t_buffer + i * sizeof(uint8_t))); // Char outputs weird if being used as an integer
} else {
THROW exception::TinyXMLError("Model contains macro environment property '%s' of unsupported type '%s', "
"in XMLStateWriter::writeMacroEnvironment()\n", name.c_str(), prop.type.name());
}
if (i + 1 != element_ct)
ss << ",";
}
pListElement->SetText(ss.str().c_str());
pElement->InsertEndChild(pListElement);
}
// Release temp buffer
free(t_buffer);
}
}
pRoot->InsertEndChild(pElement);
macro_environment_written = true;
}
void XMLStateWriter::writeAgents(const util::StringPairUnorderedMap<std::shared_ptr<const AgentVector>>& agents_map) {
if (!doc || !pRoot) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeAgents(), in XMLStateWriter::writeAgents()");
} else if (agents_written) {
THROW exception::UnknownInternalError("writeAgents() can only be called once per write session, in XMLStateWriter::writeAgents()");
}
// for each agent types
for (const auto &[key, vec] : agents_map) {
// For each agent state
const std::string &agent_name = key.first;
const std::string &state_name = key.second;
unsigned int populationSize = vec->size();
if (populationSize) {
for (unsigned int i = 0; i < populationSize; ++i) {
// Create vars block
tinyxml2::XMLElement * pXagentElement = doc->NewElement("xagent");
const AgentVector::CAgent instance = vec->at(i);
const VariableMap &mm = vec->getVariableMetaData();
// Add agent's name to block
tinyxml2::XMLElement * pXagentNameElement = doc->NewElement("name");
pXagentNameElement->SetText(agent_name.c_str());
pXagentElement->InsertEndChild(pXagentNameElement);
// Add state's name to block
tinyxml2::XMLElement * pStateNameElement = doc->NewElement("state");
pStateNameElement->SetText(state_name.c_str());
pXagentElement->InsertEndChild(pStateNameElement);
// for each variable
for (auto iter_mm = mm.begin(); iter_mm != mm.end(); ++iter_mm) {
const std::string variable_name = iter_mm->first;
tinyxml2::XMLElement* pListElement = doc->NewElement(variable_name.c_str());
if (i == 0)
pListElement->SetAttribute("type", iter_mm->second.type.name());
// Output properties
std::stringstream ss;
// Loop through elements, to construct csv string
for (unsigned int el = 0; el < iter_mm->second.elements; ++el) {
if (iter_mm->second.type == std::type_index(typeid(float))) {
ss << instance.getVariable<float>(variable_name, el);
} else if (iter_mm->second.type == std::type_index(typeid(double))) {
ss << instance.getVariable<double>(variable_name, el);
} else if (iter_mm->second.type == std::type_index(typeid(int64_t))) {
ss << instance.getVariable<int64_t>(variable_name, el);
} else if (iter_mm->second.type == std::type_index(typeid(uint64_t))) {
ss << instance.getVariable<uint64_t>(variable_name, el);
} else if (iter_mm->second.type == std::type_index(typeid(int32_t))) {
ss << instance.getVariable<int32_t>(variable_name, el);
} else if (iter_mm->second.type == std::type_index(typeid(uint32_t))) {
ss << instance.getVariable<uint32_t>(variable_name, el);
} else if (iter_mm->second.type == std::type_index(typeid(int16_t))) {
ss << instance.getVariable<int16_t>(variable_name, el);
} else if (iter_mm->second.type == std::type_index(typeid(uint16_t))) {
ss << instance.getVariable<uint16_t>(variable_name, el);
} else if (iter_mm->second.type == std::type_index(typeid(int8_t))) {
ss << static_cast<int32_t>(instance.getVariable<int8_t>(variable_name, el)); // Char outputs weird if being used as an integer
} else if (iter_mm->second.type == std::type_index(typeid(uint8_t))) {
ss << static_cast<uint32_t>(instance.getVariable<uint8_t>(variable_name, el)); // Char outputs weird if being used as an integer
} else {
THROW exception::TinyXMLError("Agent '%s' contains variable '%s' of unsupported type '%s', "
"in XMLStateWriter::writeFullModelState()\n", agent_name.c_str(), variable_name.c_str(), iter_mm->second.type.name());
}
if (el + 1 != iter_mm->second.elements)
ss << ",";
}
pListElement->SetText(ss.str().c_str());
pXagentElement->InsertEndChild(pListElement);
}
// Insert xagent block into doc root
pRoot->InsertEndChild(pXagentElement);
}
} // if state has agents
}
agents_written = true;
}
} // namespace io
} // namespace flamegpu