From 8fbc74009df5dd03e811625e5454fafcba189eba Mon Sep 17 00:00:00 2001 From: ilkankilic Date: Wed, 8 Apr 2026 17:36:47 +0200 Subject: [PATCH 1/2] fix: match synapse replay by population and node id --- bluecellulab/cell/core.py | 19 +++--- bluecellulab/circuit/simulation_access.py | 25 ++++--- .../synapse_replay/synapse_replay.h5 | Bin 11440 -> 11440 bytes tests/test_cell/test_core.py | 62 ++++++++++++++++++ tests/test_circuit/test_simulation_access.py | 33 +++++++++- 5 files changed, 119 insertions(+), 20 deletions(-) diff --git a/bluecellulab/cell/core.py b/bluecellulab/cell/core.py index b11389aa..306ac3d1 100644 --- a/bluecellulab/cell/core.py +++ b/bluecellulab/cell/core.py @@ -850,19 +850,22 @@ def add_synapse_replay( if not file_path.exists(): raise FileNotFoundError(f"Spike file not found: {str(file_path)}") - synapse_spikes: dict = get_synapse_replay_spikes(str(file_path)) + + synapse_spikes: dict[CellId, np.ndarray] = get_synapse_replay_spikes(str(file_path)) + for synapse_id, synapse in self.synapses.items(): - source_population = synapse.syn_description["source_population_name"] - pre_gid = CellId( - source_population, int(synapse.syn_description[SynapseProperty.PRE_GID]) + pre_cell_id = CellId( + str(synapse.syn_description["source_population_name"]), + int(synapse.syn_description[SynapseProperty.PRE_GID]), ) - if pre_gid.id in synapse_spikes: - spikes_of_interest = synapse_spikes[pre_gid.id] - # filter spikes of interest >=stimulus.delay, <=stimulus.duration + + if pre_cell_id in synapse_spikes: + spikes_of_interest = synapse_spikes[pre_cell_id] spikes_of_interest = spikes_of_interest[ (spikes_of_interest >= stimulus.delay) & (spikes_of_interest <= stimulus.duration) ] + connection = bluecellulab.Connection( synapse, pre_spiketrain=spikes_of_interest, @@ -872,7 +875,7 @@ def add_synapse_replay( spike_location=spike_location, ) logger.debug( - f"Added synapse replay from {pre_gid} to {self.cell_id.id}, {synapse_id}" + f"Added synapse replay from {pre_cell_id} to {self.cell_id.id}, {synapse_id}" ) self.connections[synapse_id] = connection diff --git a/bluecellulab/circuit/simulation_access.py b/bluecellulab/circuit/simulation_access.py index bf0eeec1..67105362 100644 --- a/bluecellulab/circuit/simulation_access.py +++ b/bluecellulab/circuit/simulation_access.py @@ -171,17 +171,17 @@ def get_spikes(self) -> dict[CellId, np.ndarray]: return outdat.to_dict() -def get_synapse_replay_spikes(f_name: str) -> dict: +def get_synapse_replay_spikes(f_name: str) -> dict[CellId, np.ndarray]: """Read the .h5 file containing the spike replays. Args: f_name: Path to SONATA .h5 spike file. Returns: - Dictionary mapping node_id to np.array of spike times. + Dictionary mapping CellId(population, node_id) to np.array of spike times. """ all_spikes = [] - with h5py.File(f_name, 'r') as f: + with h5py.File(f_name, "r") as f: if "spikes" not in f: raise ValueError("spike file is missing required 'spikes' group.") @@ -190,7 +190,13 @@ def get_synapse_replay_spikes(f_name: str) -> dict: timestamps = pop_group["timestamps"][:] node_ids = pop_group["node_ids"][:] - pop_spikes = pd.DataFrame({"t": timestamps, "node_id": node_ids}) + pop_spikes = pd.DataFrame( + { + "t": timestamps, + "population": str(population), + "node_id": node_ids, + } + ) pop_spikes = pop_spikes.astype({"node_id": int}) all_spikes.append(pop_spikes) @@ -201,9 +207,10 @@ def get_synapse_replay_spikes(f_name: str) -> dict: if (spikes["t"] < 0).any(): logger.warning("Found negative spike times... Clipping them to 0") - spikes["t"].clip(lower=0., inplace=True) + spikes["t"] = spikes["t"].clip(lower=0.0) - # Group spikes by node_id and ensure spike times are sorted in ascending order. - # This is critical because NEURON's VecStim requires monotonically increasing times per train. - grouped = spikes.groupby("node_id")["t"] - return {k: np.sort(np.asarray(v.values)) for k, v in grouped} + grouped = spikes.groupby(["population", "node_id"])["t"] + return { + CellId(str(population), int(node_id)): np.sort(np.asarray(times.values)) + for (population, node_id), times in grouped + } diff --git a/tests/examples/sonata_unit_test_sims/synapse_replay/synapse_replay.h5 b/tests/examples/sonata_unit_test_sims/synapse_replay/synapse_replay.h5 index aeb3a7b81e995a3ec62e6af6ae91ff208f6bf452..567d11fb837a039709fcc3519e3689f128d45c00 100644 GIT binary patch delta 60 zcmdlGxgl~x0W+h(#*OSulN&fWCO5E3h-YLL6yzr-<`$F|#}^dkXQd{WWaj4;Pu|Yy QxcCDr3!}j1kDU9J0HnkgqyPW_ delta 40 scmdlGxgl~x0rSQU0!)(|SS2_a86d!8;zs+;4_F0Q7&$h7 Date: Thu, 9 Apr 2026 09:39:40 +0200 Subject: [PATCH 2/2] add comment --- bluecellulab/circuit/simulation_access.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bluecellulab/circuit/simulation_access.py b/bluecellulab/circuit/simulation_access.py index 67105362..099b6110 100644 --- a/bluecellulab/circuit/simulation_access.py +++ b/bluecellulab/circuit/simulation_access.py @@ -209,6 +209,8 @@ def get_synapse_replay_spikes(f_name: str) -> dict[CellId, np.ndarray]: logger.warning("Found negative spike times... Clipping them to 0") spikes["t"] = spikes["t"].clip(lower=0.0) + # Group spikes by CellId (population, node_id) and sort each spike train, + # since NEURON VecStim requires monotonically increasing times. grouped = spikes.groupby(["population", "node_id"])["t"] return { CellId(str(population), int(node_id)): np.sort(np.asarray(times.values))