HypercubeCascade Python SDK
September 11, 2026 · View on GitHub
Static fields have no natural clock. HypercubeCascade preprocesses each
length-N field with two frozen hypercube stages — one etalon transit, then
a short reservoir orbit — and trains a small
HypercubeCNN readout only on the
end state. One class — Cascade — owns collect → train → predict.
This is a map API, not a stream API. There is no per-tick input sequence
and no next-step fit on a 1D signal (that is
HypercubeESN). Time here is
synthetic and per sample.
C++ core and contracts: CPP_SDK.md.
The concept in depth: CascadeWhitePaper.md.
PyPI-facing package story: python/README.md.
Package version: single source python/hypercube_cascade/_version.py
(hypercube_cascade.__version__ and wheel metadata both read it).
Contents
- Installation
- Quick start
- What a map is
- Pipeline vocabulary
- API reference
- Input data layout
- Data types
- Error handling
- Model persistence
- Limitations
- Dependencies
Installation
From PyPI (preferred)
Pre-built wheels — no compiler required:
pip install hypercube-cascade
Import as import hypercube_cascade as hc (PyPI name hypercube-cascade).
Wheels cover Python 3.10–3.14 on common Windows (x64), Linux (x86_64,
aarch64), and macOS (x86_64, arm64) builds. NumPy is the only runtime
dependency.
From source (full repository)
Compile only from a full clone of HypercubeCascade. The extension links
the C++ core and vendored HypercubeCNN that sit outside the python/
package directory; a python/-only tree is not enough.
Requirements: Python 3.10+, C++23 compiler (GCC 13+, Clang 17+, MSVC 2022+), CMake 3.20+, scikit-build-core, pybind11, NumPy.
git clone https://github.com/dliptak001/HypercubeCascade.git
cd HypercubeCascade/python
pip install .
On Windows with MinGW (e.g. CLion toolchain):
pip install scikit-build-core pybind11 numpy
$env:PATH = "C:\path\to\mingw\bin;" + $env:PATH
$env:CMAKE_GENERATOR = "Ninja"
$env:CMAKE_MAKE_PROGRAM = "C:\path\to\ninja.exe"
$env:CC = "C:\path\to\mingw\bin\gcc.exe"
$env:CXX = "C:\path\to\mingw\bin\g++.exe"
pip install . --no-build-isolation
Running tests
From the python/ directory after install:
pip install ".[test]"
pytest tests/ -v --import-mode=importlib
Or from the repository root: pytest python/tests/ -v --import-mode=importlib.
Importlib mode avoids the source tree shadowing the installed _core
extension. Use the pytest entry point, not python -m pytest — the latter
puts the current directory on sys.path, and from python/ the source
package (which has no compiled _core) then shadows the installed one.
Examples
The Quick start below is enough after pip install. Longer
demos live in the git tree under
python/examples/ — they are not part of
the wheel. From a clone, repository root:
pip install hypercube-cascade # or: pip install ./python
python python/examples/synthetic_classification.py
Quick start
import numpy as np
import hypercube_cascade as hc
dim = 7
N = 1 << dim
rng = np.random.default_rng(0)
fields = rng.standard_normal((128, N), dtype=np.float32)
labels = rng.integers(0, 4, size=128, dtype=np.int32)
cas = hc.Cascade(
dim=dim,
exciter_subcube_dim=5,
ic_seed=2,
readout_num_outputs=4,
readout_task="classification",
readout_epochs=80,
)
cas.fit(fields, labels)
print(cas.accuracy_on_collected()) # train-set only — not a test score
print(cas.predict_class(fields[0]))
Explicit (full control)
cas = hc.Cascade(
dim=6,
exciter_subcube_dim=5,
history_depth=4,
T=50,
interstage_scale=5.5,
readout_num_outputs=3,
readout_task="classification",
)
cas.collect_batch(fields_train, labels_train)
cas.train()
logits = cas.predict(fields_test[0]) # shape (num_outputs,)
cls = cas.predict_class(fields_test[0])
test_acc = cas.accuracy(fields_test, labels_test) # held-out, fresh maps
fit is clear_collected (optional) → collect_batch → train. Prefer
fit for a first pass; use collect/train when you append batches or retrain
without re-mapping every field.
What a map is
x (length-N field, host-packed)
│
▼
etalon transit → × interstage_scale → reload frozen IC
│
▼
drive T re-addressed passes → end state × readout_scale
│
▼
features (N) → HypercubeCNN → logits / values
- N = 2^dim vertices / field length (dim 5…12; one dim for all stages).
- Exciter and reservoir weights are frozen after construction; only the readout trains.
- Predict always runs a fresh map.
- Host packing (MNIST → N, spectra → N, …) is your problem — this package does not reshape domain data onto the cube.
The CNN head never sees the original field; it sees what the transit and the orbit leave behind.
Pipeline vocabulary
| Term | Meaning |
|---|---|
| Field | Length-N float32 vector on the cube (you pack domain data) |
| Transit | One frozen etalon sweep: field in, same-length field out |
| Map | Transit → gain → reload frozen IC → T orbit passes → end features |
| Collect | Run a map → append features + label/target |
| Train | Batch-train HCNN on all collected samples |
| Predict | Fresh map + readout forward |
| N | Vertices / field length = 2^dim |
| M | history_depth — delay-line depth |
| subcube_dim | Etalon face size; one walk covers 2^subcube_dim vertices |
| T | Drive-pass count per map (must be ≥ 1 — no auto value) |
| interstage_scale / readout_scale | The two gains between the stages |
Unlike HypercubeWTF there is no readout_slices (B) knob and no train-noise
or bypass knob: features are always the newest slice (length N), and noise or
bypass comparisons are the host's job.
API reference
Constructor Cascade(dim, **kwargs)
All knobs are fixed at construction (same contract as C++ CascadeConfig).
One dim is stamped onto the Exciter, the Reservoir, and the Readout.
import hypercube_cascade as hc
cas = hc.Cascade(
dim=7, # required; 5–12; N = 2^dim
T=100, # orbit passes; must be >= 1
interstage_scale=1.0, # transit → orbit gain (finite, > 0)
readout_scale=1.0, # orbit → readout gain (finite, > 0)
ic_seed=1, # frozen episode IC (not a weight seed)
collect_threads=0, # 0 = auto
exciter_seed=7934791766227647176,
exciter_input_scaling=0.02,
exciter_weight_scaling=0.02,
exciter_subcube_dim=6, # [1, dim] — set <= dim when dim < 6!
reservoir_seed=7934791766227647176,
spectral_radius=0.999,
reservoir_input_scaling=0.02,
leak_rate=1.0,
history_depth=16, # M
bias_scaling=0.003,
verbose=False,
readout_num_outputs=1,
readout_task="regression", # or "classification"
# … readout_* kwargs below
)
Cascade, exciter, and reservoir
| Parameter | Type | Default | Description |
|---|---|---|---|
dim | int | required | Hypercube dimension [5, 12]. N = 2^dim, all stages. |
T | int | 100 | Orbit passes per map. Must be ≥ 1 (no 0 = auto). |
interstage_scale | float | 1.0 | Gain on the transit output before the orbit. Finite, > 0. |
readout_scale | float | 1.0 | Gain on the orbit end state before the readout. Finite, > 0. |
ic_seed | int | 1 | Frozen episode IC seed (separate from both weight seeds). |
collect_threads | int | 0 | Bulk workers: 0 = auto, 1 = serial, K = K workers. |
exciter_seed | int | 7934791766227647176 | Exciter weight-init seed (matches C++). |
exciter_input_scaling | float | 0.02 | Scalar applied once to the field before the transit. |
exciter_weight_scaling | float | 0.02 | Exciter neighbor weights are U(-1, 1) × this. |
exciter_subcube_dim | int | 6 | Etalon face size; walk covers 2^subcube_dim vertices. Valid [1, dim] — the default 6 is rejected below dim 6. |
reservoir_seed | int | 7934791766227647176 | Reservoir weight-init seed (matches C++). |
spectral_radius | float | 0.999 | Target spectral radius for recurrent weights. |
reservoir_input_scaling | float | 0.02 | Reservoir input drive coefficient. |
leak_rate | float | 1.0 | Leaky integrator; 1.0 = full replacement. |
history_depth | int | 16 | Delay-line depth M ∈ [1, 64]. |
bias_scaling | float | 0.003 | Per-neuron bias after tanh; 0 disables. |
verbose | bool | False | Reservoir construction banner. |
Gain tuning: the default injection (interstage_scale=1.0 with
reservoir_input_scaling=0.02) is deliberately weak — on many tasks the
features come out crushed toward zero and the readout cannot learn. Probe with
run(x) + last_reservoir() and raise interstage_scale (the in-tree demos
use 1.0–5.5) until the end state is alive.
Readout (HCNN)
| Parameter | Type | Default | Description |
|---|---|---|---|
readout_num_outputs | int | 1 | Classes (classification) or regression width. |
readout_task | str | "regression" | "regression" or "classification". |
readout_num_layers | int | 1 | Conv(+Pool) stages. 0 = auto min(dim−2, 2). |
readout_conv_channels | int | 16 | Base channel count for the first conv. |
readout_epochs | int | 200 | Batch-train epochs. |
readout_batch_size | int | 32 | Mini-batch size. |
readout_lr_max | float | 0.0015 | Cosine peak LR. Keep ≤ ~0.005 to avoid NaN. |
readout_lr_min_frac | float | 0.01 | Floor = lr_max * lr_min_frac. |
readout_lr_decay_epochs | int | 0 | Cosine horizon; 0 = use readout_epochs. |
readout_weight_decay | float | 0.0 | L2 on CNN weights. |
readout_momentum | float | 0.9 | SGD momentum; ignored under the default Adam optimizer. |
readout_activation | str | "tanh" | "tanh", "relu", "leaky_relu", or "none". |
readout_seed | int | 42 | CNN weight-init seed. |
readout_num_threads | int | 0 | HCNN workers: 0 = auto, 1 = single-threaded. |
readout_restore_best_epoch | bool | True | Restore best-epoch weights after batch train. |
readout_best_epoch_holdout_frac | float | 0.0 | Tail hold-out for best-epoch scoring; 0 = full train set. |
readout_use_pooling | bool | True | Antipodal pool after each conv. |
Not bound in Python yet (C++ ReadoutConfig only): optimizer choice (C++
default Adam), pool type, channel growth, batch-norm. C++ defaults apply.
Methods
| Method | Role |
|---|---|
run(x) | Map one field (no training-set append). Updates last_features() and the stage probes. |
last_features() | Length-N float32 from the most recent completed map — updated by every map, including bulk calls (last row). |
last_exciter() | Transit output from the last serial map (run, collect, predict, predict_class). Not updated by bulk calls. |
last_interstage() | Transit × interstage_scale (the orbit drive) — same lifetime as last_exciter. |
last_reservoir() | Orbit end state before readout_scale — same lifetime as last_exciter. |
clear_collected() | Drop the batch training buffer. |
collect(x, target) | Serial append one sample (label or regression vector). |
collect_batch(fields, targets) | Bulk parallel append. |
fit(fields, targets, *, clear=True) | Optional clear → collect → train. Returns self. |
train() | Batch-train HCNN on all collected samples. Does not clear the set. |
predict(x) | Fresh map + forward → shape (num_outputs,) float32. |
predict_class(x) | Fresh map + argmax class (classification task only). |
accuracy_on_collected() | Accuracy on the collected training set only. |
r2_on_collected() | R² on the collected training set only. |
accuracy(fields, labels) | Fresh bulk maps + accuracy on a held-out set (classification). |
r2(fields, targets) | Fresh bulk maps + R² on a held-out set (regression). |
save(path) / load(path) | Pickle constructor config + readout weights. |
save_readout_hcnn_model(path_stem) | Portable stem.hcnw + stem.arch.json. |
load_readout_hcnn_model(path_stem, *, mode="eval") | Load HCNW into this instance ("eval" or "resume_train"). |
readout_arch_summary() | Human-readable HCNN architecture and parameter counts. |
Properties
| Property | Meaning |
|---|---|
dim, N, T, M | Geometry and orbit knobs (N = 2^dim, M = history depth) |
subcube_dim, walk_size | Etalon face dim and 2^subcube_dim |
interstage_scale, readout_scale | The two gains |
feature_size | Floats per sample / last_features — always N |
num_collected | Samples in the batch training buffer |
num_outputs | Readout width |
exciter_seed, reservoir_seed, ic_seed | The three seeds |
exciter_input_scaling, exciter_weight_scaling | Exciter config mirrors |
spectral_radius, realized_spectral_radius | Target vs post-rescale estimate |
reservoir_input_scaling, leak_rate, history_depth, bias_scaling | Reservoir config mirrors |
collect_threads | Bulk-worker preference (0 = auto) |
readout_task | "regression" or "classification" |
readout_best_epoch | 1-based best epoch after restore; else 0 |
verbose | Construction banner flag |
Input data layout
- Fields must be length N per sample. Prefer shape
(count, N)for bulk APIs; a flat lengthcount * Nvector is also accepted. - Host packing (images, spectra, sensors → N) is outside this package.
- Classification labels: integer class indices in
[0, num_outputs)(enforced at collect / scoring). Shape(count,)for bulk calls. - Regression targets: shape
(count, num_outputs)float32 (or flatcount * num_outputs). - Single-sample methods accept any array that ravel-flattens to the right length.
Data types
| Role | Preferred type | Notes |
|---|---|---|
| Fields / features / predictions | float32 | Other dtypes converted via NumPy to contiguous float32 |
| Class labels | int32 (or Python int) | Must be in [0, num_outputs) (C++ enforces) |
| Bool as a class label | rejected on serial collect | collect raises TypeError; use an integer index. Bulk collect_batch coerces via int32 (do not rely on bool labels). |
Error handling
Python-side checks raise ValueError or TypeError with a short message (bad
dim, exciter_subcube_dim, task string, activation, field shape, label
count, …). Native std::invalid_argument maps to ValueError; other C++
failures typically surface as RuntimeError via pybind11.
Typical mistakes:
- Field length ≠ N
exciter_subcube_dimleft at its default 6 with dim 5T = 0(there is no auto value — C++ requires T ≥ 1)interstage_scale/readout_scalezero, negative, or non-finite- Bulk
fields/targetsrow counts disagree - Class label outside
[0, num_outputs) predict_class/accuracyon a regression model- Calling
trainoraccuracy_on_collectedwith an empty collected set
Model persistence
| Mechanism | What is stored | Collected samples? |
|---|---|---|
save / pickle | Constructor config + readout weight blob | No (num_collected is 0 after load) |
save_readout_hcnn_model | Portable HCNW + arch sidecar | No |
Pickle version is bumped when the serialized layout changes; newer libraries reject unknown future versions with an upgrade message.
cas.save("model.pkl")
cas2 = hc.Cascade.load("model.pkl") # same ctor knobs + weights; empty collect buffer
cas.save_readout_hcnn_model("export/stem") # stem.hcnw + stem.arch.json
# Target instance must build a matching HCNN input shape / task (same dim and
# readout_* architecture knobs as the exporter — not only dim/outputs).
cas3 = hc.Cascade(
dim=cas.dim,
exciter_subcube_dim=cas.subcube_dim,
history_depth=cas.history_depth,
readout_num_outputs=cas.num_outputs,
readout_task=cas.readout_task,
# plus any non-default readout_num_layers / channels / pooling / …
)
cas3.load_readout_hcnn_model("export/stem", mode="eval")
The preprocessors themselves are never serialized — they reconstruct exactly from the constructor seeds and scalars. A pickle therefore captures the whole product: config in, identical frozen stages out, plus the trained readout.
Prefer save / load when you want a full Python round-trip of the product
config. Prefer HCNW when you need a portable HypercubeCNN weight export.
Security: load uses pickle.load. Never load untrusted files.
Limitations
- One
Cascadeinstance is not thread-safe for concurrent public calls from multiple host threads. Bulk parallelism is internal only. accuracy_on_collected/r2_on_collectedonly score samples you already collected (and typically trained on). Useaccuracy/r2on held-out fields for real evaluation.last_exciter/last_interstage/last_reservoirare serial-map probes; bulk calls do not update them (last_featuresis updated by every map).- No train-noise or bypass knob (HypercubeWTF has both); add noise or run comparisons in host code.
- A few readout knobs remain C++-only (optimizer, pool type, channel growth, batch-norm); see constructor tables above.
- Native contracts, map mechanics, and host integration detail: CPP_SDK.md.
Dependencies
| Layer | What |
|---|---|
| Runtime | NumPy |
| Wheel install | No compiler |
| From-source build | Full repo clone, C++23, CMake ≥ 3.20, scikit-build-core, pybind11 |
The HypercubeCNN readout is built into the extension — no separate HCNN package.