diff --git a/include/bbp/sonata/config.h b/include/bbp/sonata/config.h index aa2be41..dfaeae3 100644 --- a/include/bbp/sonata/config.h +++ b/include/bbp/sonata/config.h @@ -457,8 +457,10 @@ class SONATA_API SimulationConfig double delay{}; /// Time duration for how long input is activated (ms) double duration{}; - /// Node set which is affected by input - std::string nodeSet; + /// Node set which is affected by input. Not allowed in case of CompartmentSet + nonstd::optional nodeSet{nonstd::nullopt}; + /// CompartmentSet which is affected by the input. It has priority over nodeSet + nonstd::optional compartmentSet{nonstd::nullopt}; }; struct InputLinear: public InputBase { @@ -796,10 +798,15 @@ class SONATA_API SimulationConfig const std::string& getCompartmentSetsFile() const noexcept; /** - * Returns the name of node set to be instantiated for the simulation, default = None + * Returns the name of the node set to be instantiated for the simulation, default = None */ const nonstd::optional& getNodeSet() const noexcept; + /** + * Returns the name of the compartment set to be instantiated for the simulation, default = None + */ + const nonstd::optional& getCompartmentSet() const noexcept; + /** * Returns the metadata section */ diff --git a/python/bindings.cpp b/python/bindings.cpp index 04fdeeb..a2efd58 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -967,7 +967,10 @@ PYBIND11_MODULE(_libsonata, m) { DOC_SIMULATIONCONFIG(InputBase, duration)) .def_readonly("node_set", &SimulationConfig::InputBase::nodeSet, - DOC_SIMULATIONCONFIG(InputBase, nodeSet)); + DOC_SIMULATIONCONFIG(InputBase, nodeSet)) + .def_readonly("compartment_set", + &SimulationConfig::InputBase::compartmentSet, + DOC_SIMULATIONCONFIG(InputBase, compartmentSet)); py::class_(simConf, "Linear") .def_readonly("amp_start", diff --git a/python/generated/docstrings.h b/python/generated/docstrings.h index 82d6029..ca20621 100644 --- a/python/generated/docstrings.h +++ b/python/generated/docstrings.h @@ -893,6 +893,8 @@ static const char *__doc_bbp_sonata_SimulationConfig_InputBase_module = R"doc(Ty static const char *__doc_bbp_sonata_SimulationConfig_InputBase_nodeSet = R"doc(Node set which is affected by input)doc"; +static const char *__doc_bbp_sonata_SimulationConfig_InputBase_compartmentSet = R"doc(Compartment set which is affected by input)doc"; + static const char *__doc_bbp_sonata_SimulationConfig_InputHyperpolarizing = R"doc()doc"; static const char *__doc_bbp_sonata_SimulationConfig_InputHyperpolarizing_representsPhysicalElectrode = R"doc(Whether this input represents a physical electrode. Default is false)doc"; diff --git a/python/tests/test_config.py b/python/tests/test_config.py index 119b031..a77e505 100644 --- a/python/tests/test_config.py +++ b/python/tests/test_config.py @@ -486,6 +486,7 @@ def test_basic(self): "ex_extracellular_stimulation", "ex_hyperpolarizing", "ex_linear", + "ex_linear_compartment_set", "ex_noise_mean", "ex_noise_meanpercent", "ex_OU", @@ -506,9 +507,13 @@ def test_basic(self): self.assertEqual(self.config.input('ex_linear').delay, 0) self.assertEqual(self.config.input('ex_linear').duration, 15) self.assertEqual(self.config.input('ex_linear').node_set, "Column") + self.assertEqual(self.config.input('ex_linear').compartment_set, None) self.assertEqual(self.config.input('ex_linear').amp_start, 0.15) self.assertEqual(self.config.input('ex_linear').amp_end, 0.15) + self.assertEqual(self.config.input('ex_linear_compartment_set').node_set, None) + self.assertEqual(self.config.input('ex_linear_compartment_set').compartment_set, "cs1") + self.assertEqual(self.config.input('ex_rel_linear').input_type.name, 'current_clamp') self.assertEqual(self.config.input('ex_rel_linear').module.name, 'relative_linear') self.assertEqual(self.config.input('ex_rel_linear').delay, 0) @@ -723,3 +728,43 @@ def test_simulation_config_failures(self): """ SimulationConfig(contents, "./") self.assertEqual(e.exception.args, ('Replay spike_file should be a SONATA h5 file', )) + + with self.assertRaises(SonataError) as e: + contents = """ + { + "run": { "random_seed": 12345, "dt": 0.05, "tstop": 1000 }, + "inputs" : { + "ex_linear": { + "input_type": "current_clamp", + "module": "linear", + "amp_start": 0.15, + "delay": 0, + "duration": 15, + "node_set":"Column", + "compartment_set":"cs1" + } + } + } + """ + SimulationConfig(contents, "./") + self.assertEqual(e.exception.args, ('`node_set` is not allowed if `compartment_set` is set in input ex_linear', )) + + with self.assertRaises(SonataError) as e: + contents = """ + { + "run": { "random_seed": 12345, "dt": 0.05, "tstop": 1000 }, + "inputs" : { + "ex_linear": { + "input_type": "current_clamp", + "module": "linear", + "amp_start": 0.15, + "delay": 0, + "duration": 15 + } + } + } + """ + SimulationConfig(contents, "./") + self.assertEqual(e.exception.args, ('One of `node_set` or `compartment_set` need to have a value in input ex_linear', )) + + diff --git a/src/config.cpp b/src/config.cpp index a604460..c4aee13 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -403,7 +403,17 @@ SimulationConfig::Input parseInputModule(const nlohmann::json& valueIt, parseMandatory(valueIt, "input_type", debugStr, input.inputType); parseMandatory(valueIt, "delay", debugStr, input.delay); parseMandatory(valueIt, "duration", debugStr, input.duration); - parseMandatory(valueIt, "node_set", debugStr, input.nodeSet); + + parseOptional(valueIt, "node_set", input.nodeSet); + parseOptional(valueIt, "compartment_set", input.compartmentSet); + + if (input.nodeSet.has_value() && input.compartmentSet.has_value()) { + throw SonataError("`node_set` is not allowed if `compartment_set` is set in " + + debugStr); + } else if (!input.nodeSet.has_value() && !input.compartmentSet.has_value()) { + throw SonataError("One of `node_set` or `compartment_set` need to have a value in " + + debugStr); + } }; switch (module) { @@ -466,16 +476,8 @@ SimulationConfig::Input parseInputModule(const nlohmann::json& valueIt, case Module::noise: { SimulationConfig::InputNoise ret; parseCommon(ret); - const auto mean = valueIt.find("mean"); - const auto mean_percent = valueIt.find("mean_percent"); - - if (mean != valueIt.end()) { - parseOptional(valueIt, "mean", ret.mean); - } - - if (mean_percent != valueIt.end()) { - parseOptional(valueIt, "mean_percent", ret.meanPercent); - } + parseOptional(valueIt, "mean", ret.mean); + parseOptional(valueIt, "mean_percent", ret.meanPercent); if (ret.mean.has_value() && ret.meanPercent.has_value()) { throw SonataError("Both `mean` or `mean_percent` have values in " + debugStr); diff --git a/tests/data/config/simulation_config.json b/tests/data/config/simulation_config.json index e570d54..e54fe51 100644 --- a/tests/data/config/simulation_config.json +++ b/tests/data/config/simulation_config.json @@ -73,6 +73,14 @@ "duration": 15, "node_set":"Column" }, + "ex_linear_compartment_set": { + "input_type": "current_clamp", + "module": "linear", + "amp_start": 0.15, + "delay": 0, + "duration": 15, + "compartment_set":"cs1" + }, "ex_rel_linear": { "input_type": "current_clamp", "module": "relative_linear", diff --git a/tests/test_config.cpp b/tests/test_config.cpp index 205023d..ae675b4 100644 --- a/tests/test_config.cpp +++ b/tests/test_config.cpp @@ -359,7 +359,6 @@ TEST_CASE("SimulationConfig") { CHECK(configAllSects.sectionConfigure == "%s.gSK_E2bar_SK_E2 = 0"); CHECK_THROWS_AS(config.getReport("DoesNotExist"), SonataError); - CHECK(config.listReportNames() == std::set{ "axonal_comp_centers", "cell_imembrane", @@ -587,6 +586,7 @@ TEST_CASE("SimulationConfig") { "ex_extracellular_stimulation", "ex_hyperpolarizing", "ex_linear", + "ex_linear_compartment_set", "ex_noise_mean", "ex_noise_meanpercent", "ex_OU", @@ -1202,15 +1202,66 @@ TEST_CASE("SimulationConfig") { }, "inputs": { "linear": { - "input_type": "current_clamp", - "module": "spike_replay", - "delay": 0, - "duration": 15, - "node_set":"Column" + "input_type": "current_clamp", + "module": "linear", + "delay": 0, + "duration": 15, + "node_set":"Column" } } })"; - CHECK_THROWS_AS(SimulationConfig(contents, "./"), SonataError); + CHECK_THROWS_WITH( + SimulationConfig(contents, "./"), + Catch::Matchers::Contains("amp_start") + ); + } + { // Both node_set and compartment_set are given in an input object + auto contents = R"({ + "run": { + "random_seed": 12345, + "dt": 0.05, + "tstop": 1000 + }, + "inputs": { + "linear": { + "input_type": "current_clamp", + "module": "linear", + "delay": 0, + "duration": 15, + "amp_start": 1, + "node_set":"Column", + "compartment_set":"cs1" + } + } + })"; + CHECK_THROWS_WITH( + SimulationConfig(contents, "./"), + Catch::Matchers::Contains("node_set") && + Catch::Matchers::Contains("compartment_set") + ); + } + { // Both node_set and compartment_set are missing in an input object + auto contents = R"({ + "run": { + "random_seed": 12345, + "dt": 0.05, + "tstop": 1000 + }, + "inputs": { + "linear": { + "input_type": "current_clamp", + "module": "linear", + "delay": 0, + "duration": 15, + "amp_start": 1 + } + } + })"; + CHECK_THROWS_WITH( + SimulationConfig(contents, "./"), + Catch::Matchers::Contains("node_set") && + Catch::Matchers::Contains("compartment_set") + ); } { // Both mean and mean_percent are given in a noise input object auto contents = R"({