Program Listing for File JSONStateWriter.cu
↰ Return to documentation for file (src/flamegpu/io/JSONStateWriter.cu)
#include "flamegpu/io/JSONStateWriter.h"
#include <iostream>
#include <fstream>
#include <string>
#include <numeric>
#include <set>
#include <algorithm>
#include <memory>
#include <map>
#include <functional>
#include "flamegpu/exception/FLAMEGPUException.h"
#include "flamegpu/model/AgentDescription.h"
#include "flamegpu/simulation/AgentVector.h"
#include "flamegpu/simulation/CUDASimulation.h"
#include "flamegpu/util/StringPair.h"
#include "flamegpu/simulation/detail/EnvironmentManager.cuh"
#include "flamegpu/simulation/detail/CUDAMacroEnvironment.h"
namespace flamegpu {
namespace io {
JSONStateWriter::JSONStateWriter()
: StateWriter() {}
void JSONStateWriter::beginWrite(const std::string &output_file, bool pretty_print) {
this->outputPath = output_file;
if (isWriting()) {
THROW exception::UnknownInternalError("Writing already active, in JSONStateWriter::beginWrite()");
}
outStream.open(output_file, std::ios::out|std::ios::binary|std::ios::trunc);
if (!outStream.is_open()) {
THROW exception::InvalidFilePath("Failed to open file '%s', in JSONStateWriter::beginWrite()", output_file.c_str());
}
if (pretty_print) {
outStream << std::setw(4);
}
// Reset json cache
j = {};
// Clear flags
this->config_written = false;
this->stats_written = false;
this->environment_written = false;
this->macro_environment_written = false;
this->agents_written = false;
}
void JSONStateWriter::endWrite() {
if (!isWriting()) {
THROW exception::UnknownInternalError("Writing not active, in JSONStateWriter::endWrite()");
}
outStream << j;
outStream.close();
}
void JSONStateWriter::writeConfig(const Simulation *sim_instance) {
if (!isWriting()) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeConfig(), in JSONStateWriter::writeConfig()");
} else if (config_written) {
THROW exception::UnknownInternalError("writeConfig() can only be called once per write session, in JSONStateWriter::writeConfig()");
}
// Simulation config
if (sim_instance) {
const auto& sim_cfg = sim_instance->getSimulationConfig();
j["config"]["simulation"]["input_file"] = sim_cfg.input_file;
j["config"]["simulation"]["step_log_file"] = sim_cfg.step_log_file;
j["config"]["simulation"]["exit_log_file"] = sim_cfg.exit_log_file;
j["config"]["simulation"]["common_log_file"] = sim_cfg.common_log_file;
j["config"]["simulation"]["truncate_log_files"] = sim_cfg.truncate_log_files;
j["config"]["simulation"]["random_seed"] = sim_cfg.random_seed;
j["config"]["simulation"]["steps"] = sim_cfg.steps;
j["config"]["simulation"]["verbosity"] = static_cast<unsigned int>(sim_cfg.verbosity);
j["config"]["simulation"]["timing"] = sim_cfg.timing;
#ifdef FLAMEGPU_VISUALISATION
j["config"]["simulation"]["console_mode"] = sim_cfg.console_mode;
#endif
}
// CUDA config
if (auto* cudamodel_instance = dynamic_cast<const CUDASimulation*>(sim_instance)) {
const auto& cuda_cfg = cudamodel_instance->getCUDAConfig();
j["config"]["cuda"]["device_id"] = cuda_cfg.device_id;
j["config"]["cuda"]["inLayerConcurrency"] = cuda_cfg.inLayerConcurrency;
}
config_written = true;
}
void JSONStateWriter::writeStats(unsigned int iterations) {
if (!isWriting()) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeIterations(), in JSONStateWriter::writeIterations()");
} else if (stats_written) {
THROW exception::UnknownInternalError("writeIterations() can only be called once per write session, in JSONStateWriter::writeIterations()");
}
// General runtime stats (e.g. we could add timing data in future)
j["stats"]["step_count"] = iterations;
// in future could also support random seed, run args etc
stats_written = true;
}
void JSONStateWriter::writeEnvironment(const std::shared_ptr<const detail::EnvironmentManager>& env_manager) {
if (!isWriting()) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeEnvironment(), in JSONStateWriter::writeEnvironment()");
} else if (environment_written) {
THROW exception::UnknownInternalError("writeEnvironment() can only be called once per write session, in JSONStateWriter::writeEnvironment()");
}
// Environment properties
if (env_manager) {
auto& j_env = j["environment"];
const char *env_buffer = reinterpret_cast<const char *>(env_manager->getHostBuffer());
// for each environment property
for (auto &a : env_manager->getPropertiesMap()) {
if (a.second.elements == 1) {
if (a.second.type == std::type_index(typeid(float))) {
j_env[a.first] = *reinterpret_cast<const float*>(env_buffer + a.second.offset);
} else if (a.second.type == std::type_index(typeid(double))) {
j_env[a.first] = *reinterpret_cast<const double*>(env_buffer + a.second.offset);
} else if (a.second.type == std::type_index(typeid(int64_t))) {
j_env[a.first] = *reinterpret_cast<const int64_t*>(env_buffer + a.second.offset);
} else if (a.second.type == std::type_index(typeid(uint64_t))) {
j_env[a.first] = *reinterpret_cast<const uint64_t*>(env_buffer + a.second.offset);
} else if (a.second.type == std::type_index(typeid(int32_t))) {
j_env[a.first] = *reinterpret_cast<const int32_t*>(env_buffer + a.second.offset);
} else if (a.second.type == std::type_index(typeid(uint32_t))) {
j_env[a.first] = *reinterpret_cast<const uint32_t*>(env_buffer + a.second.offset);
} else if (a.second.type == std::type_index(typeid(int16_t))) {
j_env[a.first] = *reinterpret_cast<const int16_t*>(env_buffer + a.second.offset);
} else if (a.second.type == std::type_index(typeid(uint16_t))) {
j_env[a.first] = *reinterpret_cast<const uint16_t*>(env_buffer + a.second.offset);
} else if (a.second.type == std::type_index(typeid(int8_t))) {
j_env[a.first] = static_cast<int32_t>(*reinterpret_cast<const int8_t*>(env_buffer + a.second.offset)); // Char outputs weird if being used as an integer
} else if (a.second.type == std::type_index(typeid(uint8_t))) {
j_env[a.first] = static_cast<uint32_t>(*reinterpret_cast<const uint8_t*>(env_buffer + a.second.offset)); // Char outputs weird if being used as an integer
} else {
THROW exception::JSONError("Model contains environment property '%s' of unsupported type '%s', "
"in JSONStateWriter::writeEnvironment()\n", a.first.c_str(), a.second.type.name());
}
}
j_env[a.first] = {};
// Loop through elements, to construct array
for (unsigned int el = 0; el < a.second.elements; ++el) {
if (a.second.type == std::type_index(typeid(float))) {
j_env[a.first].emplace_back(*reinterpret_cast<const float*>(env_buffer + a.second.offset + (el * sizeof(float))));
} else if (a.second.type == std::type_index(typeid(double))) {
j_env[a.first].emplace_back(*reinterpret_cast<const double*>(env_buffer + a.second.offset + (el * sizeof(double))));
} else if (a.second.type == std::type_index(typeid(int64_t))) {
j_env[a.first].emplace_back(*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))) {
j_env[a.first].emplace_back(*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))) {
j_env[a.first].emplace_back(*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))) {
j_env[a.first].emplace_back(*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))) {
j_env[a.first].emplace_back(*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))) {
j_env[a.first].emplace_back(*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))) {
j_env[a.first].emplace_back(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))) {
j_env[a.first].emplace_back(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::JSONError("Model contains environment property '%s' of unsupported type '%s', "
"in JSONStateWriter::writeEnvironment()\n", a.first.c_str(), a.second.type.name());
}
}
}
if (!j_env.size()) {
j.erase("environment");
}
}
environment_written = true;
}
void JSONStateWriter::writeMacroEnvironment(const std::shared_ptr<const detail::CUDAMacroEnvironment>& macro_env, std::initializer_list<std::string> filter) {
if (!isWriting()) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeMacroEnvironment(), in JSONStateWriter::writeMacroEnvironment()");
} else if (macro_environment_written) {
THROW exception::UnknownInternalError("writeMacroEnvironment() can only be called once per write session, in JSONStateWriter::writeMacroEnvironment()");
}
// Macro Environment
if (macro_env) {
auto& j_menv = j["macro_environment"];
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 JSONStateWriter::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));
j_menv[name] = {};
for (size_t i = 0; i < element_ct; ++i) {
if (prop.type == std::type_index(typeid(float))) {
j_menv[name].emplace_back(*reinterpret_cast<const float*>(t_buffer + i * sizeof(float)));
} else if (prop.type == std::type_index(typeid(double))) {
j_menv[name].emplace_back(*reinterpret_cast<const double*>(t_buffer + i * sizeof(double)));
} else if (prop.type == std::type_index(typeid(int64_t))) {
j_menv[name].emplace_back(*reinterpret_cast<const int64_t*>(t_buffer + i * sizeof(int64_t)));
} else if (prop.type == std::type_index(typeid(uint64_t))) {
j_menv[name].emplace_back(*reinterpret_cast<const uint64_t*>(t_buffer + i * sizeof(uint64_t)));
} else if (prop.type == std::type_index(typeid(int32_t))) {
j_menv[name].emplace_back(*reinterpret_cast<const int32_t*>(t_buffer + i * sizeof(int32_t)));
} else if (prop.type == std::type_index(typeid(uint32_t))) {
j_menv[name].emplace_back(*reinterpret_cast<const uint32_t*>(t_buffer + i * sizeof(uint32_t)));
} else if (prop.type == std::type_index(typeid(int16_t))) {
j_menv[name].emplace_back(*reinterpret_cast<const int16_t*>(t_buffer + i * sizeof(int16_t)));
} else if (prop.type == std::type_index(typeid(uint16_t))) {
j_menv[name].emplace_back(*reinterpret_cast<const uint16_t*>(t_buffer + i * sizeof(uint16_t)));
} else if (prop.type == std::type_index(typeid(int8_t))) {
j_menv[name].emplace_back(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))) {
j_menv[name].emplace_back(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::JSONError("Model contains macro environment property '%s' of unsupported type '%s', "
"in JSONStateWriter::writeFullModelState()\n", name.c_str(), prop.type.name());
}
}
}
// Release temp buffer
free(t_buffer);
}
if (!j_menv.size()) {
j.erase("macro_environment");
}
}
macro_environment_written = true;
}
void JSONStateWriter::writeAgents(const util::StringPairUnorderedMap<std::shared_ptr<const AgentVector>>& agents_map) {
if (!isWriting()) {
THROW exception::UnknownInternalError("beginWrite() must be called before writeAgents(), in JSONStateWriter::writeAgents()");
} else if (agents_written) {
THROW exception::UnknownInternalError("writeAgents() can only be called once per write session, in JSONStateWriter::writeAgents()");
}
// AgentStates
auto& j_agt = j["agents"];
// Build a set of agent names
std::set<std::string> agent_names;
for (const auto& [key, _] : agents_map) {
agent_names.emplace(key.first);
}
// Process agents one at a time by iterating the map once per agent type
for (const auto &agt : agent_names) {
auto& j_agt_t = j_agt[agt];
for (const auto &agent : agents_map) {
const std::string &agent_name = agent.first.first;
if (agent_name != agt)
continue;
const std::string &state_name = agent.first.second;
const VariableMap &agent_vars = agent.second->getVariableMetaData();
// States
const unsigned int populationSize = agent.second->size();
// Only log states with agents
if (populationSize) {
j_agt_t[state_name] = {};
for (unsigned int i = 0; i < populationSize; ++i) {
AgentVector::CAgent instance = agent.second->at(i);
nlohmann::ordered_json t_agt;
// for each variable
for (auto var : agent_vars) {
// Set name
const std::string variable_name = var.first;
t_agt[variable_name] = {};
// Loop through elements, to construct array
for (unsigned int el = 0; el < var.second.elements; ++el) {
if (var.second.type == std::type_index(typeid(float))) {
t_agt[variable_name].emplace_back(instance.getVariable<float>(variable_name, el));
} else if (var.second.type == std::type_index(typeid(double))) {
t_agt[variable_name].emplace_back(instance.getVariable<double>(variable_name, el));
} else if (var.second.type == std::type_index(typeid(int64_t))) {
t_agt[variable_name].emplace_back(instance.getVariable<int64_t>(variable_name, el));
} else if (var.second.type == std::type_index(typeid(uint64_t))) {
t_agt[variable_name].emplace_back(instance.getVariable<uint64_t>(variable_name, el));
} else if (var.second.type == std::type_index(typeid(int32_t))) {
t_agt[variable_name].emplace_back(instance.getVariable<int32_t>(variable_name, el));
} else if (var.second.type == std::type_index(typeid(uint32_t))) {
t_agt[variable_name].emplace_back(instance.getVariable<uint32_t>(variable_name, el));
} else if (var.second.type == std::type_index(typeid(int16_t))) {
t_agt[variable_name].emplace_back(instance.getVariable<int16_t>(variable_name, el));
} else if (var.second.type == std::type_index(typeid(uint16_t))) {
t_agt[variable_name].emplace_back(instance.getVariable<uint16_t>(variable_name, el));
} else if (var.second.type == std::type_index(typeid(int8_t))) {
t_agt[variable_name].emplace_back(instance.getVariable<int8_t>(variable_name, el)); // Char outputs weird if being used as an integer
} else if (var.second.type == std::type_index(typeid(uint8_t))) {
t_agt[variable_name].emplace_back(instance.getVariable<uint8_t>(variable_name, el)); // Char outputs weird if being used as an integer
} else {
THROW exception::JSONError("Agent '%s' contains variable '%s' of unsupported type '%s', "
"in JSONStateWriter::writeAgents()\n", agent.first.first.c_str(), variable_name.c_str(), var.second.type.name());
}
}
}
j_agt_t[state_name].push_back(t_agt);
}
}
}
}
agents_written = true;
}
} // namespace io
} // namespace flamegpu