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..e50fa028 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -103,7 +103,7 @@ using Variables = std::map; Variables readVariables(const nlohmann::json& json) { Variables variables; - if (json.find("networks") == json.end() || json.find("manifest") == json.end()) { + if (json.find("manifest") == json.end()) { return variables; } @@ -482,8 +482,12 @@ 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 + const auto rawJson = nlohmann::json::parse(content); + const auto vars = replaceVariables(readVariables(rawJson)); + _json = expandVariables(rawJson, vars); + } template void parseMandatory(const Iterator& it, @@ -560,9 +564,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 +581,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 +604,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..1172131c 100644 --- a/tests/data/config/simulation_config.json +++ b/tests/data/config/simulation_config.json @@ -1,5 +1,8 @@ { - "run": { + "manifest": { + "$OUTPUT_DIR": "." + }, + "run": { "tstop": 1000, "dt": 0.025, "random_seed": 201506, @@ -8,7 +11,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..74e7248d 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,17 +312,36 @@ 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("manifest_network") { + auto contents = R"({ + "manifest": { + "$CIRCUIT_DIR": "./circuit" + }, + "network": "$CIRCUIT_DIR/circuit_config.json", + "run": { + "dt": 0.05, + "tstop": 1000 + } + })"; + namespace fs = ghc::filesystem; + const auto basePath = fs::absolute(fs::path("./").parent_path()); + const auto config = SimulationConfig(contents, basePath); + const auto network = fs::absolute(basePath / "circuit" / fs::path("circuit_config.json")); + CHECK(config.getNetwork() == network.lexically_normal()); + } SECTION("Exception") { { // No run section auto contents = R"({})";