Python bindings for the HypercubeCNN C++ core: a dependency-free hypercube
CNN (N = 2^DIM vertices per channel). Same host contracts as
CPP_SDK.md.
Package: hypercube-cnn · import: hypercube_cnn · version: aligned
with the C++ project (currently 1.0.0).
pip install hypercube-cnnPre-built wheels: Python 3.10–3.13 on Windows (x64), Linux (x86_64, aarch64), macOS (x86_64, arm64). No compiler required.
git clone https://github.com/dliptak001/HypercubeCNN.git
cd HypercubeCNN
pip install .Requires Python 3.10+, C++23 compiler, CMake ≥ 3.21. On Windows with CLion MinGW,
see python/README.md for the toolchain env vars and
--no-build-isolation.
pip install ".[test]"
pytest python/tests/ -v --import-mode=importlibUse a working directory outside python/ so the source tree does not shadow
the installed _core extension. Wheel CI runs only
python/tests/test_wheel.py (import + one predict/step).
import numpy as np
import hypercube_cnn as hc
net = hc.HCNNConfig(
dim=6,
num_outputs=3,
layers=[
hc.LayerSpec.conv(8, bn=True),
hc.LayerSpec.pool("max"),
hc.LayerSpec.conv(8),
],
weight_seed=1,
).build()
x = np.random.randn(net.N).astype(np.float32) # full capacity
logits = net.predict(x) # (num_outputs,)
cls = net.predict_class(x)
net.train_step(x, target=0, params=hc.TrainParams(learning_rate=1e-3))
net.save("model") # model.hcnw + model.arch.json
net2 = hc.HCNN.load("model")Incremental stack (same product):
net = hc.HCNN(dim=6, num_outputs=3, task=hc.TaskType.Classification)
net.add_conv(8)
net.add_pool(hc.PoolType.MAX)
net.add_conv(8)
net.randomize_weights(seed=1)Same rules as the C++ SDK (language-agnostic):
| Contract | Rule |
|---|---|
| Capacity | Always input_channels * 2**dim. Power of two is topology. Pack non-power-of-two data in the host. |
| Pad | Short inputs zero-fill the tail; over-long raise. Prefer full capacity after packing. |
| Task / loss | Classification → CE; Regression → sum-style MSE. Fixed by task at construct. |
| BatchNorm | Stats over vertices of one sample (per channel), not the mini-batch. Use set_training. |
| Outputs | Raw logits / preds — never softmax in forward. |
| Arch lifecycle | Stack changes invalidate weights; train/infer need randomize_weights for the current stack. |
| Weights | Params + BN stats; not optimizer moments. |
| Model I/O | HCNW does not store the layer graph; keep arch JSON beside weights. |
| Concurrency | One instance exclusive-use; expose num_threads. Not thread-safe across threads. |
| GIL | Released during long C++ train/infer calls; do not touch the same net from another thread. |
| Kind | Preferred shape |
|---|---|
| Single sample, 1 ch | (N,) with N = 2**dim |
| Single sample, C ch | (C, N) or flat (C*N,) channel-major |
| Batch | (B, input_length) or (B, C, N) |
| Class targets | (B,) int |
| Regression targets | (num_outputs,) or (B, num_outputs) float32 |
| Weights blob | (weight_count,) float32 |
| Spatial image | (H, W) float32; batch (B, H, W) before embed |
dim is in [3, 30]. Demos typically use 5–8.
Contiguous float32 (inputs/weights) and int32 (class labels) are produced automatically when needed.
Activation:NONE,RELU,LEAKY_RELU,TANHPoolType:MAX,AVGTaskType:Classification,RegressionOptimizerType:SGD,ADAM(default at construction is Adam)SpatialEmbedMode:RowMajorPad,ResizeToFit,DualPlaneResize
hc.TrainParams(
learning_rate=1e-3,
momentum=0.0, # SGD only
weight_decay=0.0,
shuffle_seed=0, # epoch: 0 = sequential; nonzero = shuffle
class_weights=None, # optional (num_outputs,) float32, classification
)hc.LayerSpec.conv(c_out, activation="relu", use_bias=True, bn=False)
hc.LayerSpec.pool("max") # or PoolType.MAX
cfg = hc.HCNNConfig(dim=6, num_outputs=3, layers=[...], weight_seed=1)
net = cfg.build()
hc.summarize_arch(dim, num_outputs, input_channels, layers) # -> ArchParamSummary
hc.apply_arch(net, layers)| Area | Methods / properties |
|---|---|
| Construct | HCNN(dim, num_outputs=10, input_channels=1, task=..., num_threads=0) |
| Stack | add_conv, add_pool, apply_layers, layers |
| Init | randomize_weights(scale=0.0, seed=42), weights_initialized |
| Mode | set_training, set_optimizer, set_train_defaults |
| Infer | predict, predict_class, forward, forward_batch |
| Train | train_step, train_batch, train_epoch (target form from task) |
| Weights | weight_count, get_weights, set_weights |
| Arch I/O | export_arch(), from_arch(dict), from_layers([...]) |
| File I/O | save_weights, load_weights, save, load |
| Sizing | dim, N, capacity, num_outputs, input_channels, num_conv, num_pool, task, optimizer |
Classification targets: int class index (step) or (B,) int (batch/epoch).
Regression targets: float array of length num_outputs per sample.
Same definitions as C++ HCNNTrainHelpers (MNIST / timeseries demos):
lr = hc.cosine_lr(1e-3, 1e-4, epoch, num_epochs) # epoch 0 -> max, last -> min
r = hc.evaluate_classification(net, X, y) # mean CE; accuracy in [0, 100]
# r.loss, r.accuracy, r.correct, r.count
rr = hc.evaluate_regression(net, X, T) # rr.mse, rr.r2, rr.target_varOptional product (not part of the conv graph). Full contracts: spatial_preprocess.md.
# Augment at native H×W, then embed to length N (always full capacity)
aug = hc.SpatialAugmenter(rot_deg_max=12, shear_x_max=0.15, border_value=-1.0)
emb = hc.SpatialEmbedder(
dim=11,
mode=hc.SpatialEmbedMode.DualPlaneResize,
pad_value=-1.0, # digit-like background — not 0
)
img = ... # (H, W) float32
work = aug.apply(img, seed=epoch)
x = emb.embed(work) # (N,); train/infer with this full lengthModes: RowMajorPad, ResizeToFit, DualPlaneResize.
After embed, pass full capacity — short lengths zero-pad in the network and
can wipe a non-zero pad_value.
HCNW stores parameters + coarse checks only (same binary format as C++
hcnn::save_weights). The layer graph is not in the weight file.
| Artifact | Content |
|---|---|
model.hcnw |
Weights (little-endian IEEE float32 blob) |
model.arch.json |
Versioned arch sidecar (format: hcnn_arch, version: 1) |
net.save("model") # both files
net2 = hc.HCNN.load("model") # or load("model.hcnw")
# explicit
net.save_weights("model.hcnw")
# write export_arch() to model.arch.json yourself if neededfrom_arch / load rebuild the stack, randomize (required before loading
weights), then restore parameters. Optimizer defaults to Adam; moments are not
in HCNW.
Arch JSON future versions raise ValueError with an upgrade message. HCNW
architecture mismatch or bad magic raise RuntimeError.
import pickle
blob = pickle.dumps(net) # arch + weight blob (not optimizer moments)
net2 = pickle.loads(blob)Prefer HCNW + arch for durable models and C++ interop. Never unpickle data from untrusted sources (pickle can execute code).
In-repo recipes (not installed with the wheel):
| Script | Role |
|---|---|
| examples/python/synthetic_classification.py | Short CE train loop |
| examples/python/synthetic_regression.py | Short MSE train loop |
| examples/python/arch_and_weights_io.py | Arch JSON + HCNW identity |
| examples/python/spatial_embed_smoke.py | Spatial pad contract + DualPlane |
See examples/python/README.md.
C++ MNIST / timeseries examples remain the heavy recipes; Python Tier 1 stays synthetic and offline.
- Extension compiles core + train-helpers + spatial into
_core(scikit-build-core). - Wheels:
HCNN_NATIVE_ARCHoff; portableHYPERCUBE_ARCH(x86-64-v2/none). HCNN_FAST_TANHdefaults ON (matches C++ library default).- MinGW: static libgcc/libstdc++; ships
libwinpthread-1.dllon Windows wheels. - Do not reconfigure CLion
cmake-build-*for the Python package.
C++ contracts: CPP_SDK.md.
Spatial: spatial_preprocess.md.