bsb-neuron 7.6.1__tar.gz → 8.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bsb-neuron
3
- Version: 7.6.1
3
+ Version: 8.0.1
4
4
  Summary: NEURON simulator adapter for the BSB framework.
5
5
  Author-email: Robin De Schepper <robin@alexandria.sc>, Dimitri Rodarie <dimitri.rodarie@unipv.it>
6
- Requires-Python: >=3.10,<4
6
+ Requires-Python: >=3.11,<4
7
7
  Description-Content-Type: text/markdown
8
8
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
9
9
  License-File: LICENSE
10
- Requires-Dist: bsb-core~=7.0
11
- Requires-Dist: nrn-patch~=7.0; sys_platform != 'win32'
12
- Requires-Dist: arborize[neuron]~=7.0; sys_platform != 'win32'
13
- Requires-Dist: bsb-core[parallel] ; extra == "parallel"
14
- Provides-Extra: parallel
10
+ Requires-Dist: bsb-core~=8.0
11
+ Requires-Dist: nrn-patch~=8.0 ; sys_platform != 'win32'
12
+ Requires-Dist: arborize[neuron]~=8.0 ; sys_platform != 'win32'
15
13
 
16
14
  [![Build Status](https://github.com/dbbs-lab/bsb/actions/workflows/main.yml/badge.svg)](https://github.com/dbbs-lab/bsb/actions/workflows/main.yml)
17
15
  [![Documentation](https://readthedocs.org/projects/bsb-neuron/badge/?version=latest)](https://bsb-neuron.readthedocs.io/en/latest/?badge=latest)
@@ -1,4 +1,3 @@
1
- import contextlib
2
1
  import itertools
3
2
 
4
3
  import numpy as np
@@ -6,10 +5,12 @@ from bsb import (
6
5
  AdapterError,
7
6
  Chunk,
8
7
  DatasetNotFoundError,
8
+ ResultsError,
9
9
  SimulationData,
10
10
  SimulationError,
11
11
  SimulationResult,
12
12
  SimulatorAdapter,
13
+ multiplex_channels,
13
14
  report,
14
15
  )
15
16
  from neo import AnalogSignal
@@ -26,35 +27,94 @@ class NeuronSimulationData(SimulationData):
26
27
 
27
28
 
28
29
  class NeuronResult(SimulationResult):
29
- def record(self, obj, **annotations):
30
+ def __init__(self, *args, **kwargs):
31
+ super().__init__(*args, **kwargs)
32
+ # A device's channels, recorded across however many calls to `record` it
33
+ # makes, one flush of one multiplexed signal apiece: keyed on the device, not
34
+ # drawn per call, so every point/synapse of a device lands in the one signal.
35
+ self._channels = {}
36
+
37
+ def record(self, obj, *, device, target: dict, name: str, units: str):
38
+ """
39
+ Record a NEURON variable every time step, as a recording of a device.
40
+
41
+ Every call for the same device joins one multiplexed signal, flushed as a
42
+ single ``AnalogSignal`` with one channel per call, rather than one signal per
43
+ call: cheap to write and to merge across ranks, at the scale of a device
44
+ rather than the scale of a cell.
45
+
46
+ :param obj: Reference to the NEURON variable to record, such as ``seg._ref_v``.
47
+ :param device: The device the recording belongs to.
48
+ :type device: bsb_neuron.device.NeuronDevice
49
+ :param target: What is recorded, as the annotations of
50
+ :func:`~bsb.simulation.results.cell_annotations`,
51
+ :func:`~bsb.simulation.results.point_annotations` or
52
+ :func:`~bsb.simulation.results.synapse_annotations` give them.
53
+ :param name: What the recorded variable measures, such as ``v`` or ``i``. Has
54
+ to agree with every other call for the same device.
55
+ :param units: Units of the recorded variable. Has to agree with every other
56
+ call for the same device.
57
+ """
30
58
  from patch import p
31
- from quantities import ms
32
59
 
33
- v = p.record(obj)
60
+ if "bsb_recording_kind" not in target or "bsb_direction" not in target:
61
+ raise AdapterError(
62
+ f"Device '{device.name}' has to say what it records: pass the "
63
+ "annotations of `cell_annotations`, `point_annotations` or "
64
+ "`synapse_annotations` as `target`."
65
+ )
66
+ entry = self._channels.get(device)
67
+ if entry is None:
68
+ entry = self._channels[device] = {
69
+ "name": name,
70
+ "units": units,
71
+ "channels": [],
72
+ }
73
+ self.create_recorder(
74
+ lambda segment: self._flush_device(segment, device), device=device
75
+ )
76
+ elif entry["name"] != name or entry["units"] != units:
77
+ raise AdapterError(
78
+ f"Device '{device.name}' recorded '{entry['name']}' in "
79
+ f"'{entry['units']}' before, and now '{name}' in '{units}': every "
80
+ "channel of a device has to measure the same thing, in the same units."
81
+ )
82
+ entry["channels"].append((p.record(obj), target))
83
+
84
+ def _flush_device(self, segment, device):
85
+ """Combine every channel a device recorded since the last flush into one
86
+ multiplexed ``AnalogSignal``, and free the NEURON vectors that held them."""
87
+ from patch import p
88
+ from quantities import ms
34
89
 
35
- def flush(segment):
36
- if "units" not in annotations:
37
- annotations["units"] = "mV"
38
- segment.analogsignals.append(
39
- AnalogSignal(list(v), sampling_period=p.dt * ms, **annotations)
90
+ entry = self._channels[device]
91
+ channels = entry["channels"]
92
+ lengths = {v.size() for v, _ in channels}
93
+ if len(lengths) > 1:
94
+ raise ResultsError(
95
+ f"Device '{device.name}' recorded channels of different lengths "
96
+ f"{sorted(lengths)} this checkpoint; they cannot be combined into one "
97
+ "signal."
40
98
  )
41
- # Free the memory
99
+ data = np.array([list(v) for v, _ in channels]).T
100
+ baseline, array_annotations = multiplex_channels(
101
+ [target for _, target in channels]
102
+ )
103
+ segment.analogsignals.append(
104
+ AnalogSignal(
105
+ data,
106
+ sampling_period=p.dt * ms,
107
+ name=entry["name"],
108
+ units=entry["units"],
109
+ array_annotations=array_annotations,
110
+ **baseline,
111
+ )
112
+ )
113
+ # Free the memory
114
+ for v, _ in channels:
42
115
  if v.size():
43
116
  v.remove(0, v.size() - 1)
44
117
 
45
- self.create_recorder(flush)
46
-
47
-
48
- @contextlib.contextmanager
49
- def fill_parameter_data(parameters, data):
50
- for param in parameters:
51
- if hasattr(param, "load_data"):
52
- param.load_data(*data)
53
- yield
54
- for param in parameters:
55
- if hasattr(param, "load_data"):
56
- param.drop_data()
57
-
58
118
 
59
119
  class NeuronAdapter(SimulatorAdapter):
60
120
  initial = -65
@@ -70,7 +130,7 @@ class NeuronAdapter(SimulatorAdapter):
70
130
 
71
131
  return engine
72
132
 
73
- def prepare(self, simulation):
133
+ def prepare(self, simulation, filename=None):
74
134
  """
75
135
  Prepare the simulation environment and data structures for running a NEURON
76
136
  simulation.
@@ -90,7 +150,13 @@ class NeuronAdapter(SimulatorAdapter):
90
150
  """
91
151
 
92
152
  self.simdata[simulation] = NeuronSimulationData(
93
- simulation, result=NeuronResult(simulation)
153
+ simulation,
154
+ result=NeuronResult(
155
+ simulation,
156
+ filename=filename,
157
+ comm=self.comm,
158
+ simulation_id=self.new_run_id(),
159
+ ),
94
160
  )
95
161
  try:
96
162
  report("Preparing simulation", level=2)
@@ -170,10 +236,9 @@ class NeuronAdapter(SimulatorAdapter):
170
236
  self._allocate_transmitters(simulation)
171
237
  for conn_model in simulation.connection_models.values():
172
238
  cs = simulation.scaffold.get_connectivity_set(conn_model.name)
173
- with fill_parameter_data(conn_model.parameters, []):
174
- simdata.connections[conn_model] = conn_model.create_connections(
175
- simulation, simdata, cs
176
- )
239
+ simdata.connections[conn_model] = conn_model.create_connections(
240
+ simulation, simdata, cs
241
+ )
177
242
 
178
243
  def _allocate_transmitters(self, simulation):
179
244
  simdata = self.simdata[simulation]
@@ -282,9 +347,8 @@ class NeuronAdapter(SimulatorAdapter):
282
347
  except DatasetNotFoundError:
283
348
  data.append(itertools.repeat(None))
284
349
 
285
- with fill_parameter_data(cell_model.parameters, data):
286
- instances = cell_model.create_instances(len(ps), *data)
287
- simdata.populations[cell_model] = NeuronPopulation(cell_model, instances)
350
+ instances = cell_model.create_instances(len(ps), *data)
351
+ simdata.populations[cell_model] = NeuronPopulation(cell_model, instances)
288
352
 
289
353
 
290
354
  class NeuronPopulation(list):
@@ -1,5 +1,8 @@
1
+ import dataclasses
2
+ import typing
3
+
1
4
  import numpy as np
2
- from bsb import AdapterError, ConnectionModel, Parameter, config, types
5
+ from bsb import AdapterError, ConnectionModel, config, types
3
6
 
4
7
  from bsb_neuron._util import ignore_arborize_proxy_warnings
5
8
 
@@ -42,16 +45,46 @@ class SynapseSpec:
42
45
  """Name of the synapse model."""
43
46
  weight = config.attr(type=float, default=0.004)
44
47
  """Weight of the connection between the presynaptic and the postsynaptic cells."""
45
- delay = config.attr(type=float, default=0.0)
46
- """Delay of the transmission between the presynaptic and the postsynaptic cells."""
47
- parameters = config.list(type=Parameter)
48
- """List of parameters to assign to the synapse model."""
48
+ delay = config.attr(type=float, default=1.0)
49
+ """
50
+ Delay of the transmission between the presynaptic and the postsynaptic cells, in
51
+ milliseconds. Defaults to NEURON's own ``NetCon`` delay of 1 ms.
52
+
53
+ The delays of a network set NEURON's ``mindelay``, which
54
+ ``ParallelContext.set_maxstep`` requires to be strictly positive and at least one
55
+ :attr:`~bsb_neuron.simulation.NeuronSimulation.resolution` step. A delay of 0
56
+ therefore aborts the simulation with ``usable mindelay is 0``.
57
+ """
49
58
 
50
59
  def __init__(self, synapse_name=None, /, **kwargs):
51
60
  if synapse_name is not None:
52
61
  self._synapse = synapse_name
53
62
 
54
63
 
64
+ @dataclasses.dataclass(frozen=True, slots=True)
65
+ class Receiver:
66
+ """
67
+ A synapse a connection makes on a cell, and the connection it belongs to.
68
+
69
+ Every synapse a connection model creates keeps its receiver as ``receiver``.
70
+ """
71
+
72
+ #: The transmitter gid the synapse receives from.
73
+ gid: int
74
+ #: The postsynaptic cell.
75
+ cell: typing.Any
76
+ #: Branch and point of the synapse on the postsynaptic cell.
77
+ location: tuple[int, int]
78
+ #: The synapse specification.
79
+ spec: SynapseSpec
80
+ #: The cell model of the presynaptic cell.
81
+ pre_model: typing.Any
82
+ #: Cell id, branch and point where the connection starts on the presynaptic cell.
83
+ pre: tuple[int, int, int]
84
+ #: Name of the connectivity set the connection is in.
85
+ connectivity_set: str
86
+
87
+
55
88
  @config.node
56
89
  class TransceiverModel(NeuronConnection, classmap_entry="transceiver"):
57
90
  synapses = config.list(
@@ -59,8 +92,6 @@ class TransceiverModel(NeuronConnection, classmap_entry="transceiver"):
59
92
  required=True,
60
93
  )
61
94
  """List of synapse models to use for a connection."""
62
- parameters = config.list(type=Parameter)
63
- """List of parameters to assign to the connection."""
64
95
  source = config.attr(type=str)
65
96
  """Source variable to assign to the connection."""
66
97
 
@@ -101,24 +132,55 @@ class TransceiverModel(NeuronConnection, classmap_entry="transceiver"):
101
132
  :type simdata: bsb_neuron.simulation.NeuronSimulationData
102
133
  :type cs: bsb.storage.interfaces.ConnectivitySet
103
134
  """
104
- for post_cm, post_pop in simdata.populations.items(): # noqa: B007
105
- if post_cm.cell_type == cs.post_type:
135
+ for receiver in self.iter_receivers(simdata, cs):
136
+ receiver.cell.insert_receiver(
137
+ receiver.gid,
138
+ receiver.spec.synapse,
139
+ receiver.location,
140
+ source=self.source,
141
+ weight=receiver.spec.weight,
142
+ delay=receiver.spec.delay,
143
+ )
144
+ # The receiver's synapse is the one it just appended to its section.
145
+ location = receiver.cell.get_location(receiver.location)
146
+ location.section.synapses[-1].receiver = receiver
147
+
148
+ def iter_receivers(self, simdata, cs):
149
+ """
150
+ Iterate the synapses the connectivity set makes on the cells of this rank, in
151
+ the order :meth:`create_receivers` inserts them.
152
+
153
+ :type simdata: bsb_neuron.simulation.NeuronSimulationData
154
+ :type cs: bsb.storage.interfaces.ConnectivitySet
155
+ :returns: The receivers, one per synapse.
156
+ :rtype: typing.Iterator[Receiver]
157
+ """
158
+ for post_model, post_pop in simdata.populations.items(): # noqa: B007
159
+ if post_model.cell_type == cs.post_type:
106
160
  break
107
161
  else:
108
162
  raise AdapterError(f"No pop found for {cs.pre_type.name}")
109
- pre, post = cs.load_connections().incoming().to(simdata.chunks).all()
163
+ pre_model = next(
164
+ (cm for cm in simdata.populations if cm.cell_type == cs.pre_type), None
165
+ )
166
+ query = cs.load_connections().incoming().to(simdata.chunks)
167
+ pre, post = query.all()
168
+ # The same connections with the presynaptic cells as placement set ids, which
169
+ # is how a synapse names the cell it receives from.
170
+ pre_globals = query.as_globals().all()[0]
110
171
  transmitters = simdata.transmap[self]["receivers"]
111
- for pre_loc, post_loc in zip(pre[:, :2], post, strict=False):
112
- gid = transmitters[tuple(pre_loc)]
172
+ for pre_loc, pre_global, post_loc in zip(pre, pre_globals, post, strict=True):
173
+ gid = transmitters[tuple(pre_loc[:2])]
113
174
  cell = post_pop[post_loc[0]]
114
175
  for spec in self.synapses:
115
- cell.insert_receiver(
116
- gid,
117
- spec.synapse,
118
- post_loc[1:],
119
- source=self.source,
120
- weight=spec.weight,
121
- delay=spec.delay,
176
+ yield Receiver(
177
+ gid=gid,
178
+ cell=cell,
179
+ location=tuple(int(i) for i in post_loc[1:]),
180
+ spec=spec,
181
+ pre_model=pre_model,
182
+ pre=tuple(int(i) for i in pre_global),
183
+ connectivity_set=cs.tag,
122
184
  )
123
185
 
124
186
  def __lt__(self, other):
@@ -1,4 +1,4 @@
1
- from bsb import LocationTargetting, config, warn
1
+ from bsb import LocationTargetting, config, point_annotations, warn
2
2
 
3
3
  from .._util import ignore_arborize_proxy_warnings
4
4
  from ..device import NeuronDevice
@@ -24,19 +24,22 @@ class CurrentClamp(NeuronDevice, classmap_entry="current_clamp"):
24
24
  for location in self.locations.get_locations(target):
25
25
  if clamped:
26
26
  warn(f"Multiple current clamps placed on {target}")
27
- self._add_clamp(
28
- simdata,
29
- location,
30
- name=self.name,
31
- cell_type=target.cell_model.name,
32
- cell_id=target.id,
33
- )
27
+ self._add_clamp(simdata, target, location)
34
28
  clamped = True
35
29
 
36
30
  @ignore_arborize_proxy_warnings()
37
- def _add_clamp(self, simdata, location, **annotations):
31
+ def _add_clamp(self, simdata, target, location):
38
32
  sx = location.arc(0.5)
39
33
  clamp = location.section.iclamp(
40
34
  x=sx, delay=self.before, duration=self.duration, amplitude=self.amplitude
41
35
  )
42
- simdata.result.record(clamp._ref_i, **annotations, units="nA")
36
+ # The clamp records the current it injects.
37
+ simdata.result.record(
38
+ clamp._ref_i,
39
+ device=self,
40
+ target=point_annotations(
41
+ target.cell_model, target.id, *location._loc, sx, "stimulate"
42
+ ),
43
+ name="i",
44
+ units="nA",
45
+ )
@@ -0,0 +1,59 @@
1
+ from bsb import LocationTargetting, config, synapse_annotations, warn
2
+
3
+ from .._util import ignore_arborize_proxy_warnings
4
+ from ..device import NeuronDevice
5
+
6
+
7
+ @config.node
8
+ class SynapseRecorder(NeuronDevice, classmap_entry="synapse_recorder"):
9
+ locations = config.attr(type=LocationTargetting, required=True)
10
+ """Location of the synapse recorder on the section"""
11
+ synapse_types = config.list()
12
+ """List of synaptic types"""
13
+
14
+ @ignore_arborize_proxy_warnings()
15
+ def implement(self, adapter, simulation, simdata):
16
+ for _model, pop in self.targetting.get_targets(
17
+ adapter, simulation, simdata
18
+ ).items():
19
+ for target in pop:
20
+ # Locations on the same section share its synapses, so each synapse
21
+ # is recorded once however many of the targeted locations hold it.
22
+ synapses = {
23
+ id(synapse): synapse
24
+ for location in self.locations.get_locations(target)
25
+ for synapse in location.section.synapses
26
+ if not self.synapse_types
27
+ or synapse.synapse_name in self.synapse_types
28
+ }
29
+ for synapse in synapses.values():
30
+ self._record_synaptic_current(simdata.result, target, synapse)
31
+
32
+ def _record_synaptic_current(self, result, target, synapse):
33
+ receiver = getattr(synapse, "receiver", None)
34
+ if receiver is None:
35
+ warn(
36
+ f"Device '{self.name}' cannot record a '{synapse.synapse_name}' synapse "
37
+ f"of cell {target.id}: it was not made by a connection, so where it is "
38
+ "and what it connects is unknown."
39
+ )
40
+ return
41
+ pre_id, pre_branch, pre_point = receiver.pre
42
+ location = target.get_location(receiver.location)
43
+ result.record(
44
+ synapse._pp._ref_i,
45
+ device=self,
46
+ name="i",
47
+ units="nA",
48
+ target=synapse_annotations(
49
+ (target.cell_model, target.id, *receiver.location, location.arc(0.5)),
50
+ synapse.synapse_name,
51
+ "record",
52
+ pre=(
53
+ None
54
+ if receiver.pre_model is None
55
+ else (receiver.pre_model, pre_id, pre_branch, pre_point)
56
+ ),
57
+ connectivity_set=receiver.connectivity_set,
58
+ ),
59
+ )
@@ -1,6 +1,6 @@
1
1
  import warnings
2
2
 
3
- from bsb import LocationTargetting, config, types
3
+ from bsb import LocationTargetting, config, point_annotations, types
4
4
 
5
5
  from .._util import ignore_arborize_proxy_warnings
6
6
  from ..device import NeuronDevice
@@ -25,25 +25,22 @@ class VoltageClamp(NeuronDevice, classmap_entry="vclamp"):
25
25
  """Voltage value in the `before` and `after` delays"""
26
26
 
27
27
  def implement(self, adapter, simulation, simdata):
28
- for target in self.targetting.get_targets(adapter, simulation, simdata):
29
- clamped = False
30
- for location in self.locations.get_locations(target):
31
- if clamped:
32
- warnings.warn(
33
- f"Multiple voltage clamps placed on {target}",
34
- stacklevel=2,
35
- )
36
- self._add_clamp(
37
- simdata.results,
38
- location,
39
- name=self.name,
40
- cell_type=target.cell_model.name,
41
- cell_id=target.id,
42
- )
43
- clamped = True
28
+ for _model, pop in self.targetting.get_targets(
29
+ adapter, simulation, simdata
30
+ ).items():
31
+ for target in pop:
32
+ clamped = False
33
+ for location in self.locations.get_locations(target):
34
+ if clamped:
35
+ warnings.warn(
36
+ f"Multiple voltage clamps placed on {target}",
37
+ stacklevel=2,
38
+ )
39
+ self._add_clamp(simdata.result, target, location)
40
+ clamped = True
44
41
 
45
42
  @ignore_arborize_proxy_warnings()
46
- def _add_clamp(self, results, location, **annotations):
43
+ def _add_clamp(self, results, target, location):
47
44
  sx = location.arc(0.5)
48
45
  clamp = location.section.vclamp(
49
46
  voltage=self.voltage,
@@ -54,4 +51,13 @@ class VoltageClamp(NeuronDevice, classmap_entry="vclamp"):
54
51
  if (v := getattr(self, k)) is not None
55
52
  },
56
53
  )
57
- results.record(clamp, **annotations)
54
+ # The clamp records the current it injects to hold the voltage.
55
+ results.record(
56
+ clamp._ref_i,
57
+ device=self,
58
+ target=point_annotations(
59
+ target.cell_model, target.id, *location._loc, sx, "stimulate"
60
+ ),
61
+ name="i",
62
+ units="nA",
63
+ )
@@ -1,4 +1,4 @@
1
- from bsb import LocationTargetting, config
1
+ from bsb import LocationTargetting, config, point_annotations
2
2
 
3
3
  from .._util import ignore_arborize_proxy_warnings
4
4
  from ..device import NeuronDevice
@@ -17,16 +17,18 @@ class VoltageRecorder(NeuronDevice, classmap_entry="voltage_recorder"):
17
17
  ).items():
18
18
  for target in pop:
19
19
  for location in self.locations.get_locations(target):
20
- self._add_voltage_recorder(
21
- simdata.result,
22
- location,
23
- name=self.name,
24
- cell_type=target.cell_model.name,
25
- cell_id=target.id,
26
- )
20
+ self._add_voltage_recorder(simdata.result, target, location)
27
21
 
28
22
  @ignore_arborize_proxy_warnings()
29
- def _add_voltage_recorder(self, results, location, **annotations):
23
+ def _add_voltage_recorder(self, results, target, location):
30
24
  section = location.section
31
25
  x = location.arc(0)
32
- results.record(section(x)._ref_v, **annotations)
26
+ results.record(
27
+ section(x)._ref_v,
28
+ device=self,
29
+ target=point_annotations(
30
+ target.cell_model, target.id, *location._loc, x, "record"
31
+ ),
32
+ name="v",
33
+ units="mV",
34
+ )
@@ -6,18 +6,19 @@ build-backend = "flit_core.buildapi"
6
6
 
7
7
  [project]
8
8
  name = "bsb-neuron"
9
- version = "7.6.1"
9
+ version = "8.0.1"
10
10
  readme = "README.md"
11
- requires-python = ">=3.10,<4"
11
+ requires-python = ">=3.11,<4"
12
12
  dynamic = [ "description" ]
13
13
  classifiers = [
14
14
  "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)"
15
15
  ]
16
16
  dependencies = [
17
- "bsb-core~=7.0",
18
- "nrn-patch~=7.0; sys_platform != 'win32'",
19
- "arborize[neuron]~=7.0; sys_platform != 'win32'"
17
+ "bsb-core~=8.0",
18
+ "nrn-patch~=8.0 ; sys_platform != 'win32'",
19
+ "arborize[neuron]~=8.0 ; sys_platform != 'win32'"
20
20
  ]
21
+ optional-dependencies = { }
21
22
 
22
23
  [[project.authors]]
23
24
  name = "Robin De Schepper"
@@ -30,15 +31,27 @@ dependencies = [
30
31
  [project.license]
31
32
  file = "LICENSE"
32
33
 
33
- [project.optional-dependencies]
34
- parallel = [ "bsb-core[parallel]" ]
35
-
36
34
  [project.entry-points."bsb.simulation_backends"]
37
35
  neuron = "bsb_neuron"
38
36
 
39
37
  [tool.uv]
40
38
  default-groups = [ "dev", "docs", "test" ]
41
- sources = { }
39
+
40
+ [tool.uv.sources.bsb-hdf5]
41
+ path = "../bsb-hdf5"
42
+ editable = true
43
+
44
+ [tool.uv.sources.bsb-test]
45
+ path = "../bsb-test"
46
+ editable = true
47
+
48
+ [tool.uv.sources.bsb-otel]
49
+ path = "../bsb-otel"
50
+ editable = true
51
+
52
+ [tool.uv.sources.sphinxext-bsb]
53
+ path = "../../libs/sphinxext-bsb"
54
+ editable = true
42
55
 
43
56
  [tool.flit.module]
44
57
  name = "bsb_neuron"
@@ -1,37 +0,0 @@
1
- from bsb import LocationTargetting, config
2
-
3
- from .._util import ignore_arborize_proxy_warnings
4
- from ..device import NeuronDevice
5
-
6
-
7
- @config.node
8
- class SynapseRecorder(NeuronDevice, classmap_entry="synapse_recorder"):
9
- locations = config.attr(type=LocationTargetting, required=True)
10
- """Location of the synapse recorder on the section"""
11
- synapse_types = config.list()
12
- """List of synaptic types"""
13
-
14
- @ignore_arborize_proxy_warnings()
15
- def implement(self, adapter, simulation, simdata):
16
- for _model, pop in self.targetting.get_targets(
17
- adapter, simulation, simdata
18
- ).items():
19
- for target in pop:
20
- for location in self.locations.get_locations(target):
21
- for synapse in location.section.synapses:
22
- if (
23
- not self.synapse_types
24
- or synapse.synapse_name in self.synapse_types
25
- ):
26
- _record_synaptic_current(
27
- simdata.result,
28
- synapse,
29
- name=self.name,
30
- cell_type=target.cell_model.name,
31
- cell_id=target.id,
32
- synapse_type=synapse.synapse_name,
33
- )
34
-
35
-
36
- def _record_synaptic_current(result, synapse, **annotations):
37
- result.record(synapse._pp._ref_i, **annotations, units="nA")
File without changes
File without changes
@@ -11,10 +11,10 @@ class NeuronSimulation(Simulation):
11
11
  Interface between the scaffold model and the NEURON simulator.
12
12
  """
13
13
 
14
- initial = config.attr(type=float, default=-65.0)
15
- """Initial membrane potential for all neurons."""
16
14
  resolution = config.attr(type=types.float(min=0.0), default=0.1)
17
15
  """Simulation time step size in milliseconds."""
16
+ initial = config.attr(type=float, default=-65.0)
17
+ """Initial membrane potential for all neurons."""
18
18
  temperature = config.attr(type=float, required=True)
19
19
  """Temperature of the circuit during simulation."""
20
20
  cell_models: config._attrs.cfgdict[NeuronCell] = config.dict(