From 9f47299db09f1dd29fbe629f85d0de2876567612 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Thu, 28 May 2026 15:15:31 +0200 Subject: [PATCH 1/5] Fix spike recording bug. --- VERSION | 2 +- arbor/simulation.cpp | 7 ++++++- doc/concepts/decor.rst | 2 +- doc/python/interconnectivity.rst | 2 +- doc/python/simulation.rst | 4 ++++ python/example/network_ring_mpi.py | 6 +++--- python/example/single_cell_bluepyopt_l5pc.py | 16 +++++++++------- 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/VERSION b/VERSION index 26acbf080b..aa22d3ce39 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.12.2 +0.12.3 diff --git a/arbor/simulation.cpp b/arbor/simulation.cpp index 2297860529..c7a63994d8 100644 --- a/arbor/simulation.cpp +++ b/arbor/simulation.cpp @@ -434,7 +434,12 @@ time_type simulation_state::run(time_type tfinal, time_type dt) { // Present spikes to user-supplied callbacks. PE(spikeio); if (local_export_callback_) local_export_callback_(all_local_spikes); - if (global_export_callback_) global_export_callback_(spikes.from_local.values()); + // If we are asked to export all spikes, clench your teeth and collate all spikes. + // TODO(TH): probably need to deprecate this, then. + if (global_export_callback_) { + auto global_spikes = ctx_->distributed->gather_spikes(all_local_spikes); + global_export_callback_(global_spikes.values()); + } PL(spikeio); // Append events formed from global spikes to per-cell pending event queues. diff --git a/doc/concepts/decor.rst b/doc/concepts/decor.rst index 36e8379015..92528753e7 100644 --- a/doc/concepts/decor.rst +++ b/doc/concepts/decor.rst @@ -382,7 +382,7 @@ between cells. An example where we're interested in when a threshold of ``-10 mV # Placing a threshold detector might look like this. decor = arbor.decor() - decor.place('"root"', arbor.threshold_detector(-10), "detector") + decor.place('"root"', arbor.threshold_detector(-10*U.mV), "detector") # At this point, "detector" could be connected to another cell, # and it would transmit events upon the voltage crossing the threshold. diff --git a/doc/python/interconnectivity.rst b/doc/python/interconnectivity.rst index 97202694fb..9733cb8042 100644 --- a/doc/python/interconnectivity.rst +++ b/doc/python/interconnectivity.rst @@ -58,7 +58,7 @@ Interconnectivity # Place 'expsyn' mechanism on "synapse_site", and a threshold detector at "root" decor = A.decor() decor.place('"synapse_site"', 'expsyn', 'syn') - decor.place('"root"', arbor.threshold_detector(-10), 'detector') + decor.place('"root"', arbor.threshold_detector(-10*U.mV), 'detector') # Implement the connections_on() function on a recipe as follows: def connections_on(gid): diff --git a/doc/python/simulation.rst b/doc/python/simulation.rst index 09b497509b..6614ae47ed 100644 --- a/doc/python/simulation.rst +++ b/doc/python/simulation.rst @@ -200,6 +200,10 @@ over the local and distributed hardware resources (see :ref:`pydomdec`). Then, t Record all generated spikes from cells on all MPI ranks. + **Note** This convenience method has non-trivial performance + implications as Arbor will collate all spikes on all ranks. Prefer + ``local`` unless you are absolutely required otherwise and willing to + pay the cost. Recording spikes ---------------- diff --git a/python/example/network_ring_mpi.py b/python/example/network_ring_mpi.py index 2a30010844..7858799d22 100644 --- a/python/example/network_ring_mpi.py +++ b/python/example/network_ring_mpi.py @@ -68,7 +68,7 @@ def make_cable_cell(gid): # (4) Attach a single synapse. .place('"synapse_site"', A.synapse("expsyn"), "syn") # Attach a detector with threshold of -10 mV. - .place('"root"', A.threshold_detector(-10), "detector") + .place('"root"', A.threshold_detector(-10 * U.mV), "detector") ) return A.cable_cell(tree, decor, labels) @@ -114,7 +114,7 @@ def event_generators(self, gid): # (10) Place a probe at the root of each cell. def probes(self, gid): - return [A.cable_probe_membrane_voltage('"root"')] + return [A.cable_probe_membrane_voltage('"root"', tag='Um')] def global_properties(self, kind): return self.props @@ -139,7 +139,7 @@ def global_properties(self, kind): # (15) Attach a sampler to the voltage probe on cell 0. Sample rate of 1 sample every ms. # Sampling period increased w.r.t network_ring.py to reduce amount of data -handles = [sim.sample((gid, 0), A.regular_schedule(1 * U.ms)) for gid in range(ncells)] +handles = [sim.sample((gid, 'Um'), A.regular_schedule(1 * U.ms)) for gid in range(ncells)] # (16) Run simulation sim.run(ncells * 5 * U.ms) diff --git a/python/example/single_cell_bluepyopt_l5pc.py b/python/example/single_cell_bluepyopt_l5pc.py index 42a08ed6f8..b6143c38a2 100755 --- a/python/example/single_cell_bluepyopt_l5pc.py +++ b/python/example/single_cell_bluepyopt_l5pc.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import arbor as A +from arbor import units as U + import pandas import seaborn import sys @@ -27,19 +29,19 @@ ) # (3) Define stimulus and spike detector, adjust discretization -decor.place('"soma_center"', A.i_clamp(tstart=295, duration=5, current=1.9)) +decor.place('"soma_center"', A.i_clamp(tstart=295*U.ms, duration=5*U.ms, current=1.9*U.nA)) # Add spike detector -decor.place('"soma_center"', A.threshold_detector(-10), "detector") +decor.place('"soma_center"', A.threshold_detector(-10*U.mV), "detector") # Adjust discretization (single CV on soma, default everywhere else) -cvp = A.cv_policy_max_extent_um(1.0) | A.cv_policy_single('"soma"') +cvp = A.cv_policy_max_extent_um(1.0*U.um) | A.cv_policy_single('"soma"') # (4) Create the cell. cell = A.cable_cell(morpho, decor, labels, cvp) # (5) Declare a probe. -probe = A.cable_probe_membrane_voltage('"dend1"') +probe = A.cable_probe_membrane_voltage('"dend1"', tag='Um') # (6) Create a class that inherits from A.recipe @@ -90,11 +92,11 @@ def global_properties(self, gid): # Instruct the simulation to record the spikes and sample the probe sim.record(A.spike_recording.all) -probe_id = A.cell_member(0, 0) -handle = sim.sample(probe_id, A.regular_schedule(0.02)) +probe_id = A.cell_member(0, 'Um') +handle = sim.sample(probe_id, A.regular_schedule(0.02*U.ms)) # (8) Run the simulation -sim.run(tfinal=600, dt=0.025) +sim.run(tfinal=600*U.ms, dt=0.025*U.ms) # (9) Print or display the results spikes = sim.spikes() From 93e46876ac911aa43e5463f6e816c3333c671ba2 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Fri, 29 May 2026 07:59:44 +0200 Subject: [PATCH 2/5] Breaking: remove global spike callback. --- VERSION | 2 +- doc/cpp/simulation.rst | 5 +++++ example/busyring/ring.cpp | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index aa22d3ce39..554c968455 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.12.3 +0.12.3rc0 diff --git a/doc/cpp/simulation.rst b/doc/cpp/simulation.rst index 78fe0ada49..d0a7359e79 100644 --- a/doc/cpp/simulation.rst +++ b/doc/cpp/simulation.rst @@ -89,6 +89,11 @@ Class documentation during a simulation. See :cpp:func:`set_local_spike_callback` and :cpp:func:`set_global_spike_callback`. + **Note** This convenience method has non-trivial performance + implications as Arbor will collate all spikes on all ranks. Prefer + ``local`` unless you are absolutely required otherwise and willing to + pay the cost. + **Constructor:** .. cpp:function:: simulation(const recipe& rec, const domain_decomposition& decomp, const context& ctx, std::uint64_t seed) diff --git a/example/busyring/ring.cpp b/example/busyring/ring.cpp index 1718dc5eb5..5688e008d9 100644 --- a/example/busyring/ring.cpp +++ b/example/busyring/ring.cpp @@ -342,7 +342,9 @@ int main(int argc, char** argv) { // Write spikes to file if (root) { std::cout << "\n" << ns << " spikes generated at rate of " - << params.duration/ns << " ms between spikes\n"; + << params.duration/ns << " ms between spikes\n" + << recorded_spikes.size() << " where recorded\n" +; if (!recorded_spikes.empty()) { std::ofstream fid(params.odir + "/" + params.name + "_spikes.gdf"); if (!fid.good()) { @@ -541,4 +543,4 @@ arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& param // Make a CV between every sample in the sample tree. return {arb::morphology(tree), decor, {}, arb::cv_policy_every_segment()}; -} \ No newline at end of file +} From 9e9b8b2b0c35a5d6bdfade21ed6f5fd909c02bce Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Fri, 29 May 2026 08:53:09 +0200 Subject: [PATCH 3/5] Update docs and examples --- VERSION | 2 +- arbor/include/arbor/simulation.hpp | 4 +- arbor/simulation.cpp | 9 +--- doc/cpp/simulation.rst | 22 ++-------- example/adex/adex.cpp | 2 +- example/brunel/brunel.cpp | 17 ++++---- example/busyring/ring.cpp | 42 +++++++++---------- example/dryrun/dryrun.cpp | 40 ++++++++---------- example/gap_junctions/gap_junctions.cpp | 40 ++++++++---------- .../network_description.cpp | 38 +++++++---------- example/plasticity/plasticity.cpp | 2 +- example/ring/ring.cpp | 9 ++-- python/simulation.cpp | 6 --- test/unit/test_adex_cell_group.cpp | 14 +++---- test/unit/test_lif_cell_group.cpp | 4 +- test/unit/test_simulation.cpp | 4 +- test/unit/test_spikes.cpp | 2 +- 17 files changed, 103 insertions(+), 154 deletions(-) diff --git a/VERSION b/VERSION index 554c968455..420b4fc259 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.12.3rc0 +0.12.3-rc0 diff --git a/arbor/include/arbor/simulation.hpp b/arbor/include/arbor/simulation.hpp index 824a2e731c..6ce8754a89 100644 --- a/arbor/include/arbor/simulation.hpp +++ b/arbor/include/arbor/simulation.hpp @@ -74,8 +74,8 @@ struct ARB_ARBOR_API simulation { std::size_t num_spikes() const; - // Register a callback that will perform a export of the global - // spike vector. + // Register a callback that will perform a export of the global spike + // vector. REMOVED void set_global_spike_callback(spike_export_function = spike_export_function{}); // Register a callback that will perform a export of the rank local diff --git a/arbor/simulation.cpp b/arbor/simulation.cpp index c7a63994d8..363cfb763b 100644 --- a/arbor/simulation.cpp +++ b/arbor/simulation.cpp @@ -100,7 +100,6 @@ struct simulation_state { return tau; } - spike_export_function global_export_callback_; spike_export_function local_export_callback_; epoch_function epoch_callback_; label_resolution_map source_resolution_map_; @@ -434,12 +433,6 @@ time_type simulation_state::run(time_type tfinal, time_type dt) { // Present spikes to user-supplied callbacks. PE(spikeio); if (local_export_callback_) local_export_callback_(all_local_spikes); - // If we are asked to export all spikes, clench your teeth and collate all spikes. - // TODO(TH): probably need to deprecate this, then. - if (global_export_callback_) { - auto global_spikes = ctx_->distributed->gather_spikes(all_local_spikes); - global_export_callback_(global_spikes.values()); - } PL(spikeio); // Append events formed from global spikes to per-cell pending event queues. @@ -625,7 +618,7 @@ std::size_t simulation::num_spikes() const { } void simulation::set_global_spike_callback(spike_export_function export_callback) { - impl_->global_export_callback_ = std::move(export_callback); + throw std::runtime_error("Attempted to call deprecated method: set_global_spike_callback"); } void simulation::set_local_spike_callback(spike_export_function export_callback) { diff --git a/doc/cpp/simulation.rst b/doc/cpp/simulation.rst index d0a7359e79..bf87918ab4 100644 --- a/doc/cpp/simulation.rst +++ b/doc/cpp/simulation.rst @@ -86,13 +86,7 @@ Class documentation .. cpp:type:: spike_export_function = std::function&)> User-supplied callback function used as a sink for spikes generated - during a simulation. See :cpp:func:`set_local_spike_callback` and - :cpp:func:`set_global_spike_callback`. - - **Note** This convenience method has non-trivial performance - implications as Arbor will collate all spikes on all ranks. Prefer - ``local`` unless you are absolutely required otherwise and willing to - pay the cost. + during a simulation. See :cpp:func:`set_local_spike_callback`. **Constructor:** @@ -118,10 +112,9 @@ Class documentation **I/O:** - .. cpp:function:: sampler_association_handle add_sampler(\ - cell_member_predicate probeset_ids,\ - schedule sched,\ - sampler_function f) + .. cpp:function:: sampler_association_handle add_sampler(cell_member_predicate probeset_ids,\ + schedule sched,\ + sampler_function f) Note: sampler functions may be invoked from a different thread than that which is called :cpp:func:`run`. @@ -150,13 +143,6 @@ Class documentation The total number of spikes generated since either construction or the last call to :cpp:func:`reset`. - .. cpp:function:: void set_global_spike_callback(spike_export_function export_callback) - - Register a callback that will periodically be passed a vector with all of - the spikes generated over all domains (the global spike vector) since - the last call. - Will be called on the MPI rank/domain with id 0. - .. cpp:function:: void set_local_spike_callback(spike_export_function export_callback) Register a callback that will periodically be passed a vector with all of diff --git a/example/adex/adex.cpp b/example/adex/adex.cpp index cc02d699e2..96dc4afc75 100644 --- a/example/adex/adex.cpp +++ b/example/adex/adex.cpp @@ -96,7 +96,7 @@ int main(int argc, char** argv) { sim.add_sampler(arb::all_probes, arb::regular_schedule(opt.dt * U::ms), sampler); - sim.set_global_spike_callback([](const auto& spks) { + sim.set_local_spike_callback([](const auto& spks) { for (const auto& spk: spks) { std::cerr << spk.time << ", " << spk.source.gid << ", " << spk.source.index << '\n'; } diff --git a/example/brunel/brunel.cpp b/example/brunel/brunel.cpp index 5ea90607e2..c40657a26b 100644 --- a/example/brunel/brunel.cpp +++ b/example/brunel/brunel.cpp @@ -244,15 +244,16 @@ struct brunel_recipe: public recipe { }; int main(int argc, char** argv) { - bool root = true; - try { + bool root = true; + int rank = 0; #ifdef ARB_MPI_ENABLED arbenv::with_mpi guard(argc, argv, false); unsigned num_threads = arbenv::default_concurrency(); int gpu_id = arbenv::find_private_gpu(MPI_COMM_WORLD); auto context = arb::make_context(arb::proc_allocation{num_threads, gpu_id}, MPI_COMM_WORLD); - root = arb::rank(context) == 0; + rank = arb::rank(context); + root = rank == 0; #else auto context = arb::make_context(arbenv::default_allocation()); #endif @@ -277,8 +278,8 @@ int main(int argc, char** argv) { std::fstream spike_out; auto spike_file_output = options.spike_file_output; - if (spike_file_output != "" && root) { - spike_out = sup::open_or_throw(spike_file_output, std::ios_base::out, false); + if (spike_file_output != "") { + spike_out = sup::open_or_throw(spike_file_output + "-" + std::to_string(rank) + ".gdf", std::ios_base::out, false); } meters.checkpoint("setup", context); @@ -326,9 +327,9 @@ int main(int argc, char** argv) { // Set up spike recording. std::vector recorded_spikes; if (spike_out) { - sim.set_global_spike_callback([&recorded_spikes](auto& spikes) { - recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); - }); + sim.set_local_spike_callback([&recorded_spikes](auto& spikes) { + recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); + }); } meters.checkpoint("model-init", context); diff --git a/example/busyring/ring.cpp b/example/busyring/ring.cpp index 5688e008d9..5120014689 100644 --- a/example/busyring/ring.cpp +++ b/example/busyring/ring.cpp @@ -247,6 +247,7 @@ std::string get_arbor_config_str() { } int main(int argc, char** argv) { + int rank = 0; try { bool root = true; @@ -260,7 +261,7 @@ int main(int argc, char** argv) { arbenv::with_mpi guard(argc, argv, false); resources.gpu_id = arbenv::find_private_gpu(MPI_COMM_WORLD); auto context = arb::make_context(resources, MPI_COMM_WORLD); - auto rank = arb::rank(context); + rank = arb::rank(context); root = rank == 0; #else resources.gpu_id = arbenv::default_gpu(); @@ -321,11 +322,10 @@ int main(int argc, char** argv) { // Set up recording of spikes to a vector on the root process. std::vector recorded_spikes; - if (root && params.record_spikes) { - sim.set_global_spike_callback( - [&recorded_spikes](const std::vector& spikes) { - recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); - }); + if (params.record_spikes) { + sim.set_local_spike_callback([&recorded_spikes](const std::vector& spikes) { + recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); + }); } meters.checkpoint("model-init", context); @@ -341,23 +341,19 @@ int main(int argc, char** argv) { // Write spikes to file if (root) { - std::cout << "\n" << ns << " spikes generated at rate of " - << params.duration/ns << " ms between spikes\n" - << recorded_spikes.size() << " where recorded\n" -; - if (!recorded_spikes.empty()) { - std::ofstream fid(params.odir + "/" + params.name + "_spikes.gdf"); - if (!fid.good()) { - std::cerr << "Warning: unable to open file spikes.gdf for spike output\n"; - } - else { - char linebuf[45]; - for (auto spike: recorded_spikes) { - auto n = std::snprintf( - linebuf, sizeof(linebuf), "%u %.4f\n", - unsigned{spike.source.gid}, float(spike.time)); - fid.write(linebuf, n); - } + std::cout << "\n" << ns << " spikes generated at rate of " << params.duration/ns << " ms between spikes\n"; + } + if (!recorded_spikes.empty()) { + std::ofstream fid(params.odir + "/" + params.name + "-" + std::to_string(rank) + "_spikes.gdf"); + if (!fid.good()) { + std::cerr << "Warning: unable to open file spikes.gdf for spike output\n"; + } + else { + char linebuf[45]; + for (auto spike: recorded_spikes) { + auto n = std::snprintf(linebuf, sizeof(linebuf), + "%u %.4f\n", unsigned{spike.source.gid}, float(spike.time)); + fid.write(linebuf, n); } } } diff --git a/example/dryrun/dryrun.cpp b/example/dryrun/dryrun.cpp index b49b651f07..974c008528 100644 --- a/example/dryrun/dryrun.cpp +++ b/example/dryrun/dryrun.cpp @@ -125,6 +125,7 @@ class tile_desc: public arb::tile { }; int main(int argc, char** argv) { + int rank = 0; try { #ifdef ARB_MPI_ENABLED arbenv::with_mpi guard(argc, argv, false); @@ -142,7 +143,8 @@ int main(int argc, char** argv) { else { ctx = arb::make_context(resources, MPI_COMM_WORLD); if (params.defaulted) params.num_ranks = arb::num_ranks(ctx); - root = arb::rank(ctx)==0; + rank = arb::rank(ctx); + root = rank == 0; } #endif @@ -182,12 +184,9 @@ int main(int argc, char** argv) { // Set up recording of spikes to a vector on the root process. std::vector recorded_spikes; - if (root) { - sim.set_global_spike_callback( - [&recorded_spikes](const std::vector& spikes) { - recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); - }); - } + sim.set_local_spike_callback([&recorded_spikes](const std::vector& spikes) { + recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); + }); meters.checkpoint("model-init", ctx); @@ -201,23 +200,20 @@ int main(int argc, char** argv) { << params.duration/ns << " ms between spikes\n\n"; // Write spikes to file - if (root) { - std::ofstream fid("spikes.gdf"); - if (!fid.good()) { - std::cerr << "Warning: unable to open file spikes.gdf for spike output\n"; - } - else { - char linebuf[45]; - for (auto spike: recorded_spikes) { - auto n = std::snprintf( - linebuf, sizeof(linebuf), "%u %.4f\n", - unsigned{spike.source.gid}, float(spike.time)); - fid.write(linebuf, n); - } + std::ofstream fid("spikes" + std::to_string(rank) + ".gdf"); + if (!fid.good()) { + std::cerr << "Warning: unable to open file spikes.gdf for spike output\n"; + } + else { + char linebuf[45]; + for (auto spike: recorded_spikes) { + auto n = std::snprintf(linebuf, sizeof(linebuf), + "%u %.4f\n", unsigned{spike.source.gid}, float(spike.time)); + fid.write(linebuf, n); } - // Write the samples to a json file. - write_trace_json(voltage.at(0)); } + // Write the samples to a json file. + write_trace_json(voltage.at(0)); auto profile = arb::profile::profiler_summary(); std::cout << profile << "\n"; diff --git a/example/gap_junctions/gap_junctions.cpp b/example/gap_junctions/gap_junctions.cpp index ccc3fcd760..5ccd732a36 100644 --- a/example/gap_junctions/gap_junctions.cpp +++ b/example/gap_junctions/gap_junctions.cpp @@ -133,6 +133,7 @@ class gj_recipe: public arb::recipe { }; int main(int argc, char** argv) { + int rank = 0; try { bool root = true; @@ -142,8 +143,7 @@ int main(int argc, char** argv) { int gpu_id = arbenv::find_private_gpu(MPI_COMM_WORLD); auto context = arb::make_context(arb::proc_allocation{nt, gpu_id}, MPI_COMM_WORLD); { - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); + rank = arb::rank(context); root = rank==0; } #else @@ -191,12 +191,9 @@ int main(int argc, char** argv) { // Set up recording of spikes to a vector on the root process. std::vector recorded_spikes; - if (root) { - sim.set_global_spike_callback( - [&recorded_spikes](const std::vector& spikes) { - recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); - }); - } + sim.set_local_spike_callback([&recorded_spikes](const std::vector& spikes) { + recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); + }); meters.checkpoint("model-init", context); @@ -210,20 +207,19 @@ int main(int argc, char** argv) { // Write spikes to file if (root) { - std::cout << "\n" << ns << " spikes generated at rate of " - << params.sim_duration/ns << " ms between spikes\n"; - std::ofstream fid("spikes.gdf"); - if (!fid.good()) { - std::cerr << "Warning: unable to open file spikes.gdf for spike output\n"; - } - else { - char linebuf[45]; - for (auto spike: recorded_spikes) { - auto n = std::snprintf( - linebuf, sizeof(linebuf), "%u %.4f\n", - unsigned{spike.source.gid}, float(spike.time)); - fid.write(linebuf, n); - } + std::cout << "\n" << ns << " spikes generated at rate of " << params.sim_duration/ns << " ms between spikes\n"; + } + std::ofstream fid("spikes-" + std::to_string(rank) + ".gdf"); + if (!fid.good()) { + std::cerr << "Warning: unable to open file spikes.gdf for spike output\n"; + } + else { + char linebuf[45]; + for (auto spike: recorded_spikes) { + auto n = std::snprintf(linebuf, sizeof(linebuf), + "%u %.4f\n", + unsigned{spike.source.gid}, float(spike.time)); + fid.write(linebuf, n); } } diff --git a/example/network_description/network_description.cpp b/example/network_description/network_description.cpp index 8f80814be7..8cc7147fbe 100644 --- a/example/network_description/network_description.cpp +++ b/example/network_description/network_description.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -60,7 +59,6 @@ ring_params read_options(int argc, char** argv); using arb::cell_gid_type; using arb::cell_kind; using arb::cell_lid_type; -using arb::cell_member_type; using arb::cell_size_type; using arb::time_type; @@ -165,6 +163,7 @@ class ring_recipe: public arb::recipe { int main(int argc, char** argv) { try { + int rank = 0; bool root = true; arb::proc_allocation resources; @@ -174,7 +173,8 @@ int main(int argc, char** argv) { arbenv::with_mpi guard(argc, argv, false); resources.gpu_id = arbenv::find_private_gpu(MPI_COMM_WORLD); auto context = arb::make_context(resources, MPI_COMM_WORLD); - root = arb::rank(context) == 0; + rank = arb::rank(context); + root = rank == 0; #else resources.gpu_id = arbenv::default_gpu(); auto context = arb::make_context(resources); @@ -217,12 +217,9 @@ int main(int argc, char** argv) { // Set up recording of spikes to a vector on the root process. std::vector recorded_spikes; - if (root) { - sim.set_global_spike_callback( - [&recorded_spikes](const std::vector& spikes) { - recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); - }); - } + sim.set_local_spike_callback([&recorded_spikes](const std::vector& spikes) { + recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); + }); meters.checkpoint("model-init", context); @@ -250,21 +247,14 @@ int main(int argc, char** argv) { std::cout << "\n" << ns << " spikes generated at rate of " << params.duration / ns << " ms between spikes\n"; - std::ofstream fid("spikes.gdf"); - if (!fid.good()) { - std::cerr << "Warning: unable to open file spikes.gdf for spike output\n"; - } - else { - char linebuf[45]; - for (auto spike: recorded_spikes) { - auto n = std::snprintf(linebuf, - sizeof(linebuf), - "%u %.4f\n", - unsigned{spike.source.gid}, - float(spike.time)); - fid.write(linebuf, n); - } - } + } + std::ofstream fid("spikes-" + std::to_string(rank) + ".gdf"); + if (!fid.good()) std::cerr << "Warning: unable to open file spikes.gdf for spike output\n"; + char linebuf[45]; + for (auto spike: recorded_spikes) { + auto n = std::snprintf(linebuf, sizeof(linebuf), + "%u %.4f\n", unsigned{spike.source.gid}, float(spike.time)); + fid.write(linebuf, n); } // Write the samples to a json file. diff --git a/example/plasticity/plasticity.cpp b/example/plasticity/plasticity.cpp index 110cfb6f39..5ad3ed61b2 100644 --- a/example/plasticity/plasticity.cpp +++ b/example/plasticity/plasticity.cpp @@ -113,7 +113,7 @@ int main(int argc, char** argv) { auto ctx = arb::make_context(arb::proc_allocation{8, -1}); auto sim = arb::simulation(rec, ctx); sim.add_sampler(arb::all_probes, arb::regular_schedule(dt*arb::units::ms), sampler); - sim.set_global_spike_callback(spike_cb); + sim.set_local_spike_callback(spike_cb); print_header(0, 1); sim.run(1.0*arb::units::ms, dt*arb::units::ms); rec.add_connection(2); diff --git a/example/ring/ring.cpp b/example/ring/ring.cpp index db488dfcd4..822aa79314 100644 --- a/example/ring/ring.cpp +++ b/example/ring/ring.cpp @@ -171,12 +171,9 @@ int main(int argc, char** argv) { // Set up recording of spikes to a vector on the root process. std::vector recorded_spikes; - if (root) { - sim.set_global_spike_callback( - [&recorded_spikes](const std::vector& spikes) { - recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); - }); - } + sim.set_local_spike_callback([&recorded_spikes](const std::vector& spikes) { + recorded_spikes.insert(recorded_spikes.end(), spikes.begin(), spikes.end()); + }); meters.checkpoint("model-init", context); diff --git a/python/simulation.cpp b/python/simulation.cpp index ea23429fa3..9166ba0b53 100644 --- a/python/simulation.cpp +++ b/python/simulation.cpp @@ -136,17 +136,11 @@ class simulation_shim { switch (policy) { case spike_recording::off: - sim_->set_global_spike_callback(); sim_->set_local_spike_callback(); break; case spike_recording::local: - sim_->set_global_spike_callback(); sim_->set_local_spike_callback(spike_recorder); break; - case spike_recording::all: - sim_->set_global_spike_callback(spike_recorder); - sim_->set_local_spike_callback(); - break; } } diff --git a/test/unit/test_adex_cell_group.cpp b/test/unit/test_adex_cell_group.cpp index 5783d96925..40a882221e 100644 --- a/test/unit/test_adex_cell_group.cpp +++ b/test/unit/test_adex_cell_group.cpp @@ -177,7 +177,7 @@ TEST(adex_cell_group, ring) std::vector spike_buffer; - sim.set_global_spike_callback( + sim.set_local_spike_callback( [&spike_buffer](const std::vector& spikes) { spike_buffer.insert(spike_buffer.end(), spikes.begin(), spikes.end()); } @@ -236,7 +236,7 @@ TEST(adex_cell_group, probe) { std::vector spikes; - sim.set_global_spike_callback([&spikes](const std::vector& spk) { + sim.set_local_spike_callback([&spikes](const std::vector& spk) { for (const auto& s: spk) spikes.push_back(s); }); @@ -648,8 +648,8 @@ TEST(adex_cell_group, probe) { ASSERT_FALSE(testing::seq_eq(ums[{1, "a"}], exp)); // now check the spikes std::sort(spikes.begin(), spikes.end()); - EXPECT_EQ(spikes.size(), 0u); - std::vector sexp{}; + EXPECT_EQ(spikes.size(), 6u); + std::vector sexp{{{0, 0}, 2}, {{0, 0}, 3}, {{0, 0}, 4}, {{0, 0}, 5}, {{1, 0}, 2}, {{1, 0}, 5}}; ASSERT_EQ(spikes, sexp); } @@ -671,7 +671,7 @@ TEST(adex_cell_group, probe_with_connections) { std::vector spikes; - sim.set_global_spike_callback( + sim.set_local_spike_callback( [&spikes](const std::vector& spk) { for (const auto& s: spk) spikes.push_back(s); } ); @@ -1085,7 +1085,7 @@ TEST(adex_cell_group, probe_with_connections) { ASSERT_FALSE(testing::seq_eq(ums[{1, "a"}], exp)); // now check the spikes std::sort(spikes.begin(), spikes.end()); - EXPECT_EQ(spikes.size(), 4u); - std::vector sexp{{{0, 0}, 2}, {{0, 0}, 3}, {{0, 0}, 4}, {{0, 0}, 5},}; + EXPECT_EQ(spikes.size(), 6u); + std::vector sexp{{{0, 0}, 2}, {{0, 0}, 3}, {{0, 0}, 4}, {{0, 0}, 5}, {{1, 0}, 2}, {{1, 0}, 5}}; ASSERT_EQ(spikes, sexp); } diff --git a/test/unit/test_lif_cell_group.cpp b/test/unit/test_lif_cell_group.cpp index 624ec4f53f..614dd39a2c 100644 --- a/test/unit/test_lif_cell_group.cpp +++ b/test/unit/test_lif_cell_group.cpp @@ -202,7 +202,7 @@ TEST(lif_cell_group, ring) std::vector spike_buffer; - sim.set_global_spike_callback( + sim.set_local_spike_callback( [&spike_buffer](const std::vector& spikes) { spike_buffer.insert(spike_buffer.end(), spikes.begin(), spikes.end()); } @@ -696,7 +696,7 @@ TEST(lif_cell_group, probe_with_connections) { std::vector spikes; - sim.set_global_spike_callback( + sim.set_local_spike_callback( [&spikes](const std::vector& spk) { for (const auto& s: spk) spikes.push_back(s.time); } ); diff --git a/test/unit/test_simulation.cpp b/test/unit/test_simulation.cpp index ea29be92a8..3527e153d9 100644 --- a/test/unit/test_simulation.cpp +++ b/test/unit/test_simulation.cpp @@ -76,7 +76,7 @@ TEST(simulation, null_builder) { } } -TEST(simulation, spike_global_callback) { +TEST(simulation, spike_local_callback) { constexpr unsigned n = 5; double t_max = 10.; @@ -175,7 +175,7 @@ TEST(simulation, restart) { simulation sim(rec, ctx, decomp); std::vector collected; - sim.set_global_spike_callback([&](const std::vector& spikes) { + sim.set_local_spike_callback([&](const std::vector& spikes) { collected.insert(collected.end(), spikes.begin(), spikes.end()); }); diff --git a/test/unit/test_spikes.cpp b/test/unit/test_spikes.cpp index cceb3d20de..f71e989b82 100644 --- a/test/unit/test_spikes.cpp +++ b/test/unit/test_spikes.cpp @@ -232,7 +232,7 @@ TEST(SPIKES_TEST_CLASS, threshold_watcher_interpolation) { auto decomp = arb::partition_load_balance(rec, context); arb::simulation sim(rec, context, decomp); - sim.set_global_spike_callback( + sim.set_local_spike_callback( [&spikes](const std::vector& recorded_spikes) { spikes.insert(spikes.end(), recorded_spikes.begin(), recorded_spikes.end()); }); From 6523bab908171cce66093df0148ed2517069ea43 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Fri, 29 May 2026 09:18:55 +0200 Subject: [PATCH 4/5] re-scope `root` --- example/brunel/brunel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/brunel/brunel.cpp b/example/brunel/brunel.cpp index c40657a26b..db0d632e84 100644 --- a/example/brunel/brunel.cpp +++ b/example/brunel/brunel.cpp @@ -244,8 +244,8 @@ struct brunel_recipe: public recipe { }; int main(int argc, char** argv) { + bool root = true; try { - bool root = true; int rank = 0; #ifdef ARB_MPI_ENABLED arbenv::with_mpi guard(argc, argv, false); From eec4f3cfeaee1a0eaf76e0e2f86db2ce19250e97 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Fri, 29 May 2026 09:36:09 +0200 Subject: [PATCH 5/5] Format. --- python/example/network_ring_mpi.py | 6 ++++-- python/example/single_cell_bluepyopt_l5pc.py | 16 +++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/python/example/network_ring_mpi.py b/python/example/network_ring_mpi.py index 7858799d22..73b3899a16 100644 --- a/python/example/network_ring_mpi.py +++ b/python/example/network_ring_mpi.py @@ -114,7 +114,7 @@ def event_generators(self, gid): # (10) Place a probe at the root of each cell. def probes(self, gid): - return [A.cable_probe_membrane_voltage('"root"', tag='Um')] + return [A.cable_probe_membrane_voltage('"root"', tag="Um")] def global_properties(self, kind): return self.props @@ -139,7 +139,9 @@ def global_properties(self, kind): # (15) Attach a sampler to the voltage probe on cell 0. Sample rate of 1 sample every ms. # Sampling period increased w.r.t network_ring.py to reduce amount of data -handles = [sim.sample((gid, 'Um'), A.regular_schedule(1 * U.ms)) for gid in range(ncells)] +handles = [ + sim.sample((gid, "Um"), A.regular_schedule(1 * U.ms)) for gid in range(ncells) +] # (16) Run simulation sim.run(ncells * 5 * U.ms) diff --git a/python/example/single_cell_bluepyopt_l5pc.py b/python/example/single_cell_bluepyopt_l5pc.py index b6143c38a2..f5ffd40ce1 100755 --- a/python/example/single_cell_bluepyopt_l5pc.py +++ b/python/example/single_cell_bluepyopt_l5pc.py @@ -29,19 +29,21 @@ ) # (3) Define stimulus and spike detector, adjust discretization -decor.place('"soma_center"', A.i_clamp(tstart=295*U.ms, duration=5*U.ms, current=1.9*U.nA)) +decor.place( + '"soma_center"', A.i_clamp(tstart=295 * U.ms, duration=5 * U.ms, current=1.9 * U.nA) +) # Add spike detector -decor.place('"soma_center"', A.threshold_detector(-10*U.mV), "detector") +decor.place('"soma_center"', A.threshold_detector(-10 * U.mV), "detector") # Adjust discretization (single CV on soma, default everywhere else) -cvp = A.cv_policy_max_extent_um(1.0*U.um) | A.cv_policy_single('"soma"') +cvp = A.cv_policy_max_extent_um(1.0 * U.um) | A.cv_policy_single('"soma"') # (4) Create the cell. cell = A.cable_cell(morpho, decor, labels, cvp) # (5) Declare a probe. -probe = A.cable_probe_membrane_voltage('"dend1"', tag='Um') +probe = A.cable_probe_membrane_voltage('"dend1"', tag="Um") # (6) Create a class that inherits from A.recipe @@ -92,11 +94,11 @@ def global_properties(self, gid): # Instruct the simulation to record the spikes and sample the probe sim.record(A.spike_recording.all) -probe_id = A.cell_member(0, 'Um') -handle = sim.sample(probe_id, A.regular_schedule(0.02*U.ms)) +probe_id = A.cell_member(0, "Um") +handle = sim.sample(probe_id, A.regular_schedule(0.02 * U.ms)) # (8) Run the simulation -sim.run(tfinal=600*U.ms, dt=0.025*U.ms) +sim.run(tfinal=600 * U.ms, dt=0.025 * U.ms) # (9) Print or display the results spikes = sim.spikes()