HypercubeESN C++ SDK
September 11, 2026 · View on GitHub
Static C++ library for reservoir computing on Boolean hypercube graphs: a fixed
Reservoir plus a trainable HypercubeCNN Readout, wrapped by ESN.
Package version 2.0.2 (project(HypercubeESN VERSION 2.0.2)). Breaking
changes and migration: CHANGELOG.md.
Deep dives: Reservoir.md · Readout.md.
House defaults (2.0): match ReservoirConfig / ReadoutConfig in headers —
verbose = false, spectral_radius = 0.999, input_scaling = 0.02,
bias_scaling = 0.003, num_layers = 1 (0 = auto), readout_slices = 1,
external feedback off (D = 0).
Contents
What's in the SDK
After installation:
<prefix>/
include/HypercubeESN/
ESN.h -- public API (the only header consumers need)
Reservoir.h -- included by ESN.h
Readout.h -- types used by the ESN API (ReadoutConfig, enums)
lib/
libHypercubeESNCore.a
lib/cmake/HypercubeESN/
HypercubeESNConfig.cmake
HypercubeESNTargets.cmake
HypercubeESNConfigVersion.cmake
Include <HypercubeESN/ESN.h> (installed) or "ESN.h" (FetchContent) and link
HypercubeESN::HypercubeESNCore (or HypercubeESNCore in FetchContent builds).
Reservoir.h / Readout.h come along transitively; their public types are part
of the API surface.
The convolutional readout comes from HypercubeCNN, vendored at
third_party/HypercubeCNN. HypercubeESNCore links it transitively — consumers
do not name it. See Dependencies.
Building from source
Requirements: C++23 (GCC 13+, Clang 17+, MSVC 2022+), CMake 4.1+.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --install build --prefix /path/to/sdk
Using the SDK
CMake FetchContent (recommended)
cmake_minimum_required(VERSION 4.1)
project(MyApp)
set(CMAKE_CXX_STANDARD 23)
include(FetchContent)
FetchContent_Declare(
HypercubeESN
GIT_REPOSITORY https://github.com/dliptak001/HypercubeESN.git
GIT_TAG v2.0.0 # pin a release tag when cut; check GitHub Releases
)
FetchContent_MakeAvailable(HypercubeESN)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE HypercubeESNCore)
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
Pin GIT_TAG to a release for reproducible builds. Include paths are set
automatically — #include "ESN.h".
HypercubeCNN is vendored in-tree; no sibling checkout or network fetch.
Installed SDK (find_package)
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --install build --prefix /path/to/sdk
cmake_minimum_required(VERSION 4.1)
project(MyApp)
set(CMAKE_CXX_STANDARD 23)
find_package(HypercubeESN REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE HypercubeESN::HypercubeESNCore)
cmake -B build -DCMAKE_PREFIX_PATH=/path/to/sdk
cmake --build build
Minimal example
FetchContent-style include ("ESN.h"). Installed SDK: <HypercubeESN/ESN.h>.
#include "ESN.h"
#include <cmath>
#include <vector>
#include <iostream>
int main()
{
constexpr size_t dim = 7; // N = 128 (= 2⁷) neurons
constexpr size_t warmup = 200;
constexpr size_t collect = 2000;
std::vector<float> signal(warmup + collect + 1);
for (size_t t = 0; t < signal.size(); ++t)
signal[t] = std::sin(0.1f * static_cast<float>(t));
ESNConfig cfg;
cfg.reservoir.dim = dim; // hypercube dimension (5-16)
cfg.reservoir.seed = 74119; // per-task surveyed seed
cfg.readout.epochs = 25;
cfg.readout.batch_size = 128;
cfg.readout.lr_max = 0.003f;
// cfg.readout_slices = 1; // default: newest reservoir slice only
ESN esn(cfg);
esn.ReservoirWarmup(signal.data(), warmup);
esn.ReservoirRun(signal.data() + warmup, collect);
std::vector<float> targets(collect);
for (size_t t = 0; t < collect; ++t)
targets[t] = signal[warmup + t + 1]; // next-step targets
size_t train_size = 1400;
size_t test_size = collect - train_size;
esn.Train(targets.data(), train_size);
double r2 = esn.R2(targets.data(), train_size, test_size);
std::cout << "R2: " << r2 << "\n";
return 0;
}
Pipeline vocabulary
inputs [+ optional ext-fb]
│
▼
Reservoir (fixed)
│
▼
SliceAt(0 .. B-1) ── pack B blocks of N ──▶ HCNN readout (trained) ──▶ y
Only the readout emits y. External feedback is an input into the reservoir (caller-owned closed-loop drive), not a second path to y.
| Term | Meaning |
|---|---|
| Timestep | One ReservoirStep |
| N | Reservoir neurons = 2dim (ReservoirNeuronCount) |
| M | history_depth — delay-line depth the recurrent gather uses |
| B | readout_slices — power of two, 1 ≤ B ≤ M; ages packed into the readout |
| Reservoir state | Newest slice only (Outputs / CopyReservoirState) — N floats |
| Readout input | What the HCNN sees: B blocks of N (ReadoutInputWidth) |
| Open loop | Task input only |
| Closed loop | Also stage external_feedback on the reservoir |
Not thread-safe. Const predict paths share a scratch buffer — one ESN per thread.
API Reference
Hypercube dimension: dim
ReservoirConfig::dim sets the reservoir hypercube size. N = 2dim neurons at
construction. Valid range [5, 16] — out of range throws
std::invalid_argument. One concrete Reservoir / ESN type serves every
dimension (no per-dim templates).
| dim | Neurons | Typical use |
|---|---|---|
| 5 | 32 | Fast prototyping, embedded |
| 6 | 64 | Light benchmarks |
| 7 | 128 | Standard benchmarks |
| 8 | 256 | Production, complex tasks |
| 9–12 | 512–4096 | Research, high-capacity tasks |
| 13–16 | 8192–65536 | Large-scale research |
When readout_slices = B > 1, the HCNN start dimension is
reservoir.dim + log2(B) (set by ESN — do not set readout.dim yourself).
Enums
Declared in Readout.h.
ReadoutTask
| Value | Description |
|---|---|
Regression | MSE loss. Raw network outputs at inference (no automatic target centering). num_outputs = number of targets. |
Classification | Softmax + cross-entropy in the loss only. num_outputs = number of classes. Labels are int class indices in [0, num_outputs); Predict returns raw logits (argmax for the label). |
ReadoutActivation
Per-Conv activation (ReadoutConfig::activation).
| Value | Description |
|---|---|
TANH | Hyperbolic tangent (default) |
RELU | Rectified linear |
LEAKY_RELU | Leaky rectified linear |
NONE | Identity |
ReadoutPoolType
| Value | Description |
|---|---|
Max | Antipodal max pool (default when pooling is on) |
Avg | Antipodal average pool |
ReadoutOptimizer
| Value | Description |
|---|---|
Adam | Default |
Sgd | Heavy-ball SGD; uses momentum |
ReadoutLoadMode
| Value | Description |
|---|---|
Eval | Load parameters only (default; safe for inference) |
ResumeTrain | Also reset optimizer moments for continued online training |
ReservoirConfig
Construction-time reservoir parameters. Defaults are a sensible starting point; production callers set dim, seed, spectral radius, and history depth per task (surveyed offline).
struct ReservoirConfig
{
size_t dim = 10; // N = 1 << dim; range [5, 16]
uint64_t seed = 7934791766227647176ULL;
float spectral_radius = 0.999f; // target for recurrent block only
float leak_rate = 1.0f; // (0, 1]
float input_scaling = 0.02f; // weights × scaling/√dim
size_t num_inputs = 1; // must divide N
size_t history_depth = 16; // M in [1, 64]
bool verbose = false; // construction banner; demos may set true
size_t num_external_feedback_channels = 0; // 0 = off; else [1, N]
float external_feedback_scaling = 0.5f;
float bias_scaling = 0.003f; // after tanh; 0 disables
};
| Field | Type | Default | Description |
|---|---|---|---|
dim | size_t | 10 | Hypercube dimension; N = 2^dim. [5, 16]. |
seed | uint64_t | 7934791766227647176 | Master RNG seed (SplitMix64 substreams: recurrent / input / external-feedback / bias / SR probe). Screen per dim/task. |
spectral_radius | float | 0.999 | Target ρ of the recurrent companion operator (MN×MN when M > 1). Drive ports are outside the rescale. |
leak_rate | float | 1.0 | state = (1 − leak) * old + leak * (tanh(s) + bias). (0, 1]. |
input_scaling | float | `0.02$ | \text{Input} \text{weights} \text{U}(−1{,}1) \text{then} \times $input_scaling / √dim` (fan-in variance). Local construction, not a universal optimum — retune per task/dim. |
num_inputs | size_t | 1 | Input channels; must divide N. Channel k drives [k·N/K, (k+1)·N/K). |
history_depth | size_t | 16 | Delay-line depth M [1, 64]. Recurrent gather over M published slices. Independent of how many ages the readout packs (B). See Reservoir.md. |
verbose | bool | false | One construction banner on stdout. |
num_external_feedback_channels | size_t | 0 | D external-feedback channels. 0 = path off. Else [1, N] (need not divide N). See ReservoirFeedbackMechanism.md. |
external_feedback_scaling | float | 0.5 | Like input; only if D > 0. Outside SR rescale. |
bias_scaling | float | 0.003 | Per-neuron bias U(−1,1)×scale, after tanh. 0 disables. Survives Clear; not in snapshots. |
GetConfig().spectral_radius / ESN::TargetSpectralRadius() is the target.
Post-secant estimate: Reservoir::GetRealizedSpectralRadius() /
ESN::RealizedSpectralRadius().
ReadoutConfig
HCNN architecture and training. Under ESN, dim is overwritten to
reservoir.dim + log2(B) — leave it at 0.
struct ReadoutConfig {
size_t dim = 0; // set by ESN — do not set
int num_outputs = 1;
ReadoutTask task = ReadoutTask::Regression;
int num_layers = 1; // typical; 0 = auto min(dim-2, 2)
bool use_pooling = true;
ReadoutPoolType pool_type = ReadoutPoolType::Max;
int conv_channels = 16;
int channel_growth = 2;
bool use_batchnorm = false;
ReadoutOptimizer optimizer = ReadoutOptimizer::Adam;
int epochs = 200;
int batch_size = 32;
float lr_max = 0.0015f; // keep ≤ ~0.005
float lr_min_frac = 0.01f;
int lr_decay_epochs = 0; // 0 = use epochs
float weight_decay = 0.0f;
float momentum = 0.9f; // SGD heavy-ball; 0 = plain SGD; ignored by Adam
uint64_t seed = 42; // full 64-bit HCNN weight-init seed
ReadoutActivation activation = ReadoutActivation::TANH;
size_t num_threads = 0; // 0=auto, 1=ST, N=N workers
bool restore_best_epoch = true;
float best_epoch_holdout_frac = 0.0f;
};
| Field | Type | Default | Description |
|---|---|---|---|
dim | size_t | 0 | Features per sample = 2dim. Set by ESN from reservoir dim + log2(B). |
num_outputs | int | 1 | Regression targets or class count. |
task | ReadoutTask | Regression | Task head. |
num_layers | int | 1 | Conv(+Pool) stages. Default 1 (house default for most tasks). 0 → auto min(dim − 2, 2). With pooling: assert n ≤ dim − 2. |
use_pooling | bool | true | Antipodal pool after each conv (mixes every bit, including block-index bits when B > 1). |
pool_type | ReadoutPoolType | Max | Max or Avg when pooling is on. |
conv_channels | int | 16 | First-layer channels. |
channel_growth | int | 2 | Multiplier after each stage. |
use_batchnorm | bool | false | Per-conv BN; grows the weight blob. |
optimizer | ReadoutOptimizer | Adam | Forwarded to HypercubeCNN. |
epochs | int | 200 | Batch-train epochs. Ignored by online TrainStep*. |
batch_size | int | 32 | Mini-batch size (batch mode). |
lr_max | float | 0.0015 | Cosine peak. Keep ≤ ~0.005 to avoid NaN. |
lr_min_frac | float | 0.01 | Floor = lr_max * lr_min_frac. |
lr_decay_epochs | int | 0 | Cosine horizon; 0 = use epochs. |
weight_decay | float | 0.0 | L2 weight decay. |
momentum | float | 0.9 | SGD heavy-ball; 0 = plain SGD. Ignored by Adam (the default optimizer). |
seed | uint64_t | 42 | HCNN weight-init seed (full 64-bit). |
activation | ReadoutActivation | TANH | After each Conv. |
num_threads | size_t | 0 | HCNN workers: 0 auto, 1 single-threaded (use for multi-ESN hosts), N workers. |
restore_best_epoch | bool | true | Restore best epoch (min MSE / max accuracy) at end of Train. |
best_epoch_holdout_frac | float | 0.0 | Tail hold-out for scoring; train on prefix. 0 = score full train set. Clamped to [0, 0.5]. |
See Readout.md and the vendor pin in ../third_party/HypercubeCNN/VENDORED.md.
ESNConfig
struct ESNConfig {
ReservoirConfig reservoir;
ReadoutConfig readout;
// B ages packed into the readout (power of two, 1 ≤ B ≤ history_depth).
// ESN sets readout.dim = reservoir.dim + log2(B).
size_t readout_slices = 1;
};
| Field | Description |
|---|---|
reservoir | Fixed dynamical core. |
readout | HCNN architecture + training. Leave dim at 0. |
readout_slices | B delay-line ages (newest first). Must be ≥ 1, a power of two, and ≤ reservoir.history_depth. B = 1 → readout input is one N-vector. B = 2 → two blocks, identity map. B > 2 → consecutive ages land on block indices two bits apart (pair map) so a Hamming-1 kernel can see both from midpoint vertices. Widening B does not change reservoir dynamics. |
ESN
Complete pipeline: Reservoir → pack B slices → Readout. Constructed from one
ESNConfig. Readout hyperparameters are fixed at construction — no per-call
config overloads on Train. Move-only (not copyable). Pointer APIs have
std::span overloads that check lengths.
ESN esn(cfg);
// Drive
esn.ReservoirStep(inputs, external_feedback /* optional */);
esn.ReservoirWarmup(inputs, num_steps); // or span (count = size / NumInputs)
esn.ReservoirRun(inputs, num_steps);
esn.ReservoirRun(inputs, num_steps, /*clear_recorded=*/true);
esn.ReservoirClear();
// Batch train / score on recorded readout inputs
esn.Train(targets, train_size);
esn.R2(targets, start, count); // full buffer covering [0, start+count)
esn.R2FromWindow(window, start, count); // window-only targets
esn.NRMSE(targets, start, count);
esn.Accuracy(labels, start, count); // int labels
// Streaming
esn.TrainStep(target, lr, weight_decay);
esn.TrainStepBatch(readout_inputs, targets, count, lr, weight_decay);
esn.CopyReadoutInput(out); // B×N
esn.CopyReservoirState(out); // N only (newest slice)
// Predict
esn.Predict();
esn.PredictFromRecorded(timestep);
esn.PredictFromReadoutInput(readout_input); // B·N; PredictFromState is an alias
// Persist / inspect
esn.GetConfig();
esn.Dim(); // == ReservoirHypercubeDimension()
esn.TargetSpectralRadius();
esn.RealizedSpectralRadius();
esn.GetReadoutState();
esn.SetReadoutState(state, mode);
esn.SaveReadoutHcnnModel(stem);
esn.LoadReadoutHcnnModel(stem, mode);
esn.ReadoutArchSummary();
esn.ReadoutBestEpoch();
Construction
explicit ESN(const ESNConfig& cfg);
Builds the reservoir (Create) and the HCNN eagerly (MakeReadoutConfig fills
readout.dim). Both weight sets are ready before the first Train / TrainStep.
ESNConfig cfg;
cfg.reservoir.dim = 8;
cfg.reservoir.seed = 74119;
cfg.reservoir.spectral_radius = 0.99f;
cfg.readout_slices = 1; // or 2, 4, … ≤ history_depth
cfg.readout.epochs = 1000;
cfg.readout.batch_size = 512;
cfg.readout.lr_max = 0.001f;
ESN esn(cfg);
Reservoir driving
ReservoirStep
Pointer and std::span overloads (span form validates lengths):
void ReservoirStep(const float* inputs, const float* external_feedback = nullptr);
void ReservoirStep(std::span<const float> inputs,
std::span<const float> external_feedback = {});
One timestep: stage task inputs (NumInputs() floats), optionally stage
external feedback (NumExternalFeedbackChannels() floats, or nullptr to
skip), then Reservoir::Step. No learning.
Throws if external_feedback is non-null when D = 0.
ReservoirWarmup
void ReservoirWarmup(const float* inputs, size_t num_steps);
void ReservoirWarmup(std::span<const float> inputs); // count = size / NumInputs()
Drive without recording (wash out zero initial state). Layout: num_steps × NumInputs() row-major. No external feedback — use ReservoirStep if needed.
Typical warmup: 100–500 steps. Span form requires inputs.size() to be a
multiple of NumInputs().
Values are not clamped; pass already-bounded signals.
ReservoirRun
void ReservoirRun(const float* inputs, size_t num_steps, bool clear_recorded = false);
void ReservoirRun(std::span<const float> inputs, bool clear_recorded = false);
Drive and append each assembled readout input (B×N) to the internal buffer
for Train / metrics. Same input layout as warmup. No external feedback.
clear_recorded = true discards prior rows first (live reservoir and readout
weights untouched).
ReservoirClear
void ReservoirClear();
Zero reservoir dynamics (state + history). Recorded rows and readout weights are preserved.
Batch training
Train
// Regression
void Train(const float* targets, size_t train_size);
void Train(std::span<const float> targets, size_t train_size);
// Classification
void Train(const int* class_labels, size_t train_size);
void Train(std::span<const int> class_labels, size_t train_size);
Fit the HCNN on recorded timesteps [0, train_size). A second call continues
from current weights — construct a new ESN for a fresh init. Task is fixed at
construction; the wrong pointer / span type throws std::logic_error.
- Regression:
train_size × NumOutputs()floats, row-major. Span size must equal that product. - Classification:
train_sizeints (class indices in[0, NumOutputs())). Span size must equaltrain_size.
Throws if train_size > NumCollectedStates().
Streaming training
CNN is built at construction — no separate init. Warm up the reservoir, then
interleave ReservoirStep with TrainStep / Predict. epochs is ignored;
loop length is the caller's.
TrainStep
void TrainStep(const float* target, float lr, float weight_decay = 0.0f); // regression
void TrainStep(std::span<const float> target, float lr, float weight_decay = 0.0f);
void TrainStep(int class_label, float lr, float weight_decay = 0.0f); // classification
One gradient step on the current readout input (assembled after your last
drive). Regression: NumOutputs() floats. Classification: one integer class
index. Online hosts typically schedule lr with CosineLR /
ExponentialDecayLR from Readout.h (epochs is ignored here).
TrainStepBatch
void TrainStepBatch(const float* readout_inputs, const float* targets, size_t count,
float lr, float weight_decay = 0.0f); // regression
void TrainStepBatch(const float* readout_inputs, const int* class_labels, size_t count,
float lr, float weight_decay = 0.0f); // classification
void TrainStepBatch(std::span<const float> readout_inputs, std::span<const float> targets,
float lr, float weight_decay = 0.0f);
void TrainStepBatch(std::span<const float> readout_inputs, std::span<const int> class_labels,
float lr, float weight_decay = 0.0f);
Mini-batch of caller-supplied readout inputs (count × ReadoutInputWidth()).
Assemble rows with CopyReadoutInput (not CopyReservoirState, unless B = 1).
Span forms infer count from readout_inputs.size() / ReadoutInputWidth().
CopyReadoutInput / CopyReservoirState
void CopyReadoutInput(float* out) const; // ReadoutInputWidth() = B×N
void CopyReadoutInput(std::span<float> out) const;
void CopyReservoirState(float* out) const; // N (newest slice only)
void CopyReservoirState(std::span<float> out) const;
Prediction and evaluation
Recorded window
std::vector<float> PredictFromRecorded(size_t timestep) const;
double R2(const float* targets, size_t start, size_t count) const;
double R2(std::span<const float> targets, size_t start, size_t count) const;
double R2FromWindow(std::span<const float> targets_window, size_t start, size_t count) const;
double NRMSE(const float* targets, size_t start, size_t count) const;
double NRMSEFromWindow(std::span<const float> targets_window, size_t start, size_t count) const;
double Accuracy(const int* labels, size_t start, size_t count) const;
double AccuracyFromWindow(std::span<const int> labels_window, size_t start, size_t count) const;
R2/NRMSE/Accuracy: targets must cover[0, start+count)— pass the full array; methods index fromstart. Do not pre-slice.*FromWindow: targets / labels are only the scored rows (countsamples). Recorded states still usestart. Use these when you already hold a sliced buffer.- R²: average of per-output coefficients of determination. 1.0 = perfect.
Regression layout: stride =
NumOutputs(). - NRMSE: mean over outputs of RMSE / std(target). 0 = perfect. Degenerate target variance → +inf on that output.
- Accuracy: integer class labels; multi-class argmax; single-output thresholds the logit at 0.
Live / caller-supplied
std::vector<float> Predict() const; // assemble live, then forward
void Predict(float* out) const;
void Predict(std::span<float> out) const;
std::vector<float> PredictFromReadoutInput(const float* readout_input) const;
void PredictFromReadoutInput(const float* readout_input, float* out) const;
std::vector<float> PredictFromReadoutInput(std::span<const float> readout_input) const;
void PredictFromReadoutInput(std::span<const float> readout_input, std::span<float> out) const;
// Historical aliases — same as PredictFromReadoutInput:
std::vector<float> PredictFromState(const float* readout_input) const;
void PredictFromState(const float* readout_input, float* out) const;
PredictFromReadoutInput never reads the reservoir — pass a
ReadoutInputWidth() buffer (e.g. from CopyReadoutInput). Softmax is not
applied; classification returns logits.
State access and accessors
std::vector<float> CollectedStates() const; // T × ReadoutInputWidth(), row-major
CollectedStates is the recorded readout inputs (B×N per row), not just
the newest reservoir slice. Name is historical.
| Method | Returns |
|---|---|
NumCollectedStates() | Rows recorded by ReservoirRun |
NumInputs() | Input channels per timestep |
NumOutputs() | Readout width (targets or classes) |
NumExternalFeedbackChannels() | D (0 = no ext-fb port) |
ReservoirHypercubeDimension() / Dim() | cfg.reservoir.dim |
ReservoirNeuronCount() | N = 2dim |
ReadoutInputWidth() | B × N |
ReadoutBlockCount() | B |
ReadoutBlockOf(slot) | Physical block index for logical age slot |
TargetSpectralRadius() | Configured recurrent ρ target |
RealizedSpectralRadius() | Post-rescale estimate from construction |
GetConfig() | ESNConfig with derived readout.dim filled |
Readout persistence
Reservoir weights are deterministic from config + seed. Persist GetConfig() and
the readout.
ESN::ReadoutState
| Field | Description |
|---|---|
weights | Opaque vector<double> (unversioned HCNN blob). Round-trip only. |
is_trained | True if the network exists (true after construction — not “has seen data”). |
| Method | Description |
|---|---|
GetReadoutState() | Snapshot weights |
SetReadoutState(state, mode=Eval) | Inject into the live net. No-op if !is_trained |
ReadoutBestEpoch() | 1-based best epoch after last batch Train with restore, else 0 |
SaveReadoutHcnnModel(stem) | Portable stem.hcnw + stem.arch.json |
LoadReadoutHcnnModel(stem, mode=Eval) | Load after arch sidecar validation |
ReadoutArchSummary() | Human-readable stack + parameter counts |
ESNConfig cfg = esn.GetConfig();
auto state = esn.GetReadoutState();
// serialize cfg + state …
ESN restored(cfg);
restored.SetReadoutState(state);
Standalone Reservoir and Readout
Most adopters only construct ESN. Reservoir.h and Readout.h are still
public (included by ESN.h).
Reservoir — Reservoir::Create(cfg) returns unique_ptr<Reservoir>
(non-copyable, non-movable). Per-step contract: InjectInput / optional
InjectExternalFeedback, then Step(), then Outputs() / SliceAt(age).
Clear() zeros dynamics. TakeSnapshot / RestoreSnapshot round-trip the
delay line (not weights). GetConfig() + seed rebuilds matching weights;
GetRealizedSpectralRadius() is the post-rescale estimate.
Readout — construct from ReadoutConfig (set dim yourself if you are
not going through ESN). Train / TrainStep* / PredictRaw / PredictClass
/ R2 / Accuracy / Weights / SetState / SaveHcnnModel. Online hosts
schedule lr with the free functions in Readout.h:
float CosineLR(float progress, float lr_max, float lr_min);
float ExponentialDecayLR(float progress, float lr_max, float lr_min);
progress is clamped to [0, 1]. Batch Train uses HCNN's own cosine schedule
from epochs / lr_decay_epochs instead.
Dependencies
HypercubeCNN — hypercube convolutional stack used by Readout.
- Vendored read-only snapshot at
third_party/HypercubeCNN(seeVENDORED.md). - Built transitively via
add_subdirectory; offline and version-pinned. - Linked through HypercubeESNCore — consumers only link HypercubeESNCore.
- Public HypercubeESN surface is ESN / Reservoir / Readout types; full HCNN API
is not re-exported (
hcnn::HCNNis PIMPL'd insideReadout).
No other external dependencies beyond the C++ standard library.