Skip to content

Apply the border correction before pooling spike trains in instantaneous_rate - #708

Open
adityasingh2400 wants to merge 2 commits into
NeuralEnsemble:masterfrom
adityasingh2400:fix/instantaneous-rate-border-correction-pooling
Open

Apply the border correction before pooling spike trains in instantaneous_rate#708
adityasingh2400 wants to merge 2 commits into
NeuralEnsemble:masterfrom
adityasingh2400:fix/instantaneous-rate-border-correction-pooling

Conversation

@adityasingh2400

Copy link
Copy Markdown

Combining the two documented flags border_correction=True and pool_spike_trains=True on a list of spike trains raises IndexError.

import neo
import quantities as pq
from elephant.kernels import GaussianKernel
from elephant.statistics import instantaneous_rate

st1 = neo.SpikeTrain([0.1, 0.3, 0.5, 0.7] * pq.s, t_start=0*pq.s, t_stop=1*pq.s)
st2 = neo.SpikeTrain([0.2, 0.4, 0.6, 0.8] * pq.s, t_start=0*pq.s, t_stop=1*pq.s)

instantaneous_rate([st1, st2], sampling_period=10 * pq.ms,
                   kernel=GaussianKernel(50 * pq.ms),
                   border_correction=True, pool_spike_trains=True)

On current master:

  File "elephant/statistics.py", line 1139, in instantaneous_rate
    rate[:, i] *= len(spiketrain) /\
    ~~~~^^^^^^
IndexError: index 1 is out of bounds for axis 1 with size 1

Pooling runs first and collapses the column axis to a single column, then the border correction loops over every input spike train and rescales rate[:, i], so every column past the first is out of bounds.

The order is the bug, not the indexing. The correction is defined per spike train, and its second half rescales a column so that the integral over it returns the spike count of the matching spike train, so it has to run while the columns still correspond one to one with the spike trains. Correcting first also preserves the documented meaning of pool_spike_trains, "calculate firing rates averaged over spike trains", because the pooled rate is then exactly the mean over the columns of the pool_spike_trains=False result, an invariant that already holds when border_correction=False.

This is not a judgement call, the same order is already in the file. For an elephant.trials input the trials branch passes pool_spike_trains: False down and averages the corrected per spike train rates afterwards. The two routes disagree today: with the same four spike trains and the same flags, wrapping them in TrialsFromLists works while passing them as a plain list raises IndexError. After this change both return the same array, with a max absolute difference of 0.0, which the test now asserts.

Behaviour on inputs that already worked is unchanged. With border_correction=False, pool_spike_trains=True the output is bit identical to master for both trim=False and trim=True, including shape, t_start, sampling_period and the kernel annotation.

The new test checks the output shape (n_bins, 1), equality with the column mean of the un-pooled result, that the integral over the pooled rate equals the mean spike count, and agreement with the trials path.

Reverting statistics.py to master fails with the IndexError above. After, 102 passed, 1 skipped and 33 subtests passed. pycodestyle reports no new findings. The 5 pre-existing statistics.py doctest failures are identical on master and on this branch, consistent with known issue #686.

I have not added myself to doc/authors.rst, since it is a numbered institutional affiliation list. Happy to add an entry if you would like one.

Disclosure: this change was prepared with AI assistance. I have reviewed and tested it.

instantaneous_rate(border_correction=True, pool_spike_trains=True)
raised IndexError. Pooling collapsed the column axis of the rate to a
single column, and the border correction that runs afterwards loops over
all input spike trains and rescales rate[:, i] per spike train, so every
column past the first was out of bounds.

The correction is defined per spike train, it rescales a column so that
the integral over it returns the spike count of the matching spike
train, which means it has to run before the columns are averaged.
Correcting first also keeps the documented meaning of pool_spike_trains,
'firing rates averaged over spike trains', because the pooled rate is
then exactly the mean over the columns of the pool_spike_trains=False
result. It matches the order already used for elephant.trials input,
where the trials branch computes the per spike train rates with
pool_spike_trains=False and averages the corrected result afterwards.
The same spike trains wrapped in an elephant.trials object already
return the mean of the border corrected per spike train rates, because
the trials branch passes pool_spike_trains=False down and averages
afterwards. Assert that the list input now agrees with it exactly.
@adityasingh2400

