In-memory spreading activation engine with CSR graph and C FFI.
Sproink models associative memory as energy spreading through a weighted graph. Seed nodes are activated, energy propagates along edges with decay, and the result is a ranked set of associated nodes -- useful for recommendation, semantic search, and knowledge retrieval.
- CSR graph -- compressed sparse row storage for cache-friendly traversal
- 3-step propagation -- spread, squash (sigmoid), inhibit per iteration
- Lateral inhibition -- top-M winner-take-all competition suppresses noise
- Oja's rule -- Hebbian weight updates with normalization for online learning
- Jaccard affinity -- tag-based edge generation from shared attributes
- Rayon parallelism -- parallel activation spreading across nodes
- C FFI -- full C API with opaque handles (
sproink.h), usable from any language
use sproink::*;
let edges = [
EdgeInput {
source: NodeId::new(0), target: NodeId::new(1),
weight: EdgeWeight::new(0.8).unwrap(),
kind: EdgeKind::Positive, last_activated: None,
},
EdgeInput {
source: NodeId::new(1), target: NodeId::new(2),
weight: EdgeWeight::new(0.6).unwrap(),
kind: EdgeKind::Positive, last_activated: None,
},
];
let graph = CsrGraph::build(3, &edges).unwrap();
let engine = Engine::new(&graph);
let config = PropagationConfig::builder().build(); // defaults: 3 steps, decay=0.7, spread=0.85
let seeds = vec![Seed {
node: NodeId::new(0),
activation: Activation::new(1.0).unwrap(),
source: None,
}];
let results = engine.activate(&seeds, &config).unwrap();
for r in &results {
println!("node={:?} activation={:.3} distance={}", r.node, r.activation.get(), r.distance);
}#include <math.h>
#include "sproink.h"
uint32_t sources[] = {0, 1};
uint32_t targets[] = {1, 2};
double weights[] = {0.8, 0.6};
uint8_t kinds[] = {0, 0};
SproinkGraph *g = sproink_graph_build(3, 2, sources, targets, weights, kinds);
SproinkResults *r = sproink_activate(g, 1, (uint32_t[]){0}, (double[]){1.0},
NULL, 3, 0.85, 1.0, 0.001, 5.0, 0.5, false, 0.15, 5, NAN, NAN);
uint32_t len = sproink_results_len(r);
// ... read results ...
sproink_results_free(r);
sproink_graph_free(g);Build the shared and static libraries with the ffi feature:
cargo rustc --release --features ffi --crate-type cdylib
cargo rustc --release --features ffi --crate-type staticlibEach cargo rustc --crate-type invocation overrides the declared crate-type for that build, producing only the requested artifact. Run once per artifact you need. The C header sproink.h is regenerated by build.rs whenever the ffi feature is enabled.
Link against libsproink.so / libsproink.dylib / sproink.dll (cdylib) or libsproink.a (staticlib).
| Module | Purpose |
|---|---|
graph |
CSR graph storage, edge types, construction |
engine |
Propagation loop: spread, squash, inhibit |
squash |
Sigmoid activation squashing |
inhibition |
Top-M lateral inhibition |
hebbian |
Oja's rule for Hebbian weight learning |
affinity |
Jaccard similarity for edge generation |
types |
Core newtypes (NodeId, Activation, EdgeWeight, etc.) |
error |
Error types |
ffi |
C-compatible API surface (sproink.h) |
- Seed -- initial nodes receive activation energy
- Spread -- energy flows along outgoing edges, scaled by weight and decay
- Squash -- sigmoid function normalizes activations
- Inhibit -- top-M competition suppresses low activations (optional)
- Repeat steps 2-4 for
max_stepsiterations - Learn -- Oja's rule updates edge weights from co-activation pairs (optional)
make ci # fmt-check, lint, test, build
make test # cargo test
make bench # criterion benchmarks
make coverage # generate lcov coverageSee CONTRIBUTING.md for full development workflow.
Apache 2.0 -- Copyright 2026 Nic van Dessel