Skip to content

Commit de628b8

Browse files
authored
SonataConfig.parsedModifications rework (#461)
## Context modifications is now a list of dicts Fix: #458 Fix: #460 Fix: #462 ## Scope - make `modifications` a list - remove validator: it was validating just stuff coming from libsonata. Libsonata has the fields by construction - adapt configuration and tests - register modification types using an enum, not the name of the class ## Testing - add test for #460
1 parent 2550c2d commit de628b8

File tree

7 files changed

+179
-77
lines changed

7 files changed

+179
-77
lines changed

neurodamus/core/configuration.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def init(cls, config_file, cli_options):
254254
cls.connections = cls._config_parser.parsedConnects
255255
cls.stimuli = cls._config_parser.parsedStimuli
256256
cls.reports = cls._config_parser.parsedReports
257-
cls.modifications = cls._config_parser.parsedModifications or {}
257+
cls.modifications = cls._config_parser.parsedModifications
258258
cls.beta_features = cls._config_parser.beta_features
259259
cls.cli_options = CliOptions(**(cli_options or {}))
260260

@@ -555,16 +555,6 @@ def _stimulus_params(config: _SimConfig):
555555
)
556556

557557

558-
@SimConfig.validator
559-
def _modification_params(config: _SimConfig):
560-
required_fields = (
561-
"Target",
562-
"Type",
563-
)
564-
for name, mod_block in config.modifications.items():
565-
_check_params("Modification " + name, mod_block, required_fields, ())
566-
567-
568558
def make_circuit_config(config_dict, req_morphology=True):
569559
if config_dict.get("CellLibraryFile") == "<NONE>":
570560
config_dict["CellLibraryFile"] = False

neurodamus/io/sonata_config.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -310,14 +310,7 @@ def parsedReports(self):
310310

311311
@property
312312
def parsedModifications(self):
313-
item_translation = {"node_set": "Target"}
314-
result = {}
315-
for modification in self._sim_conf.conditions.modifications():
316-
setting = self._translate_dict(item_translation, modification)
317-
self._adapt_libsonata_fields(setting)
318-
result[modification.name] = setting
319-
320-
return result
313+
return self._sim_conf.conditions.modifications()
321314

322315
@staticmethod
323316
def _adapt_libsonata_fields(rep):

neurodamus/modification_manager.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,18 @@ def __init__(self, target_manager):
4141
self._modifications = []
4242

4343
def interpret(self, target_spec, mod_info):
44-
mod_t_name = mod_info["Type"]
45-
mod_t = self._mod_types.get(mod_t_name)
44+
mod_t = self._mod_types.get(mod_info.type)
45+
4646
if not mod_t:
47-
raise ConfigurationError(f"Unknown Modification {mod_t_name}")
47+
raise ConfigurationError(f"Unknown Modification {mod_info.type}")
4848
target = self._target_manager.get_target(target_spec)
4949
cell_manager = self._target_manager._cell_manager
5050
mod = mod_t(target, mod_info, cell_manager)
5151
self._modifications.append(mod)
5252

5353
@classmethod
5454
def register_type(cls, mod_class):
55-
"""Registers a new class as a handler for a new modification type"""
56-
cls._mod_types[mod_class.__name__] = mod_class
55+
cls._mod_types[mod_class.MOD_TYPE] = mod_class
5756
return mod_class
5857

5958

@@ -64,7 +63,9 @@ class TTX:
6463
Uses TTXDynamicsSwitch as in BGLibPy. Overrides HOC version, which is outdated
6564
"""
6665

67-
def __init__(self, target, mod_info: dict, cell_manager):
66+
MOD_TYPE = libsonata.SimulationConfig.ModificationBase.ModificationType.TTX
67+
68+
def __init__(self, target, mod_info: libsonata.SimulationConfig.ModificationTTX, cell_manager):
6869
tpoints = target.get_point_list(
6970
cell_manager,
7071
section_type=libsonata.SimulationConfig.Report.Sections.all,
@@ -90,8 +91,15 @@ class ConfigureAllSections:
9091
Use case is modifying mechanism variables from config.
9192
"""
9293

