From 9a624185e008be4f8c3314377345de7fd531f6a9 Mon Sep 17 00:00:00 2001 From: chaoming Date: Wed, 8 Jul 2026 17:41:08 +0800 Subject: [PATCH] fix(dyn): PoissonGroup never spikes due to boolean rand_like dtype PoissonGroup.update drew random values with rand_like(self.spike.value). Since spike is boolean-typed and rand_like inherits the input dtype, the draw was boolean (approximately all True), so the spike-probability comparison was always False and the group never fired. This silenced every network driven by PoissonGroup, including the decision_making_network and stdp simulation examples. Force a real uniform draw with dtype=float. Add firing-rate regression tests (default and batching mode); the previous shape-only test missed this. --- brainpy/dyn/others/input.py | 7 ++++--- brainpy/dyn/others/input_test.py | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/brainpy/dyn/others/input.py b/brainpy/dyn/others/input.py index 58b9fa770..9cd2b3f5d 100644 --- a/brainpy/dyn/others/input.py +++ b/brainpy/dyn/others/input.py @@ -230,10 +230,11 @@ def __init__( self.reset_state(self.mode) def update(self): - spikes = bm.random.rand_like(self.spike.value) <= (self.freqs * share['dt'] / 1000.) + # ``dtype=float`` is required: ``self.spike`` is boolean, and ``rand_like`` + # defaults to the input dtype. Without it the draw is boolean (~all True), + # making the ``<= prob`` comparison always False so the group never fires. + spikes = bm.random.rand_like(self.spike.value, dtype=float) <= (self.freqs * share['dt'] / 1000.) spikes = bm.asarray(spikes, dtype=self.spk_type) - # import jax - # jax.debug.print('PoissonGroup: freqs = {f}, spikes = {s}', f=self.freqs, s=spikes) self.spike.value = spikes return spikes diff --git a/brainpy/dyn/others/input_test.py b/brainpy/dyn/others/input_test.py index 762c9abf3..475d1385c 100644 --- a/brainpy/dyn/others/input_test.py +++ b/brainpy/dyn/others/input_test.py @@ -13,9 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== +import numpy as np from absl.testing import parameterized import brainpy as bp +import brainpy.math as bm from brainpy.dyn.others import input @@ -35,3 +37,36 @@ def test_PoissonGroup(self): progress_bar=False) runner.run(30.) self.assertTupleEqual(runner.mon['spike'].shape, (300, 2)) + + def test_PoissonGroup_fires_at_expected_rate(self): + # Regression: PoissonGroup must actually emit spikes at ~freqs Hz. + # A dtype bug (boolean rand_like) previously made it fire at 0 Hz while + # still returning the correct output shape, so a shape-only check missed it. + bm.random.seed(1234) + freqs = 200. # Hz + num = 200 + duration = 1000. # ms + model = input.PoissonGroup(size=num, freqs=freqs) + runner = bp.DSRunner(model, monitors=['spike'], progress_bar=False) + runner.run(duration) + spikes = np.asarray(runner.mon['spike']) + empirical_rate = spikes.sum() / num / (duration / 1000.) + # Poisson counting noise here is < 1%; 15% tolerance is safe yet still + # rejects the 0 Hz regression by a wide margin. + self.assertAlmostEqual(empirical_rate, freqs, delta=0.15 * freqs) + + def test_PoissonGroup_fires_in_batching_mode(self): + # The float draw must preserve the batched shape of ``spike`` and still + # produce the expected rate across the batch axis. + bm.random.seed(1234) + freqs = 200. # Hz + num, batch = 30, 4 + duration = 1000. # ms + model = input.PoissonGroup(size=num, freqs=freqs, mode=bm.BatchingMode(batch)) + runner = bp.DSRunner(model, monitors=['spike'], progress_bar=False) + runner.run(duration) + spikes = np.asarray(runner.mon['spike']) # (batch, time, num) + self.assertEqual(spikes.shape[0], batch) + self.assertEqual(spikes.shape[-1], num) + empirical_rate = spikes.sum() / (batch * num) / (duration / 1000.) + self.assertAlmostEqual(empirical_rate, freqs, delta=0.15 * freqs)