spikegen 0.1.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 or non-reproducible spike train
4
+ title: ""
5
+ labels: bug
6
+ ---
7
+
8
+ ## Call
9
+
10
+ The generator and parameters, including the seed, for example
11
+ `homogeneous_poisson(rate=50, duration=2, seed=0)`.
12
+
13
+ ## Expected
14
+
15
+ What you expected (a property, a count range, or reproducibility).
16
+
17
+ ## Actual
18
+
19
+ What you observed.
20
+
21
+ ## Environment
22
+
23
+ - spikegen version:
24
+ - Python version:
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a process or capability for spikegen
4
+ title: ""
5
+ labels: enhancement
6
+ ---
7
+
8
+ ## What
9
+
10
+ The process or capability you would like.
11
+
12
+ ## Why
13
+
14
+ The workflow this would support.
15
+
16
+ ## References
17
+
18
+ The definition or paper that pins down the expected behavior.
@@ -0,0 +1,13 @@
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
+ - [ ] Seeded reproducibility and range invariants covered for any new generator
9
+ - [ ] `uv run ruff check .` passes
10
+ - [ ] `uv run mypy src` passes
11
+ - [ ] `uv run pytest` passes
12
+ - [ ] No runtime dependencies added
13
+ - [ ] 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,20 @@
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 population generation.
11
+ - An optional NumPy fast path.
12
+
13
+ ## [0.1.0]
14
+
15
+ ### Added
16
+ - `regular`, `homogeneous_poisson`, `inhomogeneous_poisson` (thinning), and `gamma_renewal`
17
+ spike-train generators returning plain sorted lists of times, with explicit seeds.
18
+ - `with_refractory` to enforce a minimum inter-spike interval.
19
+ - Validation with clear errors, and a test suite with exact values, seeded reproducibility,
20
+ structural invariants, and Hypothesis property tests.
@@ -0,0 +1,48 @@
1
+ # spikegen
2
+
3
+ Pure-Python spike-train generators. Zero runtime dependencies. Complements spikedist.
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 --with twine twine check dist/*` before publishing)
12
+
13
+ ## Architecture
14
+
15
+ `src/spikegen/`:
16
+ - `_validate.py` shared validation (rate, duration, seed, positivity)
17
+ - `processes.py` the generators (regular, homogeneous and inhomogeneous Poisson, gamma
18
+ renewal) plus with_refractory
19
+ - `__init__.py` public surface
20
+
21
+ See `docs/architecture.md` for the algorithms.
22
+
23
+ ## Conventions
24
+
25
+ - Generators return plain sorted `list[float]` of spike times.
26
+ - Stochastic generators take an explicit integer `seed` and are reproducible via
27
+ `random.Random(seed)`.
28
+ - Parameters are keyword-only; no default values.
29
+ - Use `1 - random()` inside the exponential-interval log to avoid log(0).
30
+ - No runtime dependencies (standard library `random` and `math` only).
31
+
32
+ ## Testing rules
33
+
34
+ - Exact values for the deterministic generators (regular, with_refractory).
35
+ - Seeded reproducibility for the stochastic generators.
36
+ - Structural invariants (sorted, within [0, duration)).
37
+ - Hypothesis property tests; bug fixes start with a failing test.
38
+
39
+ ## Release
40
+
41
+ - Semantic versioning; update `CHANGELOG.md` and `__version__`.
42
+ - Gates: `uv run pytest && uv run ruff check . && uv run mypy src && uv build && uv run twine check dist/*`.
43
+ - Publish to PyPI, tag `vX.Y.Z`, GitHub release.
44
+
45
+ ## Style
46
+
47
+ - No em dash characters in docs, comments, or commit messages.
48
+ - 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,33 @@
1
+ # Contributing to spikegen
2
+
3
+ Thanks for your interest. This project values correctness, reproducibility, and zero
4
+ 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
+ A standard virtual environment with `pip install -e ".[dev]"` works the same way.
17
+
18
+ ## Guidelines
19
+
20
+ - No runtime dependencies. The standard library `random` and `math` are enough.
21
+ - Seeded generators must be reproducible: the same seed gives the same train. Tests assert
22
+ this.
23
+ - Every generator needs structural tests (sorted, within range) and, where it is
24
+ deterministic, exact-value tests.
25
+ - Parameters are keyword-only with no default values.
26
+ - A bug fix starts with a failing test.
27
+ - Run `uv run ruff format .` before committing.
28
+ - Commit messages follow `type(scope): description`.
29
+
30
+ ## Reporting issues
31
+
32
+ Open an issue with the call and its parameters, the seed, and what you expected versus what
33
+ you observed.
spikegen-0.1.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.
@@ -0,0 +1,123 @@
1
+ Metadata-Version: 2.4
2
+ Name: spikegen
3
+ Version: 0.1.0
4
+ Summary: Generate spike trains (Poisson, gamma renewal, regular, inhomogeneous) in pure Python with zero dependencies.
5
+ Project-URL: Homepage, https://github.com/amaar-mc/spikegen
6
+ Project-URL: Repository, https://github.com/amaar-mc/spikegen
7
+ Project-URL: Issues, https://github.com/amaar-mc/spikegen/issues
8
+ Project-URL: Changelog, https://github.com/amaar-mc/spikegen/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,point-process,poisson-process,renewal-process,simulation,spike-train,spiking-neural-networks
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
+ # spikegen
53
+
54
+ <p align="center">
55
+ <img src="assets/logo.png" alt="spikegen logo" width="160">
56
+ </p>
57
+
58
+ [![PyPI](https://img.shields.io/pypi/v/spikegen)](https://pypi.org/project/spikegen/)
59
+ [![CI](https://github.com/amaar-mc/spikegen/actions/workflows/ci.yml/badge.svg)](https://github.com/amaar-mc/spikegen/actions/workflows/ci.yml)
60
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
61
+
62
+ Generate spike trains in pure Python with zero dependencies. Poisson, gamma renewal, regular, and inhomogeneous processes, returned as plain sorted lists of spike times, with explicit seeds for reproducibility.
63
+
64
+ ## Install
65
+
66
+ ```sh
67
+ pip install spikegen
68
+ ```
69
+
70
+ ## 30-second example
71
+
72
+ ```python
73
+ from spikegen import homogeneous_poisson, regular, gamma_renewal, with_refractory
74
+
75
+ homogeneous_poisson(rate=50.0, duration=2.0, seed=0) # Poisson spikes in [0, 2)
76
+ regular(rate=10.0, duration=1.0) # [0.0, 0.1, 0.2, ...]
77
+ gamma_renewal(rate=20.0, shape=2.0, duration=1.0, seed=0) # more regular than Poisson
78
+
79
+ spikes = homogeneous_poisson(rate=80.0, duration=1.0, seed=0)
80
+ with_refractory(spikes, refractory=0.002) # enforce a 2 ms refractory period
81
+ ```
82
+
83
+ Times are in the same units as `1 / rate` (seconds if rate is in Hz). Seeded processes are
84
+ reproducible: the same seed gives the same train.
85
+
86
+ ## Why this exists
87
+
88
+ Generating synthetic spike trains is a daily need, but the generators live inside heavy
89
+ frameworks: `elephant` requires neo and quantities, `pyspike` is NumPy-based, and other
90
+ options are old or partial. `spikegen` is a small, dependency-free generator that returns
91
+ plain lists of floats, so reproducible spike trains are one import away. It pairs with
92
+ [spikedist](https://pypi.org/project/spikedist/): generate trains, then measure the
93
+ distance between them.
94
+
95
+ ## Processes
96
+
97
+ - `regular(rate, duration)`: evenly spaced spikes. Deterministic.
98
+ - `homogeneous_poisson(rate, duration, seed)`: constant-rate Poisson process.
99
+ - `inhomogeneous_poisson(rate_fn, max_rate, duration, seed)`: time-varying rate by thinning.
100
+ - `gamma_renewal(rate, shape, duration, seed)`: gamma inter-spike intervals; shape 1 is
101
+ Poisson, larger shape is more regular.
102
+ - `with_refractory(times, refractory)`: drop spikes within a minimum interval.
103
+
104
+ All parameters are keyword-only and explicit.
105
+
106
+ ## Testing
107
+
108
+ ```sh
109
+ pip install -e ".[dev]"
110
+ pytest
111
+ ```
112
+
113
+ Tests cover exact values for the deterministic generators, seeded reproducibility, the
114
+ rate-bound and ordering invariants, and the validation paths, with property tests via
115
+ Hypothesis.
116
+
117
+ ## Contributing
118
+
119
+ Issues and pull requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md).
120
+
121
+ ## License
122
+
123
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,72 @@
1
+ # spikegen
2
+
3
+ <p align="center">
4
+ <img src="assets/logo.png" alt="spikegen logo" width="160">
5
+ </p>
6
+
7
+ [![PyPI](https://img.shields.io/pypi/v/spikegen)](https://pypi.org/project/spikegen/)
8
+ [![CI](https://github.com/amaar-mc/spikegen/actions/workflows/ci.yml/badge.svg)](https://github.com/amaar-mc/spikegen/actions/workflows/ci.yml)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
10
+
11
+ Generate spike trains in pure Python with zero dependencies. Poisson, gamma renewal, regular, and inhomogeneous processes, returned as plain sorted lists of spike times, with explicit seeds for reproducibility.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ pip install spikegen
17
+ ```
18
+
19
+ ## 30-second example
20
+
21
+ ```python
22
+ from spikegen import homogeneous_poisson, regular, gamma_renewal, with_refractory
23
+
24
+ homogeneous_poisson(rate=50.0, duration=2.0, seed=0) # Poisson spikes in [0, 2)
25
+ regular(rate=10.0, duration=1.0) # [0.0, 0.1, 0.2, ...]
26
+ gamma_renewal(rate=20.0, shape=2.0, duration=1.0, seed=0) # more regular than Poisson
27
+
28
+ spikes = homogeneous_poisson(rate=80.0, duration=1.0, seed=0)
29
+ with_refractory(spikes, refractory=0.002) # enforce a 2 ms refractory period
30
+ ```
31
+
32
+ Times are in the same units as `1 / rate` (seconds if rate is in Hz). Seeded processes are
33
+ reproducible: the same seed gives the same train.
34
+
35
+ ## Why this exists
36
+
37
+ Generating synthetic spike trains is a daily need, but the generators live inside heavy
38
+ frameworks: `elephant` requires neo and quantities, `pyspike` is NumPy-based, and other
39
+ options are old or partial. `spikegen` is a small, dependency-free generator that returns
40
+ plain lists of floats, so reproducible spike trains are one import away. It pairs with
41
+ [spikedist](https://pypi.org/project/spikedist/): generate trains, then measure the
42
+ distance between them.
43
+
44
+ ## Processes
45
+
46
+ - `regular(rate, duration)`: evenly spaced spikes. Deterministic.
47
+ - `homogeneous_poisson(rate, duration, seed)`: constant-rate Poisson process.
48
+ - `inhomogeneous_poisson(rate_fn, max_rate, duration, seed)`: time-varying rate by thinning.
49
+ - `gamma_renewal(rate, shape, duration, seed)`: gamma inter-spike intervals; shape 1 is
50
+ Poisson, larger shape is more regular.
51
+ - `with_refractory(times, refractory)`: drop spikes within a minimum interval.
52
+
53
+ All parameters are keyword-only and explicit.
54
+
55
+ ## Testing
56
+
57
+ ```sh
58
+ pip install -e ".[dev]"
59
+ pytest
60
+ ```
61
+
62
+ Tests cover exact values for the deterministic generators, seeded reproducibility, the
63
+ rate-bound and ordering invariants, and the validation paths, with property tests via
64
+ Hypothesis.
65
+
66
+ ## Contributing
67
+
68
+ Issues and pull requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md).
69
+
70
+ ## License
71
+
72
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,18 @@
1
+ # Security Policy
2
+
3
+ ## Scope
4
+
5
+ `spikegen` is a pure computation library with no runtime dependencies, no network access,
6
+ and no file system access. The attack surface is limited to incorrect results from
7
+ malformed input, which the library guards against with explicit validation.
8
+
9
+ ## Reporting a vulnerability
10
+
11
+ If you find a security issue, please email amaar2cool@gmail.com with details and steps to
12
+ reproduce. Please do not open a public issue for security reports. You can expect an
13
+ initial response within a few days.
14
+
15
+ ## Supported versions
16
+
17
+ The latest published minor version receives fixes. Pre-1.0 releases may introduce breaking
18
+ changes in minor versions, as allowed by semantic versioning.
Binary file
@@ -0,0 +1,45 @@
1
+ # Architecture
2
+
3
+ `spikegen` is a small set of pure functions over the standard library `random` and `math`.
4
+ Each generator returns a sorted `list[float]` of spike times in `[0, duration)`.
5
+
6
+ ## Regular
7
+
8
+ `regular` places spikes at `0, 1/rate, 2/rate, ...`. The index is multiplied by the step
9
+ rather than accumulated, so floating-point error does not drift across a long train.
10
+
11
+ ## Homogeneous Poisson
12
+
13
+ `homogeneous_poisson` draws exponential inter-spike intervals: the next interval is
14
+ `-ln(1 - U) / rate` for a uniform U. Using `1 - U` keeps the argument of the log away from
15
+ zero. Spikes accumulate until the time leaves `[0, duration)`.
16
+
17
+ ## Inhomogeneous Poisson
18
+
19
+ `inhomogeneous_poisson` uses thinning: propose spikes from a homogeneous process at
20
+ `max_rate`, then keep each proposed spike at time t with probability `rate_fn(t) / max_rate`.
21
+ `rate_fn` must return a value in `[0, max_rate]`; a value outside that range raises, since
22
+ it would make the thinning probability invalid.
23
+
24
+ ## Gamma renewal
25
+
26
+ `gamma_renewal` draws inter-spike intervals from a gamma distribution with the given shape
27
+ and a scale of `1 / (rate * shape)`, so the mean interval is `1 / rate`. Shape 1 reduces to
28
+ the exponential intervals of a Poisson process; larger shapes give more regular spiking.
29
+
30
+ ## Refractory filter
31
+
32
+ `with_refractory` sorts the input and keeps a spike only when it is at least `refractory`
33
+ after the previously kept spike, which models an absolute refractory period.
34
+
35
+ ## Reproducibility
36
+
37
+ Every stochastic generator takes an integer `seed` and uses `random.Random(seed)`, so the
38
+ same arguments always produce the same train. The tests rely on this for exact assertions
39
+ and confirm that different seeds give different trains.
40
+
41
+ ## Why pure Python
42
+
43
+ The processes are short and standard, and the standard library provides uniform and gamma
44
+ variates. Implementing them with no dependencies keeps installation trivial and the output
45
+ (plain lists of floats) immediately usable, including by spikedist.
@@ -0,0 +1,32 @@
1
+ # Charter
2
+
3
+ ## Purpose
4
+
5
+ Provide correct, reproducible, dependency-free spike-train generators that return plain
6
+ lists of spike times, so synthetic spike data is one import away and drops straight into
7
+ other lightweight tools.
8
+
9
+ ## Scope
10
+
11
+ - The common point processes: regular, homogeneous Poisson, inhomogeneous Poisson (by
12
+ thinning), and gamma renewal.
13
+ - A refractory-period filter.
14
+ - Explicit seeding for reproducibility.
15
+
16
+ ## Non-goals
17
+
18
+ - A simulation framework or neuron models. spikegen produces spike times, not dynamics.
19
+ - A required NumPy dependency. NumPy may appear later only as an optional accelerator.
20
+ - Analysis of spike trains. spikedist covers distances and similarities.
21
+
22
+ ## Principles
23
+
24
+ - Correctness and reproducibility first. Seeded generators are deterministic and tested.
25
+ - Zero runtime dependencies.
26
+ - Small, stable, explicit API. Keyword-only parameters.
27
+ - Plain data out (lists of floats), so it composes with anything.
28
+
29
+ ## Audience
30
+
31
+ Computational neuroscience researchers and students, and neuromorphic engineers who need
32
+ reproducible synthetic spike trains without a framework.
@@ -0,0 +1,11 @@
1
+ # Logo prompt
2
+
3
+ The logo in `assets/logo.png` was generated with OpenAI `gpt-image-1` (1024x1024,
4
+ medium quality) from this prompt:
5
+
6
+ > Minimal geometric logo for a library that generates neural spike trains. A single
7
+ > horizontal baseline with thin vertical tick marks at irregular intervals rising from it,
8
+ > like a freshly generated Poisson spike train. Flat vector, premium research-lab
9
+ > aesthetic. White background, black ticks and baseline, one pale blue accent tick. Subtle
10
+ > thin grid. Centered, generous negative space, no text, no letters, no robots, no neon,
11
+ > crisp and high-signal.
@@ -0,0 +1,19 @@
1
+ """Generate a few spike trains and print their spike counts.
2
+
3
+ Run with: python examples/generate.py
4
+ """
5
+
6
+ from spikegen import gamma_renewal, homogeneous_poisson, regular, with_refractory
7
+
8
+ duration = 1.0
9
+
10
+ poisson = homogeneous_poisson(rate=50.0, duration=duration, seed=0)
11
+ gamma = gamma_renewal(rate=50.0, shape=4.0, duration=duration, seed=0)
12
+ clock = regular(rate=50.0, duration=duration)
13
+ refractory = with_refractory(poisson, refractory=0.005)
14
+
15
+ print(f"duration: {duration} s, target rate: 50 Hz")
16
+ print(f" regular {len(clock)} spikes")
17
+ print(f" poisson {len(poisson)} spikes")
18
+ print(f" gamma (shape 4) {len(gamma)} spikes")
19
+ print(f" poisson + 5 ms ref {len(refractory)} spikes")
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "spikegen"
7
+ version = "0.1.0"
8
+ description = "Generate spike trains (Poisson, gamma renewal, regular, inhomogeneous) in pure Python with zero dependencies."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Amaar Chughtai" }]
13
+ keywords = [
14
+ "spike-train",
15
+ "neuroscience",
16
+ "computational-neuroscience",
17
+ "neuromorphic",
18
+ "poisson-process",
19
+ "renewal-process",
20
+ "point-process",
21
+ "spiking-neural-networks",
22
+ "simulation",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 4 - Beta",
26
+ "Intended Audience :: Science/Research",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Topic :: Scientific/Engineering",
34
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
35
+ "Typing :: Typed",
36
+ ]
37
+ dependencies = []
38
+
39
+ [project.optional-dependencies]
40
+ dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11", "hypothesis>=6"]
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/amaar-mc/spikegen"
44
+ Repository = "https://github.com/amaar-mc/spikegen"
45
+ Issues = "https://github.com/amaar-mc/spikegen/issues"
46
+ Changelog = "https://github.com/amaar-mc/spikegen/blob/main/CHANGELOG.md"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/spikegen"]
50
+
51
+ [tool.ruff]
52
+ line-length = 100
53
+ src = ["src", "tests"]
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
57
+
58
+ [tool.mypy]
59
+ strict = true
60
+ files = ["src"]
@@ -0,0 +1,18 @@
1
+ """Generate spike trains in pure Python with zero dependencies."""
2
+
3
+ from .processes import (
4
+ gamma_renewal,
5
+ homogeneous_poisson,
6
+ inhomogeneous_poisson,
7
+ regular,
8
+ with_refractory,
9
+ )
10
+
11
+ __all__ = [
12
+ "gamma_renewal",
13
+ "homogeneous_poisson",
14
+ "inhomogeneous_poisson",
15
+ "regular",
16
+ "with_refractory",
17
+ ]
18
+ __version__ = "0.1.0"
@@ -0,0 +1,26 @@
1
+ from math import isfinite
2
+
3
+
4
+ def check_rate(rate: float) -> None:
5
+ if not isfinite(rate) or rate <= 0:
6
+ raise ValueError(f"rate must be a positive number, received {rate!r}")
7
+
8
+
9
+ def check_duration(duration: float) -> None:
10
+ if not isfinite(duration) or duration < 0:
11
+ raise ValueError(f"duration must be a non-negative number, received {duration!r}")
12
+
13
+
14
+ def check_positive(name: str, value: float) -> None:
15
+ if not isfinite(value) or value <= 0:
16
+ raise ValueError(f"{name} must be a positive number, received {value!r}")
17
+
18
+
19
+ def check_non_negative(name: str, value: float) -> None:
20
+ if not isfinite(value) or value < 0:
21
+ raise ValueError(f"{name} must be a non-negative number, received {value!r}")
22
+
23
+
24
+ def check_seed(seed: int) -> None:
25
+ if not isinstance(seed, int):
26
+ raise ValueError(f"seed must be an integer, received {seed!r}")
@@ -0,0 +1,102 @@
1
+ import math
2
+ import random
3
+ from collections.abc import Callable, Sequence
4
+
5
+ from ._validate import (
6
+ check_duration,
7
+ check_non_negative,
8
+ check_positive,
9
+ check_rate,
10
+ check_seed,
11
+ )
12
+
13
+
14
+ def regular(*, rate: float, duration: float) -> list[float]:
15
+ """Evenly spaced spikes at 0, 1/rate, 2/rate, ... within [0, duration). Deterministic.
16
+ Indices are multiplied by the step rather than accumulated to avoid floating drift."""
17
+ check_rate(rate)
18
+ check_duration(duration)
19
+ step = 1.0 / rate
20
+ times: list[float] = []
21
+ i = 0
22
+ t = 0.0
23
+ while t < duration:
24
+ times.append(t)
25
+ i += 1
26
+ t = i * step
27
+ return times
28
+
29
+
30
+ def homogeneous_poisson(*, rate: float, duration: float, seed: int) -> list[float]:
31
+ """Homogeneous Poisson process on [0, duration) with the given rate, drawing
32
+ exponential inter-spike intervals. Seeded for reproducibility."""
33
+ check_rate(rate)
34
+ check_duration(duration)
35
+ check_seed(seed)
36
+ rng = random.Random(seed)
37
+ times: list[float] = []
38
+ t = 0.0
39
+ while True:
40
+ # 1 - U avoids log(0) when U happens to be 0.
41
+ t += -math.log(1.0 - rng.random()) / rate
42
+ if t >= duration:
43
+ break
44
+ times.append(t)
45
+ return times
46
+
47
+
48
+ def inhomogeneous_poisson(
49
+ *, rate_fn: Callable[[float], float], max_rate: float, duration: float, seed: int
50
+ ) -> list[float]:
51
+ """Inhomogeneous Poisson process by thinning: propose spikes at max_rate and keep each
52
+ at time t with probability rate_fn(t) / max_rate. rate_fn must return a value in
53
+ [0, max_rate] for every t in [0, duration). Seeded for reproducibility."""
54
+ check_positive("max_rate", max_rate)
55
+ check_duration(duration)
56
+ check_seed(seed)
57
+ rng = random.Random(seed)
58
+ times: list[float] = []
59
+ t = 0.0
60
+ while True:
61
+ t += -math.log(1.0 - rng.random()) / max_rate
62
+ if t >= duration:
63
+ break
64
+ rate = rate_fn(t)
65
+ if rate < 0.0 or rate > max_rate:
66
+ raise ValueError(f"rate_fn returned {rate!r} at t={t!r}, outside [0, max_rate]")
67
+ if rng.random() < rate / max_rate:
68
+ times.append(t)
69
+ return times
70
+
71
+
72
+ def gamma_renewal(*, rate: float, shape: float, duration: float, seed: int) -> list[float]:
73
+ """Gamma renewal process: inter-spike intervals follow a gamma distribution with the
74
+ given shape and a mean of 1/rate. shape = 1 reduces to a Poisson process; larger shape
75
+ gives more regular spiking. Seeded for reproducibility."""
76
+ check_rate(rate)
77
+ check_positive("shape", shape)
78
+ check_duration(duration)
79
+ check_seed(seed)
80
+ rng = random.Random(seed)
81
+ scale = 1.0 / (rate * shape) # mean interval = shape * scale = 1 / rate
82
+ times: list[float] = []
83
+ t = 0.0
84
+ while True:
85
+ t += rng.gammavariate(shape, scale)
86
+ if t >= duration:
87
+ break
88
+ times.append(t)
89
+ return times
90
+
91
+
92
+ def with_refractory(times: Sequence[float], *, refractory: float) -> list[float]:
93
+ """Enforce a minimum inter-spike interval by dropping each spike that falls within
94
+ refractory of the previously kept spike. Inputs are sorted first."""
95
+ check_non_negative("refractory", refractory)
96
+ kept: list[float] = []
97
+ last: float | None = None
98
+ for t in sorted(times):
99
+ if last is None or t - last >= refractory:
100
+ kept.append(t)
101
+ last = t
102
+ return kept
File without changes
@@ -0,0 +1,65 @@
1
+ import pytest
2
+
3
+ from spikegen import (
4
+ gamma_renewal,
5
+ homogeneous_poisson,
6
+ inhomogeneous_poisson,
7
+ regular,
8
+ with_refractory,
9
+ )
10
+
11
+
12
+ def test_regular_exact() -> None:
13
+ assert regular(rate=2.0, duration=1.0) == [0.0, 0.5]
14
+ assert regular(rate=1.0, duration=3.0) == [0.0, 1.0, 2.0]
15
+ assert regular(rate=10.0, duration=0.0) == []
16
+
17
+
18
+ def test_with_refractory_exact() -> None:
19
+ assert with_refractory([0.0, 0.1, 0.15, 0.3], refractory=0.12) == [0.0, 0.15, 0.3]
20
+ assert with_refractory([0.3, 0.0, 0.1], refractory=0.05) == [0.0, 0.1, 0.3]
21
+
22
+
23
+ def test_homogeneous_reproducible_and_structural() -> None:
24
+ a = homogeneous_poisson(rate=50.0, duration=2.0, seed=7)
25
+ assert a == homogeneous_poisson(rate=50.0, duration=2.0, seed=7)
26
+ assert homogeneous_poisson(rate=50.0, duration=2.0, seed=8) != a
27
+ assert all(0.0 <= t < 2.0 for t in a)
28
+ assert a == sorted(a)
29
+ assert all(a[i] < a[i + 1] for i in range(len(a) - 1))
30
+
31
+
32
+ def test_gamma_reproducible_and_in_range() -> None:
33
+ g = gamma_renewal(rate=20.0, shape=2.0, duration=5.0, seed=3)
34
+ assert g == gamma_renewal(rate=20.0, shape=2.0, duration=5.0, seed=3)
35
+ assert all(0.0 <= t < 5.0 for t in g)
36
+ assert g == sorted(g)
37
+
38
+
39
+ def test_inhomogeneous_zero_rate_gives_no_spikes() -> None:
40
+ assert inhomogeneous_poisson(rate_fn=lambda t: 0.0, max_rate=100.0, duration=1.0, seed=1) == []
41
+
42
+
43
+ def test_inhomogeneous_reproducible_and_in_range() -> None:
44
+ def rate_fn(t: float) -> float:
45
+ return 50.0 if t < 0.5 else 5.0
46
+
47
+ a = inhomogeneous_poisson(rate_fn=rate_fn, max_rate=50.0, duration=1.0, seed=2)
48
+ assert a == inhomogeneous_poisson(rate_fn=rate_fn, max_rate=50.0, duration=1.0, seed=2)
49
+ assert all(0.0 <= t < 1.0 for t in a)
50
+
51
+
52
+ def test_inhomogeneous_rejects_out_of_range_rate() -> None:
53
+ with pytest.raises(ValueError):
54
+ inhomogeneous_poisson(rate_fn=lambda t: 200.0, max_rate=50.0, duration=1.0, seed=1)
55
+
56
+
57
+ def test_validation() -> None:
58
+ with pytest.raises(ValueError):
59
+ regular(rate=0.0, duration=1.0)
60
+ with pytest.raises(ValueError):
61
+ homogeneous_poisson(rate=10.0, duration=-1.0, seed=1)
62
+ with pytest.raises(ValueError):
63
+ gamma_renewal(rate=10.0, shape=0.0, duration=1.0, seed=1)
64
+ with pytest.raises(ValueError):
65
+ with_refractory([0.0], refractory=-0.1)
@@ -0,0 +1,25 @@
1
+ from hypothesis import given
2
+ from hypothesis import strategies as st
3
+
4
+ from spikegen import gamma_renewal, homogeneous_poisson
5
+
6
+ rates = st.floats(min_value=1.0, max_value=200.0, allow_nan=False, allow_infinity=False)
7
+ durations = st.floats(min_value=0.0, max_value=3.0, allow_nan=False, allow_infinity=False)
8
+ shapes = st.floats(min_value=0.5, max_value=5.0, allow_nan=False, allow_infinity=False)
9
+ seeds = st.integers(min_value=0, max_value=100000)
10
+
11
+
12
+ @given(rates, durations, seeds)
13
+ def test_poisson_is_sorted_and_in_range(rate: float, duration: float, seed: int) -> None:
14
+ spikes = homogeneous_poisson(rate=rate, duration=duration, seed=seed)
15
+ assert spikes == sorted(spikes)
16
+ assert all(0.0 <= t < duration for t in spikes)
17
+
18
+
19
+ @given(rates, shapes, durations, seeds)
20
+ def test_gamma_is_sorted_and_in_range(
21
+ rate: float, shape: float, duration: float, seed: int
22
+ ) -> None:
23
+ spikes = gamma_renewal(rate=rate, shape=shape, duration=duration, seed=seed)
24
+ assert spikes == sorted(spikes)
25
+ assert all(0.0 <= t < duration for t in spikes)