Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ jobs:
- name: smoke test
run: |
pip install --find-links target/wheels hebb-py
python -c "import hebb; print('hebb', hebb.__version__)"
python -c "import hebb_py; print('hebb_py', hebb_py.__version__)"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ __pycache__/
dist/
*.egg-info/
.venv/
/Cargo.lock
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This repo is **library-only**. The desktop app and visualizer live at [hebb-proj
## Repo shape

- `src/` — the `hebb` Rust crate. Pure-Rust, no I/O by default. Filesystem support is opt-in behind the `disk` feature.
- `python/` — PyO3 bindings. Cargo package `hebb-py`, cdylib `[lib] name = "hebb"` so Python users `import hebb`. Built with maturin; produces the `hebb-py` PyPI wheel.
- `python/` — PyO3 bindings. Cargo package `hebb-py`, cdylib `[lib] name = "hebb_py"` so Python users `import hebb_py`. Built with maturin; produces the `hebb-py` PyPI wheel.
- `tests/` — integration tests against the public Rust API.
- `SCHEMA.md` — on-disk format spec for `.cortex/` folders.

Expand Down Expand Up @@ -36,7 +36,7 @@ Don't publish a `0.x` bump unless `cargo test --features disk` and `maturin buil
## Compatibility

- Rust API: keep `pub` surface stable within a `0.x` line. Breaking changes get a minor-version bump.
- Python API: `import hebb` exposes `Sim`, `Cortex`, and the `seeds` submodule. Treat those as a public contract.
- Python API: `import hebb_py` exposes `Sim`, `Cortex`, and the `seeds` submodule. Treat those as a public contract.
- On-disk format: `.cortex/` folders are versioned via `metadata.json`. Bumping the schema requires a migration path or a hard version gate in `format/metadata.rs`.

## Git hygiene
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The `hebb` crate is two things at once:

| You are… | Use `hebb` as… | Start here |
| --- | --- | --- |
| A **computational-neuroscience / SNN / neuromorphic researcher** | A fast, scriptable spiking-network simulator (Rust crate or `import hebb` from Python) | [Use as a library](#use-as-a-library) |
| A **computational-neuroscience / SNN / neuromorphic researcher** | A fast, scriptable spiking-network simulator (Rust crate or `import hebb_py` from Python) | [Use as a library](#use-as-a-library) |
| A **Rust developer** integrating spiking models into a larger system | A pure-Rust, no-I/O crate that drops cleanly into anything (wasm, FFI, embedded sim, server) | [Use as a library](#use-as-a-library) |
| A **PyTorch / SNN-ML researcher** | A fast event-driven runtime for inference / online learning, complementing surrogate-gradient training in snnTorch / Norse / BindsNET / SpikingJelly | [Vision](#vision) |
| A **Hebb desktop / visualizer contributor** | The substrate the app depends on. New neuron / synapse / format work lands here. | [Repository layout](#repository-layout) |
Expand All @@ -42,7 +42,7 @@ The `hebb` crate is two things at once:
- **Plastic synapses** — STDP and dopamine-gated R-STDP, with parameters streamable through the on-disk format.
- **Deterministic seed generators** — `random`, `ring`, `small-world`, `layered`. Same seed → same network.
- **Embed-anywhere** — pure-Rust, no I/O, no async runtime, no unsafe. WASM-ready. Filesystem support is opt-in behind the `disk` feature.
- **Python bindings** — `import hebb`; same engine, same domain types, same on-disk format.
- **Python bindings** — `import hebb_py`; same engine, same domain types, same on-disk format.

## Repository layout

Expand All @@ -63,14 +63,14 @@ cargo add hebb
pip install hebb-py
```

> The PyPI distribution is `hebb-py` because the bare `hebb` name on PyPI is taken by an unrelated astronomy package. The Python module name is still `hebb`.
> Both the PyPI distribution and the Python module are `hebb-py` / `hebb_py`. The bare `hebb` name on PyPI is taken by an unrelated astronomy package, and `import hebb_py` avoids colliding with it.

Drive the substrate from Python:

```python
import hebb
import hebb_py

sim = hebb.Sim()
sim = hebb_py.Sim()
a = sim.add_neuron()
b = sim.add_neuron()
sim.add_edge(a, b, weight=0.9)
Expand Down Expand Up @@ -133,7 +133,7 @@ cargo test --features disk
# Python bindings (requires maturin)
pip install maturin
maturin develop --features pyo3/extension-module
python -c "import hebb; print(hebb.__version__)"
python -c "import hebb_py; print(hebb_py.__version__)"
```

## Contributing
Expand Down
10 changes: 6 additions & 4 deletions python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ name = "hebb-py"
version = "0.1.0"
edition = "2021"
publish = false
description = "Python bindings for hebb — `import hebb`"
description = "Python bindings for hebb — `import hebb_py`"

# The cargo package is `hebb-py`.
# The compiled cdylib is `hebb` so Python users say `import hebb`.
# The cargo package is `hebb-py`. The compiled cdylib is `hebb_py` so
# Python users say `import hebb_py`. Matching the PyPI distribution
# name avoids collision with the unrelated `hebb` PyPI package (an
# astronomy library that occupies the bare `hebb` import).
[lib]
name = "hebb"
name = "hebb_py"
crate-type = ["cdylib"]

[dependencies]
Expand Down
26 changes: 13 additions & 13 deletions python/src/cortex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ fn map_disk_err(e: snn::disk::DiskError) -> PyErr {
///
/// Example
/// -------
/// >>> import hebb
/// >>> import hebb_py
/// >>> # Open a folder the desktop created.
/// >>> cx = hebb.Cortex.open("/path/to/My.cortex/")
/// >>> cx = hebb_py.Cortex.open("/path/to/My.cortex/")
/// >>> print(cx.cortex_type, cx.name, len(cx.node_ids()))
/// >>> # Add five neurons + a couple of synapses.
/// >>> a = cx.add_neuron(label="input")
Expand Down Expand Up @@ -313,7 +313,7 @@ impl PyCortex {
// doesn't reach disk — the cost is dominated by allocation,
// not by per-record work, and it keeps the safety contract
// honest. Researchers who want a true memcpy can call
// `hebb.disk.write_atomic` from a separate API once we
// `hebb_py.disk.write_atomic` from a separate API once we
// expose it; for now this path is "fast in the marshalling
// sense, still validated in the format sense".
let buf = bytes.as_bytes();
Expand Down Expand Up @@ -342,7 +342,7 @@ impl PyCortex {
self.inner.save().map_err(map_disk_err)
}

/// Bulk-apply a [`PySeed`] generated by `hebb.seeds.*` — the
/// Bulk-apply a [`PySeed`] generated by `hebb_py.seeds.*` — the
/// substrate validates once and persists `topology.json` once, so a
/// 1000-neuron seed isn't O(n²) writes. Returns
/// `(added_nodes, added_edges)`.
Expand Down Expand Up @@ -402,9 +402,9 @@ fn runtime(msg: impl Into<String>) -> PyErr {
PyRuntimeError::new_err(msg.into())
}

// ── Seeds — `hebb.seeds` submodule ─────────────────────────────
// ── Seeds — `hebb_py.seeds` submodule ─────────────────────────────

/// A pre-built seed network. Construct via `hebb.seeds.random`,
/// A pre-built seed network. Construct via `hebb_py.seeds.random`,
/// `.ring`, `.small_world`, `.layered`. Apply via `Cortex.apply_seed`.
///
/// Holds an `Option<Seed>` internally so `apply_seed` can take the
Expand Down Expand Up @@ -459,7 +459,7 @@ fn map_seed_err(e: snn::seeds::SeedError) -> PyErr {
PyValueError::new_err(e.to_string())
}

/// `hebb.seeds.random(n, p, seed=0, weight_lo=0.4, weight_hi=0.6, delay_ms=1.0)`
/// `hebb_py.seeds.random(n, p, seed=0, weight_lo=0.4, weight_hi=0.6, delay_ms=1.0)`
#[pyfunction]
#[pyo3(signature = (n, p, seed = 0, weight_lo = None, weight_hi = None, delay_ms = None))]
fn random_seed(
Expand All @@ -475,7 +475,7 @@ fn random_seed(
Ok(PySeed { inner: Some(s) })
}

/// `hebb.seeds.ring(n, k, seed=0, weight_lo=..., weight_hi=..., delay_ms=...)`
/// `hebb_py.seeds.ring(n, k, seed=0, weight_lo=..., weight_hi=..., delay_ms=...)`
#[pyfunction]
#[pyo3(signature = (n, k, seed = 0, weight_lo = None, weight_hi = None, delay_ms = None))]
fn ring_seed(
Expand All @@ -491,7 +491,7 @@ fn ring_seed(
Ok(PySeed { inner: Some(s) })
}

/// `hebb.seeds.small_world(n, k, p_rewire, seed=0, ...)`
/// `hebb_py.seeds.small_world(n, k, p_rewire, seed=0, ...)`
#[pyfunction]
#[pyo3(signature = (n, k, p_rewire, seed = 0, weight_lo = None, weight_hi = None, delay_ms = None))]
fn small_world_seed(
Expand All @@ -508,7 +508,7 @@ fn small_world_seed(
Ok(PySeed { inner: Some(s) })
}

/// `hebb.seeds.layered(layers, seed=0, ...)` — layers is a list
/// `hebb_py.seeds.layered(layers, seed=0, ...)` — layers is a list
/// of layer sizes, e.g. `[2, 5, 1]`.
#[pyfunction]
#[pyo3(signature = (layers, seed = 0, weight_lo = None, weight_hi = None, delay_ms = None))]
Expand All @@ -524,8 +524,8 @@ fn layered_seed(
Ok(PySeed { inner: Some(s) })
}

/// Register `hebb.seeds` as a submodule. Called from the parent
/// `hebb` `#[pymodule]` entry in `lib.rs`.
/// Register `hebb_py.seeds` as a submodule. Called from the parent
/// `hebb_py` `#[pymodule]` entry in `lib.rs`.
pub fn register_seeds_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let py = parent.py();
let m = PyModule::new_bound(py, "seeds")?;
Expand All @@ -539,7 +539,7 @@ pub fn register_seeds_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(layered_seed, &m)?)?;

// Pythonic aliases on the submodule so call sites read naturally:
// `hebb.seeds.random(...)` instead of `random_seed(...)`.
// `hebb_py.seeds.random(...)` instead of `random_seed(...)`.
let random_fn = m.getattr("random_seed")?;
m.add("random", random_fn)?;
let ring_fn = m.getattr("ring_seed")?;
Expand Down
10 changes: 5 additions & 5 deletions python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Python bindings for the `hebb` substrate.
//!
//! Built as a maturin-managed cdylib; exposes a `hebb` Python
//! Built as a maturin-managed cdylib; exposes a `hebb_py` Python
//! module whose only class is [`Sim`], a thin wrapper around
//! [`hebb::SimEngine`].
//!
Expand Down Expand Up @@ -130,8 +130,8 @@ fn py_to_json(value: &Bound<'_, PyAny>) -> PyResult<Value> {
///
/// Example
/// -------
/// >>> import hebb
/// >>> sim = hebb.Sim()
/// >>> import hebb_py
/// >>> sim = hebb_py.Sim()
/// >>> a = sim.add_neuron()
/// >>> b = sim.add_neuron()
/// >>> sim.add_edge(a, b, weight=0.7)
Expand Down Expand Up @@ -345,11 +345,11 @@ impl PySim {
}
}

/// `import hebb` entry point. Adds the `Sim` class plus a
/// `import hebb_py` entry point. Adds the `Sim` class plus a
/// `__version__` string sourced from the cargo package version so
/// Python callers can sanity-check what they linked.
#[pymodule]
fn hebb(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
fn hebb_py(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PySim>()?;
m.add_class::<PyCortex>()?;
register_seeds_submodule(m)?;
Expand Down
Loading