From 6606b49225f35949ea8ae4a1dce2cf6e6cf5f59b Mon Sep 17 00:00:00 2001 From: Weina Ji Date: Thu, 24 Mar 2022 17:32:47 +0100 Subject: [PATCH] New parameters in SimulationConfig - Parse floating point with type "double" to align with parser "nlohmann::json" - Parse variables in "manifest" section and apply them to the configuration file - Parse "network" parameter for the path of circuit_config file --- include/bbp/sonata/config.h | 14 +++++--- python/bindings.cpp | 1 + python/tests/test.py | 7 ++-- src/config.cpp | 42 +++++++++++++++++------- tests/data/config/simulation_config.json | 9 +++-- tests/test_config.cpp | 13 +++++--- 6 files changed, 61 insertions(+), 25 deletions(-) diff --git a/include/bbp/sonata/config.h b/include/bbp/sonata/config.h index 3edd6968..c223af9b 100644 --- a/include/bbp/sonata/config.h +++ b/include/bbp/sonata/config.h @@ -186,9 +186,9 @@ class SONATA_API SimulationConfig */ struct Run { /// Biological simulation end time in milliseconds - float tstop{}; + double tstop{}; /// Integration step duration in milliseconds - float dt{}; + double dt{}; }; /** * Parameters to override simulator output for spike reports @@ -208,11 +208,11 @@ class SONATA_API SimulationConfig /// Report type. Possible values: "compartment", "summation", "synapse" std::string type; /// Interval between reporting steps in milliseconds - float dt{}; + double dt{}; /// Time to step reporting in milliseconds - float startTime{}; + double startTime{}; /// Time to stop reporting in milliseconds - float endTime{}; + double endTime{}; /// Report filename. Default is "_SONATA.h5" std::string fileName; }; @@ -265,6 +265,8 @@ class SONATA_API SimulationConfig */ const Report& getReport(const std::string& name) const; + const std::string& getNetwork() const noexcept; + private: // JSON string const std::string _jsonContent; @@ -277,6 +279,8 @@ class SONATA_API SimulationConfig Output _output; // List of reports std::unordered_map _reports; + // Path of circuit config file for the simulation + std::string _network; class Parser; friend class Parser; diff --git a/python/bindings.cpp b/python/bindings.cpp index ed07225f..fcba57c6 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -561,6 +561,7 @@ PYBIND11_MODULE(_libsonata, m) { .def_property_readonly("json", &SimulationConfig::getJSON) .def_property_readonly("run", &SimulationConfig::getRun) .def_property_readonly("output", &SimulationConfig::getOutput) + .def_property_readonly("network", &SimulationConfig::getNetwork) .def("report", &SimulationConfig::getReport, "name"_a); bindPopulationClass( diff --git a/python/tests/test.py b/python/tests/test.py index d5662a5a..4b178d14 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -585,7 +585,7 @@ def test_basic(self): self.assertEqual(self.config.base_path, os.path.abspath(os.path.join(PATH, 'config'))) self.assertEqual(self.config.run.tstop, 1000) - self.assertTrue(abs(self.config.run.dt - 0.025) < 0.01) + self.assertEqual(self.config.run.dt, 0.025) self.assertEqual(self.config.output.output_dir, os.path.abspath(os.path.join(PATH, 'config/output'))) @@ -593,12 +593,15 @@ def test_basic(self): self.assertEqual(self.config.report('soma').cells, 'Mosaic') self.assertEqual(self.config.report('soma').type, 'compartment') - self.assertTrue(abs(self.config.report('compartment').dt - 0.1) < 0.01) + self.assertEqual(self.config.report('compartment').dt, 0.1) self.assertEqual(self.config.report('axonal_comp_centers').start_time, 0) self.assertEqual(self.config.report('axonal_comp_centers').file_name, os.path.abspath(os.path.join(PATH, 'config/axon_centers.h5'))) self.assertEqual(self.config.report('cell_imembrane').end_time, 500) + self.assertEqual(self.config.network, + os.path.abspath(os.path.join(PATH, 'config/circuit_config.json'))) + def test_json(self): temp_config = json.loads(self.config.json) self.assertEqual(temp_config['run']['tstop'], 1000) diff --git a/src/config.cpp b/src/config.cpp index e950e013..5ac25a91 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -100,15 +100,9 @@ nlohmann::json expandVariables(const nlohmann::json& json, using Variables = std::map; -Variables readVariables(const nlohmann::json& json) { +// parse variables named like $[a-zA-Z0-9_]* like in manifest sections +Variables parseManifest(const nlohmann::json& manifest) { Variables variables; - - if (json.find("networks") == json.end() || json.find("manifest") == json.end()) { - return variables; - } - - const auto manifest = json["manifest"]; - const std::regex regexVariable(R"(\$[a-zA-Z0-9_]*)"); for (auto it = manifest.begin(); it != manifest.end(); ++it) { @@ -124,6 +118,16 @@ Variables readVariables(const nlohmann::json& json) { return variables; } +// read variables in manifest section for circuit_config +Variables readVariables(const nlohmann::json& json) { + Variables variables; + + if (json.find("networks") == json.end() || json.find("manifest") == json.end()) { + return variables; + } + return parseManifest(json["manifest"]); +} + std::string toAbsolute(const fs::path& base, const fs::path& path) { const auto absolute = path.is_absolute() ? path : fs::absolute(base / path); return absolute.lexically_normal().string(); @@ -482,8 +486,14 @@ class SimulationConfig::Parser { public: Parser(const std::string& content, const std::string& basePath) - : _basePath(fs::absolute(fs::path(basePath)).lexically_normal()) - , _json(nlohmann::json::parse(content)) {} + : _basePath(fs::absolute(fs::path(basePath)).lexically_normal()) { + // Parse manifest section and expand JSON string + _json = nlohmann::json::parse(content); + if (_json.contains("manifest")) { + const auto vars = replaceVariables(parseManifest(_json["manifest"])); + _json = expandVariables(_json, vars); + } + } template void parseMandatory(const Iterator& it, @@ -560,9 +570,14 @@ class SimulationConfig::Parser return result; } + const std::string parseNetwork() const { + auto val = _json.find("network") != _json.end() ? _json["network"] : "circuit_config.json"; + return toAbsolute(_basePath, val); + } + private: const fs::path _basePath; - const nlohmann::json _json; + nlohmann::json _json; }; SimulationConfig::SimulationConfig(const std::string& content, const std::string& basePath) @@ -572,6 +587,7 @@ SimulationConfig::SimulationConfig(const std::string& content, const std::string _run = parser.parseRun(); _output = parser.parseOutput(); _reports = parser.parseReports(); + _network = parser.parseNetwork(); } SimulationConfig SimulationConfig::fromFile(const std::string& path) { @@ -594,6 +610,10 @@ const SimulationConfig::Output& SimulationConfig::getOutput() const noexcept { return _output; } +const std::string& SimulationConfig::getNetwork() const noexcept { + return _network; +} + const SimulationConfig::Report& SimulationConfig::getReport(const std::string& name) const { const auto it = _reports.find(name); if (it == _reports.end()) diff --git a/tests/data/config/simulation_config.json b/tests/data/config/simulation_config.json index e32efc5e..0be4b6e2 100644 --- a/tests/data/config/simulation_config.json +++ b/tests/data/config/simulation_config.json @@ -1,5 +1,10 @@ { - "run": { + "manifest": { + "$OUTPUT_DIR": ".", + "$INPUT_DIR": "." + }, + "network": "$INPUT_DIR/circuit_config.json", + "run": { "tstop": 1000, "dt": 0.025, "random_seed": 201506, @@ -8,7 +13,7 @@ "forward_skip": 500 }, "output": { - "output_dir": "output", + "output_dir": "$OUTPUT_DIR/output", "spikes_file": "out.h5" }, "reports": { diff --git a/tests/test_config.cpp b/tests/test_config.cpp index b032066c..1d5e199d 100644 --- a/tests/test_config.cpp +++ b/tests/test_config.cpp @@ -296,8 +296,8 @@ TEST_CASE("SimulationConfig") { const auto config = SimulationConfig::fromFile("./data/config/simulation_config.json"); CHECK_NOTHROW(config.getRun()); using Catch::Matchers::WithinULP; - REQUIRE_THAT(config.getRun().tstop, WithinULP(1000.f, 1)); - REQUIRE_THAT(config.getRun().dt, WithinULP(0.025f, 1)); + CHECK(config.getRun().tstop == 1000); + CHECK(config.getRun().dt == 0.025); namespace fs = ghc::filesystem; const auto basePath = fs::absolute( @@ -312,15 +312,18 @@ TEST_CASE("SimulationConfig") { CHECK(config.getReport("soma").cells == "Mosaic"); CHECK(config.getReport("soma").type == "compartment"); - CHECK(config.getReport("compartment").dt == 0.1f); - CHECK(config.getReport("axonal_comp_centers").startTime == 0.f); + CHECK(config.getReport("compartment").dt == 0.1); + CHECK(config.getReport("axonal_comp_centers").startTime == 0.); const auto axonalFilePath = fs::absolute(basePath / fs::path("axon_centers.h5")); CHECK(config.getReport("axonal_comp_centers").fileName == axonalFilePath.lexically_normal()); - CHECK(config.getReport("cell_imembrane").endTime == 500.f); + CHECK(config.getReport("cell_imembrane").endTime == 500.); CHECK_NOTHROW(nlohmann::json::parse(config.getJSON())); CHECK(config.getBasePath() == basePath.lexically_normal()); + + const auto network = fs::absolute(basePath / fs::path("circuit_config.json")); + CHECK(config.getNetwork() == network.lexically_normal()); } SECTION("Exception") {