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.
@@ -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,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ uv.lock
6
+ build/
7
+ dist/
8
+ *.egg-info/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .hypothesis/
13
+ .coverage
14
+ htmlcov/
15
+ .DS_Store
@@ -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.
@@ -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.
@@ -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
+ [![CI](https://github.com/amaar-mc/spikedist/actions/workflows/ci.yml/badge.svg)](https://github.com/amaar-mc/spikedist/actions/workflows/ci.yml)
55
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./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
+ [![CI](https://github.com/amaar-mc/spikedist/actions/workflows/ci.yml/badge.svg)](https://github.com/amaar-mc/spikedist/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./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.