Copy link
Copy Markdown
Author

The docs jobs here are failing on a remote fetch, not on this branch.

The sphinx build dies executing a notebook that pulls sample data over HTTPS, ending in HTTPError: HTTP Error 403: Forbidden. That is the download being refused, so the same build should pass once the remote is serving normally again.

The other elephant PR I have open, #706, is red on the same theme from the other direction, with SSLCertVerificationError on the TestDownloadDatasets cases. Neither of these branches touches anything involved in fetching data.

@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Status

coverage: 45.709% (-42.9%) from 88.605% — adityasingh2400:fix/instantaneous-rate-border-correction-pooling into NeuralEnsemble:master

@adityasingh2400

Copy link
Copy Markdown
Author

Following up on my note above, which only covered the docs job. The pip, mpi and conda jobs have since gone red too, and they are the same story.

On the latest run here the only failures are test_datasets.py::TestDownloadDatasets::test_valid_data_with_integrity_check and test_valid_data_with_failed_integrity_check, with URLError: [Errno 110] Connection timed out, against 773 passed and 5 skipped. Across my three open PRs the same two tests have now failed with three different network errors in about an hour, a certificate verification failure, a 403, and now a timeout, which points at the data host rather than at any branch.

Worth flagging one knock-on effect: Coveralls reports coverage dropping to roughly 45.7 percent from 88.6. That looks alarming but is consistent with the failing jobs never uploading their coverage, leaving only a partial report merged. I would not read it as a real coverage regression from these changes.

@CozySocksAlways

Copy link
Copy Markdown
Contributor

Hello :)
Thanks for the contributions! Most of the failing tests are due to network issues with the GitHub runner, which you can safely ignore for now. The docs failure however looks like an unrelated upstream dependency going wonky, I'll look into it as well.
And yup your read on the coverage is correct, it's only a partial report, meaning it isn't a real regression.
I'll have a go through your changes and get back to you soon.

@CozySocksAlways
CozySocksAlways self-requested a review August 5, 2026 16:54
@CozySocksAlways CozySocksAlways added the bugfix Fix for an indentified bug. label Aug 5, 2026
@adityasingh2400

Copy link
Copy Markdown
Author

Thanks, and good to have the coverage read confirmed rather than assumed.

No rush on the review. I will leave the three of them alone rather than pushing rebases at them, since nothing on these branches will move those particular jobs.

One small data point for the docs failure if it helps when you dig in: across the three PRs that fetch has now failed four different ways in about an hour, a certificate verification error, a 403, a connection reset, and a timeout. That spread looks more like the host refusing or throttling than a single dependency behaving badly, though it could of course be both.

@adityasingh2400

Copy link
Copy Markdown
Author

Thanks, that all matches what I found.

On the docs failure, I had already chased that one down before your message and opened #709 for it, so you may not need to spend time on it.

Short version: requirements-docs.txt asks for jupyter>=1.0.0, which pulls IPython unpinned, and IPython 9.16 removed the backend2gui alias that matplotlib below 3.9 imports in install_repl_displayhook(). import viziphant reaches it through holoviews calling plt.switch_backend.

Raising the matplotlib cap does not work, which I only found by trying it. viziphant 0.4.0 is the latest release and still calls matplotlib.cm.get_cmap, removed in 3.9, so the cap is load bearing. Pinning ipython<9.16 restores the last working combination and spade.ipynb executes clean again.

Happy to close #709 if you would rather fix it a different way, it is a two line change.

@adityasingh2400

Copy link
Copy Markdown
Author

I managed to pull the full job logs for all five of my open PRs here, so this is now measured rather than inferred. Posting it on this one since the picture is the same across all of them.

PR result failing tests
#706 2 failed, 773 passed test_valid_data_with_failed_integrity_check, test_valid_data_with_integrity_check
#707 2 failed, 773 passed same two
#708 1 failed, 773 passed test_valid_data_with_failed_integrity_check
#709 3 failed, 770 passed test_valid_data, test_valid_data_with_integrity_check, test_valid_data_with_path
#710 2 failed, 771 passed test_valid_data_existing, test_valid_data_with_integrity_check

