kumulant API reference
Kumulant is a streaming machine-learning library for Kotlin Multiplatform. Every model runs online: feed observations into an accumulator one at a time, ask for a result whenever you want one, and memory stays bounded no matter how long the stream runs. Two accumulators of the same shape can be merged into one, so partial results from parallel workers stitch back together without re-reading inputs.
Mental model
Every accumulator implements com.eignex.kumulant.core.Stat. The contract is three verbs and one constant:
update(...)folds a single observation into the running state. The exact signature depends on the modality (see the stat package for the four modality interfaces).read()materialises the current state as an immutable Result. Reads never mutate; call them as often as you like.merge(snapshot)folds another accumulator's snapshot into this one. Snapshots are the merge unit, not live accumulators; that is what lets merge cross a process boundary.concurrency: Concurrencyrecords the thread-safety contract the stat was built for. See com.eignex.kumulant.core.Concurrency.
reset() returns the stat to its construction baseline. create() spawns a fresh accumulator with the same configuration, optionally overriding the concurrency mode.
val mean = MeanStat()
for (x in doubleArrayOf(1.0, 2.0, 3.0)) mean.update(x)
val snapshot = mean.read()
println(snapshot.mean) // 2.0
val peer = MeanStat()
for (x in doubleArrayOf(4.0, 5.0)) peer.update(x)
mean.merge(peer.read())
println(mean.read().mean) // 3.0The compile-checked version of this example lives at com.eignex.kumulant.samples.basicMeanLifecycle, pulled into the rendered API docs via @sample directives on individual classes.
What's in the library
com.eignex.kumulant.core: the Stat and Result interfaces, the Concurrency enum, and the cross-cutting result traits.
com.eignex.kumulant.stat: concrete accumulators grouped by family:
summary,quantile,cardinality,sketch,rate,decay,regression(withglm/andtree/subfamilies),score,calibration,anomaly,event,change,forecast.com.eignex.kumulant.operation: composable wrappers that change how a stat sees its input (filtering, weighting, windowing, sampling, lagging) or how it reports its output (folding, transforming, projecting).
com.eignex.kumulant.schema: typed, named, wire-portable schemas. Declare a bag of stats once, materialise it into a live StatGroup, encode the schema to wire and rehydrate on the other side.
com.eignex.kumulant.bandit: multi-armed and contextual bandits built on the same Stat/Result foundation.
Conventions
Every concrete stat ships a sibling
StatSpecdata class (ordata objectfor parameter-less stats) carrying configuration only. Specs are@Serializablewith@SerialNamediscriminators matching the Kotlin class names, so polymorphic JSON / CBOR / Protobuf put the same type strings on the wire regardless of format.Every public type has KDoc with a one-sentence summary, then
**Use cases:**,**Memory:**,**Update:**, and**Concurrency:**sections. See com.eignex.kumulant.stat.summary.MeanStat for the canonical shape.Results are immutable, sealed where it makes sense, and structurally comparable via
equals/hashCode. The value that comes out ofread()is the same value that goes intomerge()over the wire.
Packages
Indexless multi-armed bandits. Each round, the bandit picks one of K arms via choose(), the caller observes a reward, and update(arm, value, weight) folds it back into that arm's accumulator. No per-round feature vector; for that, see com.eignex.kumulant.bandit.contextual.
Typed, named, wire-portable schemas for declaring bags of stats. A StatSchema does three things:
Approximate distinct-count estimators. Both entries take a stream of opaque Long keys (com.eignex.kumulant.core.DiscreteStat) and answer "how many distinct keys have I seen?" in bounded memory. The right pre-step is to hash domain-specific keys through com.eignex.kumulant.math.hash64 / com.eignex.kumulant.math.Hasher64 so the input has uniform 64-bit entropy; value.hashCode().toLong() only provides 32 bits and skews the estimators on low-cardinality domains.
Predictive recurrences with multi-cell state. Their results expose forecast(steps) projections, distinguishing them from the decay family's running-moment shape.
Throughput estimators. All three implement com.eignex.kumulant.core.HasRate, so a downstream consumer can pull rate (events per second) or per(duration) through one trait regardless of which underlying estimator produced the snapshot.
Family root for the regression-modality stats and the cross-cutting infrastructure they share. The single-output linear-model family lives in glm, the decision-tree and random-forest family in tree. What sits directly in this package is the small set of stats that don't fit either subfamily, plus the strategy types both rely on.