spikedist 0.2.0__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.
- spikedist-0.2.0/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- spikedist-0.2.0/.github/ISSUE_TEMPLATE/feature_request.md +18 -0
- spikedist-0.2.0/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- spikedist-0.2.0/.github/workflows/ci.yml +22 -0
- spikedist-0.2.0/.gitignore +15 -0
- spikedist-0.2.0/CHANGELOG.md +27 -0
- spikedist-0.2.0/CLAUDE.md +45 -0
- spikedist-0.2.0/CODE_OF_CONDUCT.md +37 -0
- spikedist-0.2.0/CONTRIBUTING.md +38 -0
- spikedist-0.2.0/LICENSE +21 -0
- spikedist-0.2.0/PKG-INFO +178 -0
- spikedist-0.2.0/README.md +127 -0
- spikedist-0.2.0/SECURITY.md +19 -0
- spikedist-0.2.0/docs/architecture.md +91 -0
- spikedist-0.2.0/docs/charter.md +36 -0
- spikedist-0.2.0/examples/compare.py +24 -0
- spikedist-0.2.0/pyproject.toml +60 -0
- spikedist-0.2.0/src/spikedist/__init__.py +10 -0
- spikedist-0.2.0/src/spikedist/_validate.py +18 -0
- spikedist-0.2.0/src/spikedist/hunter_milton.py +58 -0
- spikedist-0.2.0/src/spikedist/pairwise.py +21 -0
- spikedist-0.2.0/src/spikedist/py.typed +0 -0
- spikedist-0.2.0/src/spikedist/schreiber.py +52 -0
- spikedist-0.2.0/src/spikedist/van_rossum.py +50 -0
- spikedist-0.2.0/src/spikedist/victor_purpura.py +47 -0
- spikedist-0.2.0/tests/test_hunter_milton.py +45 -0
- spikedist-0.2.0/tests/test_pairwise.py +25 -0
- spikedist-0.2.0/tests/test_properties.py +61 -0
- spikedist-0.2.0/tests/test_schreiber.py +41 -0
- spikedist-0.2.0/tests/test_van_rossum.py +49 -0
- spikedist-0.2.0/tests/test_victor_purpura.py +48 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: A wrong distance or unexpected behavior
|
|
4
|
+
title: ""
|
|
5
|
+
labels: bug
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Input
|
|
9
|
+
|
|
10
|
+
The two spike trains and the parameters, for example
|
|
11
|
+
`victor_purpura([0.0, 1.0], [0.5], cost=1.0)`.
|
|
12
|
+
|
|
13
|
+
## Expected
|
|
14
|
+
|
|
15
|
+
The value you expected, with a source if it is a published reference.
|
|
16
|
+
|
|
17
|
+
## Actual
|
|
18
|
+
|
|
19
|
+
The value you observed.
|
|
20
|
+
|
|
21
|
+
## Environment
|
|
22
|
+
|
|
23
|
+
- spikedist version:
|
|
24
|
+
- Python version:
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest a metric or capability for spikedist
|
|
4
|
+
title: ""
|
|
5
|
+
labels: enhancement
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## What
|
|
9
|
+
|
|
10
|
+
The metric or capability you would like.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
The analysis this would support.
|
|
15
|
+
|
|
16
|
+
## References
|
|
17
|
+
|
|
18
|
+
The paper or definition that pins down the expected behavior and normalization.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
What this changes and why.
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] Tests added or updated (a bug fix starts with a failing test)
|
|
8
|
+
- [ ] Exact reference-value test for any new metric
|
|
9
|
+
- [ ] Property tests for the relevant metric axioms
|
|
10
|
+
- [ ] `uv run ruff check .` passes
|
|
11
|
+
- [ ] `uv run mypy src` passes
|
|
12
|
+
- [ ] `uv run pytest` passes
|
|
13
|
+
- [ ] No runtime dependencies added
|
|
14
|
+
- [ ] No em dash characters in docs or commit messages
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python }}
|
|
19
|
+
- run: pip install -e ".[dev]"
|
|
20
|
+
- run: ruff check .
|
|
21
|
+
- run: mypy src
|
|
22
|
+
- run: pytest -q
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Planned
|
|
10
|
+
- Multi-unit van Rossum.
|
|
11
|
+
- Fast O(n) van Rossum via the Houghton-Kreuz markage recursion.
|
|
12
|
+
- An optional NumPy fast path for large pairwise computations.
|
|
13
|
+
|
|
14
|
+
## [0.2.0]
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `schreiber(a, b, *, sigma)`: Gaussian correlation similarity in [0, 1].
|
|
18
|
+
- `hunter_milton(a, b, *, tau)`: nearest-neighbor similarity in (0, 1].
|
|
19
|
+
- `pairwise(trains, metric)`: full matrix of any metric over a list of trains.
|
|
20
|
+
|
|
21
|
+
## [0.1.0]
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `victor_purpura(a, b, *, cost)`: Victor-Purpura distance via an O(n*m) dynamic program.
|
|
25
|
+
- `van_rossum(a, b, *, tau)`: van Rossum distance via the closed form of the exponential-kernel inner products.
|
|
26
|
+
- Input validation with clear errors; spikes treated as an unordered set and sorted internally.
|
|
27
|
+
- Test suite with exact closed-form reference values and Hypothesis property tests for the metric axioms.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# spikedist
|
|
2
|
+
|
|
3
|
+
Pure-Python spike-train distance metrics. Zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
- Create env and install: `uv venv && uv pip install -e ".[dev]"`
|
|
8
|
+
- Test: `uv run pytest -q`
|
|
9
|
+
- Lint: `uv run ruff check .` (format with `uv run ruff format .`)
|
|
10
|
+
- Types: `uv run mypy src`
|
|
11
|
+
- Build: `uv build` (then `uv run twine check dist/*` before publishing)
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
`src/spikedist/` with one module per metric:
|
|
16
|
+
- `_validate.py` shared input validation; returns sorted finite spike times
|
|
17
|
+
- `victor_purpura.py` edit distance via an O(n*m) dynamic program
|
|
18
|
+
- `van_rossum.py` kernel distance via the closed form of exponential-kernel inner products
|
|
19
|
+
- `__init__.py` public surface
|
|
20
|
+
|
|
21
|
+
See `docs/architecture.md` for the algorithms and the van Rossum normalization.
|
|
22
|
+
|
|
23
|
+
## Conventions
|
|
24
|
+
|
|
25
|
+
- Pure functions, strict typing, zero runtime dependencies.
|
|
26
|
+
- Metric parameters (`cost`, `tau`) are required keyword-only arguments; no default values.
|
|
27
|
+
- Spikes are an unordered set of times, sorted internally.
|
|
28
|
+
- Validate inputs and raise clear ValueError messages on bad data.
|
|
29
|
+
|
|
30
|
+
## Testing rules
|
|
31
|
+
|
|
32
|
+
- Exact closed-form reference values for each metric (see README definitions).
|
|
33
|
+
- Property tests (Hypothesis) for metric axioms: identity, symmetry, non-negativity, triangle inequality.
|
|
34
|
+
- Bug fixes start with a failing test.
|
|
35
|
+
|
|
36
|
+
## Release
|
|
37
|
+
|
|
38
|
+
- Semantic versioning; update `CHANGELOG.md` and `__version__`.
|
|
39
|
+
- Gates: `uv run pytest && uv run ruff check . && uv run mypy src && uv build && uv run twine check dist/*`.
|
|
40
|
+
- Prefer TestPyPI first, then PyPI. Tag `vX.Y.Z` and a GitHub release.
|
|
41
|
+
|
|
42
|
+
## Style
|
|
43
|
+
|
|
44
|
+
- No em dash characters in docs, comments, or commit messages.
|
|
45
|
+
- Comments explain non-obvious reasoning only.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and maintainers pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
|
10
|
+
orientation.
|
|
11
|
+
|
|
12
|
+
## Our standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to a positive environment:
|
|
15
|
+
|
|
16
|
+
- Showing empathy and kindness toward other people.
|
|
17
|
+
- Being respectful of differing opinions, viewpoints, and experiences.
|
|
18
|
+
- Giving and gracefully accepting constructive feedback.
|
|
19
|
+
- Focusing on what is best for the community.
|
|
20
|
+
|
|
21
|
+
Examples of unacceptable behavior:
|
|
22
|
+
|
|
23
|
+
- Harassment, insulting or derogatory comments, and personal or political attacks.
|
|
24
|
+
- Public or private harassment.
|
|
25
|
+
- Publishing others' private information without explicit permission.
|
|
26
|
+
- Other conduct which could reasonably be considered inappropriate.
|
|
27
|
+
|
|
28
|
+
## Enforcement
|
|
29
|
+
|
|
30
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
31
|
+
reported to the maintainer at amaar2cool@gmail.com. All complaints will be
|
|
32
|
+
reviewed and investigated promptly and fairly.
|
|
33
|
+
|
|
34
|
+
## Attribution
|
|
35
|
+
|
|
36
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
|
37
|
+
version 2.1.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Contributing to spikedist
|
|
2
|
+
|
|
3
|
+
Thanks for your interest. This project values correctness, a small surface area,
|
|
4
|
+
and zero runtime dependencies.
|
|
5
|
+
|
|
6
|
+
## Development
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
uv venv
|
|
10
|
+
uv pip install -e ".[dev]"
|
|
11
|
+
uv run pytest -q
|
|
12
|
+
uv run ruff check .
|
|
13
|
+
uv run mypy src
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If you do not use uv, a standard virtual environment with `pip install -e ".[dev]"`
|
|
17
|
+
works the same way.
|
|
18
|
+
|
|
19
|
+
## Guidelines
|
|
20
|
+
|
|
21
|
+
- No runtime dependencies. NumPy and friends may be optional accelerators only.
|
|
22
|
+
- Every metric needs exact reference-value tests and property tests for the
|
|
23
|
+
metric axioms it satisfies (identity, symmetry, non-negativity, and the
|
|
24
|
+
triangle inequality where it holds).
|
|
25
|
+
- Metric parameters are required keyword-only arguments. No default values.
|
|
26
|
+
- Keep functions pure and fully typed; `mypy --strict` must pass.
|
|
27
|
+
- A bug fix starts with a failing test.
|
|
28
|
+
- Commit messages follow `type(scope): description`.
|
|
29
|
+
|
|
30
|
+
## Adding a metric
|
|
31
|
+
|
|
32
|
+
State the definition and its normalization in the docstring and the README, give
|
|
33
|
+
at least one exact closed-form test case, and add the relevant property tests.
|
|
34
|
+
|
|
35
|
+
## Reporting issues
|
|
36
|
+
|
|
37
|
+
Open an issue with the two spike trains, the parameters, the expected value with
|
|
38
|
+
a source, and the value you observed.
|
spikedist-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amaar Chughtai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
spikedist-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spikedist
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Spike-train distance and similarity metrics (Victor-Purpura, van Rossum, Schreiber, Hunter-Milton) in pure Python with zero dependencies.
|
|
5
|
+
Project-URL: Homepage, https://github.com/amaar-mc/spikedist
|
|
6
|
+
Project-URL: Repository, https://github.com/amaar-mc/spikedist
|
|
7
|
+
Project-URL: Issues, https://github.com/amaar-mc/spikedist/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/amaar-mc/spikedist/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Amaar Chughtai
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Amaar Chughtai
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: computational-neuroscience,neuromorphic,neuroscience,spike-distance,spike-metric,spike-train,spiking-neural-networks,van-rossum,victor-purpura
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Science/Research
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Topic :: Scientific/Engineering
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
43
|
+
Classifier: Typing :: Typed
|
|
44
|
+
Requires-Python: >=3.10
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: hypothesis>=6; extra == 'dev'
|
|
47
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
48
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
49
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
# spikedist
|
|
53
|
+
|
|
54
|
+
[](https://github.com/amaar-mc/spikedist/actions/workflows/ci.yml)
|
|
55
|
+
[](./LICENSE)
|
|
56
|
+
|
|
57
|
+
Spike-train distance and similarity metrics in pure Python with zero dependencies. Implements the Victor-Purpura and van Rossum distances and the Schreiber and Hunter-Milton similarities on plain sequences of spike times.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
pip install spikedist
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 30-second example
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from spikedist import victor_purpura, van_rossum, schreiber, hunter_milton
|
|
69
|
+
|
|
70
|
+
a = [0.010, 0.025, 0.090] # spike times in seconds
|
|
71
|
+
b = [0.012, 0.030, 0.095]
|
|
72
|
+
|
|
73
|
+
victor_purpura(a, b, cost=100.0) # edit distance, cost is the q parameter
|
|
74
|
+
van_rossum(a, b, tau=0.012) # kernel distance, tau is the time constant
|
|
75
|
+
schreiber(a, b, sigma=0.010) # Gaussian correlation similarity in [0, 1]
|
|
76
|
+
hunter_milton(a, b, tau=0.012) # nearest-neighbor similarity in (0, 1]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Spike times can be Python lists, tuples, or any sequence of numbers, including
|
|
80
|
+
NumPy arrays. They are treated as an unordered set of event times and sorted
|
|
81
|
+
internally. There is no NumPy requirement.
|
|
82
|
+
|
|
83
|
+
## Why this exists
|
|
84
|
+
|
|
85
|
+
The Victor-Purpura and van Rossum distances are two of the most cited spike-train
|
|
86
|
+
metrics, but every Python implementation lives inside a heavy framework or a
|
|
87
|
+
compiled extension:
|
|
88
|
+
|
|
89
|
+
- `elephant` implements both, but requires `neo` and `quantities` and works on
|
|
90
|
+
`neo.SpikeTrain` objects with units.
|
|
91
|
+
- `pymuvr` is a fast multi-unit van Rossum implementation, but is a C++ extension
|
|
92
|
+
and requires NumPy.
|
|
93
|
+
- `pyspike` is excellent for ISI-distance, SPIKE-distance, and SPIKE-synchrony,
|
|
94
|
+
but does not implement Victor-Purpura or van Rossum.
|
|
95
|
+
|
|
96
|
+
`spikedist` is a small, typed, dependency-free package for when you just want the
|
|
97
|
+
distance between two spike trains.
|
|
98
|
+
|
|
99
|
+
## Definitions
|
|
100
|
+
|
|
101
|
+
### Victor-Purpura
|
|
102
|
+
|
|
103
|
+
`victor_purpura(a, b, *, cost)` is the minimum total cost to turn train `a` into
|
|
104
|
+
train `b` using three operations: insert a spike (cost 1), delete a spike
|
|
105
|
+
(cost 1), and shift a spike by `dt` (cost `cost * abs(dt)`). `cost` is the
|
|
106
|
+
parameter usually written `q`. It is computed with an O(n*m) dynamic program.
|
|
107
|
+
|
|
108
|
+
- `cost = 0` counts only the difference in spike count.
|
|
109
|
+
- As `cost` grows, shifting becomes expensive and each unmatched spike approaches
|
|
110
|
+
a cost of 2.
|
|
111
|
+
|
|
112
|
+
### van Rossum
|
|
113
|
+
|
|
114
|
+
`van_rossum(a, b, *, tau)` convolves each train with a causal exponential kernel
|
|
115
|
+
`exp(-t / tau)` and returns the Euclidean distance between the filtered signals.
|
|
116
|
+
Using the closed form of the kernel inner products,
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
D^2 = 0.5 * (Saa + Sbb - 2 * Sab), Sxy = sum over spike pairs exp(-|xi - yj| / tau)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
With this normalization the distance between an empty train and a single spike is
|
|
123
|
+
`sqrt(0.5)`, and as `tau` grows large the distance approaches
|
|
124
|
+
`abs(len(a) - len(b)) / sqrt(2)`.
|
|
125
|
+
|
|
126
|
+
Both distances are true metrics: non-negative, symmetric, zero only between equal
|
|
127
|
+
trains, and they satisfy the triangle inequality. These properties are tested.
|
|
128
|
+
|
|
129
|
+
### Schreiber similarity
|
|
130
|
+
|
|
131
|
+
`schreiber(a, b, *, sigma)` convolves each train with a Gaussian of width `sigma`
|
|
132
|
+
and returns the cosine similarity of the filtered signals, in `[0, 1]`. It is 1
|
|
133
|
+
for identical trains.
|
|
134
|
+
|
|
135
|
+
### Hunter-Milton similarity
|
|
136
|
+
|
|
137
|
+
`hunter_milton(a, b, *, tau)` scores each spike by `exp(-dt / tau)` to its nearest
|
|
138
|
+
neighbor in the other train and averages over both trains, giving a value in
|
|
139
|
+
`(0, 1]`. It is 1 for identical trains.
|
|
140
|
+
|
|
141
|
+
By convention both similarities treat two empty trains as identical (1.0) and a
|
|
142
|
+
non-empty train against an empty one as fully dissimilar (0.0).
|
|
143
|
+
|
|
144
|
+
### Pairwise matrices
|
|
145
|
+
|
|
146
|
+
`pairwise(trains, metric)` builds the full matrix of any metric over a list of
|
|
147
|
+
trains. Parameterize the metric with `functools.partial`:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from functools import partial
|
|
151
|
+
from spikedist import pairwise, van_rossum
|
|
152
|
+
|
|
153
|
+
pairwise(trains, partial(van_rossum, tau=0.01))
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Roadmap
|
|
157
|
+
|
|
158
|
+
- Multi-unit van Rossum.
|
|
159
|
+
- Fast O(n) van Rossum via the Houghton-Kreuz markage recursion.
|
|
160
|
+
- An optional NumPy fast path for large pairwise computations.
|
|
161
|
+
|
|
162
|
+
## Testing
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
pip install -e ".[dev]"
|
|
166
|
+
pytest
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Tests cover exact closed-form reference values and metric-property invariants
|
|
170
|
+
(identity, symmetry, non-negativity, triangle inequality) via Hypothesis.
|
|
171
|
+
|
|
172
|
+
## Contributing
|
|
173
|
+
|
|
174
|
+
Issues and pull requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# spikedist
|
|
2
|
+
|
|
3
|
+
[](https://github.com/amaar-mc/spikedist/actions/workflows/ci.yml)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
Spike-train distance and similarity metrics in pure Python with zero dependencies. Implements the Victor-Purpura and van Rossum distances and the Schreiber and Hunter-Milton similarities on plain sequences of spike times.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pip install spikedist
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 30-second example
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from spikedist import victor_purpura, van_rossum, schreiber, hunter_milton
|
|
18
|
+
|
|
19
|
+
a = [0.010, 0.025, 0.090] # spike times in seconds
|
|
20
|
+
b = [0.012, 0.030, 0.095]
|
|
21
|
+
|
|
22
|
+
victor_purpura(a, b, cost=100.0) # edit distance, cost is the q parameter
|
|
23
|
+
van_rossum(a, b, tau=0.012) # kernel distance, tau is the time constant
|
|
24
|
+
schreiber(a, b, sigma=0.010) # Gaussian correlation similarity in [0, 1]
|
|
25
|
+
hunter_milton(a, b, tau=0.012) # nearest-neighbor similarity in (0, 1]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Spike times can be Python lists, tuples, or any sequence of numbers, including
|
|
29
|
+
NumPy arrays. They are treated as an unordered set of event times and sorted
|
|
30
|
+
internally. There is no NumPy requirement.
|
|
31
|
+
|
|
32
|
+
## Why this exists
|
|
33
|
+
|
|
34
|
+
The Victor-Purpura and van Rossum distances are two of the most cited spike-train
|
|
35
|
+
metrics, but every Python implementation lives inside a heavy framework or a
|
|
36
|
+
compiled extension:
|
|
37
|
+
|
|
38
|
+
- `elephant` implements both, but requires `neo` and `quantities` and works on
|
|
39
|
+
`neo.SpikeTrain` objects with units.
|
|
40
|
+
- `pymuvr` is a fast multi-unit van Rossum implementation, but is a C++ extension
|
|
41
|
+
and requires NumPy.
|
|
42
|
+
- `pyspike` is excellent for ISI-distance, SPIKE-distance, and SPIKE-synchrony,
|
|
43
|
+
but does not implement Victor-Purpura or van Rossum.
|
|
44
|
+
|
|
45
|
+
`spikedist` is a small, typed, dependency-free package for when you just want the
|
|
46
|
+
distance between two spike trains.
|
|
47
|
+
|
|
48
|
+
## Definitions
|
|
49
|
+
|
|
50
|
+
### Victor-Purpura
|
|
51
|
+
|
|
52
|
+
`victor_purpura(a, b, *, cost)` is the minimum total cost to turn train `a` into
|
|
53
|
+
train `b` using three operations: insert a spike (cost 1), delete a spike
|
|
54
|
+
(cost 1), and shift a spike by `dt` (cost `cost * abs(dt)`). `cost` is the
|
|
55
|
+
parameter usually written `q`. It is computed with an O(n*m) dynamic program.
|
|
56
|
+
|
|
57
|
+
- `cost = 0` counts only the difference in spike count.
|
|
58
|
+
- As `cost` grows, shifting becomes expensive and each unmatched spike approaches
|
|
59
|
+
a cost of 2.
|
|
60
|
+
|
|
61
|
+
### van Rossum
|
|
62
|
+
|
|
63
|
+
`van_rossum(a, b, *, tau)` convolves each train with a causal exponential kernel
|
|
64
|
+
`exp(-t / tau)` and returns the Euclidean distance between the filtered signals.
|
|
65
|
+
Using the closed form of the kernel inner products,
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
D^2 = 0.5 * (Saa + Sbb - 2 * Sab), Sxy = sum over spike pairs exp(-|xi - yj| / tau)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
With this normalization the distance between an empty train and a single spike is
|
|
72
|
+
`sqrt(0.5)`, and as `tau` grows large the distance approaches
|
|
73
|
+
`abs(len(a) - len(b)) / sqrt(2)`.
|
|
74
|
+
|
|
75
|
+
Both distances are true metrics: non-negative, symmetric, zero only between equal
|
|
76
|
+
trains, and they satisfy the triangle inequality. These properties are tested.
|
|
77
|
+
|
|
78
|
+
### Schreiber similarity
|
|
79
|
+
|
|
80
|
+
`schreiber(a, b, *, sigma)` convolves each train with a Gaussian of width `sigma`
|
|
81
|
+
and returns the cosine similarity of the filtered signals, in `[0, 1]`. It is 1
|
|
82
|
+
for identical trains.
|
|
83
|
+
|
|
84
|
+
### Hunter-Milton similarity
|
|
85
|
+
|
|
86
|
+
`hunter_milton(a, b, *, tau)` scores each spike by `exp(-dt / tau)` to its nearest
|
|
87
|
+
neighbor in the other train and averages over both trains, giving a value in
|
|
88
|
+
`(0, 1]`. It is 1 for identical trains.
|
|
89
|
+
|
|
90
|
+
By convention both similarities treat two empty trains as identical (1.0) and a
|
|
91
|
+
non-empty train against an empty one as fully dissimilar (0.0).
|
|
92
|
+
|
|
93
|
+
### Pairwise matrices
|
|
94
|
+
|
|
95
|
+
`pairwise(trains, metric)` builds the full matrix of any metric over a list of
|
|
96
|
+
trains. Parameterize the metric with `functools.partial`:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from functools import partial
|
|
100
|
+
from spikedist import pairwise, van_rossum
|
|
101
|
+
|
|
102
|
+
pairwise(trains, partial(van_rossum, tau=0.01))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Roadmap
|
|
106
|
+
|
|
107
|
+
- Multi-unit van Rossum.
|
|
108
|
+
- Fast O(n) van Rossum via the Houghton-Kreuz markage recursion.
|
|
109
|
+
- An optional NumPy fast path for large pairwise computations.
|
|
110
|
+
|
|
111
|
+
## Testing
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
pip install -e ".[dev]"
|
|
115
|
+
pytest
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Tests cover exact closed-form reference values and metric-property invariants
|
|
119
|
+
(identity, symmetry, non-negativity, triangle inequality) via Hypothesis.
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
|
|
123
|
+
Issues and pull requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
`spikedist` is a pure computation library with no runtime dependencies, no
|
|
6
|
+
network access, and no file system access. The attack surface is limited to
|
|
7
|
+
incorrect results from malformed input, which the library guards against with
|
|
8
|
+
explicit validation.
|
|
9
|
+
|
|
10
|
+
## Reporting a vulnerability
|
|
11
|
+
|
|
12
|
+
If you find a security issue, please email amaar2cool@gmail.com with details and
|
|
13
|
+
steps to reproduce. Please do not open a public issue for security reports. You
|
|
14
|
+
can expect an initial response within a few days.
|
|
15
|
+
|
|
16
|
+
## Supported versions
|
|
17
|
+
|
|
18
|
+
The latest published minor version receives fixes. Pre-1.0 releases may introduce
|
|
19
|
+
breaking changes in minor versions, as allowed by semantic versioning.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`spikedist` is a small set of pure functions, one module per metric, sharing a
|
|
4
|
+
single input-validation helper. There is no shared state and no I/O.
|
|
5
|
+
|
|
6
|
+
## Input handling
|
|
7
|
+
|
|
8
|
+
`_validate.check_train` converts any sequence of numbers to a list of floats,
|
|
9
|
+
rejects non-finite values with a clear error, and sorts ascending. Spikes are
|
|
10
|
+
treated as an unordered set of event times, so callers do not need to pre-sort.
|
|
11
|
+
|
|
12
|
+
## Victor-Purpura distance
|
|
13
|
+
|
|
14
|
+
`victor_purpura(a, b, *, cost)` is computed with the standard dynamic program.
|
|
15
|
+
Let `g[i][j]` be the minimum cost to align the first `i` spikes of `a` with the
|
|
16
|
+
first `j` spikes of `b`. Then
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
g[0][j] = j
|
|
20
|
+
g[i][0] = i
|
|
21
|
+
g[i][j] = min(
|
|
22
|
+
g[i-1][j] + 1, # delete a_i
|
|
23
|
+
g[i][j-1] + 1, # insert b_j
|
|
24
|
+
g[i-1][j-1] + cost * |a_i - b_j|, # shift a_i onto b_j
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The implementation keeps only the previous row, so it runs in O(n*m) time and
|
|
29
|
+
O(m) memory.
|
|
30
|
+
|
|
31
|
+
## van Rossum distance
|
|
32
|
+
|
|
33
|
+
`van_rossum(a, b, *, tau)` uses the closed form of the distance between the two
|
|
34
|
+
trains after convolution with a causal exponential kernel `exp(-t / tau)`. The
|
|
35
|
+
inner product of two filtered trains is, up to the shared factor `tau / 2`,
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
S(x, y) = sum over spike pairs exp(-|xi - yj| / tau)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
so the squared distance is
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
D^2 = 0.5 * (S(a, a) + S(b, b) - 2 * S(a, b))
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This avoids numerical integration. A tiny negative value from floating-point
|
|
48
|
+
cancellation is clamped to zero before the square root.
|
|
49
|
+
|
|
50
|
+
### Normalization
|
|
51
|
+
|
|
52
|
+
With this definition the distance between an empty train and a single spike is
|
|
53
|
+
`sqrt(0.5)`, and as `tau` grows large the distance approaches
|
|
54
|
+
`|len(a) - len(b)| / sqrt(2)`. The choice is stated so results are reproducible
|
|
55
|
+
and comparable. Libraries that normalize the empty-versus-single distance to 1
|
|
56
|
+
differ from this by a constant factor.
|
|
57
|
+
|
|
58
|
+
## Schreiber similarity
|
|
59
|
+
|
|
60
|
+
`schreiber(a, b, *, sigma)` is the cosine similarity of the two trains after
|
|
61
|
+
convolution with a Gaussian of standard deviation `sigma`. The Gaussian inner
|
|
62
|
+
product reduces to a closed form, so with `K(d) = exp(-d^2 / (4 * sigma^2))` and
|
|
63
|
+
`S(x, y) = sum over spike pairs K(xi - yj)` the similarity is
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
S(a, b) / sqrt(S(a, a) * S(b, b))
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Cauchy-Schwarz bounds this by 1; a tiny floating-point overshoot is clamped. Two
|
|
70
|
+
empty trains are defined as similarity 1.0 and a single empty train as 0.0.
|
|
71
|
+
|
|
72
|
+
## Hunter-Milton similarity
|
|
73
|
+
|
|
74
|
+
`hunter_milton(a, b, *, tau)` scores each spike by `exp(-dt / tau)` where `dt` is
|
|
75
|
+
the distance to its nearest neighbor in the other train, averages that over one
|
|
76
|
+
train, and then averages the two directions. Nearest neighbors are found by
|
|
77
|
+
binary search on the sorted target train, so the cost is O((n + m) log m). The
|
|
78
|
+
empty-train conventions match the Schreiber measure.
|
|
79
|
+
|
|
80
|
+
## Pairwise matrices
|
|
81
|
+
|
|
82
|
+
`pairwise(trains, metric)` evaluates a caller-supplied metric over every ordered
|
|
83
|
+
pair of trains and returns a list of rows. It assumes nothing about the metric,
|
|
84
|
+
so it makes no symmetry shortcut; the cost is O(n^2) metric calls.
|
|
85
|
+
|
|
86
|
+
## Why pure Python
|
|
87
|
+
|
|
88
|
+
The two metrics are short, well-specified algorithms. Implementing them without
|
|
89
|
+
NumPy keeps installation trivial and the package usable anywhere. A NumPy fast
|
|
90
|
+
path for large pairwise computations is planned as an optional extra, never a
|
|
91
|
+
hard requirement.
|