Every failure in all five is in elephant/test/test_datasets.py::TestDownloadDatasets, and nothing else fails anywhere. The errors are SSLCertVerificationError: certificate verify failed: unable to get local issuer certificate and ConnectionResetError: [Errno 104] Connection reset by peer, both raised out of urllib while fetching remote datasets.

The part I find most telling is that the count differs between runs, 1 through 3, and the set of failing tests differs too, even though none of these branches touch the download path. Same code, different tests failing each time, which is what a flaky remote fetch looks like rather than a real regression.

None of the five needs a change from me as far as I can tell. Happy to be told otherwise if you see something in there I am reading wrong.

@adityasingh2400

Copy link
Copy Markdown
Author

Correcting one line in my last comment and replacing it with better evidence.

I wrote "same code, different tests failing each time". That was sloppy, those are five different branches, so it is not the same code and the variation across them proves less than I implied.

Here is the check that does settle it. master fails the same way with no PR involved. The Canary Tests run on master from 2026-08-05:

FAILED elephant/test/test_datasets.py::TestDownloadDatasets::test_valid_data_existing
  - urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] ...>
FAILED elephant/test/test_datasets.py::TestDownloadDatasets::test_valid_data_with_integrity_check
  - urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] ...>
===== 2 failed, 771 passed, 5 skipped in 966.51s =====

Same test class, same SSL error, same shape as all five of my PRs, on your own default branch. So this is not something any of the branches introduced, and the varying subset across runs is consistent with which downloads happen to fail on a given attempt.

The rest of what I posted stands: every failure in all five PRs is confined to TestDownloadDatasets, and nothing else fails anywhere.

@adityasingh2400

Copy link
Copy Markdown
Author

One more data point, and it makes this cheaper to clear than my last comment implied.

The Canary failures are intermittent rather than a standing break. On master:

2026-08-05  failure
2026-08-03  failure
2026-07-27  success
2026-07-20  success
2026-07-13  failure
2026-07-06  success

All five of my open PRs last ran CI on 2026-08-05 between 15:47 and 17:33, inside that failing window, and none has re-run since. So a re-run is plausibly all they need rather than a fix to the certificate handling. I do not have permission to trigger one.

@adityasingh2400

Copy link
Copy Markdown
Author

Thanks for the re-run. It cleared two of the four, pip and mpi both pass now.

The two that remain are separate problems.

docs fails on:

ImportError: cannot import name 'backend2gui' from 'IPython.core.pylabtools'

That is exactly what #709 pins against, and requirements-docs.txt on master still carries no ipython cap. Merging #709 should clear this job here and on the other open PRs.

conda still fails the five TestDownloadDatasets tests, but the error has changed since 05 August. It was a certificate verification failure, it is now HTTP Error 403: Forbidden on https://gin.g-node.org/NeuralEnsemble/elephant-data/raw/v1.2.2b1/README.md.

One lead on that rather than a diagnosis, since I cannot reproduce the 403 from outside CI. That URL is built from the package version, which is 1.2.2b1 on master. From here the v1.2.2b1 ref returns 404 while raw/master/README.md on the same data repo returns 200, so the data repo may not carry a ref matching the current version.

@adityasingh2400

Copy link
Copy Markdown
Author

Correcting my last comment. I said the conda 403 looked like it was on the data host and offered a missing-ref lead. The same run refutes that.

Within that run, on the same commit and within three minutes of each other:

pip    774 passed, 5 skipped
mpi    774 passed, 5 skipped
conda  5 failed, 769 passed

774 is 769 plus 5, so conda runs the same test set and fails exactly the five TestDownloadDatasets tests that pip and mpi pass. If gin were rejecting the requests, or if the v1.2.2b1 ref were missing, all three jobs would fail rather than one. So the 403 is specific to the conda environment, and my missing-ref suggestion was wrong.

I do not have a cause beyond that, so I will not guess at one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix for an indentified bug.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants