Skip to content

Commit d432aae

Browse files
committed
WIP
1 parent 948ce67 commit d432aae

6 files changed

Lines changed: 136 additions & 27 deletions

File tree

neurodamus/commands.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ def neurodamus(args=None):
7171
--report-buffer-size=<number> Override the size in MB each rank will allocate for each
7272
report buffer to hold data. When the buffer is full, the
7373
ranks will aggregate data for writing to disk. Default: 8 MB
74+
--cell-permute=[0, 1] Cell permutation [default: 0].
75+
Only available for CoreNEURON.
76+
Currently incompatible with NEURON. Options:
77+
- 0 No permutation
78+
- 1 Optimise for node adjacency
7479
"""
7580
from . import __version__
7681

neurodamus/core/configuration.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ._shmutils import SHMUtil
1212
from neurodamus.io.sonata_config import SonataConfig
1313
from neurodamus.utils.logging import log_verbose
14-
from neurodamus.utils.pyutils import ConfigT
14+
from neurodamus.utils.pyutils import ConfigT, StrEnumBase
1515

1616
EXCEPTION_NODE_FILENAME = ".exception_node"
1717
"""A file which controls which rank shows exception"""
@@ -54,7 +54,19 @@ class Feature(Enum):
5454
LoadBalance = 5
5555

5656

57+
class CellPermute(StrEnumBase):
58+
NONE = 0 # cpu
59+
NODE_ADJACENCY = 1 # cpu/gpu
60+
61+
__mapping__ = [
62+
("0", NONE),
63+
("1", NODE_ADJACENCY),
64+
]
65+
__default__ = NONE
66+
67+
5768
class CliOptions(ConfigT):
69+
cell_permute = None
5870
report_buffer_size = None
5971
build_model = None
6072
simulate_model = True
@@ -205,6 +217,7 @@ class _SimConfig:
205217
use_coreneuron = False
206218
use_neuron = True
207219
report_buffer_size = 8 # in MB
220+
cell_permute = CellPermute.default()
208221
delete_corenrn_data = False
209222
modelbuilding_steps = 1
210223
build_model = True
@@ -1052,6 +1065,19 @@ def _coreneuron_direct_mode(config: _SimConfig):
10521065
config.coreneuron_direct_mode = direct_mode
10531066

10541067

1068+
@SimConfig.validator
1069+
def _cell_permute(config: _SimConfig):
1070+
user_config = config.cli_options
1071+
if user_config.cell_permute is not None:
1072+
config.cell_permute = CellPermute.from_string(str(user_config.cell_permute))
1073+
if config.use_neuron and config.cell_permute != CellPermute.NONE:
1074+
logging.warning(
1075+
"Cell permutation is only available with CoreNEURON. "
1076+
"--cell-permute=%s will be ignored.",
1077+
config.cell_permute.to_string(),
1078+
)
1079+
1080+
10551081
def get_debug_cell_gids(cli_options):
10561082
"""Parse the --dump-cell-state option from CLI.
10571083

neurodamus/node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,7 @@ def _coreneuron_write_sim_config(self, corenrn_restore):
12871287
prcellgid=prcellgid,
12881288
celsius=getattr(SimConfig, "celsius", 34.0),
12891289
voltage=getattr(SimConfig, "v_init", -65.0),
1290-
cell_permute=CoreConfig.default_cell_permute,
1290+
cell_permute=int(SimConfig.cell_permute),
12911291
pattern=self._core_replay_file or None,
12921292
seed=SimConfig.rng_info.getGlobalSeed(),
12931293
model_stats=int(SimConfig.cli_options.model_stats),

neurodamus/report_parameters.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
import logging
44
from dataclasses import dataclass
5-
from enum import IntEnum
65
from pathlib import Path
76
from typing import TYPE_CHECKING
87

9-
from .utils.pyutils import cache_errors
8+
from .utils.pyutils import StrEnumBase, cache_errors
109

1110
if TYPE_CHECKING:
1211
from neurodamus.target_manager import TPointList
@@ -16,28 +15,6 @@ class ReportSetupError(Exception):
1615
pass
1716

1817

19-
class StrEnumBase(IntEnum):
20-
__mapping__: list[tuple[str, int]] = []
21-
# default for when there is value. Leaving None throws an error
22-
__default__ = None
23-
# default when the string is not found in the mapping
24-
__invalid__ = None
25-
26-
@classmethod
27-
def from_string(cls, s: str):
28-
if not s:
29-
return cls(cls.__default__)
30-
mapping = dict(cls.__mapping__)
31-
return cls(mapping.get(s.lower(), cls.__invalid__))
32-
33-
def to_string(self) -> str:
34-
reverse__mapping__ = {v: k for k, v in self.__mapping__}
35-
return reverse__mapping__[self]
36-
37-
def __str__(self):
38-
return f"{self.__class__.__name__}.{self.name}"
39-
40-
4118
class SectionType(StrEnumBase):
4219
ALL = 0
4320
SOMA = 1

neurodamus/utils/pyutils.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,37 @@
55
import subprocess # noqa: S404
66
import weakref
77
from bisect import bisect_left
8-
from enum import EnumMeta
8+
from enum import EnumMeta, IntEnum
99

