From cabc60c151b6c39f1d1dab7ac62a9fb61c5c5836 Mon Sep 17 00:00:00 2001 From: Robert Chisholm Date: Tue, 11 Jul 2023 14:27:14 +0100 Subject: [PATCH] Read env macro property from state file May wish to add support for setting up macro properties default value in model description? --- include/flamegpu/io/JSONStateReader.h | 9 ++- include/flamegpu/io/StateReader.h | 13 +++- include/flamegpu/io/StateReaderFactory.h | 11 ++- include/flamegpu/io/XMLStateReader.h | 5 ++ include/flamegpu/simulation/CUDASimulation.h | 4 ++ include/flamegpu/simulation/Simulation.h | 5 ++ src/flamegpu/io/JSONStateReader.cu | 73 +++++++++++++++++++- src/flamegpu/io/XMLStateReader.cu | 67 +++++++++++++++++- src/flamegpu/simulation/CUDASimulation.cu | 29 ++++++++ src/flamegpu/simulation/Simulation.cu | 7 +- 10 files changed, 209 insertions(+), 14 deletions(-) diff --git a/include/flamegpu/io/JSONStateReader.h b/include/flamegpu/io/JSONStateReader.h index 52890833e..1f188b7fe 100644 --- a/include/flamegpu/io/JSONStateReader.h +++ b/include/flamegpu/io/JSONStateReader.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "flamegpu/io/StateReader.h" #include "flamegpu/model/ModelDescription.h" @@ -24,7 +25,9 @@ class JSONStateReader : public StateReader { * Agent data will be read into 'model_state' * @param model_name Name from the model description hierarchy of the model to be loaded * @param env_desc Environment description for validating property data on load - * @param env_init Dictionary of loaded values map:<{name, index}, value> + * @param env_init Dictionary of loaded values map: + * @param macro_env_desc Macro environment description for validating property data on load + * @param macro_env_init Dictionary of loaded values map: * @param model_state Map of AgentVector to load the agent data into per agent, key should be agent name * @param input_file Filename of the input file (This will be used to determine which reader to return) * @param sim_instance Instance of the Simulation object (This is used for setting/getting config) @@ -32,7 +35,9 @@ class JSONStateReader : public StateReader { JSONStateReader( const std::string &model_name, const std::unordered_map &env_desc, - std::unordered_map&env_init, + std::unordered_map &env_init, + const std::unordered_map ¯o_env_desc, + std::unordered_map> ¯o_env_init, util::StringPairUnorderedMap> &model_state, const std::string &input_file, Simulation *sim_instance); diff --git a/include/flamegpu/io/StateReader.h b/include/flamegpu/io/StateReader.h index 14e1d1c76..d7127dfe3 100644 --- a/include/flamegpu/io/StateReader.h +++ b/include/flamegpu/io/StateReader.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "flamegpu/util/StringPair.h" #include "flamegpu/model/EnvironmentData.h" @@ -29,7 +30,9 @@ class StateReader { * Agent data will be read into 'model_state' * @param _model_name Name from the model description hierarchy of the model to be loaded * @param _env_desc Environment description for validating property data on load - * @param _env_init Dictionary of loaded values map:<{name, index}, value> + * @param _env_init Dictionary of loaded values map: + * @param _macro_env_desc Macro environment description for validating property data on load + * @param _macro_env_init Dictionary of loaded values map: * @param _model_state Map of AgentVector to load the agent data into per agent, key should be agent name * @param input Filename of the input file (This will be used to determine which reader to return) * @param _sim_instance Instance of the simulation (for configuration data IO) @@ -38,6 +41,8 @@ class StateReader { const std::string& _model_name, const std::unordered_map& _env_desc, std::unordered_map& _env_init, + const std::unordered_map& _macro_env_desc, + std::unordered_map>& _macro_env_init, util::StringPairUnorderedMap>& _model_state, const std::string& input, Simulation* _sim_instance) @@ -46,6 +51,8 @@ class StateReader { , model_name(_model_name) , env_desc(_env_desc) , env_init(_env_init) + , macro_env_desc(_macro_env_desc) + , macro_env_init(_macro_env_init) , sim_instance(_sim_instance) {} /** * Virtual destructor for correct inheritance behaviour @@ -63,11 +70,13 @@ class StateReader { virtual int parse() = 0; protected: - util::StringPairUnorderedMap>& model_state; + util::StringPairUnorderedMap> &model_state; std::string inputFile; const std::string model_name; const std::unordered_map &env_desc; std::unordered_map& env_init; + const std::unordered_map ¯o_env_desc; + std::unordered_map>& macro_env_init; Simulation *sim_instance; }; } // namespace io diff --git a/include/flamegpu/io/StateReaderFactory.h b/include/flamegpu/io/StateReaderFactory.h index 208cbf8a5..050870257 100644 --- a/include/flamegpu/io/StateReaderFactory.h +++ b/include/flamegpu/io/StateReaderFactory.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "flamegpu/io/StateReader.h" #include "flamegpu/io/XMLStateReader.h" @@ -29,7 +30,9 @@ class StateReaderFactory { * Agent data will be read into 'model_state' * @param model_name Name from the model description hierarchy of the model to be loaded * @param env_desc Environment description for validating property data on load - * @param env_init Dictionary of loaded values map:<{name, index}, value> + * @param env_init Dictionary of loaded values map: + * @param macro_env_desc Macro environment description for validating property data on load + * @param macro_env_init Dictionary of loaded values map: * @param model_state Map of AgentVector to load the agent data into per agent, key should be agent name * @param input Filename of the input file (This will be used to determine which reader to return) * @param sim_instance Instance of the Simulation object (This is used for setting/getting config) @@ -39,15 +42,17 @@ class StateReaderFactory { const std::string& model_name, const std::unordered_map& env_desc, std::unordered_map& env_init, + const std::unordered_map& macro_env_desc, + std::unordered_map>& macro_env_init, util::StringPairUnorderedMap>& model_state, const std::string& input, Simulation* sim_instance) { const std::string extension = std::filesystem::path(input).extension().string(); if (extension == ".xml") { - return new XMLStateReader(model_name, env_desc, env_init, model_state, input, sim_instance); + return new XMLStateReader(model_name, env_desc, env_init, macro_env_desc, macro_env_init, model_state, input, sim_instance); } else if (extension == ".json") { - return new JSONStateReader(model_name, env_desc, env_init, model_state, input, sim_instance); + return new JSONStateReader(model_name, env_desc, env_init, macro_env_desc, macro_env_init, model_state, input, sim_instance); } THROW exception::UnsupportedFileType("File '%s' is not a type which can be read " "by StateReaderFactory::createReader().", diff --git a/include/flamegpu/io/XMLStateReader.h b/include/flamegpu/io/XMLStateReader.h index af1f883a6..af6deaffc 100644 --- a/include/flamegpu/io/XMLStateReader.h +++ b/include/flamegpu/io/XMLStateReader.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "flamegpu/io/StateReader.h" #include "flamegpu/model/ModelDescription.h" @@ -24,6 +25,8 @@ class XMLStateReader : public StateReader { * @param model_name Name from the model description hierarchy of the model to be loaded * @param env_desc Environment description for validating property data on load * @param env_init Dictionary of loaded values map:<{name, index}, value> + * @param macro_env_desc Macro environment description for validating property data on load + * @param macro_env_init Dictionary of loaded values map: * @param model_state Map of AgentVector to load the agent data into per agent, key should be agent name * @param input_file Filename of the input file (This will be used to determine which reader to return) * @param sim_instance Instance of the Simulation object (This is used for setting/getting config) @@ -32,6 +35,8 @@ class XMLStateReader : public StateReader { const std::string &model_name, const std::unordered_map &env_desc, std::unordered_map &env_init, + const std::unordered_map ¯o_env_desc, + std::unordered_map> ¯o_env_init, util::StringPairUnorderedMap> &model_state, const std::string &input_file, Simulation *sim_instance); diff --git a/include/flamegpu/simulation/CUDASimulation.h b/include/flamegpu/simulation/CUDASimulation.h index 2c4f57e8f..c5e7b9954 100644 --- a/include/flamegpu/simulation/CUDASimulation.h +++ b/include/flamegpu/simulation/CUDASimulation.h @@ -569,6 +569,10 @@ class CUDASimulation : public Simulation { * Common method for adding this Model's data to env manager */ void initEnvironmentMgr(); + /** + * Common method for initialising macro environment properties from macro_env_init + */ + void initMacroEnvironment(); /** * Flag indicating that the model has been initialsed */ diff --git a/include/flamegpu/simulation/Simulation.h b/include/flamegpu/simulation/Simulation.h index de6a02873..65a7c56d8 100644 --- a/include/flamegpu/simulation/Simulation.h +++ b/include/flamegpu/simulation/Simulation.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "flamegpu/defines.h" #include "flamegpu/simulation/detail/AgentInterface.h" @@ -184,6 +185,10 @@ class Simulation { * Initial environment items if they have been loaded from file, prior to device selection */ std::unordered_map env_init; + /** + * Initial macro environment items if they have been loaded from file, prior to device selection + */ + std::unordered_map> macro_env_init; /** * the width of the widest layer in the concrete version of the model (calculated once) */ diff --git a/src/flamegpu/io/JSONStateReader.cu b/src/flamegpu/io/JSONStateReader.cu index c54253f50..d11bb3840 100644 --- a/src/flamegpu/io/JSONStateReader.cu +++ b/src/flamegpu/io/JSONStateReader.cu @@ -8,6 +8,7 @@ #include #include #include +#include #include "flamegpu/exception/FLAMEGPUException.h" #include "flamegpu/simulation/AgentVector.h" @@ -22,10 +23,12 @@ JSONStateReader::JSONStateReader( const std::string &model_name, const std::unordered_map &env_desc, std::unordered_map &env_init, + const std::unordered_map& macro_env_desc, + std::unordered_map>& macro_env_init, util::StringPairUnorderedMap> &model_state, const std::string &input, Simulation *sim_instance) - : StateReader(model_name, env_desc, env_init, model_state, input, sim_instance) {} + : StateReader(model_name, env_desc, env_init, macro_env_desc, macro_env_init, model_state, input, sim_instance) {} /** * This is the main sax style parser for the json state * It stores it's current position within the hierarchy with mode, lastKey and current_variable_array_index @@ -37,6 +40,8 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandler env_desc; std::unordered_map &env_init; + const std::unordered_map macro_env_desc; + std::unordered_map> ¯o_env_init; /** * Used for setting agent values */ @@ -58,10 +63,14 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandler &_env_desc, std::unordered_map &_env_init, + const std::unordered_map & _macro_env_desc, + std::unordered_map> & _macro_env_init, util::StringPairUnorderedMap> &_model_state) : filename(_filename) , env_desc(_env_desc) , env_init(_env_init) + , macro_env_desc(_macro_env_desc) + , macro_env_init(_macro_env_init) , model_state(_model_state) { } template bool processValue(const T val) { @@ -113,6 +122,50 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandlersecond.elements.begin(), it->second.elements.end(), 1, std::multiplies()); + if (current_variable_array_index == 0) { + // New property, create buffer with default value and add to map + if (!macro_env_init.emplace(lastKey, std::vector(macro_prop_elements * it->second.type_size)).second) { + THROW exception::RapidJSONError("Input file contains environment property '%s' multiple times, " + "in JSONStateReader::parse()\n", lastKey.c_str()); + } + } else if (current_variable_array_index >= macro_prop_elements) { + THROW exception::RapidJSONError("Input file contains environment property '%s' with %u elements expected %u," + "in JSONStateReader::parse()\n", lastKey.c_str(), current_variable_array_index, macro_prop_elements); + } + // Retrieve the linked any and replace the value + auto mei = macro_env_init.at(lastKey); + const std::type_index val_type = it->second.type; + if (val_type == std::type_index(typeid(float))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(double))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(int64_t))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(uint64_t))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(int32_t))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(uint32_t))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(int16_t))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(uint16_t))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(int8_t))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else if (val_type == std::type_index(typeid(uint8_t))) { + static_cast(static_cast(mei.data()))[current_variable_array_index++] = static_cast(val); + } else { + THROW exception::RapidJSONError("Model contains macro environment property '%s' of unsupported type '%s', " + "in JSONStateReader::parse()\n", lastKey.c_str(), val_type.name()); + } } else if (mode.top() == AgentInstance) { const std::shared_ptr &pop = model_state.at({current_agent, current_state}); AgentVector::Agent instance = pop->back(); @@ -192,6 +245,8 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandler()); + if (current_variable_array_index != macro_prop_elements) { + THROW exception::RapidJSONError("Input file contains environment macro property '%s' with %u elements expected %u," + "in JSONStateReader::parse()\n", lastKey.c_str(), current_variable_array_index, macro_prop_elements); + } } current_variable_array_index = 0; } else { @@ -268,7 +333,7 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandler, JSONStateReader_impl> { - enum Mode{ Nop, Root, Config, Stats, SimCfg, CUDACfg, Environment, Agents, Agent, State, AgentInstance, VariableArray }; + enum Mode{ Nop, Root, Config, Stats, SimCfg, CUDACfg, Environment, MacroEnvironment, Agents, Agent, State, AgentInstance, VariableArray }; std::stack mode; std::string lastKey; unsigned int currentIndex = 0; @@ -374,6 +439,8 @@ class JSONStateReader_agentsize_counter : public rapidjson::BaseReaderHandler(in)), std::istreambuf_iterator()); rapidjson::StringStream filess(filestring.c_str()); rapidjson::Reader reader; diff --git a/src/flamegpu/io/XMLStateReader.cu b/src/flamegpu/io/XMLStateReader.cu index 5aa3b0b66..affaf6794 100644 --- a/src/flamegpu/io/XMLStateReader.cu +++ b/src/flamegpu/io/XMLStateReader.cu @@ -11,6 +11,7 @@ #include "flamegpu/io/XMLStateReader.h" #include #include +#include #include #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" @@ -67,11 +68,13 @@ namespace io { XMLStateReader::XMLStateReader( const std::string &model_name, const std::unordered_map &env_desc, - std::unordered_map&env_init, + std::unordered_map &env_init, + const std::unordered_map& macro_env_desc, + std::unordered_map> ¯o_env_init, util::StringPairUnorderedMap> &model_state, const std::string &input, Simulation *sim_instance) - : StateReader(model_name, env_desc, env_init, model_state, input, sim_instance) {} + : StateReader(model_name, env_desc, env_init, macro_env_desc, macro_env_init, model_state, input, sim_instance) {} std::string XMLStateReader::getInitialState(const std::string &agent_name) const { for (const auto &i : model_state) { @@ -250,6 +253,66 @@ int XMLStateReader::parse() { 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) { + 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 = macro_env_desc.find(std::string(key)); + if (it == macro_env_desc.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 el = 0; + while (getline(ss, token, ',')) { + if (el == 0) { + if (!macro_env_init.emplace(std::string(key), std::vector(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' with %u elements expected %u," + "in XMLStateReader::parse()\n", key, el, elements); + } + auto mei = macro_env_init.at(key); + if (val_type == std::type_index(typeid(float))) { + static_cast(static_cast(mei.data()))[el++] = stof(token); + } else if (val_type == std::type_index(typeid(double))) { + static_cast(static_cast(mei.data()))[el++] = stod(token); + } else if (val_type == std::type_index(typeid(int64_t))) { + static_cast(static_cast(mei.data()))[el++] = stoll(token); + } else if (val_type == std::type_index(typeid(uint64_t))) { + static_cast(static_cast(mei.data()))[el++] = stoull(token); + } else if (val_type == std::type_index(typeid(int32_t))) { + static_cast(static_cast(mei.data()))[el++] = static_cast(stoll(token)); + } else if (val_type == std::type_index(typeid(uint32_t))) { + static_cast(static_cast(mei.data()))[el++] = static_cast(stoull(token)); + } else if (val_type == std::type_index(typeid(int16_t))) { + static_cast(static_cast(mei.data()))[el++] = static_cast(stoll(token)); + } else if (val_type == std::type_index(typeid(uint16_t))) { + static_cast(static_cast(mei.data()))[el++] = static_cast(stoull(token)); + } else if (val_type == std::type_index(typeid(int8_t))) { + static_cast(static_cast(mei.data()))[el++] = static_cast(stoll(token)); + } else if (val_type == std::type_index(typeid(uint8_t))) { + static_cast(static_cast(mei.data()))[el++] = static_cast(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 && sim_instance->getSimulationConfig().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 (sim_instance->getSimulationConfig().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 cts; for (pElement = pRoot->FirstChildElement("xagent"); pElement != nullptr; pElement = pElement->NextSiblingElement("xagent")) { diff --git a/src/flamegpu/simulation/CUDASimulation.cu b/src/flamegpu/simulation/CUDASimulation.cu index f3da12df4..3fd40131a 100644 --- a/src/flamegpu/simulation/CUDASimulation.cu +++ b/src/flamegpu/simulation/CUDASimulation.cu @@ -4,6 +4,7 @@ #include #include #include +#include #include "flamegpu/detail/curand.cuh" #include "flamegpu/model/AgentFunctionData.cuh" @@ -1595,6 +1596,7 @@ void CUDASimulation::initialiseSingletons() { } // Populate the environment properties initEnvironmentMgr(); + initMacroEnvironment(); // Ensure there are enough streams to execute the layer. // Taking into consideration if in-layer concurrency is disabled or not. @@ -1796,6 +1798,33 @@ void CUDASimulation::initEnvironmentMgr() { // Clear init env_init.clear(); } +void CUDASimulation::initMacroEnvironment() { + const auto &mp_map = macro_env->getPropertiesMap(); + if (mp_map.size() && mp_map.begin()->second.d_ptr) { + THROW exception::UnknownInternalError("CUDASimulation::initMacroEnvironment() called before macro environment initialised."); + } + const cudaStream_t stream = getStream(0); + // Set any properties loaded from file during arg parse stage + for (const auto &[name, buff] : macro_env_init) { + const auto it = mp_map.find(name); + if (it == mp_map.end()) { + THROW exception::InvalidEnvProperty("Macro environment init data contains unexpected property '%s', " + "in CUDASimulation::initMacroEnvironment()\n", name.c_str()); + } + // @todo Not tracking type to validate that here? + const unsigned int elements = std::accumulate(it->second.elements.begin(), it->second.elements.end(), 1, std::multiplies()); + if (elements * it->second.type_size != buff.size()) { + THROW exception::InvalidEnvProperty("Macro environment init data contains property '%s' with buffer length mismatch '%u' != '%u', " + "this should have been caught during file parsing, " + "in CUDASimulation::initMacroEnvironment()\n", name.c_str(), static_cast(buff.size()), elements * it->second.type_size); + } else { + gpuErrchk(cudaMemcpyAsync(it->second.d_ptr, buff.data(), buff.size() * sizeof(char), cudaMemcpyHostToDevice, stream)); + } + } + gpuErrchk(cudaStreamSynchronize(stream)); + // Clear init + macro_env_init.clear(); +} void CUDASimulation::resetLog() { // Track previous device id, so we can avoid costly request for device properties if not required static int previous_device_id = -1; diff --git a/src/flamegpu/simulation/Simulation.cu b/src/flamegpu/simulation/Simulation.cu index 13b8ceb8c..12aaceea0 100644 --- a/src/flamegpu/simulation/Simulation.cu +++ b/src/flamegpu/simulation/Simulation.cu @@ -61,7 +61,8 @@ void Simulation::applyConfig() { } env_init.clear(); - io::StateReader *read__ = io::StateReaderFactory::createReader(model->name, model->environment->properties, env_init, pops, config.input_file.c_str(), this); + macro_env_init.clear(); + io::StateReader *read__ = io::StateReaderFactory::createReader(model->name, model->environment->properties, env_init, model->environment->macro_properties, macro_env_init, pops, config.input_file.c_str(), this); if (read__) { read__->parse(); for (auto &agent : pops) { @@ -186,8 +187,10 @@ int Simulation::checkArgs(int argc, const char** argv) { } } env_init.clear(); + macro_env_init.clear(); const auto &env_desc = model->environment->properties; // For some reason this method returns a copy, not a reference - io::StateReader *read__ = io::StateReaderFactory::createReader(model->name, env_desc, env_init, pops, config.input_file.c_str(), this); + const auto ¯o_env_desc = model->environment->macro_properties; // For some reason this method returns a copy, not a reference + io::StateReader *read__ = io::StateReaderFactory::createReader(model->name, env_desc, env_init, macro_env_desc, macro_env_init, pops, config.input_file.c_str(), this); if (read__) { try { read__->parse();