Program Listing for File XMLStateReader.cu

Return to documentation for file (src/flamegpu/io/XMLStateReader.cu)

#include "flamegpu/io/XMLStateReader.h"
#include <sstream>
#include <algorithm>
#include <numeric>
#include <tuple>
#include <cstdio>
#include <vector>
#include <functional>
#include <memory>
#include <string>

#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/simulation/AgentVector.h"
#include "flamegpu/model/AgentDescription.h"
#include "flamegpu/model/EnvironmentDescription.h"
#include "flamegpu/simulation/CUDASimulation.h"

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

void XMLStateReader::parse(const std::string &inputFile, const std::shared_ptr<const ModelData> &model, Verbosity verbosity) {
    tinyxml2::XMLDocument doc;

    tinyxml2::XMLError errorId = doc.LoadFile(inputFile.c_str());
    XMLCheckResult(errorId);

    tinyxml2::XMLNode* pRoot = doc.FirstChild();
    if (pRoot == nullptr) {
        THROW exception::TinyXMLError("TinyXML error: Error parsing doc %s.", inputFile.c_str());
    }

    // Read config data
    tinyxml2::XMLElement* pElement = pRoot->FirstChildElement("config");
    if (pElement) {
        // Sim config
        tinyxml2::XMLElement *pSimCfgBlock = pElement->FirstChildElement("simulation");
        if (pSimCfgBlock) {
            for (auto simCfgElement = pSimCfgBlock->FirstChildElement(); simCfgElement; simCfgElement = simCfgElement->NextSiblingElement()) {
                std::string key = simCfgElement->Value();
                std::string val = simCfgElement->GetText() ? simCfgElement->GetText() : "";
                if (key == "input_file") {
                    if (inputFile != val && !val.empty())
                        if (verbosity > Verbosity::Quiet)
                            fprintf(stderr, "Warning: Input file '%s' refers to second input file '%s', this will not be loaded.\n", inputFile.c_str(), val.c_str());
                    // sim_instance->SimulationConfig().input_file = val;
                } else if (key == "step_log_file" ||
                           key == "exit_log_file" ||
                           key == "common_log_file") {
                    simulation_config.emplace(key, val);
                } else if (key == "truncate_log_files" ||
#ifdef FLAMEGPU_VISUALISATION
                           key == "console_mode" ||
#endif
                           key == "timing") {
                    for (auto& c : val)
                        c = static_cast<char>(::tolower(c));
                    if (val == "true") {
                        simulation_config.emplace(key, true);
                    } else if (val == "false") {
                        simulation_config.emplace(key, false);
                    } else {
                        simulation_config.emplace(key, static_cast<bool>(stoll(val)));
                    }
                } else if (key == "random_seed") {
                    simulation_config.emplace(key, static_cast<uint64_t>(stoull(val)));
                } else if (key == "steps") {
                    simulation_config.emplace(key, static_cast<unsigned int>(stoull(val)));
                } else if (key == "verbosity") {
                    simulation_config.emplace(key, static_cast<flamegpu::Verbosity>(stoull(val)));
                }  else if (verbosity > Verbosity::Quiet) {
                    fprintf(stderr, "Warning: Input file '%s' contains unexpected simulation config property '%s'.\n", inputFile.c_str(), key.c_str());
                }
#ifndef FLAMEGPU_VISUALISATION
                if (key == "console_mode") {
                    if (verbosity > Verbosity::Quiet)
                        fprintf(stderr, "Warning: Cannot configure 'console_mode' with input file '%s', FLAMEGPU2 library has not been built with visualisation support enabled.\n", inputFile.c_str());
                }
#endif
            }
        }
        // CUDA config
        tinyxml2::XMLElement *pCUDACfgBlock = pElement->FirstChildElement("cuda");
        if (pCUDACfgBlock) {
            for (auto cudaCfgElement = pCUDACfgBlock->FirstChildElement(); cudaCfgElement; cudaCfgElement = cudaCfgElement->NextSiblingElement()) {
                std::string key = cudaCfgElement->Value();
                std::string val = cudaCfgElement->GetText();
                if (key == "device_id") {
                    cuda_config.emplace(key, static_cast<int>(stoull(val)));
                } else if (key == "inLayerConcurrency") {
                    for (auto& c : val)
                        c = static_cast<char>(::tolower(c));
                    if (val == "true") {
                        cuda_config.emplace(key, true);
                    } else if (val == "false") {
                        cuda_config.emplace(key, false);
                    } else {
                        cuda_config.emplace(key, static_cast<bool>(stoll(val)));
                    }
                } else if (verbosity > Verbosity::Quiet) {
                    fprintf(stderr, "Warning: Input file '%s' contains unexpected cuda config property '%s'.\n", inputFile.c_str(), key.c_str());
                }
            }
        }
    } else {
        // No warning, environment node is not mandatory
    }

    // Read environment data
    pElement = pRoot->FirstChildElement("environment");
    if (pElement) {
        auto& env_props = model->environment->properties;
        for (auto envElement = pElement->FirstChildElement(); envElement; envElement = envElement->NextSiblingElement()) {
            const char *key = envElement->Value();
            std::stringstream ss(envElement->GetText());
            std::string token;
            const auto it = env_props.find(std::string(key));
            if (it == env_props.end()) {
                THROW exception::TinyXMLError("Input file contains unrecognised environment property '%s',"
                    "in XMLStateReader::parse()\n", key);
            }
            const std::type_index val_type = it->second.data.type;
            const auto elements = it->second.data.elements;
            unsigned int el = 0;
            while (getline(ss, token, ',')) {
                if (el == 0) {
                    if (!env_init.emplace(std::string(key), detail::Any(it->second.data)).second) {
                        THROW exception::TinyXMLError("Input file contains environment property '%s' multiple times, "
                            "in XMLStateReader::parse()\n", key);
                    }
                } else if (el >= it->second.data.elements) {
                    THROW exception::RapidJSONError("Input file contains environment property '%s' too many elements, expected %u,"
                        "in XMLStateReader::parse()\n", key, it->second.data.elements);
                }
                const auto ei_it = env_init.find(key);
                if (val_type == std::type_index(typeid(float))) {
                    static_cast<float*>(const_cast<void*>(ei_it->second.ptr))[el++] = stof(token);
                } else if (val_type == std::type_index(typeid(double))) {
                    static_cast<double*>(const_cast<void*>(ei_it->second.ptr))[el++] = stod(token);
                } else if (val_type == std::type_index(typeid(int64_t))) {
                    static_cast<int64_t*>(const_cast<void*>(ei_it->second.ptr))[el++] = stoll(token);
                } else if (val_type == std::type_index(typeid(uint64_t))) {
                    static_cast<uint64_t*>(const_cast<void*>(ei_it->second.ptr))[el++] = stoull(token);
                } else if (val_type == std::type_index(typeid(int32_t))) {
                    static_cast<int32_t*>(const_cast<void*>(ei_it->second.ptr))[el++] = static_cast<int32_t>(stoll(token));
                } else if (val_type == std::type_index(typeid(uint32_t))) {
                    static_cast<uint32_t*>(const_cast<void*>(ei_it->second.ptr))[el++] = static_cast<uint32_t>(stoull(token));
                } else if (val_type == std::type_index(typeid(int16_t))) {
                    static_cast<int16_t*>(const_cast<void*>(ei_it->second.ptr))[el++] = static_cast<int16_t>(stoll(token));
                } else if (val_type == std::type_index(typeid(uint16_t))) {
                    static_cast<uint16_t*>(const_cast<void*>(ei_it->second.ptr))[el++] = static_cast<uint16_t>(stoull(token));
                } else if (val_type == std::type_index(typeid(int8_t))) {
                    static_cast<int8_t*>(const_cast<void*>(ei_it->second.ptr))[el++] = static_cast<int8_t>(stoll(token));
                } else if (val_type == std::type_index(typeid(uint8_t))) {
                    static_cast<uint8_t*>(const_cast<void*>(ei_it->second.ptr))[el++] = static_cast<uint8_t>(stoull(token));
                } else {
                    THROW exception::TinyXMLError("Model contains environment property '%s' of unsupported type '%s', "
                        "in XMLStateReader::parse()\n", key, val_type.name());
                }
            }
            if (el != elements && verbosity > Verbosity::Quiet) {
                fprintf(stderr, "Warning: Environment array property '%s' expects '%u' elements, input file '%s' contains '%u' elements.\n",
                    key, elements, inputFile.c_str(), el);
            }
        }
    } else if (verbosity > Verbosity::Quiet) {
        fprintf(stderr, "Warning: Input file '%s' does not contain environment node.\n", inputFile.c_str());
    }

    // Read macro environment data
    pElement = pRoot->FirstChildElement("macro_environment");
    if (pElement) {
        auto& env_macro_props = model->environment->macro_properties;
        for (auto envElement = pElement->FirstChildElement(); envElement; envElement = envElement->NextSiblingElement()) {
            const char *key = envElement->Value();
            std::stringstream ss(envElement->GetText());
            std::string token;
            const auto it = env_macro_props.find(std::string(key));
            if (it == env_macro_props.end()) {
                THROW exception::TinyXMLError("Input file contains unrecognised macro environment property '%s',"
                    "in XMLStateReader::parse()\n", key);
            }
            const std::type_index val_type = it->second.type;
            const unsigned int elements = std::accumulate(it->second.elements.begin(), it->second.elements.end(), 1, std::multiplies<unsigned int>());
            unsigned int el = 0;
            while (getline(ss, token, ',')) {
                if (el == 0) {
                    if (!macro_env_init.emplace(std::string(key), std::vector<char>(elements * it->second.type_size)).second) {
                        THROW exception::TinyXMLError("Input file contains macro environment property '%s' multiple times, "
                            "in XMLStateReader::parse()\n", key);
                    }
                } else if (el >= elements) {
                    THROW exception::RapidJSONError("Input file contains macro environment property '%s' too many elements, expected %u,"
                        "in XMLStateReader::parse()\n", key, elements);
                }
                auto &mei = macro_env_init.at(key);
                if (val_type == std::type_index(typeid(float))) {
                    static_cast<float*>(static_cast<void*>(mei.data()))[el++] = stof(token);
                } else if (val_type == std::type_index(typeid(double))) {
                    static_cast<double*>(static_cast<void*>(mei.data()))[el++] = stod(token);
                } else if (val_type == std::type_index(typeid(int64_t))) {
                    static_cast<int64_t*>(static_cast<void*>(mei.data()))[el++] = stoll(token);
                } else if (val_type == std::type_index(typeid(uint64_t))) {
                    static_cast<uint64_t*>(static_cast<void*>(mei.data()))[el++] = stoull(token);
                } else if (val_type == std::type_index(typeid(int32_t))) {
                    static_cast<int32_t*>(static_cast<void*>(mei.data()))[el++] = static_cast<int32_t>(stoll(token));
                } else if (val_type == std::type_index(typeid(uint32_t))) {
                    static_cast<uint32_t*>(static_cast<void*>(mei.data()))[el++] = static_cast<uint32_t>(stoull(token));
                } else if (val_type == std::type_index(typeid(int16_t))) {
                    static_cast<int16_t*>(static_cast<void*>(mei.data()))[el++] = static_cast<int16_t>(stoll(token));
                } else if (val_type == std::type_index(typeid(uint16_t))) {
                    static_cast<uint16_t*>(static_cast<void*>(mei.data()))[el++] = static_cast<uint16_t>(stoull(token));
                } else if (val_type == std::type_index(typeid(int8_t))) {
                    static_cast<int8_t*>(static_cast<void*>(mei.data()))[el++] = static_cast<int8_t>(stoll(token));
                } else if (val_type == std::type_index(typeid(uint8_t))) {
                    static_cast<uint8_t*>(static_cast<void*>(mei.data()))[el++] = static_cast<uint8_t>(stoull(token));
                } else {
                    THROW exception::TinyXMLError("Model contains macro environment property '%s' of unsupported type '%s', "
                        "in XMLStateReader::parse()\n", key, val_type.name());
                }
            }
            if (el != elements && verbosity > Verbosity::Quiet) {
                fprintf(stderr, "Warning: Macro environment property '%s' expects '%u' elements, input file '%s' contains '%u' elements.\n",
                    key, elements, inputFile.c_str(), el);
            }
        }
    } else if (verbosity > Verbosity::Quiet) {
        fprintf(stderr, "Warning: Input file '%s' does not contain macro environment node.\n", inputFile.c_str());
    }

    // Count how many of each agent are in the file and resize state lists
    util::StringPairUnorderedMap<unsigned int> cts;
    for (pElement = pRoot->FirstChildElement("xagent"); pElement != nullptr; pElement = pElement->NextSiblingElement("xagent")) {
        std::string agent_name = pElement->FirstChildElement("name")->GetText();
        tinyxml2::XMLElement *state_element = pElement->FirstChildElement("state");
        std::string state_name = state_element ? state_element->GetText() : getInitialState(model, agent_name);
        cts[{agent_name, state_name}]++;
    }

    // Init state lists to correct size
    for (const auto& it : cts) {
        const auto& agent = model->agents.find(it.first.first);
        if (agent == model->agents.end() || agent->second->states.find(it.first.second) == agent->second->states.end()) {
            THROW exception::InvalidAgentState("Agent '%s' with state '%s', found in input file '%s', is not part of the model description hierarchy, "
                "in XMLStateReader::parse()\n Ensure the input file is for the correct model.\n", it.first.first.c_str(), it.first.second.c_str(), inputFile.c_str());
        }
        auto [_it, _] = agents_map.emplace(it.first, std::make_shared<AgentVector>(*agent->second));
        _it->second->reserve(it.second);
    }

    bool hasWarnedElements = false;
    bool hasWarnedMissingVar = false;
    // Read in agent data
    for (pElement = pRoot->FirstChildElement("xagent"); pElement != nullptr; pElement = pElement->NextSiblingElement("xagent")) {
        // Find agent name
        tinyxml2::XMLElement* pNameElement = pElement->FirstChildElement("name");
        const char* agentName = pNameElement->GetText();
        // Find agent state, use initial state if not set (means its old flame gpu 1 input file)
        tinyxml2::XMLElement* pStateElement = pElement->FirstChildElement("state");
        const std::string agentState = pStateElement ? std::string(pStateElement->GetText()) : getInitialState(model, agentName);
        const auto agentIt = agents_map.find({ agentName, agentState });
        if (agentIt == agents_map.end()) {
            THROW exception::InvalidAgentState("Agent '%s' with state '%s', found in input file '%s', is not part of the model description hierarchy, "
                "in XMLStateReader::parse()\n Ensure the input file is for the correct model.\n", agentName, agentState.c_str(), inputFile.c_str());
        }
        std::shared_ptr<AgentVector> &agentVec = agentIt->second;
        const VariableMap& agentVariables = agentVec->getVariableMetaData();
        // Create instance to store variable data in
        agentVec->push_back();
        AgentVector::Agent instance = agentVec->back();
        // Iterate agent variables
        for (auto iter = agentVariables.begin(); iter != agentVariables.end(); ++iter) {
            const std::string variable_name = iter->first;
            const auto &var_data = iter->second;
            tinyxml2::XMLElement* pVarElement = pElement->FirstChildElement(variable_name.c_str());
            if (pVarElement) {
                // Put string for the variable into a string stream
                std::stringstream ss(pVarElement->GetText());
                std::string token;
                const size_t v_size = var_data.type_size * var_data.elements;
                char* data = static_cast<char*>(const_cast<void*>(static_cast<std::shared_ptr<const AgentVector>>(agentVec)->data(variable_name)));
                // Iterate elements of the stringstream
                unsigned int el = 0;
                while (getline(ss, token, ',')) {
                    if (var_data.type == std::type_index(typeid(float))) {
                        const float t = stof(token);
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(double))) {
                        const double t = stod(token);
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(int64_t))) {
                        const int64_t t = stoll(token);
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(uint64_t))) {
                        const uint64_t t = stoull(token);
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(int32_t))) {
                        const int32_t t = static_cast<int32_t>(stoll(token));
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(uint32_t))) {
                        const uint32_t t = static_cast<uint32_t>(stoull(token));
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(int16_t))) {
                        const int16_t t = static_cast<int16_t>(stoll(token));
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(uint16_t))) {
                        const uint16_t t = static_cast<uint16_t>(stoull(token));
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(int8_t))) {
                        const int8_t t = static_cast<int8_t>(stoll(token));
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else if (var_data.type == std::type_index(typeid(uint8_t))) {
                        const uint8_t t = static_cast<uint8_t>(stoull(token));
                        memcpy(data + ((agentVec->size() - 1) * v_size) + (var_data.type_size * el++), &t, var_data.type_size);
                    } else {
                        THROW exception::TinyXMLError("Agent '%s' contains variable '%s' of unsupported type '%s', "
                            "in XMLStateReader::parse()\n", agentName, variable_name.c_str(), var_data.type.name());
                    }
                }
                // Warn if var is wrong length
                if (el != var_data.elements && !hasWarnedElements && verbosity > Verbosity::Quiet) {
                    fprintf(stderr, "Warning: Agent '%s' variable '%s' expects '%u' elements, input file '%s' contains '%u' elements.\n",
                        agentName, variable_name.c_str(), var_data.elements, inputFile.c_str(), el);
                    hasWarnedElements = true;
                }
            } else if (!hasWarnedMissingVar && variable_name.find('_', 0) != 0 && verbosity > Verbosity::Quiet) {
                fprintf(stderr, "Warning: Agent '%s' variable '%s' is missing from, input file '%s'.\n",
                    agentName, variable_name.c_str(), inputFile.c_str());
                hasWarnedMissingVar = true;
            }
        }
    }

    // Mark input as loaded
    this->input_filepath = inputFile;
}

std::string XMLStateReader::getInitialState(const std::shared_ptr<const ModelData> &model, const std::string &agent_name) {
    const auto& it = model->agents.find(agent_name);
    if (it != model->agents.end()) {
        return it->second->initial_state;
    }
    return ModelData::DEFAULT_STATE;
}
}  // namespace io
}  // namespace flamegpu