Program Listing for File XMLStateWriter.cpp

Return to documentation for file (src/flamegpu/io/XMLStateWriter.cpp)

#include "flamegpu/io/XMLStateWriter.h"
#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(
    const std::string &model_name,
    const std::shared_ptr<detail::EnvironmentManager>& env_manager,
    const util::StringPairUnorderedMap<std::shared_ptr<AgentVector>> &model,
    const unsigned int iterations,
    const std::string &output_file,
    const Simulation *_sim_instance)
    : StateWriter(model_name, env_manager, model, iterations, output_file, _sim_instance) {}

int XMLStateWriter::writeStates(bool prettyPrint) {
    tinyxml2::XMLDocument doc;

    tinyxml2::XMLNode * pRoot = doc.NewElement("states");
    doc.InsertFirstChild(pRoot);

    // Redundant for FLAMEGPU1 backwards compatibility
    tinyxml2::XMLElement * pElement = doc.NewElement("itno");
    pElement->SetText(iterations);
    pRoot->InsertEndChild(pElement);

    // Output config elements
    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);

    // 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);

    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::writeStates()\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);

    unsigned int populationSize;

    // for each agent types
    for (const auto &agent : model_state) {
        // For each agent state
        const std::string &agent_name = agent.first.first;
        const std::string &state_name = agent.first.second;

        populationSize = agent.second->size();
        if (populationSize) {
            for (unsigned int i = 0; i < populationSize; ++i) {
                // Create vars block
                tinyxml2::XMLElement * pXagentElement = doc.NewElement("xagent");

                AgentVector::Agent instance = agent.second->at(i);
                const VariableMap &mm = agent.second->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::writeStates()\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
    }

    tinyxml2::XMLError errorId = doc.SaveFile(outputFile.c_str(), !prettyPrint);
    XMLCheckResult(errorId);

    return tinyxml2::XML_SUCCESS;
}


}  // namespace io
}  // namespace flamegpu