1010
import numpy as np
1111

1212

13+
class StrEnumBase(IntEnum):
14+
__mapping__: list[tuple[str, int]] = []
15+
# default for when there is value. Leaving None throws an error
16+
__default__ = None
17+
# default when the string is not found in the mapping
18+
__invalid__ = None
19+
20+
@classmethod
21+
def from_string(cls, s: str):
22+
if not s:
23+
return cls(cls.__default__)
24+
mapping = dict(cls.__mapping__)
25+
return cls(mapping.get(s.lower(), cls.__invalid__ if cls.__invalid__ is not None else s))
26+
27+
def to_string(self) -> str:
28+
reverse__mapping__ = {v: k for k, v in self.__mapping__}
29+
return reverse__mapping__[self]
30+
31+
def __str__(self):
32+
return f"{self.__class__.__name__}.{self.name}"
33+
34+
@classmethod
35+
def default(cls):
36+
return cls(cls.__default__)
37+
38+
1339
class CumulativeError(Exception):
1440
def __init__(self, errors=None):
1541
self.errors = errors or []

tests/unit/test_cli.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import pytest
66

77
from neurodamus.core.coreneuron_report_config import CoreReportConfig
8+
from neurodamus.core.coreneuron_simulation_config import CoreSimulationConfig
9+
from neurodamus.core.configuration import SimConfig
10+
from neurodamus.core.configuration import CellPermute
811

912
@pytest.mark.parametrize(
1013
"create_tmp_simulation_config_file",
@@ -196,3 +199,75 @@ def test_cli_report_buff_invalid(create_tmp_simulation_config_file):
196199
result = subprocess.run(command, check=False, capture_output=True, text=True)
197200

198201
assert "Report buffer size must be > 0" in result.stdout
202+
203+
@pytest.mark.parametrize(
204+
"create_tmp_simulation_config_file",
205+
[{"simconfig_fixture": "ringtest_baseconfig",
206+
"extra_config": {
207+
"target_simulator": "CORENEURON",
208+
}
209+
}],
210+
indirect=True,
211+
)
212+
def test_cli_cell_permute_simple_setting(create_tmp_simulation_config_file):
213+
command = ["neurodamus", create_tmp_simulation_config_file, "--cell-permute=1", "--keep-build"]
214+
subprocess.run(command, check=False, capture_output=True, text=True)
215+
sim_conf = CoreSimulationConfig.load("build/sim.conf")
216+
assert sim_conf.cell_permute == 1
217+
218+
@pytest.mark.parametrize(
219+
"create_tmp_simulation_config_file",
220+
[{"simconfig_fixture": "ringtest_baseconfig",
221+
"extra_config": {
222+
"target_simulator": "CORENEURON",
223+
}
224+
}],
225+
indirect=True,
226+
)
227+
def test_cli_cell_permute_default(create_tmp_simulation_config_file):
228+
command = ["neurodamus", create_tmp_simulation_config_file, "--keep-build"]
229+
subprocess.run(command, check=False, capture_output=True, text=True)
230+
sim_conf = CoreSimulationConfig.load("build/sim.conf")
231+
assert sim_conf.cell_permute == 0
232+
233+
@pytest.mark.parametrize(
234+
"create_tmp_simulation_config_file",
235+
[{"simconfig_fixture": "ringtest_baseconfig",
236+
"extra_config": {
237+
"target_simulator": "CORENEURON",
238+
}
239+
}],
240+
indirect=True,
241+
)
242+
def test_cli_cell_permute_simple_setting_python(create_tmp_simulation_config_file):
243+
from neurodamus import Neurodamus
244+
nd = Neurodamus(create_tmp_simulation_config_file, cell_permute=1)
245+
assert SimConfig.cell_permute == CellPermute.NODE_ADJACENCY
246+
247+
@pytest.mark.parametrize(
248+
"create_tmp_simulation_config_file",
249+
[{"simconfig_fixture": "ringtest_baseconfig",
250+
"extra_config": {
251+
"target_simulator": "CORENEURON",
252+
}
253+
}],
254+
indirect=True,
255+
)
256+
def test_cli_cell_permute_default_python(create_tmp_simulation_config_file):
257+
from neurodamus import Neurodamus
258+
nd = Neurodamus(create_tmp_simulation_config_file)
259+
assert SimConfig.cell_permute == CellPermute.NONE
260+
261+
@pytest.mark.parametrize(
262+
"create_tmp_simulation_config_file",
263+
[{"simconfig_fixture": "ringtest_baseconfig",
264+
"extra_config": {
265+
"target_simulator": "CORENEURON",
266+
}
267+
}],
268+
indirect=True,
269+
)
270+
def test_cli_cell_permute_invalid(create_tmp_simulation_config_file):
271+
command = ["neurodamus", create_tmp_simulation_config_file, "--cell-permute=2"]
272+
result = subprocess.run(command, check=False, capture_output=True, text=True)
273+
assert "'2' is not a valid CellPermute" in result.stdout

0 commit comments

Comments
 (0)