93-
def __init__(self, target, mod_info: dict, cell_manager):
94-
config, config_attrs = self.parse_section_config(mod_info["SectionConfigure"])
94+
MOD_TYPE = libsonata.SimulationConfig.ModificationBase.ModificationType.ConfigureAllSections
95+
96+
def __init__(
97+
self,
98+
target,
99+
mod_info: libsonata.SimulationConfig.ModificationConfigureAllSections,
100+
cell_manager,
101+
):
102+
config, config_attrs = self.parse_section_config(mod_info.section_configure)
95103
tpoints = target.get_point_list(
96104
cell_manager,
97105
section_type=libsonata.SimulationConfig.Report.Sections.all,

neurodamus/node.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -861,9 +861,9 @@ def enable_modifications(self):
861861
log_stage("Enabling modifications...")
862862

863863
mod_manager = ModificationManager(self._target_manager)
864-
for name, mod_info in SimConfig.modifications.items():
865-
target_spec = TargetSpec(mod_info["Target"], None)
866-
logging.info(" * [MOD] %s: %s -> %s", name, mod_info["Type"], target_spec)
864+
for mod_info in SimConfig.modifications:
865+
target_spec = TargetSpec(mod_info.node_set, None)
866+
logging.info(" * [MOD] %s: %s -> %s", mod_info.name, mod_info.type.name, target_spec)
867867
mod_manager.interpret(target_spec, mod_info)
868868

869869
def write_and_get_population_offsets(self) -> tuple[dict, dict, dict]:

tests/scientific/test_v5_modification.py

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
from pathlib import Path
2+
import pytest
23
from neurodamus.utils.logging import log_verbose
34
# !! NOTE: Please dont import Neuron or Nd objects. pytest will trigger Neuron instantiation!
45

5-
SIM_DIR = Path(__file__).parent.parent.absolute() / "simulations" / "v5_sonata"
6-
CONFIG_FILE_MINI = SIM_DIR / "simulation_config_mini.json"
7-
8-
9-
def test_TTX_modification():
6+
from tests.conftest import V5_SONATA
7+
8+
@pytest.mark.parametrize(
9+
"create_tmp_simulation_config_file",
10+
[
11+
{
12+
"src_dir": str(V5_SONATA),
13+
"simconfig_file": "simulation_config_mini.json",
14+
"extra_config": {
15+
"conditions": {
16+
"modifications": [
17+
{
18+
"name": "applyTTX",
19+
"node_set": "Mini5",
20+
"type": "TTX"
21+
}
22+
]
23+
},
24+
}
25+
}
26+
],
27+
indirect=True,
28+
)
29+
def test_TTX_modification(create_tmp_simulation_config_file):
1030
"""
1131
A test of enabling TTX with a short simulation.
1232
Expected outcome is non-zero spike count without TTX, zero with TTX.
@@ -18,7 +38,7 @@ def test_TTX_modification():
1838
from neurodamus.node import Node
1939

2040
GlobalConfig.verbosity = LogLevel.VERBOSE
21-
n = Node(str(CONFIG_FILE_MINI))
41+
n = Node(create_tmp_simulation_config_file)
2242

2343
# setup sim
2444
n.load_targets()
@@ -30,10 +50,6 @@ def test_TTX_modification():
3050
# _spike_vecs is a list of (spikes, ids)
3151
nspike_noTTX = sum(len(spikes) for spikes, _ in n._spike_vecs)
3252

33-
# append modification to config directly
34-
TTX_mod = {"Type": "TTX", "Target": "Mini5"}
35-
SimConfig.modifications["applyTTX"] = TTX_mod
36-
3753
# setup sim again
3854
Nd.t = 0.0
3955
n._sim_ready = False
@@ -46,8 +62,29 @@ def test_TTX_modification():
4662
assert nspike_noTTX > 0
4763
assert nspike_TTX == 0
4864

49-
50-
def test_ConfigureAllSections_modification():
65+
@pytest.mark.parametrize(
66+
"create_tmp_simulation_config_file",
67+
[
68+
{
69+
"src_dir": str(V5_SONATA),
70+
"simconfig_file": "simulation_config_mini.json",
71+
"extra_config": {
72+
"conditions": {
73+
"modifications": [
74+
{
75+
"name": "no_SK_E2",
76+
"node_set": "Mini5",
77+
"type": "ConfigureAllSections",
78+
"section_configure": "%s.gSK_E2bar_SK_E2 = 0"
79+
}
80+
]
81+
},
82+
}
83+
}
84+
],
85+
indirect=True,
86+
)
87+
def test_ConfigureAllSections_modification(create_tmp_simulation_config_file):
5188
"""
5289
A test of performing ConfigureAllSections with a short simulation.
5390
Expected outcome is higher spike count when enabled.
@@ -59,7 +96,7 @@ def test_ConfigureAllSections_modification():
5996
from neurodamus.node import Node
6097

6198
GlobalConfig.verbosity = LogLevel.VERBOSE
62-
n = Node(str(CONFIG_FILE_MINI))
99+
n = Node(create_tmp_simulation_config_file)
63100

64101
# setup sim
65102
n.load_targets()
@@ -70,12 +107,6 @@ def test_ConfigureAllSections_modification():
70107
n.solve(tstop=150) # longer duration to see influence on spikes
71108
nspike_noConfigureAllSections = sum(len(spikes) for spikes, _ in n._spike_vecs)
72109

73-
# append modification to config directly
74-
ConfigureAllSections_mod = {"Type": "ConfigureAllSections",
75-
"Target": "Mini5",
76-
"SectionConfigure": "%s.gSK_E2bar_SK_E2 = 0"}
77-
SimConfig.modifications["no_SK_E2"] = ConfigureAllSections_mod
78-
79110
# setup sim again
80111
Nd.t = 0.0
81112
n._sim_ready = False

tests/unit/test_modifications.py

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,36 @@
11
import numpy as np
22
import pytest
33

4-
from tests.conftest import RINGTEST_DIR
5-
6-
from neurodamus.core.configuration import ConfigurationError, SimConfig
4+
from neurodamus.core.configuration import ConfigurationError
75
from neurodamus.modification_manager import ModificationManager
86
from neurodamus.node import Neurodamus, Node
7+
from types import SimpleNamespace
98

10-
SIMULATION_CONFIG_FILE = RINGTEST_DIR / "simulation_config.json"
9+
from tests.conftest import RINGTEST_DIR
1110

11+
SIMULATION_CONFIG_FILE = RINGTEST_DIR / "simulation_config.json"
1212

13-
def test_applyTTX():
13+
@pytest.mark.parametrize(
14+
"create_tmp_simulation_config_file",
15+
[
16+
{
17+
"simconfig_fixture": "ringtest_baseconfig",
18+
"extra_config": {
19+
"conditions": {
20+
"modifications": [
21+
{
22+
"name": "applyTTX",
23+
"type": "TTX",
24+
"node_set": "RingA"
25+
}
26+
]
27+
},
28+
},
29+
}
30+
],
31+
indirect=True,
32+
)
33+
def test_applyTTX(create_tmp_simulation_config_file):
1434
"""
1535
A test of enabling TTX with a short simulation.
1636
As Ringtest cells don't contain mechanisms that use the TTX concentration
@@ -21,7 +41,7 @@ def test_applyTTX():
2141
# NeuronWrapper needs to be imported at function level
2242
from neurodamus.core import NeuronWrapper as Nd
2343

24-
n = Node(str(SIMULATION_CONFIG_FILE))
44+
n = Node(create_tmp_simulation_config_file)
2545

2646
# setup sim
2747
n.load_targets()
@@ -33,24 +53,29 @@ def test_applyTTX():
3353
for sec in cell.all:
3454
assert not Nd.ismembrane("TTXDynamicsSwitch", sec=sec)
3555

36-
# append modification to config directly
37-
TTX_mod = {"Type": "TTX", "Target": "RingA"}
38-
SimConfig.modifications["applyTTX"] = TTX_mod
39-
4056
n.enable_modifications()
4157

4258
# check TTXDynamicsSwitch is inserted after modifications
4359
for sec in cell.all:
4460
assert Nd.ismembrane("TTXDynamicsSwitch", sec=sec)
4561

46-
4762
@pytest.mark.parametrize(
4863
"create_tmp_simulation_config_file",
4964
[
5065
{
5166
"simconfig_fixture": "ringtest_baseconfig",
5267
"extra_config": {
5368
"target_simulator": "NEURON",
69+
"conditions": {
70+
"modifications": [
71+
{
72+
"name": "no_SK_E2",
73+
"node_set": "Mosaic",
74+
"type": "ConfigureAllSections",
75+
"section_configure": "%s.gnabar_hh = 0",
76+
}
77+
]
78+
},
5479
"inputs": {
5580
"pulse": {
5681
"module": "pulse",
@@ -95,14 +120,6 @@ def test_ConfigureAllSections(create_tmp_simulation_config_file):
95120
for sec in cell.all:
96121
assert getattr(sec, sec_variable) > 0
97122

98-
# append modification to config directly
99-
ConfigureAllSections_mod = {
100-
"Type": "ConfigureAllSections",
101-
"Target": "Mosaic",
102-
"SectionConfigure": f"%s.{sec_variable} = 0",
103-
}
104-
SimConfig.modifications["no_SK_E2"] = ConfigureAllSections_mod
105-
106123
n.enable_modifications()
107124

108125
# check section variable value after modifications
@@ -158,6 +175,55 @@ def test_ConfigureAllSections_AugAssign(create_tmp_simulation_config_file):
158175
assert np.isclose(soma2.e_pas, -7)
159176

160177

178+
@pytest.mark.parametrize(
179+
"create_tmp_simulation_config_file",
180+
[
181+
{
182+
"simconfig_fixture": "ringtest_baseconfig",
183+
"extra_config": {
184+
"target_simulator": "NEURON",
185+
"conditions": {
186+
"modifications": [
187+
{
188+
"name": "no_SK_E2",
189+
"node_set": "RingA:oneCell",
190+
"type": "ConfigureAllSections",
191+
"section_configure": "%s.e_pas *= 0.1",
192+
},
193+
{
194+
"name": "no_SK_E2",
195+
"node_set": "RingA:oneCell",
196+
"type": "ConfigureAllSections",
197+
"section_configure": "%s.gnabar_hh *= 11",
198+
}
199+
200+
]
201+
},
202+
},
203+
}
204+
],
205+
indirect=True,
206+
)
207+
def test_ConfigureAllSections_AugAssign_name_clash(create_tmp_simulation_config_file):
208+
"""This should produce the same results as test_ConfigureAllSections_AugAssign
209+
210+
However, here we apply the same modification in 2 steps with modifications
211+
that have the same name. Their combined effect should be equivalent
212+
to the modification in test_ConfigureAllSections_AugAssign.
213+
"""
214+
215+
# NeuronWrapper needs to be imported at function level
216+
from neurodamus.core import NeuronWrapper as Nd
217+
218+
Neurodamus(create_tmp_simulation_config_file)
219+
soma1 = Nd._pc.gid2cell(0).soma[0]
220+
soma2 = Nd._pc.gid2cell(1).soma[0]
221+
222+
assert np.isclose(soma1.gnabar_hh, 0.12)
223+
assert np.isclose(soma1.e_pas, -70)
224+
assert np.isclose(soma2.gnabar_hh, 1.32)
225+
assert np.isclose(soma2.e_pas, -7)
226+
161227
@pytest.mark.parametrize(
162228
"create_tmp_simulation_config_file",
163229
[
@@ -193,6 +259,17 @@ def test_error_unknown_modification():
193259
with pytest.raises(ConfigurationError, match="Unknown Modification mod_blabla"):
194260
mod_manager.interpret(target_spec="dummy", mod_info={"Type": "mod_blabla"})
195261

262+
def test_error_unknown_modification():
263+
mod_manager = ModificationManager(target_manager="dummy")
264+
unknown_type = object()
265+
mod_info = SimpleNamespace(type=unknown_type)
266+
267+
with pytest.raises(
268+
ConfigurationError,
269+
match="Unknown Modification",
270+
):
271+
mod_manager.interpret(target_spec="dummy", mod_info=mod_info)
272+
196273

197274
@pytest.mark.parametrize(
198275
"create_tmp_simulation_config_file",

0 commit comments

Comments
 (0)