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.
- spikegen-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- spikegen-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +18 -0
- spikegen-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- spikegen-0.1.0/.github/workflows/ci.yml +22 -0
- spikegen-0.1.0/.gitignore +15 -0
- spikegen-0.1.0/CHANGELOG.md +20 -0
- spikegen-0.1.0/CLAUDE.md +48 -0
- spikegen-0.1.0/CODE_OF_CONDUCT.md +37 -0
- spikegen-0.1.0/CONTRIBUTING.md +33 -0
- spikegen-0.1.0/LICENSE +21 -0
- spikegen-0.1.0/PKG-INFO +123 -0
- spikegen-0.1.0/README.md +72 -0
- spikegen-0.1.0/SECURITY.md +18 -0
- spikegen-0.1.0/assets/logo.png +0 -0
- spikegen-0.1.0/docs/architecture.md +45 -0
- spikegen-0.1.0/docs/charter.md +32 -0
- spikegen-0.1.0/docs/logo-prompt.md +11 -0
- spikegen-0.1.0/examples/generate.py +19 -0
- spikegen-0.1.0/pyproject.toml +60 -0
- spikegen-0.1.0/src/spikegen/__init__.py +18 -0
- spikegen-0.1.0/src/spikegen/_validate.py +26 -0
- spikegen-0.1.0/src/spikegen/processes.py +102 -0
- spikegen-0.1.0/src/spikegen/py.typed +0 -0
- spikegen-0.1.0/tests/test_processes.py +65 -0
- spikegen-0.1.0/tests/test_properties.py +25 -0
|
@@ -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,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.
|
spikegen-0.1.0/CLAUDE.md
ADDED
|
@@ -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.
|
spikegen-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/spikegen/)
|
|
59
|
+
[](https://github.com/amaar-mc/spikegen/actions/workflows/ci.yml)
|
|
60
|
+
[](./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).
|
spikegen-0.1.0/README.md
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/spikegen/)
|
|
8
|
+
[](https://github.com/amaar-mc/spikegen/actions/workflows/ci.yml)
|
|
9
|
+
[](./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)
|