From aa39dafdae07557e5919803a260e59e7ef816294 Mon Sep 17 00:00:00 2001 From: ammar siddiqui Date: Tue, 26 May 2026 15:04:33 -0400 Subject: [PATCH] =?UTF-8?q?rename:=20Python=20module=20hebb=20=E2=86=92=20?= =?UTF-8?q?hebb=5Fpy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PyPI distribution is `hebb-py` (the bare `hebb` name is taken by an unrelated astronomy package). Aligning the module name with the distribution name makes `pip install hebb-py` / `import hebb_py` self-consistent and avoids any chance of import collision with the astronomy package. Mechanical changes: - python/Cargo.toml: `[lib] name = "hebb_py"` - python/src/lib.rs: `#[pymodule] fn hebb_py(...)` - python/src/cortex.rs: `hebb.*` Python-path comments → `hebb_py.*` - README/CLAUDE.md/CI: `import hebb` → `import hebb_py` Rust paths (`hebb::*`, `cargo add hebb`) and the crate name on crates.io are unchanged — this is a Python-side rename only. --- .github/workflows/ci.yml | 2 +- .gitignore | 1 + CLAUDE.md | 4 ++-- README.md | 12 ++++++------ python/Cargo.toml | 10 ++++++---- python/src/cortex.rs | 26 +++++++++++++------------- python/src/lib.rs | 10 +++++----- 7 files changed, 34 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66b13af..1a0ba64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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__)" diff --git a/.gitignore b/.gitignore index 202bd20..eb6bcc2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __pycache__/ dist/ *.egg-info/ .venv/ +/Cargo.lock diff --git a/CLAUDE.md b/CLAUDE.md index 3fde8ff..c02a813 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. @@ -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 diff --git a/README.md b/README.md index 6ad26d3..0224efd 100644 --- a/README.md +++ b/README.md @@ -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) | @@ -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 @@ -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) @@ -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 diff --git a/python/Cargo.toml b/python/Cargo.toml index 5578992..bba7294 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -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] diff --git a/python/src/cortex.rs b/python/src/cortex.rs index fb4a7e5..869887d 100644 --- a/python/src/cortex.rs +++ b/python/src/cortex.rs @@ -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") @@ -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(); @@ -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)`. @@ -402,9 +402,9 @@ fn runtime(msg: impl Into) -> 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` internally so `apply_seed` can take the @@ -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( @@ -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( @@ -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( @@ -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))] @@ -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")?; @@ -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")?; diff --git a/python/src/lib.rs b/python/src/lib.rs index 272fde4..11d9c06 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -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`]. //! @@ -130,8 +130,8 @@ fn py_to_json(value: &Bound<'_, PyAny>) -> PyResult { /// /// 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) @@ -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::()?; m.add_class::()?; register_seeds_submodule(m)?;