Skip to content

Math

Numerics: signal processing in both domains, estimation and fitting, interpolation, statistics, randomness and units. Everything here is plain Double/Float Kotlin with no GPU and no window, so it runs in a box, a test or a headless docs capture equally well.

47 source file(s), 2162 public symbol(s), 1623 of them carrying KDoc. Everything below is the documentation that lives beside the code — the same text the completion documentation panel shows in the editor. A blank summary means the declaration has no KDoc yet, not that this page dropped it.

interpolants.kt

smoothTo wraps a target-producing lambda so that reading it eases toward the target instead of jumping to it.

The returned Interpolant is also a HasUniform/ProgressingUniform, so it can sit where Field expects a uniform and be re-evaluated per frame. It implements NeedsStartingValue, and whoever binds it is responsible for calling startAt with the current value — until that happens the interpolant simply reports the target and marks itself complete.

Interpolation is linear and only covers Float, Double, Int, Long and Vector2f/Vector3f/Vector4f; any other type completes immediately at the target. Progress is either time-based (time seconds), a fixed number of evaluations (steps, default 100), or frame-based by supplying clock = frameClock(fps) { frameNumber }. Time-based interpolation uses System.nanoTime by default; an injected clock makes offline and fixed-rate rendering independent of scheduler pauses. The interpolation is one-shot: once it reaches the target it stays there.

inertialTo is the other half of the library, for the case smoothTo handles badly: a target that keeps moving. It takes the same time/steps/clock arguments, but eases on a smoothstep whose timing it re-solves whenever the target changes, so a re-aim carries the velocity it already had instead of restarting from a standstill.

src/jvmMain/kotlin/afield/animation/interpolation/interpolants.kt — read the source

Used in: Values over time: interpolants & keyframes

Top-level functions

Declaration Summary
fun frameClock(fps: Double, frameNumber: () -> Number): () -> Double Converts an authoritative rendered-frame counter into the seconds clock accepted by smoothTo.
fun <T> smoothTo(time: Double? = null, steps: Int? = 100, clock: (() -> Double)? = null, a: () -> T): Interpolant<T> Wraps a target-producing lambda so that reading it eases toward the target instead of jumping there — the one-word way to take the snap out of a value assignment.
fun <T> inertialTo(time: Double? = null, steps: Int? = 100, clock: (() -> Double)? = null, overshoot: Double = 0.05, a: () -> T): Interpolant<T> Wraps a target-producing lambda so that reading it eases toward the target with inertia — the version of smoothTo for a target that keeps moving.

NeedsStartingValue

interface NeedsStartingValue<T>

Implemented by a value that can only interpolate once something tells it where it is starting from.

Declaration Summary
fun startAt(a: Any?) Sets the value to ease away from, and restarts the interpolation.

Interpolant

interface Interpolant<T> : () -> T, NeedsStartingValue<T>

A callable interpolation whose starting value can be set or restarted explicitly.

InertialEasing

object InertialEasing

The arithmetic behind inertialTo, for driving an inertial blend by hand — in a game loop, or on something the interpolant itself cannot carry, like a quaternion.

Declaration Summary
data class Solved(val t: Double, val s: Double) A time and a scale for smoothstep, as returned by solve.
fun smoothstep(t: Double, s: Double): Double The scaled smoothstep s * (3t² - 2t³), held at s once t passes 1.
fun smoothstepDt(t: Double, s: Double): Double The derivative of smoothstep with respect to t — the velocity the curve is travelling at.
fun solve(x: Double, v: Double, overshoot: Double = 0.05, eps: Double = 1e-8): Solved Finds the smoothstep that is x away from its destination and moving at velocity v.

spectral.kt

Frequency-domain / spectral analysis family for the stdlib DSP toolkit.

This is the frequency-domain companion to timeseries.kt (which is entirely time-domain): a pure-Kotlin FFT, analysis windows, a Spectrum and a Spectrogram (STFT), classic spectral features (centroid / spread / rolloff / flux / flatness / RMS / energy), and streaming FloatFilter wrappers so those features drop straight into the time-domain filter chain in timeseries.kt (compose with then).

Everything is plain Double/Float math — no external DSP dependency — so it works headless in a box, a test, or a docs capture. The FFT is an iterative Cooley–Tukey radix-2 core with a Bluestein (chirp-z) fallback so arbitrary (non-power-of-two) lengths transform exactly.

src/jvmMain/kotlin/afield/audio/analysis/spectral.kt — read the source

Used in: Signal examples: time and frequency

Top-level functions

Declaration Summary
fun fft(signal: DoubleArray, sampleRate: Double = signal.size.toDouble()): Spectrum+2 overloads · also FloatArray, List<Float> Forward FFT of a real signal. sampleRate defaults to the signal length (bins == cycles/frame).
fun fft(signal: DoubleArray, window: WindowType, sampleRate: Double = signal.size.toDouble()): Spectrum Windowed forward FFT: multiplies by window first (amplitude compensated by coherent gain).
fun ifft(spectrum: Spectrum): DoubleArray Inverse FFT; returns the real part of the reconstructed signal.
fun ifftComplex(spectrum: Spectrum): Pair<DoubleArray, DoubleArray> Inverse FFT keeping both real and imaginary parts.
fun stft(signal: DoubleArray, fftSize: Int = 1024, hopSize: Int = fftSize / 4, window: WindowType = WindowType.HANN, sampleRate: Double = signal.size.toDouble()): Spectrogram+2 overloads · also FloatArray, List<Float> Short-time Fourier transform. Frames of fftSize samples are windowed by window and advanced by hopSize; the tail is zero-padded so the final partial frame is kept.
fun istft(spec: Spectrogram): DoubleArray Inverse STFT — weighted overlap-add reconstruction of a real signal from a Spectrogram. Each frame is inverse-transformed, re-windowed (synthesis window == analysis window) and overlap-added; the running window-energy is divided out so a Hann-windowed COLA STFT reconstructs faithfully.

FFT

object FFT

The transform itself: an in-place, self-contained complex FFT that accepts any length, not just a power of two.

Declaration Summary
fun isPowerOfTwo(n: Int): Boolean True for 1, 2, 4, 8, … — and false for zero and negatives, so it is safe on unvalidated input.
fun nextPowerOfTwo(n: Int): Int Smallest power of two >= n (>= 1).
fun transform(re: DoubleArray, im: DoubleArray) Forward FFT in place. re/im must be the same length; any length >= 1 is accepted (power-of-two goes through the radix-2 kernel, otherwise Bluestein).
fun inverseTransform(re: DoubleArray, im: DoubleArray) Inverse FFT in place, normalized (divides by N). By the standard identity the inverse is the forward transform with real/imag swapped, then scaled by 1/N.
fun transformRadix2(re: DoubleArray, im: DoubleArray) In-place radix-2 decimation-in-time forward transform; requires power-of-two length.
fun transformBluestein(re: DoubleArray, im: DoubleArray) In-place arbitrary-length forward transform via Bluestein's chirp-z algorithm.

WindowType

enum class WindowType

Which analysis window to taper a frame with before transforming it — the trade between how sharply you can separate two nearby frequencies and how much a strong one smears into its neighbours.

Constants: RECTANGULAR, HANN, HAMMING, BLACKMAN.

Windows

object Windows

The window functions themselves, as plain DoubleArrays you multiply a frame by.

Declaration Summary
fun make(type: WindowType, n: Int, periodic: Boolean = false): DoubleArray Build a length-n window of the given type. Symmetric (periodic=false) by default.
fun rectangular(n: Int): DoubleArray All ones — i.e. no window. The periodic distinction does not apply, so there is no such parameter.
fun hann(n: Int, periodic: Boolean = false): DoubleArray The raised-cosine window: 0.5 - 0.5·cos(2πi/d), zero at both ends, and the default worth reaching for. Symmetric unless periodic is true.
fun hamming(n: Int, periodic: Boolean = false): DoubleArray 0.54 - 0.46·cos(2πi/d) — like hann but it does not reach zero at the ends, which is what buys its slightly better resolution and slightly worse far-field leakage.
fun blackman(n: Int, periodic: Boolean = false): DoubleArray The three-term 0.42 - 0.5·cos(w) + 0.08·cos(2w) window: the strongest leakage suppression here, and the widest main lobe — about 1.5 times hann's null-to-null width, so two frequencies Hann would just separate blur into one here.
fun coherentGain(window: DoubleArray): Double Coherent gain (mean) of a window — used to compensate amplitude when a window is applied.

Spectrum

class Spectrum(val re: DoubleArray, val im: DoubleArray, val sampleRate: Double)

A single complex spectrum plus the sampleRate it was measured at. re/im are the full length-N transform; for a real input the useful ("one-sided") part is bins 0..N/2.

Declaration Summary
val re: DoubleArray
val im: DoubleArray
val sampleRate: Double
val size: Int
val binCount: Int Number of one-sided bins (DC .. Nyquist), i.e. N/2 + 1 for even N.
fun binFrequency(k: Int): Double Frequency (Hz) of bin k.
fun frequencies(): DoubleArray Frequencies (Hz) of the one-sided bins 0..N/2.
fun magnitude(): DoubleArray Full-length magnitude |Xk|.
fun magnitudeOneSided(): DoubleArray One-sided magnitude (bins 0..N/2).
fun powerOneSided(): DoubleArray One-sided power |Xk|².
fun magnitudeDbOneSided(floorDb: Double = -120.0, ref: Double = 1.0): DoubleArray One-sided magnitude in decibels, floored at floorDb.
fun phase(): DoubleArray Phase (radians) per bin.
fun peakBin(): Int Bin index of the peak one-sided magnitude.
fun peakFrequency(): Double Frequency (Hz) of the dominant one-sided component.
fun centroid(): Double
fun spread(): Double
fun rolloff(rolloffFraction: Double = 0.85): Double
fun flatness(): Double

Spectrogram

class Spectrogram(val framesRe: Array<DoubleArray>, val framesIm: Array<DoubleArray>, val sampleRate: Double, val fftSize: Int, val hopSize: Int, val window: WindowType)

The result of a short-time Fourier transform: a stack of per-frame one-sided magnitude spectra. magnitudesframe`, bin in 0..fftSize/2. Complex frames are kept too so an inverse-STFT overlap-add reconstruction is possible ([istft`](math.md#spectral-kt--istft)).

Declaration Summary
val framesRe: Array<DoubleArray> Complex STFT frames (full length-fftSize), kept for reconstruction.
val framesIm: Array<DoubleArray>
val sampleRate: Double
val fftSize: Int
val hopSize: Int
val window: WindowType
val numFrames: Int
val numBins: Int
fun binFrequency(k: Int): Double
fun frameTime(i: Int): Double Center time (seconds) of frame i.
fun magnitude(i: Int): DoubleArray One-sided magnitude of frame i.
fun magnitudes(): Array<DoubleArray> All frames as one-sided magnitudes: frame.
fun maxMagnitude(): Double Largest magnitude anywhere in the spectrogram (for normalizing a heatmap).
fun centroidTrace(): DoubleArray Per-frame spectral centroid (Hz).
fun rolloffTrace(rolloffFraction: Double = 0.85): DoubleArray Per-frame spectral rolloff (Hz).
fun fluxTrace(): DoubleArray Per-frame spectral flux (positive change vs previous frame).

SpectralFeatures

object SpectralFeatures

Single numbers that describe the shape of a spectrum — brightness, spread, where the energy stops — for driving something from a sound without looking at every bin.

Declaration Summary
fun centroid(magnitude: DoubleArray, sampleRate: Double): Double Spectral centroid (Hz): magnitude-weighted mean frequency.
fun spread(magnitude: DoubleArray, sampleRate: Double): Double Spectral spread / bandwidth (Hz): magnitude-weighted std-dev of frequency about the centroid.
fun rolloff(magnitude: DoubleArray, sampleRate: Double, rolloffFraction: Double = 0.85): Double Spectral rolloff (Hz): the frequency below which rolloffFraction of the total spectral energy (Σ magnitude) lies. Classic timbre / brightness descriptor.
fun flux(previous: DoubleArray, current: DoubleArray): Double Spectral flux: the (positive, L2) change between two consecutive magnitude spectra. A standard onset / novelty descriptor. Only positive differences are summed (half-wave rectified).
fun flatness(magnitude: DoubleArray): Double Spectral flatness (Wiener entropy): geometric mean / arithmetic mean of the power spectrum. ~1 for noise-like (flat) spectra, ->0 for tonal (peaky) spectra.
fun rms(signal: DoubleArray): Double Root-mean-square level of a time-domain buffer.
fun rms(signal: FloatArray): Double Root-mean-square level of a time-domain buffer — loudness, roughly, and the number to drive a meter from. 0.0 for an empty buffer.
fun energy(signal: DoubleArray): Double Total energy Σ x² of a time-domain buffer.

Mel

object Mel

The mel scale and its triangular filter bank — frequency re-spaced the way hearing actually works, so that a step of one band means about as much low down as high up.

Declaration Summary
fun hzToMel(hz: Double): Double Hz to mels, 2595·log10(1 + hz/700). Monotonic and 0 at 0 Hz.
fun melToHz(mel: Double): Double Mels back to Hz — the exact inverse of hzToMel.
fun filterBank(magnitude: DoubleArray, sampleRate: Double, numBands: Int = 26, minHz: Double = 0.0, maxHz: Double = sampleRate / 2.0): DoubleArray Apply a triangular mel filter bank to a one-sided magnitude spectrum, returning numBands mel-band energies spanning minHz..maxHz.

SpectralFrameFilter

abstract class SpectralFrameFilter(val fftSize: Int = 1024, val hopSize: Int = fftSize / 4, val sampleRate: Float = fftSize.toFloat(), val window: WindowType = WindowType.HANN) : FloatFilter

Base class: buffers samples, fires onFrame every hop with the current windowed frame.

Declaration Summary
val fftSize: Int
val hopSize: Int
val sampleRate: Float
val window: WindowType
override fun invoke(value: Float): Float
fun reset()

SpectralCentroidFilter

class SpectralCentroidFilter(fftSize: Int = 1024, hopSize: Int = fftSize / 4, sampleRate: Float = fftSize.toFloat(), window: WindowType = WindowType.HANN) : SpectralFrameFilter(fftSize, hopSize, sampleRate, window)

Streaming spectral centroid (Hz), held between hops.

Declaration Summary
override fun onFrame(spectrum: Spectrum): Float

SpectralRolloffFilter

class SpectralRolloffFilter(fftSize: Int = 1024, hopSize: Int = fftSize / 4, sampleRate: Float = fftSize.toFloat(), window: WindowType = WindowType.HANN, var rolloffFraction: Double = 0.85) : SpectralFrameFilter(fftSize, hopSize, sampleRate, window)

Streaming spectral rolloff (Hz), held between hops.

Declaration Summary
var rolloffFraction: Double
override fun onFrame(spectrum: Spectrum): Float

SpectralFluxFilter

class SpectralFluxFilter(fftSize: Int = 1024, hopSize: Int = fftSize / 4, sampleRate: Float = fftSize.toFloat(), window: WindowType = WindowType.HANN) : SpectralFrameFilter(fftSize, hopSize, sampleRate, window)

Streaming spectral flux (onset novelty), held between hops.

Declaration Summary
override fun onFrame(spectrum: Spectrum): Float

RmsFilter

class RmsFilter(val windowSize: Int = 256) : FloatFilter

Streaming windowed RMS: a rolling root-mean-square over the last windowSize samples. Cheap O(1)-per-sample running sum-of-squares — the time-domain energy companion to the spectral filters.

Declaration Summary
override fun invoke(value: Float): Float
fun reset()

linalg_mesh.kt

The 3D convex hull of a list of Vec3, computed with the QuickHull3D library.

One function, convexHull, which keeps the vertices on the outside of the cloud and drops the interior ones. Vertices only, in the library's own order, with no faces and no winding. Fewer than four points, or a cloud QuickHull cannot triangulate, returns the input unchanged rather than throwing, so a result the same size as the input may be a hull or a refusal and the two cannot be told apart from the return value.

The vector types themselves are in linalg.kt. Centroids, medians and bounding boxes for the same point collections are in linalg_spatial.kt, which also holds convexHullPeelingMedian and the asQuickHullPoints packer.

src/jvmMain/kotlin/afield/geometry/mesh/linalg_mesh.kt — read the source

Top-level functions

Declaration Summary
fun List<Vec3>.convexHull(): List<Vec3> The vertices of the 3D convex hull, via QuickHull3D — the points on the outside, with the interior ones dropped.

CubicBezier3D.kt

A double-precision 3D cubic Bézier for planning paths with a minimum turning radius.

CubicBezier3D evaluates positions and tangents, gives a conservative lower bound on the curve's radius with CubicBezier3D.minimumRadiusBound, and turns the curve into a polyline within a caller-chosen deviation with CubicBezier3D.flatten. Degenerate curves, cusps and invalid requests throw rather than return a guess. CubicBezier3DTest covers these cases, including invariance under translation, rotation and change of units.

src/jvmMain/kotlin/afield/geometry/path/planning/CubicBezier3D.kt — read the source

CubicBezier3D

data class CubicBezier3D(val start: Point3D, val control1: Point3D, val control2: Point3D, val end: Point3D)

An immutable double-precision cubic in caller-chosen units. start and end are endpoints; control1 and control2 are the inner controls. The caller supplies all approximation tolerances and curvature thresholds.

Declaration Summary
fun valueAt(t: Double): Point3D Position at a finite parameter t in the closed unit interval.
fun tangentAt(t: Double): Point3D Unnormalised derivative with respect to the unit parameter; it may be zero.
fun minimumRadiusBound(minimum: Double, maximumDepth: Int = 18): Double Conservative lower bound on radius across the continuous cubic, at least minimum. Subdivides Bernstein control hulls, bounding speed below and |r' × r''| above. Throws if the bound cannot be established within maximumDepth; failure is not proof that no valid curve exists. Zero chords/cusps and arithmetic overflow fail closed. This is a numerical bound, not interval-arithmetic certification.
fun flatten(maximumDeviation: Double, maximumDepth: Int = 24): List<Point3D> Polyline including both endpoints. Every subcurve's control hull is within maximumDeviation of its finite chord. This bounds curve-to-polyline distance; it does not prove containment in another shape. Throws on degenerate chords or failure to converge within maximumDepth.

Point3D.kt

A small immutable 3D point type used by the path-planning curves in this package.

Point3D carries finite double coordinates with the vector operations a curve needs: addition, difference, scaling, Point3D.dot, Point3D.cross, Point3D.length and Point3D.normalized. CubicBezier3D is built from it.

src/jvmMain/kotlin/afield/geometry/path/planning/Point3D.kt — read the source

Point3D

data class Point3D(val x: Double, val y: Double, val z: Double)

Immutable double-precision coordinates; units are chosen by the caller.

Declaration Summary
operator fun plus(b: Point3D) Coordinate-wise addition, returning a new value.
operator fun minus(b: Point3D) Coordinate-wise difference, also usable as a displacement.
operator fun unaryMinus() Negates all coordinates without changing this value.
operator fun times(scale: Double) Multiplies every coordinate by scale; non-finite results are rejected.
infix fun dot(b: Point3D) Euclidean dot product, treating the coordinates as vectors.
infix fun cross(b: Point3D) Right-handed cross product, treating the coordinates as vectors.
val length: Double Euclidean distance from the origin in the caller's units.
fun normalized(): Point3D Unit direction; rejects zero length and lengths that overflow.
fun distanceTo(b: Point3D): Double Euclidean distance to b in the caller's units.

linalg_spatial.kt

What a collection of points adds up to: centroids, outlier-resistant centres, and the bounding boxes that enclose them.

center is the plain mean and medianVec the per-axis median, both available for Vec2, Vec3 and Vec4 collections and both returning a fresh vector. aabbBounds gives the axis-aligned box over those same three dimensions, as AxisAlignedBoundingBox2D, AxisAlignedBoundingBox3D or AxisAlignedBoundingBox4D. closest is a linear scan for the nearest member of a collection.

Two summaries exist only in 3D. obbBounds returns an OrientedBoundingBox whose axes come from a PCA of the cloud, so an elongated diagonal cluster gets a tight box where aabbBounds gets a loose one; its own documentation sets out how far the power-iteration axes can be trusted. convexHullPeelingMedian strips hulls off the outside of the cloud and averages what is left, which discards outliers instead of averaging them in.

asQuickHullPoints packs a collection into the flat DoubleArray that QuickHull3D and other array-based libraries expect; the hull itself is in linalg_mesh.kt, and the vector types and their constructors are in linalg.kt.

Cuboid is a different thing from the bounding boxes above: an immutable corner-plus-size 3D box for layout arithmetic, with union, inset, translate and containment, rather than a fit to geometry.

src/jvmMain/kotlin/afield/geometry/spatial/linalg_spatial.kt — read the source

Top-level functions

Declaration Summary
fun Vec3.closest(a: Iterable<Vec3>) The member of a nearest this point by Euclidean distance.
fun Vec4.closest(a: Iterable<Vec4>) The member of a nearest this point in 4D. Linear scan; throws if a is empty.
fun Vec2.closest(a: Iterable<Vec2>) The member of a nearest this point in the plane. Linear scan; throws if a is empty, and the result is the collection's own instance rather than a copy.
fun Collection<Vec2>.center(): Vec2 The centroid — the plain mean of every point, in a fresh Vec2.
fun Collection<Vec2>.medianVec(): Vec2 The component-wise median, as a point — the outlier-resistant answer to "where is this cloud".
fun Collection<Vec2>.aabbBounds(): AxisAlignedBoundingBox2D Axis-aligned bounds of the points, as an AxisAlignedBoundingBox2D.
fun Collection<Vec3>.center(): Vec3 The centroid — the plain mean of every point, in a fresh Vec3. (0, 0, 0) when empty.
fun Collection<Vec3>.medianVec(): Vec3 The component-wise median, as a point — robust to outliers, and generally not one of the input points, since each axis is medianed independently. center is the true mean.
fun Collection<Vec3>.aabbBounds(): AxisAlignedBoundingBox3D Axis-aligned bounds of the points, as an AxisAlignedBoundingBox3D — the cheap bounding volume, one pass and no rotation.
fun Collection<Vec3>.obbBounds(): OrientedBoundingBox Bounds that turn with the data: an OrientedBoundingBox whose axes come from a PCA of the point cloud, so an elongated diagonal cluster gets a tight box where aabbBounds would get a loose one.
fun List<Vec3>.convexHullPeelingMedian(peelSteps: Int = 5): Vec3 A deeply outlier-resistant centre: strip the convex hull off the cloud peelSteps times, then take the mean of whatever is left.
fun Collection<Vec3>.asQuickHullPoints(): DoubleArray Flattens the points into the packed [x0, y0, z0, x1, …] DoubleArray that QuickHull3D and other plain-array libraries expect.
fun Collection<Vec4>.center(): Vec4 The component-wise mean of every element, in a fresh Vec4. (0, 0, 0, 0) when empty.
fun Collection<Vec4>.medianVec(): Vec4 The component-wise median across all four axes, as one Vec4. Robust to outliers; see medianVec on Collection<Vec2> for why the result is generally not one of the inputs.
fun Collection<Vec4>.aabbBounds(): AxisAlignedBoundingBox4D Axis-aligned bounds on all four axes, as an AxisAlignedBoundingBox4D. One pass; degenerate box at the origin when empty.

OrientedBoundingBox

data class OrientedBoundingBox(val center: Vec3, val axes: Array<Vec3>, val extents: FloatArray)

Oriented Bounding Box (OBB) representation.

Declaration Summary
val center: Vec3
val axes: Array<Vec3>
val extents: FloatArray

AxisAlignedBoundingBox2D

data class AxisAlignedBoundingBox2D(val min: Vec2, val max: Vec2)

Axis-Aligned Bounding Box for 2D vectors.

Declaration Summary
val min: Vec2
val max: Vec2
val size: Vec2 Width and height of the box, as max - min. Zero on both axes for a single point.
val center: Vec2 The midpoint of the box — not the centroid of the points it was built from.

AxisAlignedBoundingBox3D

data class AxisAlignedBoundingBox3D(val min: Vec3, val max: Vec3)

Axis-Aligned Bounding Box for 3D vectors.

Declaration Summary
val min: Vec3
val max: Vec3
val size: Vec3 Width, height and depth of the box, as max - min.
val center: Vec3 The midpoint of the box — not the centroid of the points it was built from.

AxisAlignedBoundingBox4D

data class AxisAlignedBoundingBox4D(val min: Vec4, val max: Vec4)

Axis-Aligned Bounding Box for 4D vectors.

Declaration Summary
val min: Vec4
val max: Vec4
val size: Vec4 Extent of the box on each of the four axes, as max - min.
val center: Vec4 The midpoint of the box — not the centroid of the points it was built from.

Cuboid

class Cuboid(val x: Float, val y: Float, val z: Float, val w: Float, val h: Float, val d: Float)

An axis-aligned 3D box held as a corner plus a size — x, y, z and w, h, d — and immutable.

Declaration Summary
fun union(r: Cuboid?): Cuboid The smallest cuboid containing both this one and r — with a null r treated as "nothing", so this is safe to fold over a nullable accumulator.
fun translate(by: Vector2d): Cuboid A copy moved in x and y only, leaving z where it was. Size is unchanged.
fun translate(by: Vector3d): Cuboid A copy moved on all three axes. Size is unchanged.
fun intersects(point: Vector3d): Boolean Does this box contain point? Closed on every side, so a point exactly on a face or corner counts as inside — two boxes sharing a face therefore both claim the points on it.
fun intersects(r: Cuboid): Boolean Do the two boxes overlap in volume? Open here, unlike the point overload: boxes that merely touch — one's face flush against the other's — do not intersect, which is what you want when packing things edge to edge.
override fun toString(): String Cuboid{x=… y=… z=… w=… h=… d=…} — the corner and the size, for debugging.
fun inset(by: Double): Cuboid A copy shrunk by by on every side: the corner moves in by that much and each dimension loses twice it. Negative values grow the box instead.
fun intersectsX(x: Double): Boolean Does the box's x span cover the coordinate x? Half-open — [this.x, this.x + w) — so neighbouring spans laid end to end claim each coordinate exactly once.
fun inside(start: Double, end: Double): Boolean Is the box's x span entirely within start..end? Containment, not overlap; y and z are ignored.
fun convert(x: Double, y: Double, z: Double): Vector3d Turns a normalized position in the box into a world one: (0,0,0) is the corner and (1,1,1) the far corner.
fun duplicate(): Cuboid An identical copy. Cuboid is immutable, so this is only needed for identity, not for safety.
override fun equals(other: Any?): Boolean Value equality on all six fields — exact Float comparison, so arithmetic drift will fail it.
override fun hashCode(): Int Consistent with equals: all six fields, so a Cuboid is safe as a map key.

autodiff.kt

Reverse-mode automatic differentiation over Double scalars — exact gradients for ordinary Kotlin arithmetic, in one pass, whatever the expression's size.

The alternative is a numerical gradient, which is the default in fitGradient and leastSquares: central differences over the loss or the residuals, 2·n extra evaluations per step, and a truncation-against-round-off compromise you tune with an fdStep. Reverse mode costs one forward pass plus one backward sweep for all n derivatives, and the answer is exact to the last few ulps.

AD and the tape

AD is a scalar that remembers how it was made. Arithmetic on AD values appends to an ADTape — a flat array of (value, up-to-two parents, up-to-two local partials) — so (a * b + sin(c)) / 2.0 reads exactly like Double arithmetic and records a graph as it goes. The backward sweep walks that array once in reverse accumulating adjoints. Because every node has at most two parents, the tape is five parallel arrays of primitives — value, two parent indices, two partials — and there is no object churn beyond the AD handles themselves.

val g = grad { p -> (p[0] - 3.0).sq() + 2.0 * (p[1] + 1.0).sq() }
g(doubleArrayOf(0.0, 0.0))          // [-6.0, 4.0], exact

val (v, d) = valueAndDerivative(0.4) { x -> exp(-x * x) * sin(6.0 * x) }

grad returns a function, so it composes with the optimizers in autodiff_optimize.kt (minimize, adam, sgd, lbfgs) without a wrapper. gradAt, valueAndGrad, jacobian and hessian are the immediate forms.

Forward mode

Dual2 is a second-order forward-mode number (value, first derivative, second derivative along a direction). It is the right tool when there is one input and many outputs — the reverse of reverse mode — and it is what makes hessian exact rather than a finite difference of an exact gradient: vᵀHv falls out of a single Dual2 pass, and polarisation recovers the off-diagonal entries. See directionalDerivative and secondDerivative.

The kink convention

abs, min, max and relu are not differentiable at their kink, so a convention has to be chosen. This library splits the derivative evenly at an exact tie: min(a, b) with a == b sends 0.5 to each argument, and relu (which is max(x, 0)) has derivative 0.5 at zero. That is what a central difference reports at a kink, so the analytic gradient and the finite-difference oracle agree there too, and it makes abs(x) and max(x, -x) agree on derivative 0 at zero, which is the minimum-norm subgradient. PyTorch instead defines relu'(0) = 0.

What is not covered

  • Mutation. AD is immutable. x += y on a var rebinds it to a fresh handle and appends a node; the handle it replaced stays valid, and an accumulation loop therefore grows the tape by one node per iteration.
  • Control flow is differentiated as taken: an if (x > 0.0) records only the branch that ran, which is the correct derivative of the piecewise function everywhere except at the switch-over. Comparisons on AD therefore return plain Boolean and carry no derivative.
  • Piecewise-constant ops (floor, round, sign, quantize) have derivative 0 by definition, and no finite-difference oracle exists at their jumps.
  • Third and higher derivatives are not available. hessian is exact; a third-order term would need a Dual3 or a nestable tape and neither is here.
  • Tape size grows with the number of operations executed, not with n. A loss over 100k samples builds a 100k-node tape per evaluation; that is the memory cost of reverse mode and it is not chunked or checkpointed.

src/jvmMain/kotlin/afield/math/autodiff/autodiff.kt — read the source

Used in: Spatial artificial life, Functions to FLine examples (Field graphics), Frames and the animation loop

Top-level functions

Declaration Summary
operator fun Double.plus(other: AD): AD 2.0 + x, with the literal folded in at no tape cost.
operator fun Double.minus(other: AD): AD 2.0 - x.
operator fun Double.times(other: AD): AD 2.0 * x.
operator fun Double.div(other: AD): AD 2.0 / x; ∂/∂x = -k/x².
operator fun Int.plus(other: AD): AD 2 + x for integer literals.
operator fun Int.minus(other: AD): AD 2 - x for integer literals.
operator fun Int.times(other: AD): AD 2 * x for integer literals.
operator fun Int.div(other: AD): AD 2 / x for integer literals.
operator fun Double.compareTo(other: AD): Int Compares a plain number against a tracked one, by value.
fun AD.reciprocal(): AD Reciprocal 1/x; ∂ = -1/x².
fun AD.sq(): AD The square x², cheaper and more readable than x * x; ∂ = 2x.
fun exp(x: AD): AD Natural exponential; ∂ = eˣ.
fun ln(x: AD): AD Natural logarithm; ∂ = 1/x. Non-positive input gives NaN/-∞ as kotlin.math does.
fun log10(x: AD): AD Base-10 logarithm; ∂ = 1/(x·ln 10).
fun log2(x: AD): AD Base-2 logarithm; ∂ = 1/(x·ln 2).
fun sqrt(x: AD): AD Square root; ∂ = 1/(2√x), which is +∞ at zero.
fun cbrt(x: AD): AD Cube root, defined for negative input too; ∂ = 1/(3·x^(2/3)).
fun sin(x: AD): AD Sine of an angle in radians; ∂ = cos x.
fun cos(x: AD): AD Cosine of an angle in radians; ∂ = -sin x.
fun tan(x: AD): AD Tangent; ∂ = 1 + tan²x.
fun asin(x: AD): AD Arc sine; ∂ = 1/√(1-x²).
fun acos(x: AD): AD Arc cosine; ∂ = -1/√(1-x²).
fun atan(x: AD): AD Arc tangent; ∂ = 1/(1+x²).
fun sinh(x: AD): AD Hyperbolic sine; ∂ = cosh x.
fun cosh(x: AD): AD Hyperbolic cosine; ∂ = sinh x.
fun tanh(x: AD): AD Hyperbolic tangent — the classic squashing nonlinearity; ∂ = 1 - tanh²x.
fun abs(x: AD): AD Absolute value; ∂ = sign x, and 0 at exactly zero — see the kink convention in the file KDoc, which is also what a central difference reports there.
fun relu(x: AD): AD The rectifier max(x, 0); ∂ = 1 for positive input, 0 for negative, and 0.5 at exactly zero under this library's even-split tie convention.
fun sigmoid(x: AD): AD The logistic sigmoid 1/(1+e⁻ˣ); ∂ = σ(1-σ). Computed in the numerically stable branch.
fun softplus(x: AD): AD The smooth rectifier ln(1+eˣ); ∂ = σ(x). Stable for large \|x\|.
fun floor(x: AD): AD Floor. Piecewise constant, so the derivative is 0 everywhere it exists.
fun ceil(x: AD): AD Ceiling. Piecewise constant, so the derivative is 0 everywhere it exists.
fun round(x: AD): AD Nearest integer. Piecewise constant, so the derivative is 0 everywhere it exists.
fun sign(x: AD): Double The sign of a tracked value as a plain Double — not an AD, since its derivative is 0 away from the origin and undefined at it.
fun AD.pow(exponent: Double): AD x raised to a constant power; ∂ = k·x^(k-1).
fun AD.pow(exponent: Int): AD x raised to a constant integer power.
fun pow(base: AD, exponent: AD): AD a raised to the power b, both tracked.
fun max(a: AD, b: AD): AD The larger of two tracked values. At an exact tie the derivative splits 0.5/0.5 — see the kink convention in the file KDoc.
fun max(a: AD, b: Double): AD The larger of a tracked value and a constant; ties split 0.5/0.5 as elsewhere.
fun min(a: AD, b: AD): AD The smaller of two tracked values. At an exact tie the derivative splits 0.5/0.5 — see the kink convention in the file KDoc.
fun min(a: AD, b: Double): AD The smaller of a tracked value and a constant; ties split 0.5/0.5 as elsewhere.
fun AD.clamp(lo: Double, hi: Double): AD Clamps into [lo, hi]; the derivative is 1 strictly inside and 0 strictly outside, with the usual even split exactly on a boundary.
fun atan2(y: AD, x: AD): AD Two-argument arc tangent, atan2(y, x), correct in all four quadrants.
fun hypot(x: AD, y: AD): AD √(x²+y²) without intermediate overflow; the partials are the components of the unit vector.
fun sum(values: List<AD>): AD The sum of a list of tracked values, as a left fold — the empty list is an error, since there is no tape to hang a zero on.
fun mean(values: List<AD>): AD The arithmetic mean of a list of tracked values.
fun product(values: List<AD>): AD The product of a list of tracked values.
fun dot(a: List<AD>, b: List<AD>): AD The dot product of two equal-length lists of tracked values.
fun sumOfSquares(values: List<AD>): AD The sum of squares — the squared Euclidean norm.
fun norm(values: List<AD>): AD The Euclidean norm √Σxᵢ². Its derivative is undefined at the origin, where √ reports ∞.
fun maxOf(values: List<AD>): AD The largest element, folded pairwise with max (so ties split evenly at each comparison).
fun minOf(values: List<AD>): AD The smallest element, folded pairwise with min.
fun logSumExp(values: List<AD>): AD ln Σ eˣⁱ, shifted by the maximum so it never overflows — the smooth maximum, and the numerically safe way to write a soft-min/soft-max penalty inside a differentiated loss.
fun softmax(values: List<AD>): List<AD> The softmax of a list — each element's share of Σeˣ, summing to one.
fun valueAndGrad(x: DoubleArray, f: (List<AD>) -> AD): ADGradient The exact value and gradient of f at x, in one forward pass and one backward sweep.
fun gradAt(x: DoubleArray, f: (List<AD>) -> AD): DoubleArray The exact gradient of f at x; valueAndGrad when you also want the value.
fun grad(f: (List<AD>) -> AD): (DoubleArray) -> DoubleArray Turns an objective into its gradient function — the curried form, and the one the optimizers in autodiff_optimize.kt take.
fun valueAndGradOf(f: (List<AD>) -> AD): (DoubleArray) -> ADGradient Turns an objective into a function returning both value and gradient — what minimize wants.
fun derivative(x: Double, f: (AD) -> AD): Double The exact derivative of a single-variable function at x.
fun valueAndDerivative(x: Double, f: (AD) -> AD): Pair<Double, Double> The value and the exact derivative of a single-variable function at x.
fun jacobian(x: DoubleArray, f: (List<AD>) -> List<AD>): Array<DoubleArray> The exact Jacobian ∂fᵢ/∂xⱼ of a vector-valued function, as rows × columns.
fun hessian(x: DoubleArray, f: (List<Dual2>) -> Dual2): Array<DoubleArray> The exact Hessian ∂²f/∂xᵢ∂xⱼ, symmetric, via second-order forward mode.

ADTape

class ADTape

The append-only record of every operation performed on the AD values that belong to it.

Declaration Summary
val size: Int The number of operations recorded so far — the tape's length, useful for budgeting.
fun variable(value: Double): AD A tracked scalar holding value with no parents — a leaf of the tape.
fun constant(value: Double): AD A tracked scalar holding value that is treated as a leaf — identical in mechanics to variable, named differently to say "I do not intend to differentiate this".
fun variables(values: DoubleArray): List<AD> variable for a whole vector, in order — the usual way to start a gradient.
fun backward(output: AD): DoubleArray Sweeps backwards from output, returning the adjoint (∂output/∂node) of every node on the tape, indexed by AD.index.

AD

class AD internal constructor(val tape: ADTape, internal val index: Int) : Comparable<AD>

A scalar that remembers how it was computed: a value plus its position on an ADTape.

Declaration Summary
val tape: ADTape The tape this value was recorded on. Two ADs can only be combined if they share one.
val value: Double The plain numeric value, with no derivative information.
operator fun plus(other: AD): AD Sum.
operator fun plus(other: Double): AD Sum with a constant — the constant contributes no derivative.
operator fun minus(other: AD): AD Difference.
operator fun minus(other: Double): AD Difference with a constant.
operator fun times(other: AD): AD Product; ∂(ab)/∂a = b.
operator fun times(other: Double): AD Product with a constant, whose value becomes the local partial.
operator fun div(other: AD): AD Quotient; ∂(a/b)/∂b = -a/b².
operator fun div(other: Double): AD Quotient by a constant.
operator fun unaryMinus(): AD Negation.
operator fun unaryPlus(): AD Identity, so +x is legal and free of surprises.
override fun compareTo(other: AD): Int Compares by value only — comparisons carry no derivative; see the control-flow caveat.
operator fun compareTo(other: Double): Int Compares by value against a plain number.
override fun toString(): String

ADGradient

data class ADGradient(val value: Double, val gradient: DoubleArray)

A value together with its exact gradient — the result of valueAndGrad.

Declaration Summary
val value: Double The objective's value at the point that was differentiated.
val gradient: DoubleArray ∂value/∂xᵢ, in the order the inputs were given.
val gradientInfNorm: Double The largest absolute component of the gradient — the usual convergence test.
override fun equals(other: Any?): Boolean
override fun hashCode(): Int

autodiff_forward.kt

Forward-mode automatic differentiation to second order — the other half of autodiff.kt.

A Dual2 is a truncated Taylor series (v, v', v'') carried along a single direction. Every operation propagates all three components, so one evaluation of an expression in Dual2 arithmetic returns the value, the directional first derivative and the directional curvature at once — no tape, no backward sweep, and no allocation beyond one three-Double object per node.

val t = taylor2(0.6) { x -> exp(-x * x) * sin(6.0 * x) }
t.value; t.first; t.second      // f, f', f'' at 0.6, all exact

Forward or reverse

Reverse mode (grad) wins when there are many inputs and one output — a loss over a parameter vector. Forward mode wins in the opposite shape (one input, many outputs: a parametric curve and every quantity derived from it), and it is the only one of the two that reaches second order. hessian in autodiff.kt is built on this file: vᵀHv is exactly Dual2.second after a pass seeded along v.

The op vocabulary mirrors autodiff.kt name for name — sin, exp, tanh, max, pow and friends are overloaded on both AD and Dual2, so the same expression body can be written once against a shape and evaluated either way (see ADFunction2).

Conventions and gaps

  • The kink convention matches autodiff.kt: max / min split evenly at an exact tie, so relu'' is 0 everywhere and relu'(0) is 0.5.
  • pow with a tracked exponent is evaluated as exp(b · ln a) and therefore requires a strictly positive base; the constant-exponent Dual2.pow has no such restriction.
  • Third and higher derivatives are not represented. A Dual3 would be a mechanical extension of the same chain rule; it is not here.

src/jvmMain/kotlin/afield/math/autodiff/autodiff_forward.kt — read the source

Used in: Differentiating Functions

Top-level functions

Declaration Summary
operator fun Double.plus(other: Dual2): Dual2 2.0 + x.
operator fun Double.minus(other: Dual2): Dual2 2.0 - x.
operator fun Double.times(other: Dual2): Dual2 2.0 * x.
operator fun Double.div(other: Dual2): Dual2 2.0 / x.
operator fun Int.plus(other: Dual2): Dual2 2 + x for integer literals.
operator fun Int.minus(other: Dual2): Dual2 2 - x for integer literals.
operator fun Int.times(other: Dual2): Dual2 2 * x for integer literals.
operator fun Int.div(other: Dual2): Dual2 2 / x for integer literals.
fun exp(x: Dual2): Dual2 Natural exponential.
fun ln(x: Dual2): Dual2 Natural logarithm.
fun log10(x: Dual2): Dual2 Base-10 logarithm.
fun log2(x: Dual2): Dual2 Base-2 logarithm.
fun sqrt(x: Dual2): Dual2 Square root.
fun cbrt(x: Dual2): Dual2 Cube root. Written out rather than delegated to Dual2.pow, because Math.pow is undefined for a negative base at a fractional exponent while the cube root is perfectly well defined there.
fun sin(x: Dual2): Dual2 Sine of an angle in radians.
fun cos(x: Dual2): Dual2 Cosine of an angle in radians.
fun tan(x: Dual2): Dual2 Tangent.
fun asin(x: Dual2): Dual2 Arc sine.
fun acos(x: Dual2): Dual2 Arc cosine.
fun atan(x: Dual2): Dual2 Arc tangent.
fun sinh(x: Dual2): Dual2 Hyperbolic sine.
fun cosh(x: Dual2): Dual2 Hyperbolic cosine.
fun tanh(x: Dual2): Dual2 Hyperbolic tangent.
fun abs(x: Dual2): Dual2 Absolute value; the derivative is 0 at exactly zero, and the curvature is 0 everywhere.
fun relu(x: Dual2): Dual2 The rectifier max(x, 0); derivative 0.5 at exactly zero, curvature 0 everywhere.
fun sigmoid(x: Dual2): Dual2 The logistic sigmoid 1/(1+e⁻ˣ); σ'' = σ(1-σ)(1-2σ).
fun softplus(x: Dual2): Dual2 The smooth rectifier ln(1+eˣ); its derivative is the sigmoid and its curvature σ(1-σ).
fun floor(x: Dual2): Dual2 Floor — piecewise constant, so both derivatives are 0.
fun ceil(x: Dual2): Dual2 Ceiling — piecewise constant, so both derivatives are 0.
fun round(x: Dual2): Dual2 Nearest integer — piecewise constant, so both derivatives are 0.
fun pow(base: Dual2, exponent: Dual2): Dual2 a raised to the power b, both tracked, as exp(b · ln a); needs a strictly positive base.
fun max(a: Dual2, b: Dual2): Dual2 The larger of two, selecting a branch; an exact tie averages both, matching the AD convention.
fun max(a: Dual2, b: Double): Dual2 The larger of a tracked value and a constant.
fun min(a: Dual2, b: Dual2): Dual2 The smaller of two, selecting a branch; an exact tie averages both.
fun min(a: Dual2, b: Double): Dual2 The smaller of a tracked value and a constant.
fun hypot(x: Dual2, y: Dual2): Dual2 √(x²+y²).
fun atan2(y: Dual2, x: Dual2): Dual2 Two-argument arc tangent, correct in all four quadrants.
fun sumDual(values: List<Dual2>): Dual2 The sum of a list.
fun meanDual(values: List<Dual2>): Dual2 The arithmetic mean of a list.
fun sumOfSquaresDual(values: List<Dual2>): Dual2 The sum of squares of a list.
fun dotDual(a: List<Dual2>, b: List<Dual2>): Dual2 The dot product of two equal-length lists.
fun normDual(values: List<Dual2>): Dual2 The Euclidean norm of a list.
fun taylor2(x: Double, f: (Dual2) -> Dual2): Dual2 The value, first and second derivative of a single-variable function at x, in one pass.
fun secondDerivative(x: Double, f: (Dual2) -> Dual2): Double The exact second derivative of a single-variable function at x.
fun directionalDerivative(x: DoubleArray, direction: DoubleArray, f: ADFunction2): Dual2 The exact directional derivative ∇f(x)·v — one forward pass, whatever the dimension.
fun gradForward(x: DoubleArray, f: ADFunction2): DoubleArray The gradient by forward mode — n passes, one per axis.

Dual2

data class Dual2(val value: Double, val first: Double, val second: Double)

A second-order forward-mode number: a value together with its first and second derivative along one direction.

Declaration Summary
val value: Double The plain value f.
val first: Double The first derivative df/dt along the seeded direction.
val second: Double The second derivative d²f/dt² along the seeded direction.
operator fun plus(other: Dual2): Dual2 Sum, componentwise.
operator fun plus(other: Double): Dual2 Sum with a constant, which shifts only the value.
operator fun minus(other: Dual2): Dual2 Difference, componentwise.
operator fun minus(other: Double): Dual2 Difference with a constant.
operator fun times(other: Dual2): Dual2 Product; the second order picks up the 2·u'·w' cross term.
operator fun times(other: Double): Dual2 Product with a constant, which scales all three orders.
operator fun div(other: Dual2): Dual2 Quotient, as this * other.reciprocal().
operator fun div(other: Double): Dual2 Quotient by a constant.
operator fun unaryMinus(): Dual2 Negation.
operator fun unaryPlus(): Dual2 Identity.
fun chain(fv: Double, d1: Double, d2: Double): Dual2 Applies an outer scalar function given its value and its first two derivatives at value — the second-order chain rule (f∘u)'' = f''·u'² + f'·u'', and the single place every unary op in this file is implemented.
fun reciprocal(): Dual2 Reciprocal 1/x.
fun sq(): Dual2 The square x².
fun pow(exponent: Double): Dual2 x raised to a constant power, valid for negative value at integer exponents.
fun pow(exponent: Int): Dual2 x raised to a constant integer power.
fun clamp(lo: Double, hi: Double): Dual2 Clamps into [lo, hi], with the even-split tie convention on each boundary.
override fun toString(): String

ADFunction2

typealias ADFunction2 = (List<Dual2>) -> Dual2

A scalar function of a vector written once against Dual2, so it can be handed to both hessian and directionalDerivative without being restated.

autodiff_geometry.kt

Differentiable geometry: tracked vectors, planar inverse kinematics on an FLine chain, and a differentiable circle packing — the part of the AD layer whose results are drawable.

ADVec2 and ADVec3 are pairs and triples of AD. They convert to and from Field's own Vec2 / Vec3 (the generated, mutable, float-backed Vector2f / Vector3f), and every operation on them is expressed in the same scalar ops as the rest of autodiff.kt. There are no vector-specific derivative rules here; the point is that (joint - target).length() reads as itself inside a loss instead of as unrolled components.

Inverse kinematics

solveIk finds the joint angles of a planar chain of fixed-length links by minimising |endEffector - target|² with exact gradients. Forward kinematics is a cumulative sum of angles and a running position — differentiable throughout — so L-BFGS reaches a sub-micron reach in tens of iterations, and the result is an FLine you can draw.

val chain = IkChain(vec(120f, 300f), doubleArrayOf(90.0, 80.0, 70.0, 55.0))
val solved = solveIk(chain, vec(360f, 150f))
showPlane(listOf(chain.toFLine(solved.angles)))

A pure reach objective is under-determined for more than two links, so IkOptions.smoothness offers a curvature penalty between consecutive links. It is off by default, and it is a trade rather than a free regulariser: the two terms compete, so a positive smoothness buys a straighter pose and charges reach accuracy for it. The charge is small out in the open and large near the base, where reaching at all needs tight folds. On the four-link chain in the example above, AutoDiffGeometryTest pins a target 216 units away as exact to under a thousandth of a unit with no penalty and inside 12 units under a heavy one; a target close to the anchor is exact without the penalty and more than a unit short at smoothness = 0.02. IkSolution.reachError reports the shortfall either way, so a solve that traded accuracy away can be told from one that converged.

Packing

packCircles minimises pairwise overlap and wall penetration under a gentle pull toward the centre. Every term is a relu(...)² hinge, which is exactly where the even-split kink convention in autodiff.kt matters: at the moment two circles touch, the penalty's gradient is half what a naive one-sided rule would give, and the packing settles rather than chattering.

src/jvmMain/kotlin/afield/math/autodiff/autodiff_geometry.kt — read the source

Used in: Differentiating Functions

Top-level functions

Declaration Summary
fun Vec2.onTape(tape: ADTape): ADVec2 This vector as an untracked constant on tape — the way a fixed target enters a loss.
fun Vec3.onTape(tape: ADTape): ADVec3 This vector as an untracked constant on tape.
fun solveIk(chain: IkChain, target: Vec2, initialAngles: DoubleArray = DoubleArray(chain.linkCount), options: IkOptions = IkOptions()): IkSolution Solves the planar inverse-kinematics problem: find joint angles putting chain's tip on target.
fun packCircles(radii: DoubleArray, width: Double, height: Double, wallWeight: Double = 4.0, pullWeight: Double = 0.004, iterations: Int = 600, rngSeed: Long = 0L): PackingRun Packs discs of the given radii into the rectangle [0, width] × [0, height] by gradient descent on a differentiable overlap penalty.

ADVec2

data class ADVec2(val x: AD, val y: AD)

A two-dimensional vector whose components are tracked AD scalars.

Declaration Summary
val x: AD The x component.
val y: AD The y component.
operator fun plus(other: ADVec2): ADVec2 Sum.
operator fun minus(other: ADVec2): ADVec2 Difference.
operator fun times(other: ADVec2): ADVec2 Componentwise product — the Hadamard product, not a dot or a cross.
operator fun times(scalar: AD): ADVec2 Scaling by a tracked scalar.
operator fun times(scalar: Double): ADVec2 Scaling by a constant.
operator fun div(scalar: Double): ADVec2 Division by a constant.
operator fun unaryMinus(): ADVec2 Negation.
infix fun dot(other: ADVec2): AD The dot product.
infix fun cross(other: ADVec2): AD The 2D cross product x₁y₂ - y₁x₂ — the signed area of the parallelogram.
fun lengthSquared(): AD The squared length, which is smooth at the origin where length is not.
fun length(): AD The Euclidean length. Its derivative is undefined at the origin; prefer lengthSquared in a loss that might pass through zero.
fun distanceTo(other: ADVec2): AD The distance to other.
fun distanceSquaredTo(other: ADVec2): AD The squared distance to other — the well-behaved choice inside a loss.
fun normalized(): ADVec2 This vector scaled to unit length. Undefined at the origin.
fun angle(): AD The angle from the positive x axis, via atan2, so it is correct in all four quadrants.
fun rotated(radians: AD): ADVec2 Rotated by radians about the origin.
fun lerp(other: ADVec2, t: AD): ADVec2 Linear interpolation toward other by t.
val value: Vec2 The plain, untracked value as one of Field's own Vec2s.
override fun toString(): String

ADVec3

data class ADVec3(val x: AD, val y: AD, val z: AD)

A three-dimensional vector whose components are tracked AD scalars — ADVec2's sibling, with the same immutability and the same conversion story to Field's Vec3.

Declaration Summary
val x: AD The x component.
val y: AD The y component.
val z: AD The z component.
operator fun plus(other: ADVec3): ADVec3 Sum.
operator fun minus(other: ADVec3): ADVec3 Difference.
operator fun times(scalar: AD): ADVec3 Scaling by a tracked scalar.
operator fun times(scalar: Double): ADVec3 Scaling by a constant.
operator fun unaryMinus(): ADVec3 Negation.
infix fun dot(other: ADVec3): AD The dot product.
infix fun cross(other: ADVec3): ADVec3 The cross product.
fun lengthSquared(): AD The squared length, smooth at the origin.
fun length(): AD The Euclidean length; undefined derivative at the origin.
fun distanceTo(other: ADVec3): AD The distance to other.
fun distanceSquaredTo(other: ADVec3): AD The squared distance to other.
fun normalized(): ADVec3 This vector scaled to unit length.
fun lerp(other: ADVec3, t: AD): ADVec3 Linear interpolation toward other by t.
val value: Vec3 The plain, untracked value as one of Field's own Vec3s.
override fun toString(): String

IkChain

data class IkChain(val origin: Vec2, val lengths: DoubleArray)

A planar chain of rigid links: where it is anchored, and how long each link is.

Declaration Summary
val origin: Vec2 Where the first link starts.
val lengths: DoubleArray The link lengths, root to tip.
val linkCount: Int How many links, and therefore how many joint angles.
val reach: Double The furthest the tip can possibly get from origin — the chain fully extended.
fun jointsAt(angles: DoubleArray): List<Vec2> The joint positions, origin first and the end effector last, for the configuration angles.
fun endEffectorAt(angles: DoubleArray): Vec2 Where the tip lands for the configuration angles.
fun toFLine(angles: DoubleArray): FLine The chain drawn as a polyline, root to tip — ready for showPlane or lines(...).
fun endEffectorAD(angles: List<AD>): ADVec2 Forward kinematics in AD arithmetic: the tip position as a differentiable function of the tracked joint angles. The one function solveIk differentiates.
override fun equals(other: Any?): Boolean
override fun hashCode(): Int

IkOptions

data class IkOptions(val smoothness: Double = 0.0, val restPull: Double = 0.0, val optimize: OptimizeOptions = OptimizeOptions(method = OptimizeMethod.LBFGS, iterations = 400, gradientTolerance = 1e-12, showProgress = false))

What shapes an solveIk solve.

Declaration Summary
val smoothness: Double Weight on Σ(θᵢ - θᵢ₋₁)², penalising sharp bends between consecutive links. Zero — the default — reaches exactly. Anything positive competes with the reach term; see the file KDoc for measured numbers.
val restPull: Double Weight on Σθᵢ², which pulls the whole configuration toward straight.
val optimize: OptimizeOptions The descent to use; L-BFGS is the right default for this smooth objective.

IkSolution

data class IkSolution(val angles: DoubleArray, val joints: List<Vec2>, val endEffector: Vec2, val reachError: Double, val run: OptimizeRun)

An solveIk result: the configuration found, where it puts the chain, and the full descent.

Declaration Summary
val angles: DoubleArray The solved joint angles, relative link to link.
val joints: List<Vec2> The joint positions for angles, origin first, tip last.
val endEffector: Vec2 Where the tip ended up.
val reachError: Double The distance from endEffector to the requested target — the number that says "it reached".
val run: OptimizeRun The optimizer's own record, including the full loss trajectory.
fun toFLine(chain: IkChain): FLine The chain as a polyline for drawing.
override fun equals(other: Any?): Boolean
override fun hashCode(): Int

PackedCircle

data class PackedCircle(val centre: Vec2, val radius: Double)

One packed disc: where it ended up and how big it is.

Declaration Summary
val centre: Vec2 The centre.
val radius: Double The radius, which packCircles does not change.
fun toFLine(segments: Int = 48): FLine This disc as a closed polygonal FLine, for showPlane or lines(...).

PackingRun

data class PackingRun(val circles: List<PackedCircle>, val worstOverlap: Double, val worstEscape: Double, val run: OptimizeRun)

An packCircles result: the packing, its residual overlap, and the descent that produced it.

Declaration Summary
val circles: List<PackedCircle> The packed discs, in the order their radii were given.
val worstOverlap: Double The largest remaining pairwise overlap, in the same units as the radii. Zero means disjoint.
val worstEscape: Double How far the worst disc pokes outside the box. Zero means fully contained.
val run: OptimizeRun The optimizer's record, including the loss trajectory.

autodiff_optimize.kt

Gradient-descent optimizers that consume the exact gradients from autodiff.kt — sgd, adam and lbfgs, behind one minimize front door.

All three are deterministic given their seed and all three record a full OptimizeRun.losses trajectory which you can hand straight to showSignals("loss" to run.losses) and look at. FnEvolveRun and FnFitRun carry a losses list of the same shape, so a fit, an evolve and an optimisation all replay and plot the same way.

val run = minimize(doubleArrayOf(-1.2, 1.0), OptimizeOptions(method = OptimizeMethod.LBFGS)) { p ->
    (1.0 - p[0]).sq() + 100.0 * (p[1] - p[0].sq()).sq()   // Rosenbrock
}
run.x          // ~[1, 1]
run.losses     // the trajectory, ready to plot

Which one

  • lbfgs is the right default for a smooth deterministic objective with a handful to a few hundred parameters: it builds a curvature model from the last m steps and takes Newton-shaped strides, so it reaches machine-precision minima in tens of iterations where Adam is still crawling. It needs the objective to actually be smooth; a kinked or noisy loss will make its line search flail.
  • adam is the robust workhorse for a rough, kinked or stochastic landscape, and the one that tolerates a badly scaled parameter vector without preconditioning.
  • sgd with momentum is here because it is sometimes what you want artistically — a slower, visibly settling trajectory with a tunable inertia, which reads as motion rather than as a solver converging.

The line search inside lbfgs is backtracking Armijo, which guarantees monotone descent but not the curvature (Wolfe) condition; the L-BFGS memory therefore skips any update pair with non-positive curvature rather than letting an indefinite model through.

src/jvmMain/kotlin/afield/math/autodiff/autodiff_optimize.kt — read the source

Used in: Differentiating Functions

Top-level functions

Declaration Summary
fun minimize(initial: DoubleArray, options: OptimizeOptions = OptimizeOptions(), objective: (List<AD>) -> AD): OptimizeRun Minimises an objective written in AD arithmetic, using exact gradients.
fun minimizeWithGradient(initial: DoubleArray, options: OptimizeOptions = OptimizeOptions(), valueAndGradient: (DoubleArray) -> ADGradient): OptimizeRun Minimises an objective you already have a value-and-gradient function for: the entry point for a gradient that does not come from this file's AD (a hand-derived one, or the exact Fn-graph gradient in autodiff_fn.kt).
fun minimizeWithGradient(initial: DoubleArray, options: OptimizeOptions, observer: ((OptimizeProgress) -> Boolean)?, valueAndGradient: (DoubleArray) -> ADGradient): OptimizeRun minimizeWithGradient with an accepted-state observer. Return true when the caller's convergence criterion is satisfied; the run stops with reason observer. Exceptions propagate (including cancellation). The default optimizer stopping rules still apply.
fun sgd(initial: DoubleArray, iterations: Int = 500, lr: Double = 0.05, momentum: Double = 0.9, nesterov: Boolean = false, objective: (List<AD>) -> AD): OptimizeRun minimize with OptimizeMethod.SGD — plain descent with momentum, and a visibly settling path.
fun adam(initial: DoubleArray, iterations: Int = 500, lr: Double = 0.05, beta1: Double = 0.9, beta2: Double = 0.999, epsilon: Double = 1e-8, objective: (List<AD>) -> AD): OptimizeRun minimize with OptimizeMethod.ADAM — the robust default for a rough or badly scaled landscape.
fun lbfgs(initial: DoubleArray, iterations: Int = 200, memory: Int = 8, gradientTolerance: Double = 1e-9, objective: (List<AD>) -> AD): OptimizeRun minimize with OptimizeMethod.LBFGS — the line-searched quasi-Newton, for smooth objectives.

OptimizeMethod

enum class OptimizeMethod

Which descent to run. See the file KDoc for when to use each.

Constants: SGD, ADAM, LBFGS.

OptimizeOptions

data class OptimizeOptions(val method: OptimizeMethod = OptimizeMethod.ADAM, val iterations: Int = 500, val learningRate: Double = 0.05, val momentum: Double = 0.9, val nesterov: Boolean = false, val beta1: Double = 0.9, val beta2: Double = 0.999, val epsilon: Double = 1e-8, val memory: Int = 8, val armijo: Double = 1e-4, val maxLineSearchSteps: Int = 32, val gradientTolerance: Double = 1e-9, val stepTolerance: Double = 1e-14, val initJitter: Double = 0.0, val rngSeed: Long = 0L, val showProgress: Boolean = true, val recordTrajectory: Boolean = true)

Everything that shapes a minimize run, kept as one immutable value so a result can carry its own provenance.

Declaration Summary
val method: OptimizeMethod Which optimizer to run.
val iterations: Int Maximum number of iterations.
val learningRate: Double Step size. Ignored by OptimizeMethod.LBFGS, whose line search chooses its own.
val momentum: Double Momentum coefficient for OptimizeMethod.SGD.
val nesterov: Boolean Use the Nesterov look-ahead form of momentum.
val beta1: Double Adam's first-moment decay.
val beta2: Double Adam's second-moment decay.
val epsilon: Double Adam's denominator floor.
val memory: Int How many (s, y) curvature pairs L-BFGS remembers.
val armijo: Double Armijo sufficient-decrease constant for the L-BFGS line search.
val maxLineSearchSteps: Int How many halvings the line search will try before giving up on an iteration.
val gradientTolerance: Double Stop when the gradient's infinity norm falls to this.
val stepTolerance: Double Stop when a step moves the parameters less than this.
val initJitter: Double If > 0, jitter the start by ±initJitter·(1+\|x\|) using rngSeed before descending.
val rngSeed: Long The single source of randomness; with initJitter = 0 the run is randomness-free anyway.
val showProgress: Boolean Whether to offer a progress bar. Turn it off for an inner loop (a restart sweep, a demo).
val recordTrajectory: Boolean Retain a copy of every parameter vector. Disable for large simulations; scalar history remains.

OptimizeStep

data class OptimizeStep(val iteration: Int, val value: Double, val gradientInfNorm: Double)

One recorded iteration: the objective and the gradient's infinity norm after the step.

Declaration Summary
val iteration: Int 1-based iteration index; iteration 0 is the starting point and lives only in OptimizeRun.losses.
val value: Double The objective value after this step.
val gradientInfNorm: Double The gradient's infinity norm after this step — the convergence signal that counts.

OptimizeRun

data class OptimizeRun(val x: DoubleArray, val value: Double, val gradient: DoubleArray, val initialX: DoubleArray, val initialValue: Double, val steps: List<OptimizeStep>, val losses: List<Double>, val trajectory: List<DoubleArray>, val iterations: Int, val evaluations: Int, val converged: Boolean, val reason: String, val options: OptimizeOptions)

The result and provenance of a minimize run, shaped like FnFitRun so the two read the same way at a call site.

Declaration Summary
val x: DoubleArray The parameters at the end of the run.
val value: Double The objective at x.
val gradient: DoubleArray The exact gradient at x.
val initialX: DoubleArray The starting parameters, after any OptimizeOptions.initJitter.
val initialValue: Double The objective at initialX.
val steps: List<OptimizeStep> Per-iteration records.
val losses: List<Double> The objective trajectory including the starting point.
val trajectory: List<DoubleArray> The parameter trajectory, indexed exactly like losses: trajectory[i](workspace.md#stdlib-kt--i) is the point at which losses[i](workspace.md#stdlib-kt--i) was measured, so trajectory.first() is initialX and trajectory.last() is x.
val iterations: Int How many iterations actually ran.
val evaluations: Int How many times the objective (and its gradient) was evaluated.
val converged: Boolean Whether a tolerance was met, as opposed to running out of iterations.
val reason: String One of "observer", "gradient", "step", "line search", "max iterations", "no iterations".
val options: OptimizeOptions The options this run was made with.
val improved: Boolean True when the run ended strictly below where it started.
val gradientInfNorm: Double The gradient's infinity norm at x.
override fun equals(other: Any?): Boolean Two runs are equal when x, value, losses, iterations, evaluations, converged, reason and options all match. trajectory is left out: it is a List<DoubleArray> and DoubleArray.equals is identity, so including it would make any two separately-computed runs unequal whatever their contents. It is fully determined by initialX, options and the objective, and x and losses compare its endpoint and its whole value trace.
override fun hashCode(): Int

OptimizeProgress

data class OptimizeProgress(val iteration: Int, val x: DoubleArray, val value: Double, val gradient: DoubleArray, val evaluations: Int, val stepNorm: Double, val lineSearchReductions: Int, val curvatureAccepted: Boolean?)

An accepted optimizer state (including iteration zero), with line-search diagnostics. x and gradient are borrowed read-only arrays, valid only during the callback. Copy them only if retaining them. Rejected trial points are never reported.

Declaration Summary
val gradient: DoubleArray, val evaluations: Int, val stepNorm: Double
val lineSearchReductions: Int, val curvatureAccepted: Boolean?)

pf.kt

An unscented Kalman filter, and a particle filter whose particles each carry one.

UnscentedKalmanFilter keeps a mean and covariance and takes the transition and observation models as lambdas; UnscentedKalmanFilter.updateWithLikelihood returns the log measurement likelihood, which is exactly what UKFParticleFilter accumulates into each particle's log weight. A UKFParticle holds a UKF for the continuous state plus a hypothesis of your own type H for the discrete or non-Gaussian part. UKFParticleFilter.resampleIfNeeded does systematic resampling once the effective sample size drops below a fraction of the particle count, and can immediately roughen the resampled means (RougheningOptions) so exact clones separate again.

State and measurement dimensions are restricted to 1..3 — the private determinant helper only covers those sizes and everything requires it. The linear algebra (Matrix is just Array<DoubleArray>; Cholesky with escalating jitter, Gauss–Jordan inverse) is private to this file, has no external dependency, and allocates fresh arrays on every step.

src/jvmMain/kotlin/afield/math/estimation/pf.kt — read the source

Used in: State estimation: tracking what you cannot measure

Top-level functions

Declaration Summary
fun diag(vararg values: Double): Matrix A square Matrix with values down the diagonal and zeros elsewhere — the usual covariance.

Matrix

typealias Matrix = Array<DoubleArray>

UKFOptions

data class UKFOptions(val alpha: Double = 0.5, val beta: Double = 2.0, val kappa: Double = 0.0, val jitter: Double = 1e-9)

The three sigma-point tuning constants of the unscented transform, plus the numerical fudge factor.

Declaration Summary
val alpha: Double
val beta: Double
val kappa: Double
val jitter: Double

RougheningOptions

data class RougheningOptions(val factor: Double = 0.08, val minimumStdDev: Double = 1e-9, val inflateCovariance: Boolean = true)

How hard to jiggle particles apart immediately after resampling — the cure for a filter that has collapsed, without saying so, onto a handful of identical clones.

Declaration Summary
val factor: Double Usually something small, such as 0.02 to 0.20. The roughening stddev is approximately: factor * stateRange * particleCount^(-1 / stateDimension)
val minimumStdDev: Double Used when all particles have the same coordinate, or when the computed roughening is tiny.
val inflateCovariance: Boolean If true, the UKF covariance is inflated by the variance of the roughening noise. This keeps the UKF covariance consistent after we perturb its mean.

UnscentedKalmanFilter

class UnscentedKalmanFilter(initialState: DoubleArray, initialCovariance: Matrix, val options: UKFOptions = UKFOptions())

An unscented Kalman filter: it tracks a quantity you cannot measure directly, keeping a best guess x and an explicit account p of how unsure it is.

Declaration Summary
val options: UKFOptions
var x: DoubleArray The current best estimate of the state. Replaced wholesale by each predict and update.
var p: Matrix The current state covariance — how unsure the filter is, and in which directions the uncertainty is correlated. Grows on predict, shrinks on update.
val dim: Int The state dimension, fixed at construction and always in 1..3.
fun predict(processNoise: Matrix, transition: (DoubleArray) -> DoubleArray) Advances the estimate one time step through transition, widening the covariance by processNoise.
fun update(measurement: DoubleArray, measurementNoise: Matrix, observe: (DoubleArray) -> DoubleArray) Folds one measurement into the estimate, pulling x toward what was observed and shrinking p.
fun updateWithLikelihood(measurement: DoubleArray, measurementNoise: Matrix, observe: (DoubleArray) -> DoubleArray): Double Performs a UKF measurement update and returns log p(measurement | predicted state). This likelihood is what the particle filter uses to update particle weights.
fun roughenMean(noiseStdDev: DoubleArray, random: Random = Random.Default, inflateCovariance: Boolean = true) Perturbs the UKF mean and optionally inflates covariance by the same variance.
fun stateCopy(): DoubleArray A snapshot of the current estimate that will not change when the filter next steps.
fun covarianceCopy(): Matrix A snapshot of the current covariance, deep-copied row by row.

UKFParticle

data class UKFParticle<H>(val ukf: UnscentedKalmanFilter, var hypothesis: H, var logWeight: Double)

One hypothesis in a UKFParticleFilter: a whole UnscentedKalmanFilter for the continuous part of the state, a value of your own type H for the discrete part, and how much the filter currently believes it.

Declaration Summary
val ukf: UnscentedKalmanFilter
var hypothesis: H
var logWeight: Double

UKFParticleFilter

class UKFParticleFilter<H>(initialParticles: List<UKFParticle<H>>, private val copyHypothesis: (H) -> H = { it })

A particle filter whose particles each carry an UnscentedKalmanFilter — for tracking something whose state is partly smooth and partly a discrete guess that might be wrong.

Declaration Summary
var particles: MutableList<UKFParticle<H>> The live ensemble, in no meaningful order. Read it to summarise the estimate; the list itself is replaced by resampling, and the particles in it are mutated in place by every step.
val size: Int How many particles the ensemble holds. Constant unless you resample, which preserves it.
fun predict(processNoise: (UKFParticle<H>) -> Matrix, transition: (hypothesis: H, state: DoubleArray) -> DoubleArray, evolveHypothesis: ((H) -> H)? = null) Advances every particle one time step, each through its own hypothesis.
fun update(measurement: DoubleArray, measurementNoise: (UKFParticle<H>) -> Matrix, observe: (hypothesis: H, state: DoubleArray) -> DoubleArray) Scores every particle against the same measurement: each one's own UKF absorbs it, and its log weight gains that particle's log likelihood — so hypotheses that predicted the measurement well gain ground on those that did not.
fun weights(): DoubleArray The particles' weights as ordinary probabilities summing to 1, in the same order as particles — for taking a weighted mean of the ensemble, or for drawing it.
fun effectiveSampleSize(): Double How many particles are effectively still contributing: 1 / Σw², which is the particle count when the weights are equal and approaches 1 as one particle takes over.
fun resampleIfNeeded(thresholdFraction: Double = 0.5, random: Random = Random.Default, roughening: RougheningOptions? = null): Boolean Resamples only if the ensemble has degenerated — effectiveSampleSize below thresholdFraction of the particle count — and reports whether it did.
fun resampleSystematic(random: Random = Random.Default, roughening: RougheningOptions? = null) Resamples unconditionally by systematic resampling: one uniform draw, then n evenly spaced strides through the cumulative weights, so a particle with weight w gets about n·w copies.
fun roughenStateMeans(options: RougheningOptions = RougheningOptions(), random: Random = Random.Default) Roughens only the continuous UKF means. This is useful immediately after resampling, when many particles may be exact clones.
fun roughenHypotheses(random: Random = Random.Default, roughen: (H, Random) -> H) Roughen non-UKF / discrete parameters, if your hypothesis type has mutable-like dynamics.
fun estimateMean(): DoubleArray The filter's single best answer: every particle's state mean, averaged by weight.
fun estimateCovariance(): Matrix The uncertainty of the whole ensemble: the weighted average of the particles' own covariances plus the scatter of their means about estimateMean.

DenseDoubleOps.kt

SIMD helpers for dense DoubleArray arithmetic, using the JDK Vector API: DenseDoubleOps provides dot, combine and scale.

src/jvmMain/kotlin/afield/math/functions/DenseDoubleOps.kt — read the source

DenseDoubleOps

object DenseDoubleOps

Allocation-free SIMD operations on equal-length dense double arrays, with scalar tails. Reductions use lane-wise accumulation, so last-bit rounding can differ from a scalar sum. Destination arrays may alias either input for elementwise operations.

Declaration Summary
fun dot(a: DoubleArray, b: DoubleArray): Double Sum of elementwise products; neither input is modified.
fun combine(out: DoubleArray, a: DoubleArray, factor: Double, b: DoubleArray) Overwrites out with a + factor * b, including when out is one of the inputs.
fun scale(a: DoubleArray, factor: Double) Multiplies every element of a by factor in place.

Functions.kt

The Fn toolkit: one-dimensional signals as values you can build, compose, sample and draw.

Nearly everything lives inside the Functions object, and most of it as extension functions, so callers normally work inside with(Functions) { ... }. A Functions.Fn is an evaluator plus the Functions.Domain it is meant to be sampled over (default 0.0..1.0). Sources — sin, saw, triangle, square, the pulseTrain / pulseBurst family, the noise and fbm generators, and keyframe interpolants such as catmullRomSpline and monotoneCubic — combine through arithmetic operators, through chainable Fn extensions (stretch, repeat, fold, quantize, echo, convolve, blur, ...), and through reusable FnTransform pipelines applied with /. Arrangement in time is timeline { } and the List<Fn> combinators (sequence, randomPermute, morph, stack, average); those rearrange Fns and timeline chunks and hand the result back to the same evaluator rather than introducing a second one.

Every combinator also records an FnGraphNode recipe alongside the closure (see FunctionGraph.kt), which is what makes graphs inspectable and rewritable rather than opaque lambdas. On the output side: toDoubleArray and the sample* helpers for numbers, toFLine for drawing, plotHtml for a self-contained plot, and Fn.plot, which displays into the active Box and throws when there is none (Box.plot takes the Fn explicitly instead).

RbfKernel, RbfKernels and RbfInterpolator at the top of the file are the scattered-data fitter behind Functions.rbf; it solves a dense n×n system per construction, so it is sized for handfuls of control points, not thousands. Its counterpart in two or more dimensions, RbfField in RbfField.kt, fits a surface through values at scattered points with the same kernels.

src/jvmMain/kotlin/afield/math/functions/Functions.kt — read the source

Used in: Functions to FLine examples (Field graphics)

RbfKernel

fun interface RbfKernel

A radial basis function: how strongly a control point still counts at distance r from the x being evaluated.

Declaration Summary
fun eval(r: Double): Double The kernel's value at radius r. r is never negative; the result may be any sign.

RbfKernels

object RbfKernels

The ready-made RbfKernel family behind Functions.rbf: four parameter-free kernels as values, and four shape-parameter kernels as factories.

Declaration Summary
val linear r itself: interpolation degenerates to straight segments between neighbouring points.
val quadratic r². Grows quickly with distance, so distant control points pull hard on the fit.
val cubic r³. The stiffest of the polynomial kernels; smooth, but prone to overshoot far from data.
val thinPlate The thin-plate spline kernel r²·ln r, with the removable singularity at r = 0 defined as zero. The default for Functions.rbf, and the one to reach for first: it needs no shape parameter, so it cannot be mis-scaled. Note that it is negative for r < 1.
fun gaussian(epsilon: Double = 1.0) A Gaussian bump exp(-(εr)²) — the one genuinely local kernel here, so influence dies away within roughly 1/ε of each control point.
fun multiquadric(c: Double = 1.0) sqrt(r² + c²) — a softened linear, rounded off near r = 0 over a width of about c.
fun inverseMultiquadric(c: Double = 1.0) 1 / sqrt(r² + c²) — decays with distance, so the fit stays local-ish; c sets the width.
fun inverseQuadratic(c: Double = 1.0) 1 / (r² + c²) — the fastest-decaying of the inverse kernels; c sets the width.

RbfInterpolator

class RbfInterpolator(private val points: List<Functions.Sample>, private val kernel: RbfKernel, private val smoothing: Double = 0.0, private val base: Functions.Fn? = null)

The scattered-data fitter behind Functions.rbf: given control points anywhere on the x axis, solve once for the kernel weights that reproduce them, then evaluate anywhere.

Declaration Summary
fun eval(x: Double): Double The fitted value at x — the base function (if any) plus every control point's weighted kernel contribution.
override fun toString(): String

Functions

object Functions

Declaration Summary
public data class Domain(val min: Double, val max: Double) : ClosedRange<Double> The interval an Fn is meant to be sampled over: closed at both ends, possibly infinite, possibly empty.
operator fun Double.rangeTo(end: Double): Domain Human-facing domain notation.
operator fun Double.rangeTo(end: Number): Domain a..b as a Domain inside a Functions scope. The endpoints are sorted, so a descending range is not empty.
operator fun Number.rangeTo(end: Double): Domain a..b as a Domain inside a Functions scope. The endpoints are sorted, so a descending range is not empty.
fun domain(range: ClosedRange<*>): Domain Converts any numeric ClosedRange — including Kotlin's IntRange, which member operators would otherwise claim — into a Domain. Throws if the endpoints are not numbers.
fun ClosedRange<*>.toDomain(): Domain This numeric range as a Domain, with the endpoints sorted.
enum class EdgeMode What happens when a sampler asks for an x outside an Fn's domain.
enum class Interpolation How a table of samples is reconstructed between its entries, for toFn and resample.
enum class PulseEdge The shape of a pulse's rise and fall ramps in the pulseTrain family.
enum class PulseProfile The envelope across the body of one pulse in the pulseTrain family, evaluated over the duty portion of each cycle.
enum class StretchShape How time is paced when stretch maps one interval onto another: LINEAR at constant speed, SMOOTHSTEP and SMOOTHERSTEP easing in and out so the source is played slowly at the ends and quickly through the middle.
enum class RectifyMode Rectifier polarity for the rectify waveshaping transform: half-wave clamps the negative excursion to zero; full-wave reflects it (|x|).
enum class Easing Named motion curves for tween, Fn.ease, and other alpha-shaping helpers — the common normalized-time shapes for procedural motion and value tweening.
val HARD: PulseEdge PulseEdge.HARD, re-exported so edge = HARD reads as DSL inside with(Functions).
val SMOOTH: PulseEdge PulseEdge.SMOOTH, re-exported so edge = SMOOTH reads as DSL inside with(Functions).
val EXP: PulseEdge PulseEdge.EXP, re-exported so edge = EXP reads as DSL inside with(Functions).
val RECT: PulseProfile PulseProfile.RECT, re-exported so profile = RECT reads as DSL inside with(Functions).
val TRIANGLE: PulseProfile PulseProfile.TRIANGLE, re-exported so profile = TRIANGLE reads as DSL — note that this is the pulse profile, not the triangle oscillator that shares the word.
val HANN: PulseProfile PulseProfile.HANN, re-exported so profile = HANN reads as DSL — the pulse profile, distinct from the standalone hannWindow function.
val GAUSSIAN: PulseProfile PulseProfile.GAUSSIAN, re-exported so profile = GAUSSIAN reads as DSL — the pulse profile, distinct from the standalone gaussian bump function.
val NEAREST: Interpolation Interpolation.NEAREST, re-exported so interpolation = NEAREST reads as DSL.
val CUBIC: Interpolation Interpolation.CUBIC, re-exported so interpolation = CUBIC reads as DSL.
data class Fn(val domain: Domain = Domain.DEFAULT, val name: String? = null, val graph: FnGraphNode? = null, private val evalFn: (Double) -> Double) A one-dimensional signal together with the interval on which it is meant to be sampled.
fun fn(domain: Domain = Domain.DEFAULT, name: String? = null, eval: (Double) -> Double): Fn Wraps an arbitrary Kotlin lambda as an Fn — the escape hatch for anything the library's own combinators cannot express.
fun fn(domain: ClosedRange<*>, name: String? = null, eval: (Double) -> Double): Fn Wraps an arbitrary Kotlin lambda as an Fn over the given numeric range — see fn; the node is opaque to graph rewriting.
fun c(value: Number, domain: Domain = Domain.DEFAULT): Fn A constant function: value everywhere, over domain (0.0..1.0 by default).
fun c(value: Number, domain: ClosedRange<*>): Fn A constant function: value everywhere, over the given numeric range. See c.
val Number.c: Fn This number as a parameter signal: a constant over Domain.ALL, so using it to configure a function will not shrink that function's domain.
fun constant(value: Number, domain: Domain = Domain.DEFAULT): Fn+1 overload · also ClosedRange<*> A constant function: value everywhere. A spelled-out alias of c, for code that would rather read as prose.
fun zero(domain: Domain = Domain.DEFAULT): Fn The constant zero over domain — the additive identity, and what the list combinators return for an empty list.
fun zero(domain: ClosedRange<*>): Fn The constant zero over the given numeric range.
fun one(domain: Domain = Domain.DEFAULT): Fn The constant one over domain — the multiplicative identity, handy as the 1 in expressions like one(d) - f.
fun one(domain: ClosedRange<*>): Fn The constant one over the given numeric range.
fun identity(domain: Domain = Domain.DEFAULT): Fn+1 overload · also ClosedRange<*> f(x) = x: the raw coordinate as a signal, named x in graphs and plots.
fun ramp(start: Fn = param(0.0), end: Fn = param(1.0), domain: Domain = Domain.DEFAULT): Fn A value ramp: a straight line whose value goes from start to end as x sweeps the function's domain. ramp(1.0, 6.0) over (0.0..1.0) is 1 at x=0 and 6 at x=1; ramp(0.0, 1.0) is the identity over the default 0..1 domain. This is the linear sibling of tween (which eases the same endpoints). An unbounded domain is previewed over a finite window via Domain.finiteSweep, so a ramp on Domain.ALL stays finite to enumerate.
fun ramp(start: Fn = param(0.0), end: Fn = param(1.0), domain: ClosedRange<*>): Fn A value ramp: a straight line whose value travels from start to end as x sweeps the domain. Endpoints may be plain numbers or signals; the Any overloads throw for anything that is neither. For the opposite reading — normalizing an x-window onto 0..1 — use remap.
fun ramp(start: Number, end: Number, domain: Domain = Domain.DEFAULT): Fn A value ramp: a straight line whose value travels from start to end as x sweeps the domain. Endpoints may be plain numbers or signals; the Any overloads throw for anything that is neither. For the opposite reading — normalizing an x-window onto 0..1 — use remap.
fun ramp(start: Number, end: Number, domain: ClosedRange<*>): Fn A value ramp: a straight line whose value travels from start to end as x sweeps the domain. Endpoints may be plain numbers or signals; the Any overloads throw for anything that is neither. For the opposite reading — normalizing an x-window onto 0..1 — use remap.
fun ramp(start: Any, end: Any, domain: Domain = Domain.DEFAULT): Fn A value ramp: a straight line whose value travels from start to end as x sweeps the domain. Endpoints may be plain numbers or signals; the Any overloads throw for anything that is neither. For the opposite reading — normalizing an x-window onto 0..1 — use remap.
fun ramp(start: Any, end: Any, domain: ClosedRange<*>): Fn A value ramp: a straight line whose value travels from start to end as x sweeps the domain. Endpoints may be plain numbers or signals; the Any overloads throw for anything that is neither. For the opposite reading — normalizing an x-window onto 0..1 — use remap.
fun remap(from: Fn = param(0.0), to: Fn = param(1.0), domain: Domain = Domain.DEFAULT): Fn A domain normalizer: maps the x-window [from, to] onto 0..1, i.e. (x - from) / (to - from). remap(0.0, 10.0) turns an x in 0..10 into a 0..1 alpha; remap(0.0, 1.0) is the identity. This is the position-of-x reading — distinct from ramp, which produces a value that travels from one number to another across the domain.
fun remap(from: Fn = param(0.0), to: Fn = param(1.0), domain: ClosedRange<*>): Fn A domain normalizer: (x - from) / (to - from), so an x in the window from..to reads out as a 0..1 alpha. Not clamped — x outside the window gives an alpha outside 0..1 — and a zero-width window answers 0.0 rather than dividing by zero. The value-travelling counterpart is ramp.
fun remap(from: Number, to: Number, domain: Domain = Domain.DEFAULT): Fn A domain normalizer: (x - from) / (to - from), so an x in the window from..to reads out as a 0..1 alpha. Not clamped — x outside the window gives an alpha outside 0..1 — and a zero-width window answers 0.0 rather than dividing by zero. The value-travelling counterpart is ramp.
fun remap(from: Number, to: Number, domain: ClosedRange<*>): Fn A domain normalizer: (x - from) / (to - from), so an x in the window from..to reads out as a 0..1 alpha. Not clamped — x outside the window gives an alpha outside 0..1 — and a zero-width window answers 0.0 rather than dividing by zero. The value-travelling counterpart is ramp.
fun remap(from: Any, to: Any, domain: Domain = Domain.DEFAULT): Fn A domain normalizer: (x - from) / (to - from), so an x in the window from..to reads out as a 0..1 alpha. Not clamped — x outside the window gives an alpha outside 0..1 — and a zero-width window answers 0.0 rather than dividing by zero. The value-travelling counterpart is ramp.
fun remap(from: Any, to: Any, domain: ClosedRange<*>): Fn A domain normalizer: (x - from) / (to - from), so an x in the window from..to reads out as a 0..1 alpha. Not clamped — x outside the window gives an alpha outside 0..1 — and a zero-width window answers 0.0 rather than dividing by zero. The value-travelling counterpart is ramp.
fun step(edge: Fn = param(0.0), domain: Domain = Domain.DEFAULT): Fn A hard step: 0 below edge, 1 from edge onwards — the Heaviside function, inclusive at the threshold itself.
fun step(edge: Fn = param(0.0), domain: ClosedRange<*>): Fn A hard step: 0 below edge, 1 from edge onwards, the threshold itself included. The soft version is smoothStep.
fun step(edge: Number, domain: Domain = Domain.DEFAULT): Fn A hard step: 0 below edge, 1 from edge onwards, the threshold itself included. The soft version is smoothStep.
fun step(edge: Number, domain: ClosedRange<*>): Fn A hard step: 0 below edge, 1 from edge onwards, the threshold itself included. The soft version is smoothStep.
fun smoothStep(edge0: Fn = param(0.0), edge1: Fn = param(1.0), domain: Domain = Domain.DEFAULT): Fn A soft step in x: 0 up to edge0, 1 from edge1 on, and the classic 3t² - 2t³ ease between them.
fun smoothStep(edge0: Fn = param(0.0), edge1: Fn = param(1.0), domain: ClosedRange<*>): Fn A soft step in x: 0 up to edge0, 1 from edge1 on, 3t² - 2t³ between. The edges are sorted, so their order does not matter; equal edges give a hard step.
fun smoothStep(edge0: Number, edge1: Number, domain: Domain = Domain.DEFAULT): Fn A soft step in x: 0 up to edge0, 1 from edge1 on, 3t² - 2t³ between. The edges are sorted, so their order does not matter; equal edges give a hard step.
fun smoothStep(edge0: Number, edge1: Number, domain: ClosedRange<*>): Fn A soft step in x: 0 up to edge0, 1 from edge1 on, 3t² - 2t³ between. The edges are sorted, so their order does not matter; equal edges give a hard step.
fun smoothStep(edge0: Any, edge1: Any, domain: Domain = Domain.DEFAULT): Fn A soft step in x: 0 up to edge0, 1 from edge1 on, 3t² - 2t³ between. The edges are sorted, so their order does not matter; equal edges give a hard step.
fun smoothStep(edge0: Any, edge1: Any, domain: ClosedRange<*>): Fn A soft step in x: 0 up to edge0, 1 from edge1 on, 3t² - 2t³ between. The edges are sorted, so their order does not matter; equal edges give a hard step.
fun tween(from: Fn = param(0.0), to: Fn = param(1.0), domain: Domain = Domain.DEFAULT, easing: Easing = Easing.SMOOTHSTEP): Fn Interpolates from from to to across domain with a named motion curve. The endpoints may themselves be signals, which keeps tweening compositional.
fun tween(from: Number, to: Number, domain: Domain = Domain.DEFAULT, easing: Easing = Easing.SMOOTHSTEP): Fn Interpolates from from to to across the domain with a named Easing — the eased sibling of ramp, and the usual way to write a piece of procedural motion. An unbounded domain is previewed over a finite window, so the alpha stays meaningful.
fun tween(from: Number, to: Number, domain: ClosedRange<*>, easing: Easing = Easing.SMOOTHSTEP): Fn Interpolates from from to to across the domain with a named Easing — the eased sibling of ramp, and the usual way to write a piece of procedural motion. An unbounded domain is previewed over a finite window, so the alpha stays meaningful.
fun Fn.ease(easing: Easing = Easing.SMOOTHSTEP, clampInput: Boolean = true): Fn Shapes a normalized signal through easing, optionally clamping it to 0..1 first.
fun sin(frequency: Fn = param(1.0), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: Domain = Domain.ALL): Fn A sine oscillator: offset + amplitude · sin(2π · frequency · x + phase).
fun sin(frequency: Fn = param(1.0), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: ClosedRange<*>): Fn A sine oscillator, offset + amplitude · sin(2π · frequency · x + phase): frequency in cycles per unit x, phase in radians. Any parameter may be a signal.
fun sin(frequency: Number, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, domain: Domain = Domain.ALL): Fn
fun sin(frequency: Number, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, domain: ClosedRange<*>): Fn
fun sin(frequency: Any, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, domain: Domain = Domain.ALL): Fn
fun sin(frequency: Any, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, domain: ClosedRange<*>): Fn
fun cos(frequency: Fn = param(1.0), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: Domain = Domain.ALL): Fn A cosine oscillator: offset + amplitude · cos(2π · frequency · x + phase) — sin shifted a quarter turn, so it starts at its peak rather than at zero.
fun cos(frequency: Fn = param(1.0), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: ClosedRange<*>): Fn A cosine oscillator, offset + amplitude · cos(2π · frequency · x + phase): frequency in cycles per unit x, phase in radians. Any parameter may be a signal.
fun cos(frequency: Number, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, domain: Domain = Domain.ALL): Fn
fun cos(frequency: Number, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, domain: ClosedRange<*>): Fn
fun cos(frequency: Any, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, domain: Domain = Domain.ALL): Fn
fun cos(frequency: Any, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, domain: ClosedRange<*>): Fn
fun saw(period: Fn = param(1.0), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: Domain = Domain.ALL): Fn A rising sawtooth: a straight ramp from -amplitude to +amplitude across each period, then an instantaneous jump back down.
fun saw(period: Fn = param(1.0), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: ClosedRange<*>): Fn A rising sawtooth from -amplitude to +amplitude across each period, then a jump back. period is a width in x units and phase an offset in those units, not radians.
fun saw(period: Number, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, domain: Domain = Domain.ALL): Fn
fun saw(period: Number, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, domain: ClosedRange<*>): Fn
fun saw(period: Any, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, domain: Domain = Domain.ALL): Fn
fun saw(period: Any, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, domain: ClosedRange<*>): Fn
fun triangle(period: Fn = param(1.0), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: Domain = Domain.ALL): Fn A triangle wave: linear up from -amplitude to +amplitude over the first half of each period, linear back down over the second — continuous everywhere, unlike saw.
fun triangle(period: Fn = param(1.0), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: ClosedRange<*>): Fn A triangle wave: linear up then linear down across each period, continuous throughout. period is a width in x units and phase an offset in those units, not radians.
fun triangle(period: Number, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, domain: Domain = Domain.ALL): Fn
fun triangle(period: Number, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, domain: ClosedRange<*>): Fn
fun triangle(period: Any, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, domain: Domain = Domain.ALL): Fn
fun triangle(period: Any, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, domain: ClosedRange<*>): Fn
fun square(period: Fn = param(1.0), phase: Fn = param(0.0), duty: Fn = param(0.5), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: Domain = Domain.ALL): Fn A square wave: +amplitude for the first duty fraction of each period and -amplitude for the rest.
fun square(period: Fn = param(1.0), phase: Fn = param(0.0), duty: Fn = param(0.5), amplitude: Fn = param(1.0), offset: Fn = param(0.0), domain: ClosedRange<*>): Fn A bipolar square wave: +amplitude for the first duty fraction of each period, -amplitude for the rest. period and phase are in x units; duty is clamped to 0..1.
fun square(period: Number, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, domain: Domain = Domain.ALL): Fn
fun square(period: Number, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, domain: ClosedRange<*>): Fn
fun square(period: Any, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, domain: Domain = Domain.ALL): Fn
fun square(period: Any, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, domain: ClosedRange<*>): Fn
fun pulseTrain(period: Fn = param(1.0), phase: Fn = param(0.0), duty: Fn = param(0.5), amplitude: Fn = param(1.0), offset: Fn = param(0.0), rise: Fn = param(0.0), fall: Fn = param(0.0), edge: PulseEdge = PulseEdge.SMOOTH, curve: Fn = param(2.0), profile: PulseProfile = PulseProfile.RECT, profileSigma: Fn = param(0.15), jitter: Fn = param(0.0), seed: Long = 0L, bipolar: Boolean = false, dutyMod: Fn = param(0.0), ampMod: Fn = param(1.0), phaseMod: Fn = param(0.0), domain: Domain = Domain.ALL): Fn The general pulse generator: one shaped pulse per period, with control over width, edges, body profile and per-cycle jitter. Where square gives you a bipolar two-level wave, this gives you an envelope you can actually design.
fun pulseTrain(period: Fn = param(1.0), phase: Fn = param(0.0), duty: Fn = param(0.5), amplitude: Fn = param(1.0), offset: Fn = param(0.0), rise: Fn = param(0.0), fall: Fn = param(0.0), edge: PulseEdge = PulseEdge.SMOOTH, curve: Fn = param(2.0), profile: PulseProfile = PulseProfile.RECT, profileSigma: Fn = param(0.15), jitter: Fn = param(0.0), seed: Long = 0L, bipolar: Boolean = false, dutyMod: Fn = param(0.0), ampMod: Fn = param(1.0), phaseMod: Fn = param(0.0), domain: ClosedRange<*>): Fn The general pulse generator: one shaped pulse per period, unipolar from offset to offset + amplitude unless bipolar is set. period, phase, rise and fall are all in x units; duty is a fraction clamped to 0..1. See the primary overload for the full conventions.
fun pulseTrain(period: Number, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Number = 0.0, ampMod: Number = 1.0, phaseMod: Number = 0.0, domain: Domain = Domain.ALL): Fn The general pulse generator: one shaped pulse per period, unipolar from offset to offset + amplitude unless bipolar is set. period, phase, rise and fall are all in x units; duty is a fraction clamped to 0..1. See the primary overload for the full conventions.
fun pulseTrain(period: Number, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Number = 0.0, ampMod: Number = 1.0, phaseMod: Number = 0.0, domain: ClosedRange<*>): Fn The general pulse generator: one shaped pulse per period, unipolar from offset to offset + amplitude unless bipolar is set. period, phase, rise and fall are all in x units; duty is a fraction clamped to 0..1. See the primary overload for the full conventions.
fun pulseTrain(period: Any, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Any = 0.0, ampMod: Any = 1.0, phaseMod: Any = 0.0, domain: Domain = Domain.ALL): Fn The general pulse generator, taking each parameter as either a number or an Fn and throwing for anything else: one shaped pulse per period, unipolar from offset to offset + amplitude unless bipolar is set. period, phase, rise and fall are in x units; duty is a fraction clamped to 0..1. See the primary overload for the full conventions.
fun pulseTrain(period: Any, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Any = 0.0, ampMod: Any = 1.0, phaseMod: Any = 0.0, domain: ClosedRange<*>): Fn The general pulse generator, taking each parameter as either a number or an Fn and throwing for anything else: one shaped pulse per period, unipolar from offset to offset + amplitude unless bipolar is set. period, phase, rise and fall are in x units; duty is a fraction clamped to 0..1. See the primary overload for the full conventions.
fun pulseTrainWidth(period: Fn = param(1.0), width: Fn = param(0.5), phase: Fn = param(0.0), amplitude: Fn = param(1.0), offset: Fn = param(0.0), rise: Fn = param(0.0), fall: Fn = param(0.0), edge: PulseEdge = PulseEdge.SMOOTH, curve: Fn = param(2.0), profile: PulseProfile = PulseProfile.RECT, profileSigma: Fn = param(0.15), jitter: Fn = param(0.0), seed: Long = 0L, bipolar: Boolean = false, dutyMod: Fn = param(0.0), ampMod: Fn = param(1.0), phaseMod: Fn = param(0.0), domain: Domain = Domain.ALL): Fn pulseTrain specified by absolute pulse width rather than duty fraction — the form you want when the pulse should keep its length while the period changes.
fun pulseTrainWidth(period: Number, width: Number = 0.5, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Number = 0.0, ampMod: Number = 1.0, phaseMod: Number = 0.0, domain: Domain = Domain.ALL): Fn pulseTrain specified by absolute pulse width in x units instead of a duty fraction; the duty is derived as width / period, clamped to 0..1, so an over-wide pulse saturates rather than wrapping. See pulseTrain for the remaining conventions.
fun pulseTrainWidth(period: Number, width: Number = 0.5, phase: Number = 0.0, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Number = 0.0, ampMod: Number = 1.0, phaseMod: Number = 0.0, domain: ClosedRange<*>): Fn pulseTrain specified by absolute pulse width in x units instead of a duty fraction; the duty is derived as width / period, clamped to 0..1, so an over-wide pulse saturates rather than wrapping. See pulseTrain for the remaining conventions.
fun pulseTrainWidth(period: Any, width: Any = 0.5, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Any = 0.0, ampMod: Any = 1.0, phaseMod: Any = 0.0, domain: Domain = Domain.ALL): Fn pulseTrain specified by absolute pulse width in x units instead of a duty fraction, taking each parameter as either a number or an Fn and throwing for anything else. The duty is derived as width / period, clamped to 0..1. See pulseTrain for the remaining conventions.
fun pulseTrainWidth(period: Any, width: Any = 0.5, phase: Any = 0.0, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Any = 0.0, ampMod: Any = 1.0, phaseMod: Any = 0.0, domain: ClosedRange<*>): Fn pulseTrain specified by absolute pulse width in x units instead of a duty fraction, taking each parameter as either a number or an Fn and throwing for anything else. The duty is derived as width / period, clamped to 0..1. See pulseTrain for the remaining conventions.
fun pulseTrainHz(frequency: Fn = param(1.0), phase: Fn = param(0.0), duty: Fn = param(0.5), amplitude: Fn = param(1.0), offset: Fn = param(0.0), rise: Fn = param(0.0), fall: Fn = param(0.0), edge: PulseEdge = PulseEdge.SMOOTH, curve: Fn = param(2.0), profile: PulseProfile = PulseProfile.RECT, profileSigma: Fn = param(0.15), jitter: Fn = param(0.0), seed: Long = 0L, bipolar: Boolean = false, dutyMod: Fn = param(0.0), ampMod: Fn = param(1.0), phaseMod: Fn = param(0.0), domain: Domain = Domain.ALL): Fn pulseTrain driven by frequency instead of period, for when repetition rate is the natural way to think about it.
fun pulseTrainHz(frequency: Number, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Number = 0.0, ampMod: Number = 1.0, phaseMod: Number = 0.0, domain: Domain = Domain.ALL): Fn pulseTrain driven by frequency in cycles per unit x rather than by period; the period is derived as 1 / frequency, and a frequency of zero degenerates to just offset. See pulseTrain for the remaining conventions.
fun pulseTrainHz(frequency: Number, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Number = 0.0, ampMod: Number = 1.0, phaseMod: Number = 0.0, domain: ClosedRange<*>): Fn pulseTrain driven by frequency in cycles per unit x rather than by period; the period is derived as 1 / frequency, and a frequency of zero degenerates to just offset. See pulseTrain for the remaining conventions.
fun pulseTrainHz(frequency: Any, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Any = 0.0, ampMod: Any = 1.0, phaseMod: Any = 0.0, domain: Domain = Domain.ALL): Fn pulseTrain driven by frequency in cycles per unit x rather than by period, taking each parameter as either a number or an Fn and throwing for anything else. The period is derived as 1 / frequency, and a frequency of zero degenerates to just offset.
fun pulseTrainHz(frequency: Any, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Any = 0.0, ampMod: Any = 1.0, phaseMod: Any = 0.0, domain: ClosedRange<*>): Fn pulseTrain driven by frequency in cycles per unit x rather than by period, taking each parameter as either a number or an Fn and throwing for anything else. The period is derived as 1 / frequency, and a frequency of zero degenerates to just offset.
fun pulseTrainPattern(pattern: DoubleArray, period: Fn = param(1.0), phase: Fn = param(0.0), duty: Fn = param(0.5), amplitude: Fn = param(1.0), offset: Fn = param(0.0), rise: Fn = param(0.0), fall: Fn = param(0.0), edge: PulseEdge = PulseEdge.SMOOTH, curve: Fn = param(2.0), profile: PulseProfile = PulseProfile.RECT, profileSigma: Fn = param(0.15), jitter: Fn = param(0.0), seed: Long = 0L, bipolar: Boolean = false, dutyMod: Fn = param(0.0), ampMod: Fn = param(1.0), phaseMod: Fn = param(0.0), domain: Domain = Domain.ALL): Fn pulseTrain with a repeating per-cycle amplitude pattern — an accent rhythm rather than an even train.
fun pulseTrainPattern(pattern: DoubleArray, period: Number, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Number = 0.0, ampMod: Number = 1.0, phaseMod: Number = 0.0, domain: Domain = Domain.ALL): Fn pulseTrain with a repeating per-cycle amplitude pattern: cycle n is scaled by pattern[n mod pattern.size]. An empty pattern returns the constant zero over the domain. See pulseTrain for the remaining conventions.
fun pulseTrainPattern(pattern: DoubleArray, period: Number, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Number = 0.0, ampMod: Number = 1.0, phaseMod: Number = 0.0, domain: ClosedRange<*>): Fn pulseTrain with a repeating per-cycle amplitude pattern: cycle n is scaled by pattern[n mod pattern.size]. An empty pattern returns the constant zero over the domain. See pulseTrain for the remaining conventions.
fun pulseTrainPattern(pattern: DoubleArray, period: Any, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Any = 0.0, ampMod: Any = 1.0, phaseMod: Any = 0.0, domain: Domain = Domain.ALL): Fn pulseTrain with a repeating per-cycle amplitude pattern, taking each parameter as either a number or an Fn and throwing for anything else: cycle n is scaled by pattern[n mod pattern.size], and an empty pattern returns the constant zero over the domain.
fun pulseTrainPattern(pattern: DoubleArray, period: Any, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, bipolar: Boolean = false, dutyMod: Any = 0.0, ampMod: Any = 1.0, phaseMod: Any = 0.0, domain: ClosedRange<*>): Fn pulseTrain with a repeating per-cycle amplitude pattern, taking each parameter as either a number or an Fn and throwing for anything else: cycle n is scaled by pattern[n mod pattern.size], and an empty pattern returns the constant zero over the domain.
fun impulseTrain(period: Fn = param(1.0), phase: Fn = param(0.0), width: Fn = param(0.02), amplitude: Fn = param(1.0), offset: Fn = param(0.0), sigma: Fn = param(0.15), jitter: Fn = param(0.0), seed: Long = 0L, domain: Domain = Domain.ALL): Fn A train of narrow Gaussian bumps — pulseTrainWidth pre-set to a GAUSSIAN profile with smooth edges, which is as close as a continuously-sampleable function gets to a Dirac comb.
fun impulseTrain(period: Number, phase: Number = 0.0, width: Number = 0.02, amplitude: Number = 1.0, offset: Number = 0.0, sigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, domain: Domain = Domain.ALL): Fn A train of narrow Gaussian bumps, one width wide (in x units) every period — pulseTrainWidth pre-set to a GAUSSIAN profile. Unipolar, from offset to offset + amplitude; the bumps never quite reach zero between them.
fun impulseTrain(period: Number, phase: Number = 0.0, width: Number = 0.02, amplitude: Number = 1.0, offset: Number = 0.0, sigma: Number = 0.15, jitter: Number = 0.0, seed: Long = 0L, domain: ClosedRange<*>): Fn A train of narrow Gaussian bumps, one width wide (in x units) every period — pulseTrainWidth pre-set to a GAUSSIAN profile. Unipolar, from offset to offset + amplitude; the bumps never quite reach zero between them.
fun impulseTrain(period: Any, phase: Any = 0.0, width: Any = 0.02, amplitude: Any = 1.0, offset: Any = 0.0, sigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, domain: Domain = Domain.ALL): Fn A train of narrow Gaussian bumps, one width wide (in x units) every period, taking each parameter as either a number or an Fn and throwing for anything else. Unipolar, from offset to offset + amplitude.
fun impulseTrain(period: Any, phase: Any = 0.0, width: Any = 0.02, amplitude: Any = 1.0, offset: Any = 0.0, sigma: Any = 0.15, jitter: Any = 0.0, seed: Long = 0L, domain: ClosedRange<*>): Fn A train of narrow Gaussian bumps, one width wide (in x units) every period, taking each parameter as either a number or an Fn and throwing for anything else. Unipolar, from offset to offset + amplitude.
fun pulseBurst(count: Int, period: Fn = param(1.0), start: Fn = param(0.0), phase: Fn = param(0.0), duty: Fn = param(0.5), amplitude: Fn = param(1.0), offset: Fn = param(0.0), rise: Fn = param(0.0), fall: Fn = param(0.0), edge: PulseEdge = PulseEdge.SMOOTH, curve: Fn = param(2.0), profile: PulseProfile = PulseProfile.RECT, profileSigma: Fn = param(0.15), domain: Domain = Domain.ALL): Fn A finite burst of count pulses beginning at start — a pulseTrain gated to a window instead of running forever.
fun pulseBurst(count: Int, period: Number, start: Number = 0.0, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, domain: Domain = Domain.ALL): Fn A finite burst of count pulses beginning at start, with offset everywhere outside the window start .. start + period * count. A count of zero or less gives the constant zero over the domain. See pulseTrain for the shaping conventions.
fun pulseBurst(count: Int, period: Number, start: Number = 0.0, phase: Number = 0.0, duty: Number = 0.5, amplitude: Number = 1.0, offset: Number = 0.0, rise: Number = 0.0, fall: Number = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Number = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Number = 0.15, domain: ClosedRange<*>): Fn A finite burst of count pulses beginning at start, with offset everywhere outside the window start .. start + period * count. A count of zero or less gives the constant zero over the domain. See pulseTrain for the shaping conventions.
fun pulseBurst(count: Int, period: Any, start: Any = 0.0, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, domain: Domain = Domain.ALL): Fn A finite burst of count pulses beginning at start, taking each parameter as either a number or an Fn and throwing for anything else. offset applies everywhere outside the window start .. start + period * count; a count of zero or less gives the constant zero.
fun pulseBurst(count: Int, period: Any, start: Any = 0.0, phase: Any = 0.0, duty: Any = 0.5, amplitude: Any = 1.0, offset: Any = 0.0, rise: Any = 0.0, fall: Any = 0.0, edge: PulseEdge = PulseEdge.SMOOTH, curve: Any = 2.0, profile: PulseProfile = PulseProfile.RECT, profileSigma: Any = 0.15, domain: ClosedRange<*>): Fn A finite burst of count pulses beginning at start, taking each parameter as either a number or an Fn and throwing for anything else. offset applies everywhere outside the window start .. start + period * count; a count of zero or less gives the constant zero.
fun gaussian(mean: Fn = param(0.0), sigma: Fn = param(1.0), amplitude: Fn = param(1.0), domain: Domain = Domain.DEFAULT): Fn A Gaussian bump: amplitude · exp(-½ ((x - mean) / sigma)²) — peak amplitude at mean, down to about 0.61 of the peak one sigma away, and never quite zero anywhere.
fun gaussian(mean: Number, sigma: Number = 1.0, amplitude: Number = 1.0, domain: Domain = Domain.DEFAULT): Fn A Gaussian bump: amplitude · exp(-½ ((x - mean) / sigma)²), peaking at mean and never quite reaching zero. A sigma of zero gives the constant zero, and the domain defaults to 0.0..1.0 rather than the whole line.
fun gaussian(mean: Number, sigma: Number = 1.0, amplitude: Number = 1.0, domain: ClosedRange<*>): Fn A Gaussian bump: amplitude · exp(-½ ((x - mean) / sigma)²), peaking at mean and never quite reaching zero. A sigma of zero gives the constant zero, and the domain defaults to 0.0..1.0 rather than the whole line.
fun gaussian(mean: Any, sigma: Any = 1.0, amplitude: Any = 1.0, domain: Domain = Domain.DEFAULT): Fn A Gaussian bump peaking at mean, taking each parameter as either a number or an Fn and throwing for anything else. A sigma of zero gives the constant zero; the bump never quite reaches zero elsewhere.
fun gaussian(mean: Any, sigma: Any = 1.0, amplitude: Any = 1.0, domain: ClosedRange<*>): Fn A Gaussian bump peaking at mean, taking each parameter as either a number or an Fn and throwing for anything else. A sigma of zero gives the constant zero; the bump never quite reaches zero elsewhere.
fun DoubleArray.toFn(domain: Domain = Domain.of(0.0, (size - 1).toDouble()), interpolation: Interpolation = Interpolation.LINEAR, outside: Double = 0.0): Fn Turns a table of samples into a continuous Fn, reading between the entries with the chosen interpolation.
fun DoubleArray.toFn(domain: ClosedRange<*>, interpolation: Interpolation = Interpolation.LINEAR, outside: Double = 0.0): Fn A table of samples as a continuous Fn, spread evenly across the given range and reading outside beyond it. See the Domain overload of toFn.
fun List<Number>.toFn(domain: Domain = Domain.of(0.0, (size - 1).toDouble()), interpolation: Interpolation = Interpolation.LINEAR, outside: Double = 0.0): Fn A list of numbers as a continuous Fn, spread evenly across domain (by default 0.0 .. size - 1) and reading outside beyond it. Copies the list into a DoubleArray first, so later mutation of the list does not affect the function.
fun List<Number>.toFn(domain: ClosedRange<*>, interpolation: Interpolation = Interpolation.LINEAR, outside: Double = 0.0): Fn A list of numbers as a continuous Fn, spread evenly across the given range and reading outside beyond it. Copies the list first, so later mutation of the list does not affect the function.
fun Fn.sampleAt(xs: DoubleArray, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0): DoubleArray Evaluates this function at an explicit list of coordinates, returning one value per entry in the same order.
fun Fn.toDoubleArray(count: Int, sampleDomain: Domain = domain, includeEnd: Boolean = true, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0): DoubleArray Samples this function at count evenly-spaced points over sampleDomain — the workhorse for getting numbers out.
fun Fn.toDoubleArray(count: Int, sampleDomain: ClosedRange<*>, includeEnd: Boolean = true, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0): DoubleArray Samples this function at count evenly-spaced points over the given range; includeEnd decides whether the last sample lands exactly on the upper bound.
fun Fn.toDoubleArray(step: Double, sampleDomain: Domain = domain, includeEnd: Boolean = true, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0): DoubleArray Samples this function every step units across sampleDomain, letting the domain's width decide how many samples that is.
fun Fn.toDoubleArray(step: Double, sampleDomain: ClosedRange<*>, includeEnd: Boolean = true, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0): DoubleArray Samples this function every step units across the given range; the step's sign is ignored and a step of zero gives an empty array.
fun Fn.toVec2List(count: Int, sampleDomain: Domain = domain, includeEnd: Boolean = true, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0): List<Vec2> Sample this function at count uniformly-spaced points and return the results as List<Vec2> where each entry is Vec2(time, value).
fun Fn.toVec2List(count: Int, sampleDomain: ClosedRange<*>, includeEnd: Boolean = true, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0): List<Vec2> Samples this function at count evenly-spaced points over the given range as Vec2(x, value) pairs. Note that the coordinates are narrowed to Float on the way out, so this is for feeding drawing and overlay APIs rather than for numerics.
fun Fn.plotHtml(sampleDomain: Domain? = null, samples: Int = 1024, heightPx: Int = 360, title: String = name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: Domain? = null, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, background: String = "#000000", primaryColor: String = "#0af", secondaryColor: String = "#f80", fontFamily: String = "\"Iosevka Web\", \"IBM Plex Mono\", monospace", syncGroup: String = ""): String Renders an interactive plot for an Fn.
fun Fn.plotHtml(sampleDomain: ClosedRange<*>, samples: Int = 1024, heightPx: Int = 360, title: String = name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: Domain? = null, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, background: String = "#000000", primaryColor: String = "#0af", secondaryColor: String = "#f80", fontFamily: String = "\"Iosevka Web\", \"IBM Plex Mono\", monospace", syncGroup: String = ""): String Renders an interactive plot of this function as self-contained HTML (samples embedded), sampling over the given range. Non-finite samples — including anything that throws while being evaluated, which is caught and turned into a gap — are drawn as breaks in the curve rather than spikes.
fun Fn.plotHtml(sampleDomain: ClosedRange<*>, samples: Int = 1024, heightPx: Int = 360, title: String = name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: ClosedRange<*>, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, background: String = "#000000", primaryColor: String = "#0af", secondaryColor: String = "#f80", fontFamily: String = "\"Iosevka Web\", \"IBM Plex Mono\", monospace", syncGroup: String = ""): String Renders an interactive plot of this function as self-contained HTML (samples embedded) over the given x range, with an explicit y range instead of one fitted to the data. Non-finite samples are drawn as gaps.
fun Fn.plot(channel: String = "plot", sampleDomain: Domain? = null, samples: Int = 1024, heightPx: Int = 360, title: String = name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: Domain? = null, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Draws this function into the currently executing box, on the given output channel, and returns the function unchanged so it can sit mid-chain.
fun Fn.plot(sampleDomain: ClosedRange<*>, channel: String = "plot", samples: Int = 1024, heightPx: Int = 360, title: String = name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: Domain? = null, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Draws this function into the currently executing box over an explicit x range, returning the function unchanged. Throws IllegalStateException if there is no active box context.
fun Fn.plot(sampleDomain: ClosedRange<*>, channel: String = "plot", samples: Int = 1024, heightPx: Int = 360, title: String = name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: ClosedRange<*>, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Draws this function into the currently executing box over explicit x and y ranges, returning the function unchanged. Throws IllegalStateException if there is no active box context.
fun Box.plot(fn: Fn, channel: String = "plot", sampleDomain: Domain? = null, samples: Int = 1024, heightPx: Int = 360, title: String = fn.name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: Domain? = null, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Draws fn into this box on the given output channel, returning fn unchanged.
fun Box.plot(fn: Fn, sampleDomain: ClosedRange<*>, channel: String = "plot", samples: Int = 1024, heightPx: Int = 360, title: String = fn.name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: Domain? = null, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Draws fn into this box over an explicit x range, returning fn unchanged. Needs no ambient box context.
fun Box.plot(fn: Fn, sampleDomain: ClosedRange<*>, channel: String = "plot", samples: Int = 1024, heightPx: Int = 360, title: String = fn.name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: ClosedRange<*>, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Draws fn into this box over explicit x and y ranges, returning fn unchanged. Needs no ambient box context.
fun plot(fn: Fn, channel: String = "plot", sampleDomain: Domain? = null, samples: Int = 1024, heightPx: Int = 360, title: String = fn.name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: Domain? = null, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Plots fn into the currently executing box — the prefix spelling of Fn.plot, for when plot(f) reads better than f.plot(). Returns fn unchanged, and throws IllegalStateException if there is no active box context.
fun plot(fn: Fn, sampleDomain: ClosedRange<*>, channel: String = "plot", samples: Int = 1024, heightPx: Int = 360, title: String = fn.name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: Domain? = null, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Plots fn into the currently executing box over an explicit x range. Returns fn unchanged; throws IllegalStateException if there is no active box context.
fun plot(fn: Fn, sampleDomain: ClosedRange<*>, channel: String = "plot", samples: Int = 1024, heightPx: Int = 360, title: String = fn.name ?: "Fn", xLabel: String = "x", yLabel: String = "f(x)", yDomain: ClosedRange<*>, edge: EdgeMode = EdgeMode.ZERO, outside: Double = Double.NaN, syncGroup: String = ""): Fn Plots fn into the currently executing box over explicit x and y ranges. Returns fn unchanged; throws IllegalStateException if there is no active box context.
class FunctionCursor(val fn: Fn, var x: Double = fn.domain.finiteSweep().min, var step: Double = 1.0) A movable read head over an Fn: remembers where it is and how far to move, so a caller can pull values one at a time instead of asking for an array.
fun Fn.cursor(start: Double = domain.finiteSweep().min, step: Double = 1.0): FunctionCursor A FunctionCursor over this function, positioned at the start of its domain with a step of 1.0 unless told otherwise. Note that a step of 1.0 across a 0.0..1.0 function covers the whole thing in one move — set step explicitly.
fun Fn.stream(start: Double = domain.finiteSweep().min, step: Double = 1.0): Sequence<Double> An infinite lazy sequence of values, starting at start and advancing by step forever.
fun Fn.stream(domain: Domain, step: Double = 1.0, includeEnd: Boolean = true): Sequence<Double> A finite lazy sequence of values across domain, one every step — the terminating counterpart to the start-based overload.
fun Fn.streamWithX(start: Double = domain.finiteSweep().min, step: Double = 1.0): Sequence<Pair<Double, Double>> An infinite lazy sequence of (x, value) pairs from start, advancing by step — stream when you need to know where each sample came from. Never terminates, so bound it with take.
enum class ChunkMapMode How a timeline chunk maps a position on the timeline into its source function's own coordinates.
enum class ChunkBlend How a timeline chunk combines with whatever earlier chunks have already contributed at that x.
val ADD: ChunkBlend ChunkBlend.ADD, re-exported so blend = ADD reads as DSL inside with(Functions).
val REPLACE: ChunkBlend ChunkBlend.REPLACE, re-exported so blend = REPLACE reads as DSL inside with(Functions).
val MULTIPLY: ChunkBlend ChunkBlend.MULTIPLY, re-exported so blend = MULTIPLY reads as DSL inside with(Functions).
data class TimelineChunk(val source: Fn, val sourceDomain: Domain = source.domain, val domain: Domain, val mapMode: ChunkMapMode = ChunkMapMode.DIRECT, val blend: ChunkBlend = ChunkBlend.REPLACE, val edge: EdgeMode = EdgeMode.ZERO, val outside: Double = 0.0, val window: Fn? = null, val gain: Fn? = null, val name: String? = source.name) One placement on a timeline: which function, which slice of it, where that slice goes, and how it is mapped and blended once it gets there.
class TimelineGraph(chunks: List<TimelineChunk>, val outside: Double = 0.0, val defaultDomain: Domain = Domain.point(0.0)) A finished arrangement: a list of TimelineChunks that can be evaluated as one signal, or turned into an Fn.
class TimelineBuilder(start: Double = 0.0, private val outside: Double = 0.0) The receiver inside timeline { }: a moving cursor, a set of named marks, and the placement verbs (Fn.place, Fn.overlap, Fn.splice, repeatToFit, stretchToFit) that lay functions out in time.
fun spliceWindow(fadeIn: Double = 0.15, fadeOut: Double = fadeIn, domain: Domain = Domain.DEFAULT): Fn A fade-in / fade-out window: 1.0 through the middle, smoothstepped down to 0.0 at whichever ends you asked to fade.
fun timelineGraph(start: Double = 0.0, outside: Double = 0.0, init: TimelineBuilder.() -> Unit): TimelineGraph Runs a TimelineBuilder block and returns the resulting TimelineGraph — timeline when you want the chunk structure to inspect or re-blend, rather than a finished Fn.
fun timeline(start: Double = 0.0, outside: Double = 0.0, name: String = "timeline", domain: Domain? = null, init: TimelineBuilder.() -> Unit): Fn Arranges functions in time and returns the result as one Fn — the main entry point for composition across x, as opposed to the arithmetic that stacks signals on top of each other.
fun List<Fn>.sequence(overlap: Number = 0.0, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "sequence"): Fn Places each function once in list order. A positive overlap turns hard joins into additive crossfades at the joins.
fun List<Fn>.sequence(overlap: Fn, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "sequence"): Fn Places each function once, in list order, with a signal-valued overlap so each join can be crossfaded by a different amount — the overlap in force at a join is sampled around that join.
fun List<Fn>.sequenceLoop(overlap: Number = 0.0, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "sequenceLoop"): Fn Plays the list in order and then repeats forever, wrapping the last join back onto the first item so the loop is seamless when overlap is positive.
fun List<Fn>.sequenceLoop(overlap: Fn, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "sequenceLoop"): Fn Plays the list in order and repeats forever, wrapping the final join onto the first item, with a signal-valued overlap sampled around each join. Unbounded above by default; below the start it reads outside. An empty list gives a zero function over an empty domain.
fun List<Fn>.randomPermute(seed: Long = 0L, overlap: Number = 0.0, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "randomPermute"): Fn Plays every function exactly once, in an order shuffled from seed — a permutation, so nothing is repeated and nothing dropped.
fun List<Fn>.randomPermute(seed: Long = 0L, overlap: Fn, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "randomPermute"): Fn Plays every function exactly once in an order shuffled deterministically from seed, with a signal-valued overlap sampled around each join. A permutation — nothing repeated, nothing dropped.
fun List<Fn>.randomPermuteLoop(seed: Long = 0L, overlap: Number = 0.0, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "randomPermuteLoop"): Fn Loops forever, re-shuffling the order on every pass — so each cycle contains all the material exactly once, but in a different arrangement.
fun List<Fn>.randomPermuteLoop(seed: Long = 0L, overlap: Fn, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "randomPermuteLoop"): Fn Loops forever, re-shuffling the order on every pass from seed and the cycle number, with a signal-valued overlap sampled around each join. Every cycle contains all the material once; the cycle length stays fixed as the contents are reordered.
fun List<Fn>.randomSequence(seed: Long = 0L, overlap: Number = 0.0, slot: Number? = null, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "randomSequence"): Fn Fills an endless run of fixed-length slots, choosing an item at random for each one — so items repeat, items get skipped, and the rhythm stays regular.
fun List<Fn>.randomSequence(seed: Long = 0L, overlap: Number = 0.0, slot: Fn, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "randomSequence"): Fn Fills an endless run of slots, choosing an item at random for each, with a signal-valued slot length — so the pulse of the sequence can itself be modulated. Items repeat and are skipped freely; each choice is hashed from seed and the slot index, and each chosen item is time-scaled to the slot.
fun List<Fn>.randomSequence(seed: Long = 0L, overlap: Fn, slot: Number, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "randomSequence"): Fn Fills an endless run of fixed-length slots, choosing an item at random for each, with a signal-valued overlap sampled around each join. Items repeat and are skipped freely; every chosen item is time-scaled to the slot length.
fun List<Fn>.randomSequence(seed: Long = 0L, overlap: Fn, slot: Fn? = null, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "randomSequence"): Fn Fills an endless run of slots, choosing an item at random for each, with both overlap and slot length as signals. slot defaults to the longest item's duration; each chosen item is time-scaled to fit, and each choice is hashed from seed and the slot index so the stream is reproducible.
fun List<Fn>.weightedSequence(weights: List<Any>, seed: Long = 0L, overlap: Number = 0.0, slot: Number? = null, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "weightedSequence"): Fn randomSequence with a thumb on the scale: each item's chance of being drawn is proportional to its weight.
fun List<Fn>.weightedSequence(weights: List<Any>, seed: Long = 0L, overlap: Fn, slot: Fn? = null, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "weightedSequence"): Fn randomSequence with per-item weights deciding the odds, and a signal-valued overlap. Weights are positional and relative, may be numbers or signals, and count as zero when missing or not positive; if all of them are zero the draw falls back to a uniform choice.
fun List<Fn>.weightedSequence(weights: DoubleArray, seed: Long = 0L, overlap: Number = 0.0, slot: Number? = null, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "weightedSequence"): Fn randomSequence with per-item weights deciding the odds. Weights line up positionally with the list and are relative rather than normalized; a weight of zero (or a missing one) excludes that item, and if every weight is zero the draw falls back to a uniform choice.
fun List<Fn>.weightedSequence(weights: DoubleArray, seed: Long = 0L, overlap: Fn, slot: Fn? = null, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "weightedSequence"): Fn randomSequence with per-item weights deciding the odds, and a signal-valued overlap. Weights are positional and relative; a zero or missing weight excludes that item, and if all are zero the draw falls back to a uniform choice.
fun List<Fn>.shuffle(seed: Long = 0L, overlap: Number = 0.0, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "shuffle"): Fn Plays every function once in a shuffled order — the friendlier name for randomPermute, with identical behaviour. Deterministic from seed, and note that it shuffles once: use shuffleLoop to keep going.
fun List<Fn>.shuffle(seed: Long = 0L, overlap: Fn, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "shuffle"): Fn Plays every function once in a shuffled order, with a signal-valued overlap — an alias of randomPermute with identical behaviour.
fun List<Fn>.shuffleLoop(seed: Long = 0L, overlap: Number = 0.0, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "shuffleLoop"): Fn Loops forever, re-shuffling on every pass — the friendlier name for randomPermuteLoop, with identical behaviour. Each cycle plays all the material once, in a fresh order derived from seed and the cycle number.
fun List<Fn>.shuffleLoop(seed: Long = 0L, overlap: Fn, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "shuffleLoop"): Fn Loops forever, re-shuffling on every pass, with a signal-valued overlap — an alias of randomPermuteLoop with identical behaviour.
fun List<Fn>.palindrome(overlap: Number = 0.0, includeEndsTwice: Boolean = false, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "palindrome"): Fn Plays the list forwards and then backwards, once — an out-and-back gesture rather than a loop.
fun List<Fn>.palindrome(overlap: Fn, includeEndsTwice: Boolean = false, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "palindrome"): Fn Plays the list forwards then backwards once, with a signal-valued overlap. The turning point — the last item — is not repeated unless includeEndsTwice is set, so [a, b, c] gives a b c b a, and lists of one or two items always double their ends.
fun List<Fn>.roundRobin(slot: Number, overlap: Number = 0.0, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "roundRobin"): Fn Cycles through the list in order, one item per fixed-length slot, forever — the deterministic counterpart to randomSequence.
fun List<Fn>.roundRobin(slot: Fn, overlap: Fn = param(0.0), domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "roundRobin"): Fn Cycles through the list in order, one item per slot, forever, with slot and overlap as signals — so the rotation's pulse can itself be modulated. Every item is time-scaled to the current slot length; anything before the domain's lower bound reads outside.
fun List<Fn>.stack(edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "stack"): Fn Sums every function in the list at each x — layering rather than sequencing, and the list form of a + b + c.
fun List<Fn>.average(edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "average"): Fn The mean of every function in the list at each x — stack divided by the list size, so layering many signals keeps the level of one.
fun List<Fn>.select(selector: Fn, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "select"): Fn Switches between the functions: at each x, selector names an index and that member alone is heard.
fun List<Fn>.morph(selector: Fn, edge: EdgeMode = EdgeMode.ZERO, outside: Double = 0.0, name: String = "morph"): Fn Crossfades along the list: selector gives a fractional index, and the two neighbouring members are blended by its fractional part with a smootherstep ease — the continuous counterpart to select.
data class FnTransform(val name: String, private val applyFn: (Fn) -> Fn) A reusable function-to-function operation: a transform you can name, hold on to, compose with others, and then apply to any number of sources.
infix fun Fn.over(domain: Domain): Fn Relabels the function's domain to domain without touching its values, exactly withDomain. To rescale the function into a new interval use stretch.
infix fun Fn.over(range: ClosedRange<*>): Fn f over (0.0..2.0) — relabels the function's domain without touching its values, exactly withDomain. To rescale the function into a new interval use stretch.
infix fun Fn.stretch(domain: Domain): Fn f stretch (0.0..2.0) — replays this function's whole current domain across the target interval, rescaling time rather than relabelling it. Requires both domains to be finite.
infix fun Fn.stretch(range: ClosedRange<*>): Fn f stretch (0.0..2.0) — replays this function's whole current domain across the target interval, rescaling time rather than relabelling it. Requires both domains to be finite.
infix fun Fn.through(transform: FnTransform): Fn f through gain(0.5) — applies an FnTransform, for when the infix word reads better than the slash.
infix fun Fn.pipe(transform: FnTransform): Fn f pipe gain(0.5) — applies an FnTransform; a synonym of through.
operator fun Fn.div(transform: FnTransform): Fn f / gain(0.5) — applies an FnTransform to this function.
val all: FnTransform A transform that widens a function's domain to the whole real line, so it stops constraining anything it is combined with. Values are untouched.
fun stretch(to: Domain, from: Domain? = null, shape: StretchShape = StretchShape.LINEAR): FnTransform A reusable transform that replays a source's whole domain across to, rescaling time — the FnTransform form of Fn.stretch.
fun stretch(to: ClosedRange<*>, from: Domain? = null, shape: StretchShape = StretchShape.LINEAR): FnTransform A reusable transform replaying a source's domain across the given range, rescaling time. Both domains must be finite or it throws.
fun stretchSmooth(to: ClosedRange<*>, from: Domain? = null): FnTransform+1 overload · also Domain stretch with a smoothstep pacing: the endpoints still line up, but the source is played slowly at the ends and quickly through the middle.
fun stretchSmoother(to: ClosedRange<*>, from: Domain? = null): FnTransform+1 overload · also Domain stretch with a smootherstep pacing — like stretchSmooth but with zero acceleration as well as zero velocity at the ends.
fun stretchBy(to: Domain, warp: Fn, from: Domain? = null): FnTransform stretch with the pacing given by a function rather than a named shape: warp maps normalized progress through the target onto normalized progress through the source.
fun stretchBy(to: ClosedRange<*>, warp: Fn, from: Domain? = null): FnTransform stretch onto the given range with the pacing given by warp, a function read on 0..1 mapping target progress to source progress.
fun delay(amount: Number): FnTransform A reusable transform that delays a source by amount, positive being later. A constant delay carries the domain with it.
fun delay(amount: Fn): FnTransform A reusable transform delaying a source by amount; a signal-valued amount makes it a time warp rather than a delay, and leaves the domain alone.
fun gain(amount: Number): FnTransform A reusable transform scaling a source's values by amount — f / gain(0.5) is f * 0.5.
fun gain(amount: Fn): FnTransform A reusable transform scaling a source's values by amount; a signal-valued amount is an amplitude envelope. Note that a bounded amount will crop the source's domain.
fun bias(amount: Number): FnTransform A reusable transform adding amount to a source's values — a DC offset.
fun bias(amount: Fn): FnTransform A reusable transform adding amount to a source's values. A signal-valued amount is a drifting offset; a bounded one will crop the source's domain.
fun clipAt(threshold: Number): FnTransform A reusable transform hard-clipping a source symmetrically to ±threshold. Named to avoid colliding with Fn.clip.
fun clipAt(threshold: Fn): FnTransform A reusable transform hard-clipping a source symmetrically to ±threshold, which may itself be a signal.
fun softClipBy(amount: Number): FnTransform A reusable transform saturating a source through a normalized tanh curve; amount is drive, not a ceiling. Named to avoid colliding with Fn.softClip.
fun softClipBy(amount: Fn): FnTransform A reusable transform saturating a source through a normalized tanh curve, with a possibly signal-valued drive amount.
fun hold(step: Number): FnTransform A reusable sample-and-hold: reads the source only every step and holds each value until the next. Stateless, so it stays freely sampleable.
fun hold(step: Fn): FnTransform A reusable sample-and-hold on a grid of step, which may itself be a signal. Stateless, so it stays freely sampleable.
fun faster(factor: Number = 2.0, about: Number = 0.0): FnTransform+1 overload · also Fn Time-scale transforms use musical language: faster(2.0) samples the source twice as quickly, while slower(2.0) stretches it by two.
fun slower(factor: Number = 2.0, about: Number = 0.0): FnTransform+1 overload · also Fn
fun named(name: String): FnTransform
fun lowPass(window: Number, samples: Int = 64): FnTransform+1 overload · also Fn
fun highPass(window: Number, samples: Int = 64): FnTransform+1 overload · also Fn
fun allPass(delay: Number, feedback: Number = 0.5, repeats: Int = 3): FnTransform+2 overloads · also Any, Fn A stateless phase color, not a recursive DSP all-pass. That distinction is intentional here: an Fn should stay pure and freely sampleable.
fun turbulence(amount: Number, frequency: Number = 8.0, octaves: Int = 4, seed: Long = 0L): FnTransform+3 overloads · also Fn
fun gateHold(gate: Fn, step: Fn, threshold: Fn = param(0.5), lookBack: Fn = param(1.0)): FnTransform+2 overloads · also Number, Any Stateless sample-and-hold driven by an external gate. Because this is a pure function, it searches backward for the latest open gate rather than storing latch state between calls.
fun crush(bits: Int): FnTransform
fun fold(threshold: Number = 1.0): FnTransform+1 overload · also Fn Waveshaping / distortion transforms — the value-domain (west-coast) counterparts to the time-domain hold / crush pair. Each is a thin factory over a pure value shaper (see Fn.fold, Fn.rectify, Fn.abs, Fn.quantize) so it composes in through / pipe / / chains and surfaces in the type-aware infix completion. They shape the signal's VALUE, distinct from hold/stepHold which clock its time.
fun rectify(mode: RectifyMode = RectifyMode.FULL): FnTransform
fun quantize(levels: Int): FnTransform
fun chebyshev(order: Int = 2): FnTransform+2 overloads · also Number, Fn
fun copyPaste(from: ClosedRange<*>, to: ClosedRange<*>, stretch: Boolean = true, edge: EdgeMode = EdgeMode.CLAMP): FnTransform+1 overload · also Domain
fun mirror(about: Number? = null): FnTransform+1 overload · also Fn
fun repeatEvery(period: Number, origin: Number = 0.0, domain: Domain = Domain.ALL): FnTransform+5 overloads · also Any, ClosedRange<*>, Fn
fun mirrorEvery(period: Number, origin: Number = 0.0, domain: Domain = Domain.ALL): FnTransform+5 overloads · also Any, ClosedRange<*>, Fn
fun randomRepeat(period: Number, seed: Long = 0L, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), sourceDomain: Domain? = null, edge: EdgeMode = EdgeMode.CLAMP): FnTransform+3 overloads · also ClosedRange<*>, Fn
fun randomCopyPaste(grain: Number, seed: Long = 0L, domain: Domain = Domain.of(0.0, Double.POSITIVE_INFINITY), sourceDomain: Domain? = null, density: Number = 1.0, edge: EdgeMode = EdgeMode.CLAMP, outside: Double = 0.0): FnTransform+5 overloads · also ClosedRange<*>, Any, Fn
infix fun Fn.then(next: Fn): Fn a then b — plays this function and then next, butted end to end with no crossfade.
data class OverlapStart(val first: Fn, val amount: Fn) The half-finished middle of a overlapping 0.2 with b — it carries the first function and the overlap until the with arrives.
infix fun Fn.overlapping(amount: Number): OverlapStart a overlapping 0.2 with b — begins a crossfaded join; the overlap is in x units, and nothing happens until with supplies the second function.
infix fun Fn.overlapping(amount: Fn): OverlapStart a overlapping f with b — begins a crossfaded join with a signal-valued overlap, sampled around the join itself. Nothing happens until with supplies the second function.
fun Fn.overlapping(amount: Number, next: Fn): Fn Plays this function then next, their join crossfaded over amount x units — the call-syntax form of a overlapping amount with next.
fun Fn.overlapping(amount: Fn, next: Fn): Fn Plays this function then next with a signal-valued crossfade amount — the call-syntax form of a overlapping amount with next.
fun Fn.overlapWith(next: Fn, amount: Number, blend: ChunkBlend = ChunkBlend.REPLACE, window: Fn = hannWindow(Domain.DEFAULT)): Fn Plays this function then next, with next starting amount units early so the two crossfade under a Hann window.
fun Fn.overlapWith(next: Fn, amount: Fn, blend: ChunkBlend = ChunkBlend.REPLACE, window: Fn = hannWindow(Domain.DEFAULT)): Fn Plays this function then next starting amount units early, crossfading under a Hann window. With a signal-valued amount the overlap actually used is sampled around the join itself. blend decides how the overlapping region combines.
fun Fn.toFLine(sampleDomain: Domain = domain, samples: Int = 256, includeEnd: Boolean = true, xScale: Double = 1.0, yScale: Double = 1.0, xOffset: Double = 0.0, yOffset: Double = 0.0): FLine Samples this function into a drawable FLine polyline: x becomes xOffset + x * xScale and the value becomes yOffset + value * yScale.
fun Fn.toFLine(sampleDomain: ClosedRange<*>, samples: Int = 256, includeEnd: Boolean = true, xScale: Double = 1.0, yScale: Double = 1.0, xOffset: Double = 0.0, yOffset: Double = 0.0): FLine Samples this function over the given range into a drawable FLine, mapping x through xOffset + x * xScale and the value through yOffset + value * yScale. No y flip is applied — negate yScale for a y-down screen space.
operator fun Fn.plus(other: Fn): Fn Adds two functions pointwise — mixing, in signal terms.
operator fun Fn.minus(other: Fn): Fn Subtracts other from this function pointwise. The result's domain is the intersection of the two.
operator fun Fn.times(other: Fn): Fn Multiplies two functions pointwise — windowing, enveloping and ring modulation are all this operator. The result's domain is the intersection of the two.
operator fun Fn.div(other: Fn): Fn Divides this function by other pointwise. The result's domain is the intersection of the two.
operator fun Fn.plus(value: Number): Fn Adds a constant to every value — a DC offset. The number becomes an unbounded parameter signal, so this never shrinks the domain.
operator fun Fn.minus(value: Number): Fn Subtracts a constant from every value. The number becomes an unbounded parameter signal, so this never shrinks the domain.
operator fun Fn.times(value: Number): Fn Scales every value by a constant — gain. The number becomes an unbounded parameter signal, so this never shrinks the domain.
operator fun Fn.div(value: Number): Fn Divides every value by a constant. The number becomes an unbounded parameter signal, so this never shrinks the domain.
operator fun Number.plus(f: Fn): Fn 2.0 + f — a constant plus a function, which is the same as f + 2.0.
operator fun Number.minus(f: Fn): Fn 1.0 - f — a constant minus a function, in that order (not the same as f - 1.0), which is how an inverted envelope is written.
operator fun Number.times(f: Fn): Fn 2.0 * f — a constant times a function, which is the same as f * 2.0.
operator fun Number.div(f: Fn): Fn 1.0 / f — a constant divided by a function, in that order; the way to write a reciprocal. Unguarded where f is zero.
fun Fn.mapY(newName: String? = name, transform: (Double) -> Double): Fn Applies an arbitrary lambda to this function's value, leaving its domain alone — the general-purpose waveshaper.
fun Fn.mapX(newName: String? = name, newDomain: Domain = domain, transform: (Double) -> Double): Fn Applies an arbitrary lambda to the coordinate before evaluating — the general-purpose time warp, the counterpart to mapY.
fun Fn.stretch(to: ClosedRange<*>, from: Domain = domain, shape: StretchShape = StretchShape.LINEAR): Fn+1 overload · also Domain Remap this function's existing coordinate domain onto to.
fun Fn.stretchFrom(from: ClosedRange<*>, to: ClosedRange<*>, shape: StretchShape = StretchShape.LINEAR): Fn+1 overload · also Domain
fun Fn.stretchSmooth(to: ClosedRange<*>, from: Domain = domain): Fn+1 overload · also Domain
fun Fn.stretchSmoother(to: ClosedRange<*>, from: Domain = domain): Fn+1 overload · also Domain
fun Fn.stretchBy(to: ClosedRange<*>, warp: Fn, from: Domain = domain): Fn+1 overload · also Domain stretch onto to with the pacing given by warp rather than a named StretchShape — see the Functions.stretchBy transform for what a warp is and how it is read.
fun Fn.stretchBy(to: ClosedRange<*>, from: Domain = domain, warp: (Double) -> Double): Fn+1 overload · also Domain
fun Fn.shift(dx: Fn = param(0.0)): Fn Slides this function along x by dx: positive values move it later (f.shift(2.0) evaluates f(x - 2)).
fun Fn.shift(dx: Number): Fn Slides this function along x by dx, positive being later. A constant shift moves the domain with it.
fun Fn.scaleX(scale: Fn = param(1.0), about: Fn = param(0.0)): Fn Scales this function along x about the point about — scaleX(2.0) makes it twice as wide, and therefore half as fast.
fun Fn.scaleX(scale: Number, about: Number = 0.0): Fn Scales this function along x about about — bigger is wider and slower. about defaults to the origin, not the function's centre; a scale of zero collapses to the value at about.
fun Fn.scaleX(scale: Any, about: Any = 0.0): Fn Scales this function along x about about, taking either argument as a number or an Fn and throwing for anything else. about defaults to the origin.
fun Fn.faster(factor: Number = 2.0, about: Number = 0.0): Fn Plays this function factor times faster about about — faster(2.0) runs at double speed. The factor's magnitude is used and floored just above zero, so it can never invert or stall.
fun Fn.faster(factor: Any, about: Any = 0.0): Fn Plays this function factor times faster about about, taking either argument as a number or an Fn and throwing for anything else.
fun Fn.faster(factor: Fn, about: Fn = param(0.0)): Fn Plays this function factor times faster about about — the musician's spelling of scaleX(1 / factor).
fun Fn.slower(factor: Number = 2.0, about: Number = 0.0): Fn Plays this function factor times slower about about — slower(2.0) stretches it by two. The factor's magnitude is used and floored just above zero.
fun Fn.slower(factor: Any, about: Any = 0.0): Fn Plays this function factor times slower about about, taking either argument as a number or an Fn and throwing for anything else.
fun Fn.slower(factor: Fn, about: Fn = param(0.0)): Fn Plays this function factor times slower about about — the musician's spelling of scaleX(factor), and the inverse of faster.
fun Fn.scaleY(scale: Fn = param(1.0)): Fn Scales this function's values by scale — gain. Identical to f * scale, spelled as a method for chaining; a signal-valued scale is an amplitude envelope.
fun Fn.scaleY(scale: Number): Fn Scales this function's values by scale — gain. The same as f * scale, spelled as a method for chaining.
fun Fn.offsetY(offset: Fn = param(0.0)): Fn Shifts this function's values by offset — a DC bias, moving the signal up or down without touching its timing. Identical to f + offset, spelled as a method for chaining.
fun Fn.offsetY(offset: Number): Fn Shifts this function's values by offset — a DC bias. The same as f + offset, spelled as a method for chaining.
fun Fn.reverse(about: Fn = param(domain.center)): Fn Plays this function backwards, mirrored about about — which defaults to the centre of its own domain, so a bounded function reverses in place.
fun Fn.reverse(about: Number): Fn Plays this function backwards, mirrored about the coordinate about. Unlike the no-argument form there is no reliance on a finite domain centre.
fun Fn.flip(): Fn Negates every value, turning the signal upside down. This flips it in value; reverse flips it in time. Differentiable, with derivative -1 everywhere.
fun Fn.abs(): Fn The absolute value of the signal — full-wave rectification, folding the negative half upward. The same shape as rectify(FULL), and like it a first-class, rewritable graph node.
fun Fn.floor(): Fn The greatest integer at or below every value — a staircase.
fun Fn.sign(): Fn The sign of the signal: -1, 0 or +1. A hard three-level comparator — everything about the magnitude is discarded.
fun Fn.atan(): Fn The arc tangent of every value, in radians, in (-pi/2, pi/2).
fun Fn.atan2(x: Fn = param(1.0)): Fn The quadrant-correct angle of the vector whose y component is this signal and whose x component is x — atan2(this, x), in radians, in [-pi, pi].
fun Fn.atan2(x: Number): Fn The angle of the vector (x, this), in radians. See the Fn overload.
fun Fn.hypot(y: Fn = param(0.0)): Fn The length of the vector whose components are this signal and y — sqrt(this² + y²), computed as Math.hypot so it neither overflows for large components nor underflows for tiny ones.
fun Fn.hypot(y: Number): Fn The length of the vector (this, y). See the Fn overload.
fun Fn.pow(exponent: Fn = param(1.0)): Fn Raises every value to the power exponent, which may itself be a signal.
fun Fn.pow(exponent: Number): Fn Raises every value to the power exponent. Unguarded, so negative values under fractional exponents give NaN.
fun Fn.exp(): Fn e to the power of every value. Grows fast: a signal reaching 10 comes out above 22000, so expect to scale afterwards.
fun Fn.log(base: Fn = param(Math.E)): Fn The logarithm of every value, in base base (natural by default), computed as ln(v) / ln(base).
fun Fn.log(base: Number): Fn The logarithm of every value in base base. Zero gives negative infinity and negative values NaN.
fun Fn.clamp(min: Fn = param(-1.0), max: Fn = param(1.0)): Fn Holds every value inside min .. max, hard — an asymmetric limiter, defaulting to the -1 .. 1 range the rest of the shaping family assumes.
fun Fn.clamp(min: Number, max: Number): Fn Holds every value inside min .. max, hard-clipped at both bounds. An inverted range throws rather than being sorted.
fun Fn.clamp(min: Any, max: Any): Fn Holds every value inside min .. max, taking either bound as a number or an Fn and throwing for anything else.
fun Fn.clip(threshold: Fn = param(1.0)): Fn Hard-clips the signal symmetrically to ±threshold — clamp for the common case where the limit is the same either side.
fun Fn.clip(threshold: Number): Fn Hard-clips the signal symmetrically to ±threshold, with a corner rather than a knee.
fun Fn.rectify(mode: RectifyMode = RectifyMode.HALF): Fn Rectifies the signal: half-wave clamps the negative excursion to zero, full-wave reflects it upward (\|x\|).
fun Fn.softClip(amount: Fn = param(1.0)): Fn Saturates the signal through a tanh curve, normalized so that an input of ±1 still comes out at ±1 — a soft knee where clip has a corner.
fun Fn.softClip(amount: Number): Fn Saturates the signal through a normalized tanh curve; amount is drive rather than a threshold, and ±1 in still gives ±1 out. An amount of zero collapses the signal to zero.
fun Fn.fold(threshold: Fn = param(1.0)): Fn A west-coast wavefolder: passes the signal through untouched while it stays inside ±threshold, and reflects it back each time it crosses — so pushing harder adds folds rather than flattening the peaks.
fun Fn.fold(threshold: Number): Fn A wavefolder: the signal passes through inside ±threshold and reflects back each time it crosses. Drive it with gain rather than by lowering the threshold; a threshold of zero or less gives the constant zero.
fun Fn.chebyshev(order: Fn = param(2.0)): Fn The harmonic-generating Chebyshev waveshaper: pushing a unit-amplitude cosine through order n yields the n-th harmonic.
fun Fn.chebyshev(order: Int): Fn The Chebyshev waveshaper of whole-number order: a unit cosine in gives that harmonic out. The drive is clamped to ±1 first.
fun Fn.chebyshev(order: Number): Fn The Chebyshev waveshaper of order, which need not be a whole number — the shaper is continuous in the order. The drive is clamped to ±1 first.
fun Fn.wrapY(min: Fn = param(-1.0), max: Fn = param(1.0)): Fn Wraps every value into min .. max by repetition instead of clipping it — the value-domain sibling of Domain.wrap.
fun Fn.wrapY(min: Number, max: Number): Fn Wraps every value into min .. max by repetition rather than clipping — discontinuous by design. The bounds are sorted, so their order does not matter.
fun Fn.wrapY(min: Any, max: Any): Fn Wraps every value into min .. max by repetition, taking either bound as a number or an Fn and throwing for anything else.
fun Fn.withEdge(edge: EdgeMode, outside: Fn = param(0.0)): Fn Bakes an out-of-domain policy into the function itself, so plain Fn.eval behaves the way Fn.evalSafe would have with these arguments.
fun Fn.withEdge(edge: EdgeMode, outside: Number): Fn Bakes an out-of-domain policy into the function, so plain evaluation clamps, wraps, mirrors or returns outside without the caller having to ask.
fun Fn.quantize(levels: Fn): Fn Snaps the signal to levels evenly-spaced steps — a staircase quantiser.
fun Fn.quantize(levels: Int): Fn Snaps the signal to levels evenly-spaced steps across -1 .. 1. Fewer than two levels gives the constant zero; values beyond ±1 land on the end steps.
fun Fn.bitCrush(bits: Fn): Fn Snaps the signal to 2^bits - 1 steps across -1 .. 1 — quantize counted in bits, for when the digital-audio idiom is what you mean.
fun Fn.bitCrush(bits: Int): Fn Snaps the signal to 2^bits - 1 steps across -1 .. 1. Zero bits or fewer gives the constant zero.
fun Fn.mix(other: Fn, amount: Fn = param(0.5)): Fn Blends this function with other by amount — 0 is all of this one, 1 is all of other, 0.5 an even mix.
fun Fn.mix(other: Fn, amount: Number): Fn Blends this function with other by amount: 0 is all of this one, 1 all of other. Not clamped, so values outside 0..1 extrapolate.
fun Fn.crossfade(other: Fn, center: Fn = param(0.0), width: Fn = param(0.0)): Fn Hands over from this function to other at a place along x: this one before center, other after, with a smootherstep transition width wide straddling the centre.
fun Fn.crossfade(other: Fn, center: Number, width: Number): Fn Hands over from this function to other at center, easing across a transition width wide. A width of zero or less is a hard cut.
fun Fn.crossfade(other: Fn, center: Any, width: Any): Fn Hands over from this function to other at center over a transition width wide, taking either as a number or an Fn and throwing for anything else.
fun Fn.cut(range: Domain, outside: Fn = param(0.0)): Fn Gates the function to range: inside it (both ends included) the original values, outside it outside.
fun Fn.cut(range: Domain, outside: Number): Fn Gates the function to range, reading outside beyond it. The declared domain is unchanged.
fun Fn.cut(range: ClosedRange<*>, outside: Fn = param(0.0)): Fn Gates the function to range, reading outside beyond it. The declared domain is unchanged.
fun Fn.cut(range: ClosedRange<*>, outside: Number): Fn Gates the function to range, reading outside beyond it. The declared domain is unchanged.
fun Fn.trim(range: ClosedRange<*>, outside: Fn = param(0.0)): Fn+3 overloads · also Domain, Number Gates the function to range, reading outside beyond it — a synonym of cut. The declared domain is unchanged.
fun Fn.slice(range: ClosedRange<*>, outside: Fn = param(0.0)): Fn+3 overloads · also Domain, Number Gates the function to range, reading outside beyond it — a synonym of cut. The declared domain is unchanged.
fun Fn.replace(range: Domain, replacement: Fn): Fn Substitutes replacement for this function over range, keeping the original everywhere else — a patch, where cut is a gate.
fun Fn.replace(range: ClosedRange<*>, replacement: Fn): Fn Substitutes replacement for this function over range and keeps the original elsewhere. The result's domain is the union of the two, and the seams are not faded.
fun Fn.window(window: Fn): Fn Multiplies this function by window — spelled as a method to make the intent ("apply an envelope") obvious. Exactly this * window, which means the result's domain is the intersection of the two, so a bounded window crops an unbounded signal.
fun Fn.amplitudeMod(mod: Fn, depth: Fn = param(1.0), bias: Fn = param(1.0)): Fn Amplitude modulation: scales this function by bias + depth * mod.
fun Fn.amplitudeMod(mod: Fn, depth: Number, bias: Number = 1.0): Fn Amplitude modulation: scales this function by bias + depth * mod. The default bias of 1 keeps the carrier present, making this tremolo rather than ring modulation.
fun Fn.amplitudeMod(mod: Fn, depth: Any, bias: Any = 1.0): Fn Amplitude modulation by bias + depth * mod, taking depth and bias as numbers or Fns and throwing for anything else.
fun Fn.ringMod(mod: Fn, depth: Fn = param(1.0)): Fn Ring modulation: multiplies this function by depth * mod, with no bias term — so wherever the modulator crosses zero the carrier disappears entirely.
fun Fn.ringMod(mod: Fn, depth: Number): Fn Ring modulation: multiplies this function by depth * mod with no bias, so the carrier vanishes wherever the modulator crosses zero.
fun Fn.warp(mod: Fn, depth: Fn = param(1.0)): Fn Warps time by the modulator: evaluates this function at x + depth * mod(x) instead of at x.
fun Fn.warp(mod: Fn, depth: Number): Fn Warps time by the modulator, evaluating this function at x + depth * mod(x) — vibrato and phase modulation in one operator.
fun Fn.quantizeX(step: Fn = param(1.0), mode: EdgeMode = EdgeMode.CLAMP): Fn Quantises the coordinate to a grid of step, so the function is only ever read at grid positions — a staircase in time rather than in value.
fun Fn.quantizeX(step: Number, mode: EdgeMode = EdgeMode.CLAMP): Fn Quantises the coordinate to a grid of step, flooring to the grid so the function reads as a staircase in time. A step of zero passes x through unchanged.
fun Fn.sampleHold(step: Fn = param(1.0), offset: Fn = param(0.0)): Fn Sample-and-hold: reads the function only at multiples of step (offset by offset) and holds each value until the next one — the classic stepped-random or arpeggio effect.
fun Fn.sampleHold(step: Number, offset: Number = 0.0): Fn Sample-and-hold: reads the function only at multiples of step from offset and holds each value until the next. Stateless; a step of zero is a no-op.
fun Fn.sampleHold(step: Any, offset: Any = 0.0): Fn Sample-and-hold on a grid of step from offset, taking either as a number or an Fn and throwing for anything else.
fun Fn.phaseMod(mod: Fn, depth: Fn = param(1.0)): Fn Phase modulation — displaces the read position by depth * mod(x). Exactly warp, under the name the synth literature uses; there is no separate integration step here.
fun Fn.phaseMod(mod: Fn, depth: Number): Fn Phase modulation — displaces the read position by depth * mod(x). Exactly warp, under a synth-flavoured name.
fun Fn.frequencyMod(mod: Fn, depth: Fn = param(1.0)): Fn Frequency modulation — displaces the read position by depth * mod(x).
fun Fn.frequencyMod(mod: Fn, depth: Number): Fn Frequency modulation — displaces the read position by depth * mod(x). The same operation as warp and phaseMod, not an integrating FM.
fun Fn.saturate(amount: Fn = param(1.0)): Fn A gentle saturator, v / (1 + \|v * amount\|) — compresses large values toward ±1 without ever quite reaching a ceiling, so nothing is ever truly flat-topped.
fun Fn.saturate(amount: Number): Fn A gentle saturator, v / (1 + \|v * amount\|): compresses toward ±1 without a hard ceiling, and without normalizing, so it also loses level. An amount of zero gives zero.
fun Fn.jitterX(amount: Fn = param(0.0), seed: Long = 0L): Fn Displaces the read position by a random amount of up to half amount either way — a deterministic wobble in time.
fun Fn.jitterX(amount: Number, seed: Long = 0L): Fn Displaces the read position by up to half amount either way, hashed from seed and floor(x) — so the displacement is constant within each unit band of x.
fun Fn.repeat(period: Fn = param(1.0), origin: Fn = param(domain.finiteSweep().min), domainOverride: Domain = Domain.ALL): Fn Loops this function every period from origin, forever — a periodic tiling of whatever the function does in one period's worth of x.
fun Fn.repeat(period: Number, origin: Number = domain.finiteSweep().min, domainOverride: Domain = Domain.ALL): Fn Loops this function every period from origin, forever, with hard cuts at the joins. The result claims domainOverride — the whole line by default.
fun Fn.repeat(period: Any, origin: Any = domain.finiteSweep().min, domainOverride: Domain = Domain.ALL): Fn Loops this function every period from origin, taking either as a number or an Fn and throwing for anything else.
fun Fn.mirrorRepeat(period: Fn = param(1.0), origin: Fn = param(domain.finiteSweep().min), domainOverride: Domain = Domain.ALL): Fn Tiles this function every period from origin, alternating forwards and backwards — so the result is continuous at the joins where plain repeat would cut.
fun Fn.mirrorRepeat(period: Number, origin: Number = domain.finiteSweep().min, domainOverride: Domain = Domain.ALL): Fn Tiles this function every period from origin, alternating forwards and backwards so the joins are continuous. The full pattern is twice the period long.
fun Fn.mirrorRepeat(period: Any, origin: Any = domain.finiteSweep().min, domainOverride: Domain = Domain.ALL): Fn Tiles this function every period from origin with alternating direction, taking either as a number or an Fn and throwing for anything else.
fun Fn.tile(count: Int, period: Fn = param(domain.finiteSweep().length), origin: Fn = param(domain.finiteSweep().min)): Fn Repeats this function exactly count times from origin and is zero outside that span — the finite counterpart to repeat.
fun Fn.tile(count: Int, period: Number, origin: Number = domain.finiteSweep().min): Fn Repeats this function count times every period from origin, and is zero outside that span. A count of zero or less gives the constant zero.
fun Fn.tile(count: Int, period: Any, origin: Any = domain.finiteSweep().min): Fn Repeats this function count times from origin and is zero outside, taking period and origin as numbers or Fns and throwing for anything else.
fun Fn.pad(left: Fn = param(0.0), right: Fn = param(0.0), value: Fn = param(0.0)): Fn Substitutes value for the function beyond left units below its domain and right units above it — a border of silence around the material, with the original passed through in between.
fun Fn.pad(left: Number, right: Number = 0.0, value: Number = 0.0): Fn Substitutes value beyond left units below the domain and right above it; inside that padded band the original evaluator still applies.
fun Fn.pad(left: Any, right: Any = 0.0, value: Any = 0.0): Fn Substitutes value beyond the padded band, taking each argument as a number or an Fn and throwing for anything else.
fun Fn.delay(amount: Fn = param(0.0)): Fn Delays the function by amount, positive being later — exactly shift, under the name that suits time-series and audio. A constant delay moves the domain with it.
fun Fn.delay(amount: Number): Fn Delays the function by amount, positive being later — exactly shift. A constant delay moves the domain with it.
fun Fn.echo(delay: Fn = param(1.0), decay: Fn = param(0.5), repeats: Int = 4): Fn Sums repeats progressively quieter copies of the function, each delay further on — a feed-forward echo.
fun Fn.echo(delay: Number, decay: Number = 0.5, repeats: Int = 4): Fn Sums repeats progressively quieter copies, each delay further on — a finite feed-forward echo with no feedback or state. A decay above 1 grows instead of fading.
fun Fn.echo(delay: Any, decay: Any = 0.5, repeats: Int = 4): Fn A finite feed-forward echo of repeats copies, taking delay and decay as numbers or Fns and throwing for anything else.
fun Fn.derivative(step: Double = 1e-3): Fn The numerical derivative, by central difference over ±step.
fun Fn.integral(step: Double = 1e-3, origin: Double = domain.finiteSweep().min): Fn The running integral from origin to x, by rectangle summation in steps of step.
fun Fn.convolve(kernel: Fn, samples: Int = 64, kernelDomain: Domain = kernel.domain, normalize: Boolean = false): Fn Convolves this function with kernel, approximated by samples rectangles across the kernel's domain.
fun Fn.correlate(kernel: Fn, samples: Int = 64, kernelDomain: Domain = kernel.domain): Fn Cross-correlates this function with kernel — convolve with the kernel reversed about its own centre, which is the difference between "how does this respond to that" and "how much does this look like that".
fun Fn.movingAverage(window: Fn = param(1.0), samples: Int = 64): Fn A box smoother: the mean of the function over a window-wide span centred on each x.
fun Fn.movingAverage(window: Number, samples: Int = 64): Fn A centred box smoother: the mean over a window-wide span around each x. A window of zero is a no-op; costs samples evaluations per sample.
fun Fn.blur(sigma: Fn = param(1.0), samples: Int = 64): Fn A Gaussian smoother of standard deviation sigma — softer than movingAverage, with no ringing, and the usual choice for taking the edge off a curve.
fun Fn.blur(sigma: Number, samples: Int = 64): Fn A Gaussian smoother of standard deviation sigma, truncated at ±3 sigma and normalized so the level is kept. A sigma of zero is a no-op.
fun hannWindow(domain: Domain = Domain.DEFAULT): Fn The Hann window: a raised cosine reaching zero at both ends of domain and 1 at the centre.
fun hannWindow(domain: ClosedRange<*>): Fn The Hann window over the given range: a raised cosine, zero at both ends and 1 at the centre.
fun hammingWindow(domain: Domain = Domain.DEFAULT): Fn The Hamming window: like hannWindow, but stopping at 0.08 rather than zero at the ends.
fun hammingWindow(domain: ClosedRange<*>): Fn The Hamming window over the given range — a raised cosine that stops at 0.08 rather than zero at the ends.
fun blackmanWindow(domain: Domain = Domain.DEFAULT): Fn The Blackman window: a three-term cosine taper, narrower in the middle than hannWindow and with markedly lower spectral sidelobes.
fun blackmanWindow(domain: ClosedRange<*>): Fn The Blackman window over the given range — a three-term cosine taper reaching zero at both ends, with low spectral sidelobes.
fun gaussianWindow(domain: Domain = Domain.DEFAULT, sigma: Fn = param(0.4)): Fn A Gaussian window centred in domain, with sigma measured as a fraction of the domain rather than in x units.
fun gaussianWindow(domain: Domain = Domain.DEFAULT, sigma: Number): Fn A Gaussian window centred in domain, with sigma as a fraction of the domain's width. It never reaches zero at the ends — use hannWindow if you need it to.
fun gaussianWindow(domain: ClosedRange<*>, sigma: Fn = param(0.4)): Fn A Gaussian window centred in the given range, with sigma as a fraction of its width. It never reaches zero at the ends.
fun gaussianWindow(domain: ClosedRange<*>, sigma: Number): Fn A Gaussian window centred in the given range, with sigma as a fraction of its width. It never reaches zero at the ends.
fun whiteNoise(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), domain: Domain = Domain.DEFAULT): Fn White noise: an independent random value in -amplitude .. amplitude for each cell of width 1 / frequency, with a hard jump between cells.
fun whiteNoise(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, domain: Domain = Domain.DEFAULT): Fn White noise: an independent random value in ±amplitude per cell of width 1 / frequency, jumping between cells. Deterministic in seed and position, so it is reproducible and seekable.
fun whiteNoise(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, domain: ClosedRange<*>): Fn White noise: an independent random value in ±amplitude per cell of width 1 / frequency, jumping between cells. Deterministic in seed and position, so it is reproducible and seekable.
fun whiteNoise(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), domain: ClosedRange<*>): Fn White noise: an independent random value in ±amplitude per cell of width 1 / frequency, jumping between cells. Deterministic in seed and position, so it is reproducible and seekable.
fun whiteNoise(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, domain: Domain = Domain.DEFAULT): Fn White noise per cell of width 1 / frequency, taking frequency and amplitude as numbers or Fns and throwing for anything else.
fun whiteNoise(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, domain: ClosedRange<*>): Fn White noise per cell of width 1 / frequency, taking frequency and amplitude as numbers or Fns and throwing for anything else.
fun valueNoise(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), domain: Domain = Domain.DEFAULT): Fn Smooth value noise: a random value per lattice cell of width 1 / frequency, interpolated between cells with a smootherstep, so the result is continuous and gently wandering rather than jumping.
fun valueNoise(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, domain: Domain = Domain.DEFAULT): Fn Smooth value noise: a random value per lattice cell of width 1 / frequency, interpolated between cells so the result is continuous. Range roughly ±amplitude; deterministic in seed and position.
fun valueNoise(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, domain: ClosedRange<*>): Fn Smooth value noise: a random value per lattice cell of width 1 / frequency, interpolated between cells so the result is continuous. Range roughly ±amplitude; deterministic in seed and position.
fun valueNoise(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), domain: ClosedRange<*>): Fn Smooth value noise: a random value per lattice cell of width 1 / frequency, interpolated between cells so the result is continuous. Range roughly ±amplitude; deterministic in seed and position.
fun valueNoise(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, domain: Domain = Domain.DEFAULT): Fn Smooth value noise on a lattice of width 1 / frequency, taking frequency and amplitude as numbers or Fns and throwing for anything else.
fun valueNoise(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, domain: ClosedRange<*>): Fn Smooth value noise on a lattice of width 1 / frequency, taking frequency and amplitude as numbers or Fns and throwing for anything else.
fun perlinNoise(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), domain: Domain = Domain.DEFAULT): Fn One-dimensional Perlin-style gradient noise: each lattice point of width 1 / frequency carries a random slope rather than a random height, so the curve passes through zero at every lattice point and bulges between them.
fun perlinNoise(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, domain: Domain = Domain.DEFAULT): Fn Perlin-style gradient noise on a lattice of width 1 / frequency: zero at every lattice point, bulging between them, and quieter than valueNoise at the same amplitude.
fun perlinNoise(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, domain: ClosedRange<*>): Fn Perlin-style gradient noise on a lattice of width 1 / frequency: zero at every lattice point, bulging between them, and quieter than valueNoise at the same amplitude.
fun perlinNoise(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), domain: ClosedRange<*>): Fn Perlin-style gradient noise on a lattice of width 1 / frequency: zero at every lattice point, bulging between them, and quieter than valueNoise at the same amplitude.
fun perlinNoise(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, domain: Domain = Domain.DEFAULT): Fn Perlin-style gradient noise on a lattice of width 1 / frequency, taking frequency and amplitude as numbers or Fns and throwing for anything else.
fun perlinNoise(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, domain: ClosedRange<*>): Fn Perlin-style gradient noise on a lattice of width 1 / frequency, taking frequency and amplitude as numbers or Fns and throwing for anything else.
fun fbm(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), octaves: Int = 5, lacunarity: Fn = param(2.0), gain: Fn = param(0.5), domain: Domain = Domain.DEFAULT): Fn Fractal Brownian motion: octaves layers of valueNoise summed, each layer lacunarity times finer and gain times quieter than the last — noise with detail at several scales at once.
fun fbm(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, octaves: Int = 5, lacunarity: Number = 2.0, gain: Number = 0.5, domain: Domain = Domain.DEFAULT): Fn Fractal Brownian motion: octaves layers of valueNoise, each lacunarity times finer and gain times quieter. Normalized, so the range stays near ±amplitude however many octaves you add.
fun fbm(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, octaves: Int = 5, lacunarity: Number = 2.0, gain: Number = 0.5, domain: ClosedRange<*>): Fn Fractal Brownian motion: octaves layers of valueNoise, each lacunarity times finer and gain times quieter. Normalized, so the range stays near ±amplitude however many octaves you add.
fun fbm(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, octaves: Int = 5, lacunarity: Any = 2.0, gain: Any = 0.5, domain: Domain = Domain.DEFAULT): Fn Fractal Brownian motion over octaves layers of valueNoise, taking the numeric parameters as numbers or Fns and throwing for anything else.
fun fbm(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, octaves: Int = 5, lacunarity: Any = 2.0, gain: Any = 0.5, domain: ClosedRange<*>): Fn Fractal Brownian motion over octaves layers of valueNoise, taking the numeric parameters as numbers or Fns and throwing for anything else.
fun fbm(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), octaves: Int = 5, lacunarity: Fn = param(2.0), gain: Fn = param(0.5), domain: ClosedRange<*>): Fn Fractal Brownian motion: octaves layers of valueNoise, each lacunarity times finer and gain times quieter. Normalized, so the range stays near ±amplitude however many octaves you add.
fun fbmPerlin(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), octaves: Int = 5, lacunarity: Fn = param(2.0), gain: Fn = param(0.5), domain: Domain = Domain.DEFAULT): Fn Fractal Brownian motion built from perlinNoise layers instead of valueNoise ones — fbm with a different grain.
fun fbmPerlin(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, octaves: Int = 5, lacunarity: Number = 2.0, gain: Number = 0.5, domain: Domain = Domain.DEFAULT): Fn Fractal Brownian motion built from perlinNoise layers: octaves of them, each lacunarity times finer and gain times quieter. Normalized, so the range stays near ±amplitude.
fun fbmPerlin(seed: Long = 0L, frequency: Number, amplitude: Number = 1.0, octaves: Int = 5, lacunarity: Number = 2.0, gain: Number = 0.5, domain: ClosedRange<*>): Fn Fractal Brownian motion built from perlinNoise layers: octaves of them, each lacunarity times finer and gain times quieter. Normalized, so the range stays near ±amplitude.
fun fbmPerlin(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, octaves: Int = 5, lacunarity: Any = 2.0, gain: Any = 0.5, domain: Domain = Domain.DEFAULT): Fn Fractal Brownian motion over octaves layers of perlinNoise, taking the numeric parameters as numbers or Fns and throwing for anything else.
fun fbmPerlin(seed: Long = 0L, frequency: Any, amplitude: Any = 1.0, octaves: Int = 5, lacunarity: Any = 2.0, gain: Any = 0.5, domain: ClosedRange<*>): Fn Fractal Brownian motion over octaves layers of perlinNoise, taking the numeric parameters as numbers or Fns and throwing for anything else.
fun fbmPerlin(seed: Long = 0L, frequency: Fn = param(1.0), amplitude: Fn = param(1.0), octaves: Int = 5, lacunarity: Fn = param(2.0), gain: Fn = param(0.5), domain: ClosedRange<*>): Fn Fractal Brownian motion built from perlinNoise layers: octaves of them, each lacunarity times finer and gain times quieter. Normalized, so the range stays near ±amplitude.
fun randomWalk(seed: Long = 0L, step: Fn = param(1.0), amplitude: Fn = param(1.0), domain: Domain = Domain.DEFAULT): Fn A random walk: the accumulated sum of one random increment of up to ±amplitude per step of x — Brownian motion, wandering without any tendency to return.
fun randomWalk(seed: Long = 0L, step: Number, amplitude: Number = 1.0, domain: Domain = Domain.DEFAULT): Fn A random walk: the accumulated sum of one increment of up to ±amplitude per step of x. Unbounded, and costs x / step hashes per evaluation — expensive far from zero.
fun randomWalk(seed: Long = 0L, step: Number, amplitude: Number = 1.0, domain: ClosedRange<*>): Fn A random walk: the accumulated sum of one increment of up to ±amplitude per step of x. Unbounded, and costs x / step hashes per evaluation — expensive far from zero.
fun randomWalk(seed: Long = 0L, step: Fn = param(1.0), amplitude: Fn = param(1.0), domain: ClosedRange<*>): Fn A random walk: the accumulated sum of one increment of up to ±amplitude per step of x. Unbounded, and costs x / step hashes per evaluation — expensive far from zero.
fun randomWalk(seed: Long = 0L, step: Any, amplitude: Any = 1.0, domain: Domain = Domain.DEFAULT): Fn A random walk accumulating one increment per step, taking step and amplitude as numbers or Fns and throwing for anything else.
fun randomWalk(seed: Long = 0L, step: Any, amplitude: Any = 1.0, domain: ClosedRange<*>): Fn A random walk accumulating one increment per step, taking step and amplitude as numbers or Fns and throwing for anything else.
data class Sample(val x: Double, val y: Double, val w: Double = 1.0) One (x, y) observation, with an optional weight — the currency of the scattered-data and sampling APIs here.
fun points(vararg pairs: Pair<Double, Double>): List<Sample> Builds a control-point list from x to y pairs, all with weight 1 — the terse way to hand-write input for rbf and friends.
fun Fn.samplesAt(xs: DoubleArray): List<Sample> Observes this function at the given coordinates, as (x, value) Samples. Not domain-checked: each point is a raw evaluation, so coordinates outside the domain may be extrapolated.
fun Fn.autoScale(sampleDomain: Domain = domain, samples: Int = 256, seed: Long = 0L, targetMin: Double = 0.0, targetMax: Double = 1.0, clamp: Boolean = false): Fn Rescales the function so that its observed range lands on targetMin .. targetMax — normalization by measurement.
fun Fn.autoScale(sampleDomain: ClosedRange<*>, samples: Int = 256, seed: Long = 0L, targetMin: Double = 0.0, targetMax: Double = 1.0, clamp: Boolean = false): Fn Rescales the function so its observed range lands on targetMin .. targetMax, measuring over the given range. The range is estimated once from samples probes, so unmeasured peaks can overshoot unless clamp is set.
fun Fn.zNormalize(sampleDomain: Domain = domain, samples: Int = 256, seed: Long = 0L, epsilon: Double = 1e-9): Fn Standardizes the function to zero mean and unit standard deviation — (v - mean) / sd, with both estimated from samples pseudo-random probes at construction.
fun Fn.zNormalize(sampleDomain: ClosedRange<*>, samples: Int = 256, seed: Long = 0L, epsilon: Double = 1e-9): Fn Standardizes the function to zero mean and unit standard deviation, measuring over the given range. A signal whose measured deviation is below epsilon becomes the constant zero.
fun Fn.whitenCdf(sampleDomain: Domain = domain, samples: Int = 2048, bins: Int = 256, seed: Long = 0L, targetMin: Double = 0.0, targetMax: Double = 1.0, clamp: Boolean = true): Fn Histogram equalization: pushes every value through the function's own cumulative distribution, so the output is spread uniformly across targetMin .. targetMax however lopsidedly the input was distributed.
fun Fn.whitenCdf(sampleDomain: ClosedRange<*>, samples: Int = 2048, bins: Int = 256, seed: Long = 0L, targetMin: Double = 0.0, targetMax: Double = 1.0, clamp: Boolean = true): Fn Histogram equalization over the given range: values are pushed through the function's own estimated CDF so the output is spread uniformly across targetMin .. targetMax. Monotone but non-linear, and estimated lazily on first use.
fun Fn.sampleUniform(count: Int, sampleDomain: Domain = domain, includeEnd: Boolean = true): List<Sample> count evenly-spaced observations across sampleDomain, as (x, value) Samples.
fun Fn.sampleUniform(count: Int, sampleDomain: ClosedRange<*>, includeEnd: Boolean = true): List<Sample> count evenly-spaced observations across the given range, as Samples. includeEnd decides whether the last one lands on the upper bound.
fun Fn.sampleJittered(count: Int, sampleDomain: Domain = domain, jitter: Double = 0.25, seed: Long = 0L): List<Sample> count observations, one per equal cell, each displaced from its cell centre by up to half jitter of a cell width — stratified sampling, which covers the domain evenly while avoiding the aliasing a regular grid produces.
fun Fn.sampleJittered(count: Int, sampleDomain: ClosedRange<*>, jitter: Double = 0.25, seed: Long = 0L): List<Sample> count stratified observations across the given range, one per cell, displaced by up to half jitter of a cell width. Deterministic in seed.
fun Fn.sampleRandom(count: Int, sampleDomain: Domain = domain, seed: Long = 0L): List<Sample> count observations at pseudo-random positions across sampleDomain — unstratified, so expect clumps and gaps; sampleJittered is usually the better-behaved choice.
fun Fn.sampleRandom(count: Int, sampleDomain: ClosedRange<*>, seed: Long = 0L): List<Sample> count observations at pseudo-random positions across the given range, unstratified and not sorted by x. Deterministic in seed.
fun Fn.sampleByDensity(count: Int, sampleDomain: Domain = domain, density: Fn, seed: Long = 0L, maxRejects: Int = 10000): List<Sample> Observations concentrated where density is high, by rejection sampling — the way to sample a signal more closely in the regions you care about.
fun Fn.sampleByDensity(count: Int, sampleDomain: ClosedRange<*>, density: Fn, seed: Long = 0L, maxRejects: Int = 10000): List<Sample> Observations concentrated where density is high, by rejection sampling over the given range. May return fewer than count samples, since it gives up after maxRejects attempts.
fun Fn.sampleZeroCrossings(sampleDomain: Domain = domain, step: Double = sampleDomain.finiteSweep().length / 128.0): List<Sample> Finds where the function crosses zero, walking sampleDomain in steps of step and linearly interpolating the crossing between the two straddling samples. Every returned Sample has a y of exactly 0.0.
fun Fn.sampleZeroCrossings(sampleDomain: ClosedRange<*>, step: Double = sampleDomain.toDomain().length / 128.0): List<Sample> Finds sign changes across the given range in steps of step, interpolating each crossing. Tangential touches, and pairs of crossings closer together than one step, are missed.
fun Fn.samplePeaks(count: Int, sampleDomain: Domain = domain, step: Double = sampleDomain.finiteSweep().length / 256.0): List<Sample> Finds the count most prominent local maxima, by three-point comparison on a grid of step.
fun Fn.samplePeaks(count: Int, sampleDomain: ClosedRange<*>, step: Double = sampleDomain.toDomain().length / 256.0): List<Sample> Finds the count most prominent local maxima across the given range, on a grid of step. Maxima only — never troughs — and ranked by absolute value.
val RbfKernels: afield.math.functions.RbfKernels The RbfKernels catalogue, re-exported so RbfKernels.thinPlate resolves inside with(Functions) without an import.
fun RbfInterpolator(points: List<Sample>, kernel: RbfKernel, smoothing: Double = 0.0, base: Fn? = null): afield.math.functions.RbfInterpolator Builds an afield.math.functions.RbfInterpolator from inside a Functions scope, so Sample and Fn need no qualification. Solves its dense system eagerly — see that class for the sizing caveats.
fun rbf(points: List<Sample>, kernel: RbfKernel = RbfKernels.thinPlate, smoothing: Double = 0.0, base: Fn? = null): Fn Fits a smooth curve through scattered control points with radial basis functions — the tool for "make me a function that passes through these", where the points need not be evenly spaced or even sorted.
fun rbfQuadratic(points: List<Sample>, smoothing: Double = 0.0, base: Fn? = null): Fn rbf with the r² kernel — stiffer than thin-plate, and more prone to overshoot away from the data.
fun rbfCubic(points: List<Sample>, smoothing: Double = 0.0, base: Fn? = null): Fn rbf with the r³ kernel — the stiffest of the polynomial family, smooth through the points but wild beyond them.
fun rbfThinPlate(points: List<Sample>, smoothing: Double = 0.0, base: Fn? = null): Fn rbf with the thin-plate kernel — the same as the default, spelled out. The one to reach for first, since it has no shape parameter to mis-scale.
fun Fn.rbf(points: List<Sample>, kernel: RbfKernel = RbfKernels.thinPlate, smoothing: Double = 0.0): Fn Adds an RBF correction to this function so the result passes through points while otherwise following this function's shape — the "nudge this curve through my control points" operation.
fun Fn.rbfQuadratic(points: List<Sample>, smoothing: Double = 0.0): Fn Nudges this function through points with an RBF correction using the r² kernel. See Fn.rbf.
fun Fn.rbfCubic(points: List<Sample>, smoothing: Double = 0.0): Fn Nudges this function through points with an RBF correction using the r³ kernel. See Fn.rbf.
fun Fn.rbfThinPlate(points: List<Sample>, smoothing: Double = 0.0): Fn Nudges this function through points with a thin-plate RBF correction — the default kernel, spelled out. See Fn.rbf.
fun Fn.granular(grainSize: Fn = param(1.0), spacing: Fn = grainSize, jitter: Fn = param(0.0), overlap: Int = 1, seed: Long = 0L, window: Fn = hannWindow(Domain.DEFAULT)): Fn Granular resynthesis: chops the function into windowed grains of grainSize laid down every spacing, and sums them back together.
fun Fn.granular(grainSize: Any, spacing: Any = grainSize, jitter: Any = 0.0, overlap: Int = 1, seed: Long = 0L, window: Fn = hannWindow(Domain.DEFAULT)): Fn Granular resynthesis into windowed grains of grainSize every spacing, taking those and jitter as numbers or Fns and throwing for anything else. overlap bounds how many neighbouring grains can be heard at once.
fun Fn.lowpassStream(alpha: Double, start: Double = domain.finiteSweep().min, step: Double = 1.0, initial: Double = 0.0): Sequence<Double> A stateful one-pole low-pass over the sampled function, as an infinite lazy sequence: y += alpha * (x - y).
fun Fn.highpassStream(alpha: Double, start: Double = domain.finiteSweep().min, step: Double = 1.0, initial: Double = 0.0): Sequence<Double> A stateful one-pole high-pass over the sampled function, as an infinite lazy sequence: y = alpha * (y + x - xPrev).
fun piecewise(default: Fn = zero(), vararg pieces: Pair<Domain, Fn>): Fn Assembles a function from explicitly-placed pieces: at each x the first piece whose range contains it wins, and default covers everything no piece claims.
data class DemoStats(val min: Double, val max: Double, val mean: Double, val rms: Double)
data class DemoSeries(val name: String, val domain: Domain, val xs: DoubleArray, val ys: DoubleArray, val stats: DemoStats)
data class DemoResult(val series: List<DemoSeries>, val notes: List<String>)
fun piecewiseLinear(points: List<Keyframe>): Fn Piecewise-linear interpolation through the keyframe positions. Outside the keyframe range the first/last value is held.
fun piecewiseLinear(points: List<Vec2>): Fn Overload accepting Vec2 pairs instead of Keyframe objects.
fun catmullRomSpline(points: List<Keyframe>, alpha: Double = 0.5): Fn Catmull-Rom spline through keyframe positions.
fun monotoneCubic(points: List<Keyframe>): Fn Monotone cubic (Fritsch-Carlson) interpolation.
fun hermiteFromHandles(points: List<Keyframe>): Fn Cubic Hermite interpolation with user-specified tangent slopes.
fun bSplineQuadratic(points: List<Keyframe>): Fn Quadratic B-spline approximation of the keyframe curve.
fun stepHold(points: List<Keyframe>): Fn Step / sample-and-hold through keyframes. Holds each keyframe value until the next keyframe time.
fun nearestValue(points: List<Keyframe>): Fn Nearest-value interpolation — returns the value of the closest keyframe.
fun cosineInterp(points: List<Keyframe>): Fn Cosine interpolation through keyframes.
fun smoothStepInterp(points: List<Keyframe>): Fn Smoothstep (Hermite) interpolation through keyframes.
fun smootherStepInterp(points: List<Keyframe>): Fn Smootherstep (Ken Perlin's 6t⁵ − 15t⁴ + 10t³) interpolation.
fun gaussianSmooth(source: Fn, sigma: Double = 0.1, taps: Int = 31): Fn Gaussian-smoothed version of a source Fn.
fun differentiate(source: Fn, h: Double = 1e-4): Fn Numerically differentiate a source Fn (central differences).
fun fromKeyframeEditor(keyframes: List<Keyframe>, sampleCount: Int = 512, interpolation: Interpolation = Interpolation.LINEAR): Fn Produces a Fn that exactly matches the keyframe editor's native evaluation (hold / linear / rational-bezier), by constructing a temporary KeyframeEvaluator and sampling it.
fun resample(source: Fn, sampleCount: Int = 256, interpolation: Interpolation = Interpolation.CUBIC): Fn Samples source at sampleCount uniform points across its own domain and returns a table-backed Fn that reconstructs between them with interpolation.
fun demo(sampleCount: Int = 512, seed: Long = 1L, domain: Domain = Domain.DEFAULT, printSummary: Boolean = true): DemoResult
fun main(args: Array<String>)

PatternRewriteSmokeTest.kt

Smoke for the pattern-rewrite layer (PatternRewrite), a structural source-to-source rewriter that lets a box author its own rewrite { ... } rules on top of the PSI macro pass.

Most cases are black-box: they feed a source string (rule plus code) through PatternRewrite.expand with the standalone FieldKtParser as the PSI environment and assert on the emitted text, with no REPL. The unquote and fragment-rule cases drive a real KotlinInterface2 through applyMacros. The cases cover match and no-match, non-linearity, paren-modulo, the precedence hazard, the quote barrier, first-match-wins declaration order, guards, variadic holes, unquote, ambient and local rules, and termination of a non-converging rule.

src/jvmMain/kotlin/afield/math/functions/PatternRewriteSmokeTest.kt — read the source

PatternRewriteSmokeTest

object PatternRewriteSmokeTest

Declaration Summary
fun runTests(): Boolean
fun main(args: Array<String>)

RbfField.kt

Scattered-data interpolation over two or more dimensions with radial basis functions: RbfField and its rbfField builder, the multi-dimensional counterpart of Functions.rbf.

src/jvmMain/kotlin/afield/math/functions/RbfField.kt — read the source

Used in: Functions to FLine examples (Field graphics)

Top-level functions

Declaration Summary
fun rbfField(samples: List<Pair<Vec2, Number>>, kernel: RbfKernel = RbfKernels.thinPlate, smoothing: Double = 0.0, trend: Boolean = true): RbfField A 2D RbfField through samples, each a point and the value there. The thin-plate kernel and the affine trend are the defaults, so with no shape parameter to choose this is the one to try first.

RbfField

class RbfField(val dimension: Int, val kernel: RbfKernel = RbfKernels.thinPlate, val smoothing: Double = 0.0, val trend: Boolean = true)

Radial basis function interpolation in two or more dimensions: a smooth surface through values scattered at points in a plane, or a volume, with no grid. The 1D version, RbfInterpolator behind Functions.rbf, fits a curve through (x, y) samples; this one fits f(point) through (point, value) samples, with the same RbfKernels.

Declaration Summary
val dimension: Int The number of coordinates per point: 2 for a surface over a plane.
val kernel: RbfKernel The radial function; distances are in the points' units, divided by each sample's radius.
val smoothing: Double Added to the kernel matrix's diagonal as a ridge term. 0 passes exactly through every sample; a positive value trades that for a calmer surface through noisy data.
val trend: Boolean Whether to fit an affine trend β₀ + β·p alongside the kernels. Needed for the thin-plate kernel to be well posed; switched off automatically while there are fewer than dimension + 1 samples, too few to determine it.
val size: Int How many samples the field passes through.
fun add(point: DoubleArray, value: Double, radius: Double = 1.0): Int Add a sample: value at point, reaching radius times as far as a radius-1 sample. Returns its index.
fun add(point: Vec2, value: Double, radius: Double = 1.0): Int add for a 2D point.
fun add(x: Double, y: Double, value: Double, radius: Double = 1.0): Int add for a 2D point.
fun removeAt(index: Int) Remove the sample at index; later samples move down one.
fun clear() Remove every sample.
operator fun invoke(point: DoubleArray): Double The fitted value at point. With no samples it is 0.
operator fun invoke(point: Vec2): Double The fitted value at point.
operator fun invoke(x: Double, y: Double): Double The fitted value at (x, y).
fun leaveOneOut(): DoubleArray For each sample, the value the field would predict there if that sample were left out and the rest refitted. Computed from one inverse of the system matrix (Rippa's formula), not n refits. Needs at least two samples.
fun leaveOneOutResiduals(): DoubleArray For each sample, its value minus the leaveOneOut prediction there.
fun leaveOneOutRmse(): Double The root mean square of leaveOneOutResiduals: one number for how well the field generalises.
override fun toString(): String

math.kt

Number-taking overloads of the trigonometric functions, so sin(1) and sin(1f) work without writing .toDouble() at every call site.

Covers the circular functions and their inverses (sin, cos, tan, asin, acos, atan, atan2) and the hyperbolic ones (sinh, cosh, tanh, asinh, acosh, atanh). Each is a one-line delegate to the matching kotlin.math function and always returns Double.

src/jvmMain/kotlin/afield/math/functions/math.kt — read the source

Top-level functions

Declaration Summary
fun sin(n: Number): Double Sine of n radians, in -1..1. Multiply by PI / 180 first if you are thinking in degrees.
fun cos(n: Number): Double Cosine of n radians, in -1..1.
fun tan(n: Number): Double Tangent of n radians; unbounded, and enormous near odd multiples of PI/2.
fun asin(n: Number): Double Arc sine of n, in radians over -PI/2..PI/2. NaN outside the domain -1..1.
fun acos(n: Number): Double Arc cosine of n, in radians over 0..PI. NaN outside the domain -1..1.
fun atan(n: Number): Double Arc tangent of n, in radians over -PI/2..PI/2. For a full-circle angle use atan2.
fun atan2(y: Number, x: Number): Double The angle of the vector (x, y) in radians over -PI..PI, measured counter-clockwise from the positive x axis.
fun sinh(n: Number): Double Hyperbolic sine of n. Unbounded, and overflows to infinity somewhere past n = 710.
fun cosh(n: Number): Double Hyperbolic cosine of n; 1 at zero and rising either side of it.
fun tanh(n: Number): Double Hyperbolic tangent of n, in -1..1.
fun asinh(n: Number): Double Inverse hyperbolic sine of n. Defined for every real input.
fun acosh(n: Number): Double Inverse hyperbolic cosine of n, >= 0. NaN below n = 1.
fun atanh(n: Number): Double Inverse hyperbolic tangent of n. Infinite at ±1 and NaN outside it — the inverse of tanh.

GaussianProcess.kt

Exact Gaussian-process regression: fit scalar observations (values and, optionally, slopes) and query the posterior mean, variance and joint covariance.

GaussianProcess factors the training covariance once and reuses that Cholesky factor for every prediction. GpPrediction is a single mean and variance with an interval; GpFunctional names a value or a derivative to query jointly; GpDerivativeObservation trains on a measured slope. GpNumerics holds the jitter and size limits, and the jitter actually used is reported back as jitterUsed. logMarginalLikelihood is the evidence that the fitters in GpFit.kt maximise.

src/jvmMain/kotlin/afield/math/gp/GaussianProcess.kt — read the source

Used in: Drawing with Gaussian processes

Top-level functions

Declaration Summary
fun gp1D(xs: DoubleArray, values: DoubleArray, kernel: GpKernel = GpKernels.rbf(), noiseStd: Double = 0.0, mean: (Double) -> Double = { 0.0 }, numerics: GpNumerics = GpNumerics(), slopes: List<GpDerivativeObservation> = emptyList()): GaussianProcess 1D observations may be unsorted, and repeated noisy observations remain independent evidence.
fun gp1D(xs: DoubleArray, values: DoubleArray, noiseStd: DoubleArray, kernel: GpKernel = GpKernels.rbf(), mean: (Double) -> Double = { 0.0 }, numerics: GpNumerics = GpNumerics(), slopes: List<GpDerivativeObservation> = emptyList()): GaussianProcess Per-observation noise: one standard deviation for each value, rather than one for all of them. Slopes carry their own.

GpNumerics

data class GpNumerics(val relativeJitter: Double = 1e-12, val maxRelativeJitter: Double = 1e-6, val covarianceTolerance: Double = 1e-10, val samplingTolerance: Double = 1e-12, val maxTrainingSize: Int = 4096, val maxJointSize: Int = 4096)

Numerical regularisation is separate from observation noise, and its actual value is reported.

Declaration Summary
val relativeJitter: Double The first jitter tried when the training Cholesky fails, as a fraction of the largest covariance diagonal.
val maxRelativeJitter: Double The ceiling on that jitter, in the same units. Each retry multiplies it by ten, and the fit throws rather than go past this.
val covarianceTolerance: Double Relative slack on kernel symmetry, on a posterior variance that comes out slightly negative, and on the noiseless-consistency check.
val samplingTolerance: Double Relative rank tolerance for the pivoted factorisation behind sampling: it decides when a direction of the joint counts as exactly pinned.
val maxTrainingSize: Int The largest number of observations, values and slopes together, that GaussianProcess.fit accepts.
val maxJointSize: Int The largest joint query GaussianProcess.posterior accepts, and the default point budget of a sampled path.

GpPrediction

data class GpPrediction(val mean: Double, val variance: Double)

Latent function uncertainty, excluding any future measurement noise.

Declaration Summary
val std: Double The standard deviation, the square root of variance.
fun interval(standardDeviations: Double = 1.96): ClosedFloatingPointRange<Double> The predicted mean plus or minus standardDeviations standard deviations; the default 1.96 is the central 95% of a normal. Latent, like the variance it is built from.

GpGradient

class GpGradient internal constructor(private val mu: DoubleArray, private val cov: Array<DoubleArray>)

The posterior gradient at one point: its mean vector and full covariance between axes.

Declaration Summary
val mean: DoubleArray The mean gradient, one component per input axis. A fresh copy on each read.
val covariance: Array<DoubleArray> Covariance between the gradient components, which the per-axis variances alone would lose. A fresh copy on each read.
val variance: DoubleArray The per-axis variances, the diagonal of covariance.

GpLeaveOneOut

data class GpLeaveOneOut(val mean: DoubleArray, val variance: DoubleArray, val standardizedResidual: DoubleArray)

Predictive distribution of each held-out observation (including its own noise).

GpFunctional

class GpFunctional private constructor(point: DoubleArray, val axis: Int?)

A linear functional of the latent function: its value at point, or its partial derivative along input axis there. Derivatives need a GpDifferentiableKernel.

Declaration Summary
val point: DoubleArray Where the functional is evaluated. A fresh copy on each read, so the query cannot be moved after the fact.

GpDerivativeObservation

class GpDerivativeObservation(point: DoubleArray, val axis: Int, val value: Double, val noiseStd: Double = 0.0)

A measured slope ∂f/∂xaxis = value at point; train on these alongside values.

Declaration Summary
val point: DoubleArray Where the slope was measured. A fresh copy on each read.

GaussianProcess

class GaussianProcess private constructor(val dimensions: Int, val kernel: GpKernel, private val priorMean: (DoubleArray) -> Double, private val x: Array<DoubleArray>, private val axes: IntArray, private val y: DoubleArray, private val noise: DoubleArray, val numerics: GpNumerics)

Exact dense GP regression in any finite input dimension, with 1D/2D/3D conveniences below. Fit once; the training Cholesky is reused by predictions. No matrix inverse is formed. Samples come from posterior, whose full covariance retains correlations between queries. Training data may include slopes (GpDerivativeObservation); values and slopes can be queried jointly (posterior of GpFunctionals, gradient, derivative). Inputs and outputs are copied. Kernel/mean closures must not change after fitting. The slope of a mean function is taken by central difference; the default zero mean has none.

Declaration Summary
val dimensions: Int The input dimension fixed at the fit. Every query point must carry this many coordinates.
val kernel: GpKernel The covariance function this fit used. Querying or training on slopes needs a GpDifferentiableKernel.
val numerics: GpNumerics The jitter and size limits in force. jitterUsed reports what the factorisation actually needed.
val trainingSize: Int Training values and slopes together.
val jitterUsed: Double Added training-diagonal variance in output-squared units; zero when no repair was needed.
val logMarginalLikelihood: Double Log p(observations | kernel, mean, noise), including any reported numerical jitter.
fun predict(vararg coordinates: Double): GpPrediction A point prediction; pass one, two or three coordinates (or *aDoubleArray).
operator fun invoke(vararg coordinates: Double): Double The posterior mean, so a fitted process can be called like the function it estimates.
fun mean(vararg coordinates: Double): Double Mean-only prediction avoids a triangular solve, O(n) kernel work.
fun variance(vararg coordinates: Double): Double The latent variance on its own, by way of a full predict — so it pays the triangular solve that GaussianProcess.mean skips.
fun derivative(axis: Int, vararg coordinates: Double): GpPrediction The latent slope ∂f/∂xaxis at a point, with its uncertainty.
fun gradient(vararg coordinates: Double): GpGradient The latent gradient at a point, with the covariance between its components.
fun predictObservation(point: DoubleArray, noiseStd: Double): GpPrediction Future measurement uncertainty adds noiseStd²; fitting noise never appears here implicitly.
fun covariance(a: DoubleArray, b: DoubleArray): Double Cross-covariance between two latent values, including off-diagonal posterior correlations.
fun covariance(a: GpFunctional, b: GpFunctional): Double Posterior covariance between any two functionals (values or slopes).
fun marginals(points: Array<DoubleArray>): List<GpPrediction> Marginals in O(m n²) time and O(n) working storage, without an m×m query covariance.
fun marginals(xs: DoubleArray): List<GpPrediction> One prediction per x, for a 1D fit, with no m×m query covariance formed. Throws unless the process was fitted in one dimension.
fun posterior(points: Array<DoubleArray>): GpJoint Full latent joint distribution. Query order is preserved; repeated points are allowed.
fun posterior(xs: DoubleArray): GpJoint The full joint over a 1D fit's values at the given xs, so a draw from it is a coherent curve. Throws unless the process was fitted in one dimension.
fun derivativePosterior(points: Array<DoubleArray>, axis: Int = 0): GpJoint The joint slope ∂f/∂xaxis at every point, e.g. a 1D derivative curve with coherent draws.
fun derivativePosterior(xs: DoubleArray): GpJoint The joint slope along a 1D fit's only input axis at each x. Throws unless the process was fitted in one dimension.
fun posterior(functionals: List<GpFunctional>): GpJoint One joint over any mix of values and slopes, in the order given.
fun condition(points: Array<DoubleArray>, values: DoubleArray, noiseStd: DoubleArray = DoubleArray(values.size), derivatives: List<GpDerivativeObservation> = emptyList()): GaussianProcess Add observations and refit, preserving the original mean/kernel/noise contract and any slopes.
fun leaveOneOut(): GpLeaveOneOut Exact leave-one-out identities from the Cholesky; does not refit n times. Values first, then slopes.

GpFields.kt

Gaussian-process regression of a scalar field sampled at Vec2 or Vec3 points.

gp2D and gp3D turn a list of points and their values into a GaussianProcess, with either one noise level for every observation or one per observation; the predict overloads read the fitted field back at a point as a GpPrediction (mean and variance).

src/jvmMain/kotlin/afield/math/gp/GpFields.kt — read the source

Used in: Drawing with Gaussian processes

Top-level functions

Declaration Summary
fun gp2D(points: List<Vec2>, values: DoubleArray, kernel: GpKernel = GpKernels.rbf(), noiseStd: Double = 0.0, mean: (DoubleArray) -> Double = { 0.0 }, numerics: GpNumerics = GpNumerics()): GaussianProcess Scalar values over the plane. Kernel length scales are measured in Vec2 coordinate units.
fun gp2D(points: List<Vec2>, values: DoubleArray, noiseStd: DoubleArray, kernel: GpKernel = GpKernels.rbf(), mean: (DoubleArray) -> Double = { 0.0 }, numerics: GpNumerics = GpNumerics()): GaussianProcess One noise standard deviation per point, for a survey whose samples are not equally reliable.
fun gp3D(points: List<Vec3>, values: DoubleArray, kernel: GpKernel = GpKernels.rbf(), noiseStd: Double = 0.0, mean: (DoubleArray) -> Double = { 0.0 }, numerics: GpNumerics = GpNumerics()): GaussianProcess Scalar values over space, distinct from a vector-valued parametric curve.
fun gp3D(points: List<Vec3>, values: DoubleArray, noiseStd: DoubleArray, kernel: GpKernel = GpKernels.rbf(), mean: (DoubleArray) -> Double = { 0.0 }, numerics: GpNumerics = GpNumerics()): GaussianProcess One noise standard deviation per point, for a volume sampled at differing quality.
fun GaussianProcess.predict(point: Vec2): GpPrediction Read the fitted field back at a point, without unpacking its coordinates first.
fun GaussianProcess.predict(point: Vec3): GpPrediction Read the fitted field back at a point in space, without unpacking its coordinates first.

GpFit.kt

Choosing GP hyperparameters from data by maximising the log marginal likelihood.

fitGpHyperparameters runs a bounded pattern search in log space over GpParameters and returns a GpFitResult with the fitted process and whether the search step converged; it finds a local optimum, so compare several starts on multimodal problems. scanGpEvidence evaluates the evidence over a fixed list of candidate values and returns a GpEvidenceScan with normalised posterior weights.

src/jvmMain/kotlin/afield/math/gp/GpFit.kt — read the source

Used in: Drawing with Gaussian processes

Top-level functions

Declaration Summary
fun fitGpHyperparameters(parameters: List<GpParameter>, maxIterations: Int = 100, logTolerance: Double = 1e-3, build: (DoubleArray) -> GaussianProcess): GpFitResult Deterministic bounded coordinate pattern search over log parameters. Works with any composed kernel, mean or noise model supplied by build. No hidden normalization, derivatives or native optimizer. Run from several initial settings and compare likelihoods for multimodal models. Each iteration tries both directions on every axis, halving the log step only when none improve. converged means the search step fell below logTolerance, not global optimality.
fun scanGpEvidence(candidates: DoubleArray, build: (Double) -> GaussianProcess): GpEvidenceScan The log marginal likelihood of the model build makes at each of candidates, as a GpEvidenceScan. Every model must have observations. One parameter at a time: whatever else build varies with the candidate is folded into the evidence without being reported.

GpParameter

data class GpParameter(val name: String, val initial: Double, val lower: Double, val upper: Double)

Positive parameter and explicit bounds, searched in log space. Same units as the model builder.

GpFitResult

class GpFitResult internal constructor(val process: GaussianProcess, values: Map<String, Double>, val evaluations: Int, val iterations: Int, val converged: Boolean, val initialLogMarginalLikelihood: Double)

A bounded local likelihood fit, not a posterior over hyperparameters or a global optimum.

Declaration Summary
val process: GaussianProcess The process built at the best parameters found, ready to predict from.
val evaluations: Int How many models the search built, including the one at the initial setting.
val iterations: Int How many pattern-search passes ran, at most the maxIterations asked for.
val converged: Boolean True when the search step fell below the tolerance, false when the iterations ran out.
val initialLogMarginalLikelihood: Double Log marginal likelihood at the initial setting, to compare against process's.
val parameters: Map<String, Double> The fitted value of each parameter, by name, clamped to the bounds it was given.

GpEvidenceScan

class GpEvidenceScan internal constructor(candidates: DoubleArray, logEvidence: DoubleArray)

Evidence for each candidate value of one parameter, with every other choice fixed by build. posteriorWeights normalise exp(log evidence): the posterior over the candidates under a uniform prior on the grid. Spread candidates in log space for a scale such as a period or length scale. A broad posterior means the data does not choose between the candidates.

Declaration Summary
val candidates: DoubleArray The candidate values that were scanned, in the order they were given. A copy.
val logEvidence: DoubleArray The log marginal likelihood at each candidate, aligned with candidates. A copy.
val posteriorWeights: DoubleArray The posterior over candidates under a uniform prior on the grid: logEvidence exponentiated against its maximum and normalised, so the weights sum to one.
val best: Double The candidate with the largest evidence. The first of them if several tie.

GpJoint.kt

A finite joint Gaussian over a set of query points: mean, covariance, and correlated samples.

GpJoint is what posterior queries return. It samples through a cached pivoted factor of its covariance (so rank-deficient covariances sample correctly; samplingRank reports the rank), and supports linear transforms and added observation noise. Getters return copies.

src/jvmMain/kotlin/afield/math/gp/GpJoint.kt — read the source

GpJoint

class GpJoint internal constructor(private val mu: DoubleArray, private val cov: Array<DoubleArray>, private val relativeTolerance: Double, private val absoluteTolerance: Double)

A finite joint Gaussian. Defensive getters keep its cached sampling factor consistent.

Declaration Summary
val size: Int How many query points this joint covers. Every array here has this length.
val mean: DoubleArray The mean at each query point. A copy.
val variance: DoubleArray The marginal variance at each query point: the diagonal of covariance.
val covariance: Array<DoubleArray> The whole covariance matrix, symmetrised on construction. A copy of every row.
fun covarianceAt(i: Int, j: Int): Double One covariance entry, without copying the matrix covariance would hand back.
val samplingRank: Int The rank of that factor, which is how many standard normals a draw consumes. Below size when the query points leave the covariance rank-deficient — repeated points, or a distribution pinned exactly by the observations.
val samplingResidualVariance: Double Maximum unrepresented marginal variance (numerical rank truncation only).
fun sample(seed: Long): DoubleArray One correlated draw at every query point. The same seed always gives the same draw.
fun sample(random: Random): DoubleArray One correlated draw, taking its randomness from random and advancing it by samplingRank normals. Call it repeatedly on one generator for independent draws.
fun sample(seed: Long, spread: Double): DoubleArray A draw whose departure from the mean is scaled by spread: 0 is the mean, 1 a draw from the distribution itself, above 1 an exaggerated one. Same seed, same direction of departure.
fun samples(count: Int, seed: Long): Array<DoubleArray> One factorisation, then independent whole-function draws.
fun fromStandardNormal(z: DoubleArray): DoubleArray Reuse standard normal coefficients to couple draws. Pivot/rank changes can still cause discontinuities.
fun linearTransform(weights: Array<DoubleArray>): GpJoint Distribution of A f: differences, weighted averages, finite-difference slopes, integrals.
fun withObservationNoise(noiseStd: DoubleArray): GpJoint Distribution of noisy future observations, with independent measurement errors.

GpKernel.kt

Covariance functions for Field's Gaussian processes, and the GpKernels catalogue of standard ones.

A GpKernel is any pure function of two input points; kernels add, multiply and scale with + and *. A GpDifferentiableKernel also supplies gradients and Hessians, which derivative observations and slope queries need, and sums, products and scalings of differentiable kernels stay differentiable. GpKernels provides RBF, Matérn, rational quadratic, periodic and periodic mixtures, constant and linear kernels, plus warp for an input transform.

src/jvmMain/kotlin/afield/math/gp/GpKernel.kt — read the source

GpKernel

fun interface GpKernel

A covariance function. Implementations and mean functions must be pure and thread safe. Sums, products and scalings of GpDifferentiableKernels are differentiable whatever their static type, so rbf() + matern(1.5) still answers derivative queries.

Declaration Summary
fun covariance(a: DoubleArray, b: DoubleArray): Double The prior covariance between the function's values at a and b, in output-squared units. The one method an implementation supplies.
operator fun plus(other: GpKernel): GpKernel A kernel whose covariance is the sum of both: a model that is the sum of two independent functions, such as a slow trend plus fast detail. Differentiable when both sides are.
operator fun times(other: GpKernel): GpKernel A kernel whose covariance is the product of both, which is how a periodic kernel is made to decay or a kernel is restricted to a region. Differentiable when both sides are.
operator fun times(variance: Double): GpKernel This kernel with its covariance scaled by variance, which must be nonnegative.

GpDifferentiableKernel

interface GpDifferentiableKernel : GpKernel

A kernel that also knows its input derivatives, so a process can be queried for, and trained on, slopes, gradients and curve velocities without finite differences. GpKernels.warp, Matérn ν = 1/2 and Smile kernels are not differentiable.

Declaration Summary
fun covarianceGradient(a: DoubleArray, b: DoubleArray, axis: Int): Double ∂k(a, b) / ∂baxis: the covariance between f(a) and ∂f/∂xaxis at b.
fun covarianceHessian(a: DoubleArray, b: DoubleArray, i: Int, j: Int): Double ∂²k(a, b) / ∂ai ∂bj: the covariance between the slope along i at a and along j at b.
operator fun plus(other: GpDifferentiableKernel): GpDifferentiableKernel The sum of two differentiable kernels, differentiable by linearity.
operator fun times(other: GpDifferentiableKernel): GpDifferentiableKernel The product of two differentiable kernels, differentiated by the product rule.
override operator fun times(variance: Double): GpDifferentiableKernel This kernel scaled by variance, gradients and Hessians scaled with it.

GpKernels

object GpKernels

Covariances in output-squared units. Length scales are in input units, one or one per axis. Every kernel here except Matérn ν = 1/2 and warp is a GpDifferentiableKernel.

Declaration Summary
fun rbf(lengthScale: Double = 1.0, variance: Double = 1.0): GpDifferentiableKernel Squared exponential: infinitely mean-square differentiable.
fun rbf(lengthScales: DoubleArray, variance: Double = 1.0): GpDifferentiableKernel The squared exponential with one length scale per input axis, so each axis can vary at its own rate. A single length scale is applied to every axis.
fun matern(nu: Double = 1.5, lengthScale: Double = 1.0, variance: Double = 1.0): GpKernel Matérn ν = 1/2, 3/2 or 5/2: respectively rough, once or twice mean-square differentiable. ν = 3/2 and 5/2 are GpDifferentiableKernels; ν = 1/2 has no derivative process.
fun matern(nu: Double, lengthScales: DoubleArray, variance: Double = 1.0): GpKernel Matérn with one length scale per input axis. nu must be 0.5, 1.5 or 2.5; anything else throws. A single length scale is applied to every axis.
fun rationalQuadratic(lengthScales: DoubleArray = doubleArrayOf(1.0), alpha: Double = 1.0, variance: Double = 1.0): GpDifferentiableKernel A scale mixture of RBF kernels; alpha controls the mixture.
fun periodic(period: Double = 1.0, lengthScale: Double = 1.0, variance: Double = 1.0): GpDifferentiableKernel Exactly periodic along every axis. Multiply by an RBF to let cycles evolve.
fun periodicMixture(periods: DoubleArray, weights: DoubleArray = DoubleArray(periods.size) { 1.0 }, lengthScale: Double = 1.0, variance: Double = 1.0): GpDifferentiableKernel Uncertainty over the period itself: a weighted sum of periodic kernels, one per candidate. Weights are normalised to sum to one, so variance keeps its meaning.
fun octavePeriodic(centerPeriod: Double, components: Int = 7, octaves: Double = 1.0, lengthScale: Double = 1.0, variance: Double = 1.0): GpDifferentiableKernel components candidate periods spread evenly in log₂ over octaves octaves centred on centerPeriod.
fun constant(variance: Double = 1.0): GpDifferentiableKernel The same covariance between every pair of points: one unknown level, drawn once, shared by the whole function. Add it to another kernel so the process can settle away from its mean function instead of returning to it between observations.
fun linear(variance: Double = 1.0, origin: DoubleArray = doubleArrayOf(0.0)): GpDifferentiableKernel Random linear functions through origin, plus a separately composable constant kernel.
fun warp(kernel: GpKernel, transform: (DoubleArray) -> DoubleArray): GpKernel Pull a covariance back through a deterministic input map (e.g. circular coordinates).

GpOptimizer.kt

Bayesian optimisation over named, bounded parameters: a GP surrogate proposes the next point to try, and you report what it scored.

val opt = GpOptimizer(listOf(GpRange("x", -2.0, 2.0)))
val best = opt.run(20) { p -> val d = p["x"]!! - 0.5; -d * d }

GpRange declares a parameter, GpAcquisition chooses how untried points are scored (expected improvement, upper confidence bound, probability of improvement), and GpTrial is one evaluated point. Drive GpOptimizer either with run/step and an objective, or by hand with suggest and observe when the score comes from somewhere else, such as a person. Covered by GpOptimizerTest.

src/jvmMain/kotlin/afield/math/gp/GpOptimizer.kt — read the source

Used in: Tuning by Gaussian process: optimisation and A/B preference

GpRange

data class GpRange(val name: String, val lower: Double, val upper: Double)

One named, bounded parameter an optimiser or preference model searches over.

GpAcquisition

sealed class GpAcquisition

How a GpOptimizer scores an untried point from the surrogate's mean and standard deviation.

Declaration Summary
data class ExpectedImprovement(val xi: Double = 0.01) : GpAcquisition() Expected improvement over the best value seen, less a margin xi (in standardised units).
data class UpperConfidenceBound(val beta: Double = 2.0) : GpAcquisition() Optimism: mean + beta · std. Larger beta explores more.
data class ProbabilityOfImprovement(val xi: Double = 0.01) : GpAcquisition() Probability of improving on the best value seen by more than xi. Greedy; mostly for comparison.

GpTrial

data class GpTrial(val parameters: Map<String, Double>, val value: Double)

One evaluated point: parameter values by name, and the objective there.

Declaration Summary
operator fun get(name: String): Double The value of one parameter by name. An unknown name is an error, not a null.

GpOptimizer

class GpOptimizer(parameters: List<GpRange>, val minimize: Boolean = false, val acquisition: GpAcquisition = GpAcquisition.ExpectedImprovement(), val seed: Long = 1L, val initialPoints: Int = max(4, parameters.size + 2), val candidates: Int = 768, val refineStarts: Int = 4, val refitEvery: Int = 1)

Bayesian optimisation of an expensive or hand-judged objective over named, bounded parameters.

Declaration Summary
val minimize: Boolean Search for the smallest value rather than the largest.
val acquisition: GpAcquisition How an untried point is scored from the surrogate's mean and standard deviation.
val seed: Long Seeds the opening design and the candidate draws, so a run repeats exactly.
val initialPoints: Int How many space-filling suggestions come before the surrogate is used at all.
val candidates: Int How many uniform random points the acquisition is scored at, besides 32 jitters of the best four.
val refineStarts: Int How many of the best candidates are refined by projected gradient ascent.
val refitEvery: Int Fit hyperparameters every this many observations; reuse the last fit in between.
val parameters: List<GpRange> The parameters being searched over, in the order their coordinates are held.
val dimensions: Int How many parameters there are.
val history: List<GpTrial> Everything observed, in order.
val observations: Int How many trials have been observed.
val best: GpTrial? The best observed trial (largest value, or smallest when minimize); null before any.
val bestSoFar: List<Double> Best value after each observation — the convergence trace.
val hyperparameters: Map<String, Double> Fitted kernel length scales (unit-cube units, by parameter name) and noise; empty before a fit.
fun observe(parameters: Map<String, Double>, value: Double) Record that parameters scored value. Parameters missing from the map are an error.
fun observe(trial: GpTrial) Record a whole GpTrial, as returned by best or carried in history.
fun suggest(): Map<String, Double> The next point to evaluate.
fun suggest(count: Int): List<Map<String, Double>> count points to evaluate together. After choosing each, it is provisionally "observed" at the surrogate's own mean (the kriging believer), which shrinks the uncertainty there and pushes the next choice elsewhere. Nothing provisional is kept.
fun step(objective: (Map<String, Double>) -> Double): GpTrial Suggest, evaluate with objective, observe; returns the trial.
fun run(iterations: Int, objective: (Map<String, Double>) -> Double): GpTrial iterations calls of step; returns best.
fun predict(parameters: Map<String, Double>): GpPrediction? Surrogate posterior of the objective at parameters, in the objective's own units; null before data.
fun acquisitionAt(parameters: Map<String, Double>): Double? The acquisition value the next suggest would assign to parameters; null during the initial design.

GpPaths.kt

Sample paths drawn from a Gaussian process: functions you can evaluate anywhere, consistently, after the draw.

GpSamplePath (from samplePath) is an exact draw extended lazily, conditioned on every point already evaluated, and limited to a few thousand points. For large fields, GpRbfFeatures builds a random-Fourier-feature approximation of an RBF kernel; its GpFeatureModel gives a path that costs the same at every query.

src/jvmMain/kotlin/afield/math/gp/GpPaths.kt — read the source

Used in: Drawing with Gaussian processes

Top-level functions

Declaration Summary
fun GaussianProcess.samplePath(seed: Long, maxPoints: Int = numerics.maxJointSize): GpSamplePath One draw from this process as a function you can call anywhere, extended lazily and exactly. maxPoints caps how many distinct locations it will answer for, and it throws past that. Use GpRbfFeatures when a field needs more.

GpSamplePath

class GpSamplePath internal constructor(private val model: GaussianProcess, seed: Long, private val maxPoints: Int)

One lazily extended exact GP draw. Repeated queries return the same value; new locations are drawn conditional on all previous locations. The law is independent of query order, but a seed's realised values are not. Mutable, single-threaded, and dense: use a feature path for large fields.

Declaration Summary
val evaluatedPoints: Int How many distinct locations have been asked for. The path stops at its point limit.
operator fun invoke(vararg coordinates: Double): Double This draw's value at coordinates, conditioned on every location already asked for. A repeated location returns its cached value; a new one costs work that grows with evaluatedPoints, and throws once the limit is reached.

GpRbfFeatures

class GpRbfFeatures(lengthScales: DoubleArray, val frequencyCount: Int = 512, val variance: Double = 1.0, val seed: Long = 0L)

Paired sine/cosine random Fourier features for an ARD RBF kernel. This is an explicit finite-rank approximation, never an automatic substitute for exact inference. The same features are used for fitting, uncertainty and Matheron-rule samples. Feature seeds describe a model, sample seeds describe functions from it. Amplitude is a variance, and length scales are in input units.

Declaration Summary
val dimensions: Int How many input coordinates these features expect: the length of lengthScales.
val featureCount: Int The length of a feature vector: two, sine and cosine, for each of frequencyCount.
fun fit(points: Array<DoubleArray>, values: DoubleArray, noiseStd: DoubleArray = DoubleArray(values.size), mean: (DoubleArray) -> Double = { 0.0 }, numerics: GpNumerics = GpNumerics()): GpFeatureModel A GpFeatureModel conditioned on values observed at points, with per-observation noiseStd and an optional prior mean function. Inference is exact for the finite-rank kernel these features define, which approximates the RBF kernel they were drawn for.
fun prior(mean: (DoubleArray) -> Double = { 0.0 }, numerics: GpNumerics = GpNumerics()): GpFeatureModel A model with no observations, for drawing functions from the prior. fit with nothing in it.

GpFeatureModel

class GpFeatureModel internal constructor(val features: GpRbfFeatures, val process: GaussianProcess, private val meanFunction: (DoubleArray) -> Double)

Finite-feature posterior: fast callable means/draws, plus the exact covariance of this approximation.

Declaration Summary
val features: GpRbfFeatures The features this model was fitted with; reuse them to build a comparable model.
val process: GaussianProcess The underlying exact process over the finite-rank kernel features define.
fun mean(vararg point: Double): Double The posterior mean at point: a dot product against weights computed once, so it costs the same however many observations the model carries.
fun predict(vararg point: Double): GpPrediction Mean and variance at point, from the underlying process.
fun samplePath(seed: Long): GpFeaturePath Draw a function once, then evaluate it anywhere, in any order, without growing a cache.

GpFeaturePath

class GpFeaturePath internal constructor(private val features: GpRbfFeatures, private val weights: DoubleArray, private val mean: (DoubleArray) -> Double)

Immutable coefficients; thread safe provided the supplied mean function is thread safe.

Declaration Summary
operator fun invoke(vararg point: Double): Double This drawn function's value at point. The same cost at every query, in any order.

GpPreference.kt

Preference learning from pairwise "A or B?" judgements, using a probit GP over a latent utility.

GpPreference records each GpComparison, estimates the utility of any parameter setting, ranks what it has seen, and proposes the next pair to ask about with nextPair. It shares GpRange with GpOptimizer, and suits tuning by eye when no numeric score exists.

src/jvmMain/kotlin/afield/math/gp/GpPreference.kt — read the source

GpComparison

data class GpComparison(val winner: Map<String, Double>, val loser: Map<String, Double>)

One judgement: winner was preferred to loser.

GpPreference

class GpPreference(parameters: List<GpRange>, val lengthScale: Double = 0.25, val noise: Double = 0.1, val exploration: Double = 1.0, val seed: Long = 1L, val candidates: Int = 512)

Learns what someone prefers from "A or B?" answers, and proposes the next pair to ask about.

Declaration Summary
val lengthScale: Double RBF length scale of the latent utility, in the unit cube every parameter is rescaled to.
val noise: Double How inconsistent the judge is allowed to be. Smaller trusts each answer more.
val exploration: Double How far nextPair's challenger reaches past the mean: it maximises mean + this · std.
val seed: Long Seeds the candidate draws, so the same answers in the same order propose the same pairs.
val candidates: Int How many uniform random candidates go into nextPair's pool, besides 32 jitters of the favourite.
val parameters: List<GpRange> The parameters being compared over, in the order their coordinates are held.
val dimensions: Int How many parameters there are.
val comparisons: List<GpComparison> Every answer so far, in order.
val newtonIterations: Int Newton iterations the last Laplace fit took (0 before any answer).
fun prefer(winner: Map<String, Double>, loser: Map<String, Double>) Record one answer.
fun choose(pair: Pair<Map<String, Double>, Map<String, Double>>, aWins: Boolean) Record one answer by which of a pair won: choose(pair, aWins = true).
fun utility(parameters: Map<String, Double>): GpPrediction Posterior latent utility at parameters (Laplace approximation); the prior before any answer.
fun utilityRelativeTo(reference: Map<String, Double>, parameters: Map<String, Double>): GpPrediction Posterior of u(parameters) − u(reference). Because answers only constrain differences, this is the useful uncertainty to draw: it is zero at the reference and small wherever comparisons tie a point to it, whereas utility's own variance keeps the unconstrained common offset.
val best: Map<String, Double>? The shown point with the largest posterior mean utility; null before any answer.
fun ranking(): List<Pair<Map<String, Double>, Double>> Every point shown so far with its posterior mean utility, best first.
fun nextPair(): Pair<Map<String, Double>, Map<String, Double>> The next two candidates to compare: the favourite, and the most promising challenger.

GpSmile.kt

Bridges from the Smile library into Field's Gaussian processes: a Smile kernel as a GpKernel, and Smile's bounded L-BFGS optimizer for fitting hyperparameters.

fitGpHyperparametersWithSmile searches log-parameter space between fixed bounds, rebuilding the process through a caller-supplied build function, and returns the best process found together with the projected gradient at the end. GaussianProcessSmileTest covers both bridges.

src/jvmMain/kotlin/afield/math/gp/GpSmile.kt — read the source

Used in: Drawing with Gaussian processes

Top-level functions

Declaration Summary
fun MercerKernel<DoubleArray>.asGpKernel(): GpKernel Use an existing Smile covariance unchanged in Field's exact inference/sampling machinery. This calls k(a,b), not Smile's fitted regression or global random source. The supplied kernel must be immutable, positive semidefinite, and accept the GP's input dimension.
fun fitGpHyperparametersWithSmile(parameters: List<GpParameter>, maxIterations: Int = 100, tolerance: Double = 1e-5, build: (DoubleArray) -> GaussianProcess): GpSmileFitResult Smile's bounded L-BFGS optimizer, with bounded finite differences in log-parameter space. Unlike Smile's regression fitter, the objective here includes Field's per-observation noise, composed kernels and mean functions. Fixed positive bounds are required; zero-noise constraints can be held fixed in build. A native optimizer failure is propagated, never hidden.

GpSmileFitResult

class GpSmileFitResult internal constructor(val process: GaussianProcess, val parameters: Map<String, Double>, val evaluations: Int, val projectedGradientMax: Double)

Smile L-BFGS-B result. A small projected gradient indicates local stationarity, not a global optimum.

Declaration Summary
val process: GaussianProcess The process rebuilt at the best parameters seen, ranked by marginal likelihood rather than by where the optimizer stopped.
val parameters: Map<String, Double> Those parameters by name, exponentiated back out of log space and held inside their bounds.
val evaluations: Int How many times fitGpHyperparametersWithSmile called its build function, the finite differences included.
val projectedGradientMax: Double The largest gradient component at the end, counting a component the optimizer is holding against a bound as zero.

GpVectorProcess.kt

Vector-valued Gaussian processes with separable covariance, for fitting curves and fields whose outputs are several coordinates.

GpVectorProcess lifts each observation to (input, output index) and reuses the exact scalar GaussianProcess, so cost is cubic in observations times output dimensions. With a GpDifferentiableKernel it trains on and answers derivatives through GpVectorDerivativeObservation and derivativePosterior. GpVectorJoint is the joint posterior over a set of points, with per-point means, covariances and samples.

src/jvmMain/kotlin/afield/math/gp/GpVectorProcess.kt — read the source

GpVectorDerivativeObservation

class GpVectorDerivativeObservation(point: DoubleArray, val axis: Int, value: DoubleArray, val noiseStd: Double = 0.0)

A measured vector derivative ∂f/∂xaxis (every output coordinate) at point.

Declaration Summary
val point: DoubleArray Where the derivative was measured. A copy.
val value: DoubleArray The measured derivative, one entry per output coordinate. A copy.

GpVectorProcess

class GpVectorProcess private constructor(val inputDimensions: Int, val outputDimensions: Int, val process: GaussianProcess)

A vector-valued GP with separable covariance k(input,input') * outputCovariance[axis,axis']. Identity output covariance gives independent coordinates; a PSD matrix gives correlated outputs. This uses exact scalar inference on lifted (input, output-index) observations, so costs are cubic in observationCount * outputDimensions. Use scalar models for large independent-output datasets. With a GpDifferentiableKernel it also answers, and trains on, input derivatives (velocities).

Declaration Summary
fun mean(vararg point: Double): DoubleArray The posterior mean vector at point, one entry per output coordinate.
fun variance(vararg point: Double): DoubleArray The marginal variance of each output coordinate at point. Coordinates may be correlated, which this does not say: for that, ask covariance or a posterior.
fun covariance(a: DoubleArray, b: DoubleArray): Array<DoubleArray> The output-by-output covariance between the vectors at a and b.
fun posterior(points: Array<DoubleArray>): GpVectorJoint The joint posterior over the vectors at points — means, covariances and correlated samples together. Limited by the scalar joint size divided by the output dimensions.
fun derivativePosterior(points: Array<DoubleArray>, axis: Int = 0): GpVectorJoint The vector derivative ∂f/∂xaxis at every point, jointly (e.g. a curve's velocity).

GpVectorJoint

class GpVectorJoint internal constructor(val dimensions: Int, val joint: GpJoint)

Input-major joint: (point0-axis0, point0-axis1, ..., point1-axis0, ...).

Declaration Summary
val size: Int How many input points this joint covers, each carrying dimensions outputs.
val mean: Array<DoubleArray> The mean vector at each point, outermost index the point.
val variance: Array<DoubleArray> The marginal variance of each output coordinate at each point.
fun covarianceAt(i: Int, j: Int = i): Array<DoubleArray> The output-by-output covariance between points i and j, which defaults to the block at one point: how that point's own coordinates covary.
fun sample(seed: Long): Array<DoubleArray> One draw: a vector at every point, correlated across both points and coordinates.
fun sample(random: java.util.Random): Array<DoubleArray> The same draw, taking its randomness from random; call it repeatedly for independent draws.
fun sample(seed: Long, spread: Double): Array<DoubleArray> See GpJoint.sample: spread scales the departure from the mean.
fun projectedVariance(index: Int, direction: DoubleArray): Double Variance of the scalar projection direction·value at one input. Direction need not be unit.

DualQuaternionf.kt

Rigid 3D transforms (rotation plus translation) as mutable unit dual quaternions, with blending.

DualQuaternionf is built from a rotation and a translation, composes with *, transforms points with transform or *, and converts to a Matrix4f with toMatrix. Two transforms blend with nlerp (fast, normalised component blend) or sclerp (screw-linear interpolation, which moves at constant speed along a single screw motion); pow scales a transform by a fraction. Both blends take the shorter path when the two quaternions have opposite signs.

Methods without a dest argument change the receiver and return it, following the rest of afield.math.linalg.

src/jvmMain/kotlin/afield/math/linalg/DualQuaternionf.kt — read the source

DualQuaternionf

open class DualQuaternionf

A mutable single-precision unit dual quaternion for rigid 3D transforms.

Declaration Summary
val real: Quaternionf
val dual: Quaternionf
fun identity(): DualQuaternionf
fun set(other: DualQuaternionf): DualQuaternionf
fun set(real: Quaternionfc, dual: Quaternionfc): DualQuaternionf
fun set(rotation: Quaternionfc, translation: Vector3fc): DualQuaternionf
fun setTranslation(translation: Vector3fc): DualQuaternionf
fun getRotation(dest: Quaternionf = Quaternionf()): Quaternionf
fun getTranslation(dest: Vector3f = Vector3f()): Vector3f
fun dot(other: DualQuaternionf): Float
fun negate(): DualQuaternionf
fun normalize(): DualQuaternionf
fun normalized(dest: DualQuaternionf = DualQuaternionf()): DualQuaternionf
fun conjugate(): DualQuaternionf
fun invert(): DualQuaternionf
fun inverted(dest: DualQuaternionf = DualQuaternionf()): DualQuaternionf
fun mul(right: DualQuaternionf): DualQuaternionf
fun mul(right: DualQuaternionf, dest: DualQuaternionf): DualQuaternionf
operator fun times(right: DualQuaternionf): DualQuaternionf
operator fun times(position: Vector3fc): Vector3f
operator fun unaryMinus(): DualQuaternionf
fun transform(position: Vector3fc, dest: Vector3f = Vector3f()): Vector3f
fun toMatrix(dest: Matrix4f = Matrix4f()): Matrix4f
fun nlerp(other: DualQuaternionf, t: Float, dest: DualQuaternionf = DualQuaternionf()): DualQuaternionf
fun sclerp(other: DualQuaternionf, t: Float, dest: DualQuaternionf = DualQuaternionf()): DualQuaternionf
fun pow(exponent: Float, dest: DualQuaternionf = DualQuaternionf()): DualQuaternionf
override fun toString(): String

Geometry.kt

Closest-point, containment and intersection tests for segments, triangles, boxes, spheres and rays.

Each function takes read-only vectors and writes its answer into a caller-supplied dest, or returns a Boolean or distance, so none of them allocates. Boxes are axis-aligned and given as a minimum and maximum corner. Segment and triangle functions handle a zero-length segment or a collapsed triangle explicitly instead of dividing by zero.

afield.math.linalg.arena has allocation-free versions of some of these that work on arena vectors inside frameScratch.

src/jvmMain/kotlin/afield/math/linalg/Geometry.kt — read the source

Used in: Linear algebra in Field

Top-level functions

Declaration Summary
fun closestPointOnSegment(point: Vector2fc, a: Vector2fc, b: Vector2fc, dest: Vector2f): Vector2f Closest point on the finite line segment [a, b]. Safe for a zero-length segment.
fun closestPointOnSegment(point: Vector3fc, a: Vector3fc, b: Vector3fc, dest: Vector3f): Vector3f Closest point on the finite line segment [a, b]. Safe for a zero-length segment.
fun closestPointsBetweenSegments(a0: Vector3fc, a1: Vector3fc, b0: Vector3fc, b1: Vector3fc, destA: Vector3f, destB: Vector3f, epsilon: Float = 1e-12f): Float Writes the closest pair on segments [a0, a1] and [b0, b1], returning their squared distance. Degenerate point-segments are supported and both destinations may alias input heap vectors.
fun intersectSegments(a0: Vector2fc, a1: Vector2fc, b0: Vector2fc, b1: Vector2fc, dest: Vector2f, epsilon: Float = 1e-7f): Boolean Single-point 2D segment intersection. Collinear overlap returns false because it has no unique point.
fun barycentricCoordinates(point: Vector3fc, a: Vector3fc, b: Vector3fc, c: Vector3fc, dest: Vector3f, epsilon: Float = 1e-12f): Boolean Writes barycentric coordinates (u, v, w) for point point relative to triangle a, b, c. Returns false for a degenerate triangle and leaves dest unchanged.
fun containsPointInTriangle(point: Vector3fc, a: Vector3fc, b: Vector3fc, c: Vector3fc, tolerance: Float = 1e-6f): Boolean
fun closestPointOnTriangle(point: Vector3fc, a: Vector3fc, b: Vector3fc, c: Vector3fc, dest: Vector3f): Vector3f Ericson-style closest point on a triangle, including degenerate edge regions.
fun closestPointOnAabb(point: Vector3fc, minimum: Vector3fc, maximum: Vector3fc, dest: Vector3f): Vector3f
fun containsPointAabb(point: Vector3fc, minimum: Vector3fc, maximum: Vector3fc): Boolean
fun intersectsAabb(aMin: Vector3fc, aMax: Vector3fc, bMin: Vector3fc, bMax: Vector3fc): Boolean
fun containsPointSphere(point: Vector3fc, center: Vector3fc, radius: Float): Boolean
fun intersectsSphere(aCenter: Vector3fc, aRadius: Float, bCenter: Vector3fc, bRadius: Float): Boolean
fun intersectsAabbSphere(minimum: Vector3fc, maximum: Vector3fc, center: Vector3fc, radius: Float): Boolean
fun rayTriangleDistance(origin: Vector3fc, direction: Vector3fc, a: Vector3fc, b: Vector3fc, c: Vector3fc, epsilon: Float = 1e-7f): Float Returns the forward ray distance, or Float.NaN when the ray misses or the triangle is degenerate.
fun intersectRayTriangle(origin: Vector3fc, direction: Vector3fc, a: Vector3fc, b: Vector3fc, c: Vector3fc, dest: Vector3f, epsilon: Float = 1e-7f): Boolean
fun intersectRayPlane(origin: Vector3fc, direction: Vector3fc, pointOnPlane: Vector3fc, normal: Vector3fc, dest: Vector3f, epsilon: Float = 1e-7f): Boolean
fun intersectRaySphere(origin: Vector3fc, direction: Vector3fc, center: Vector3fc, radius: Float, nearFar: Vector2f, epsilon: Float = 1e-12f): Boolean Writes forward (near, far) ray distances. Direction need not be normalized.
fun intersectRayAabb(origin: Vector3fc, direction: Vector3fc, minimum: Vector3fc, maximum: Vector3fc, nearFar: Vector2f, epsilon: Float = 1e-12f): Boolean Writes forward (near, far) ray distances.
fun intersectSegmentAabb(start: Vector3fc, end: Vector3fc, minimum: Vector3fc, maximum: Vector3fc, nearFar: Vector2f, epsilon: Float = 1e-12f): Boolean Writes segment parameters (near, far) in [0, 1].

ArenaGeometry.kt

Arena-vector versions of a few afield.math.linalg geometry tests, for use inside frameScratch.

Covers the closest point on a segment and on an axis-aligned box, point-in-box, and ray-box intersection. Each one reads and writes arena vectors, so it allocates nothing on the heap.

src/jvmMain/kotlin/afield/math/linalg/arena/ArenaGeometry.kt — read the source

Top-level functions

Declaration Summary
fun FrameScope.closestPointOnSegment(point: ArenaVec3f, a: ArenaVec3f, b: ArenaVec3f, dest: ArenaVec3f): ArenaVec3f Allocation-free arena form of afield.math.linalg.closestPointOnSegment.
fun FrameScope.closestPointOnAabb(point: ArenaVec3f, minimum: ArenaVec3f, maximum: ArenaVec3f, dest: ArenaVec3f): ArenaVec3f Allocation-free arena form of afield.math.linalg.closestPointOnAabb.
fun FrameScope.containsPointAabb(point: ArenaVec3f, minimum: ArenaVec3f, maximum: ArenaVec3f): Boolean
fun FrameScope.intersectRayAabb(origin: ArenaVec3f, direction: ArenaVec3f, minimum: ArenaVec3f, maximum: ArenaVec3f, nearFar: ArenaVec2f, epsilon: Float = 1e-12f): Boolean Writes forward (near, far) ray distances without allocating heap vectors.

FrameArena.kt

Per-thread scratch memory for temporary vectors, reclaimed all at once when a scope ends.

frameScratch runs a block against the current thread's FrameArena; vectors allocated inside it become invalid when the block returns. Scopes nest, each restoring its own ArenaMark. Use an explicit FrameArena (and close it) for short-lived threads, since FrameArena.current keeps its native storage until the thread calls FrameArena.closeCurrent.

With debug checks on (the default), using a vector after its scope has ended, or from another thread, throws instead of reading reused memory.

src/jvmMain/kotlin/afield/math/linalg/arena/FrameArena.kt — read the source

Top-level functions

Declaration Summary
inline fun <R> frameScratch(block: FrameScope.() -> R): R Runs an allocation-free, non-suspending scratch region on this platform thread. Nested calls are supported and restore their arena marks in LIFO order.

FrameArena

class FrameArena(initialCapacityBytes: Int = DEFAULT_CAPACITY_BYTES, val debugChecks: Boolean = ARENA_CHECKS_COMPILED) : AutoCloseable

Thread-confined, resettable scratch storage for generated linear-algebra values.

Declaration Summary
val debugChecks: Boolean
var highWaterBytes: Int
var allocationCount: Long
var growthCount: Long
var bytesCopiedDuringGrowth: Long
var resetCount: Long
val capacityBytes: Int
val usedBytes: Int
val isClosed: Boolean
val checksEnabled: Boolean
inline fun <R> scoped(block: FrameScope.() -> R): R Runs a non-suspending scratch region and invalidates everything allocated in the block when it returns.
fun reset()
fun mark(): ArenaMark
fun restore(mark: ArenaMark)
override fun close()

ArenaMark

value class ArenaMark internal constructor(internal val token: Long)

linalg.kt

The short names for Field's vector and rotation types (Vec2, Vec3, Vec4, Quat, DualQuat) and the vec, quat and dualquat constructors that build them.

vec(...) takes numbers, copies an existing vector, or concatenates a scalar with a smaller one. quat turns an angle in radians and an axis into a rotation, and dualquat pairs a rotation with a translation to give a rigid transform. v3 lifts a 2D vector into 3D with z = 0 and v2 drops the z again, for the float and the double types alike.

The rest of the file is the arithmetic the generated types do not cover: scalar times vector, vector times quaternion, vector times dual quaternion, the x infix cross product, isNaN for the 2D and 3D vectors, and %= for Quat and DualQuat. median(index) reads a single component's median across a collection of vectors and returns that one Float.

These types are mutable and most operations write into the receiver, so copy first (Vec3(existing)) when the original still matters. Swizzles and the per-vector invoke, %= and len operators are absent here because tools/linalg-generator emits them for every float vector into Swizzles.kt and VectorOperators.kt; %= for Quat and DualQuat is written out here because no generator covers those two.

Point-cloud geometry built on these types lives elsewhere: centroids, medians and bounding boxes in linalg_spatial.kt, convex hulls in linalg_mesh.kt.

src/jvmMain/kotlin/afield/math/linalg/linalg.kt — read the source

Used in: FLine examples (Field graphics), A Drawn Morph, Heard, Datalog in a box

Top-level functions

Declaration Summary
fun Collection<Vec2>.median(index: Int = 0): Float The median of one component across the collection: index 0 for x, 1 for y.
fun Collection<Vec3>.median(index: Int = 0): Float The median of one component: index 0 for x, 1 for y, 2 for z.
fun Collection<Vec4>.median(index: Int = 0): Float The median of one component: index 0 for x, 1 for y, 2 for z, 3 for w.
fun vec(x: Number, y: Number) Creates a mutable 2D Vec2 from numeric coordinates, converting each component to Float.
fun vec(x: Number, y: Number, z: Number) Creates a mutable 3D Vec3 from numeric coordinates, converting each component to Float.
fun vec(x: Number, y: Number, z: Number, w: Number) Creates a mutable 4D Vec4 from numeric coordinates, converting each component to Float.
fun vec(v: Vector2fc) Copies a 2D vector into a new mutable Vec2.
fun vec(v: Vector3fc) Copies a 3D vector into a new mutable Vec3.
fun vec(v: Vector4fc) Copies a 4D vector into a new mutable Vec4.
fun vec(v: Vec2, z: Number) A Vec3 from a Vec2 and a z — the "give this planar point a height" constructor. Copies v.
fun vec(x : Number, yz: Vec2) A Vec3 from a leading scalar and a Vec2 tail, GLSL's vec3(x, yz). Note the argument order.
fun vec(x: Number, yzw: Vec3) A Vec4 from a leading scalar and a Vec3 tail, GLSL's vec4(x, yzw).
operator fun Quat.remAssign(a: Quat) q %= other — overwrite this rotation in place with another's, keeping the same instance.
operator fun DualQuat.remAssign(a: DualQuat) t %= other — overwrite this rigid transform in place, for the same reasons as Quat.remAssign.
infix fun Vector3f.x(b: Vector3f) Cross product as an infix operator: a x b, a vector perpendicular to both, right-handed.
operator fun Number.times(s: Vector3fc): Vector3f 2 * v — scalar on the left, so the arithmetic reads the way it does on paper. New Vec3.
operator fun Number.times(s: Vector2fc): Vector2f 2 * v with the scalar on the left, for Vec2. New vector; operands untouched.
operator fun Number.times(s: Vector4fc): Vector4f 2 * v with the scalar on the left, for Vec4. New vector; operands untouched.
fun Vector3dc.isNaN() True if any component of this 3D double vector is NaN — the check == cannot do for you.
fun Vector2dc.isNaN() True if either component of this 2D double vector is NaN.
fun Vector3fc.isNaN() True if any component of this Vec3 is NaN.
fun Vector2fc.isNaN() True if either component of this Vec2 is NaN. Infinities are not covered.
operator fun Vector3fc.times(v: Quaternionfc): Vector3f p * q — the point rotated by the quaternion, into a new Vec3. Neither operand is modified.
operator fun Vector3fc.times(v: DualQuaternionf): Vector3f p * t — the point moved by the rigid transform (rotate then translate), into a new Vec3.
operator fun Vector2fc.times(v: Quaternionfc): Vector2fc p * q for a planar point: rotates (x, y, 0) by the quaternion and returns the xy of the result.
fun quat(angle: Number = 0.0, axis: Vector3f = vec(0, 0, 1)) A rotation of angle radians about axis, as a Quat.
fun dualquat(rotation: Quaternionfc = Quaternionf(), translation: Vector3fc = vec(0, 0, 0)) A rigid transform from a rotation and a translation — the everyday DualQuat constructor.
fun dualquat(real: Quaternionfc, dual: Quaternionfc) A DualQuat from its raw real and dual parts, normalized on the way out.

Top-level values

Declaration Summary
val Vector2dc.v3: Vector3d This 2D double vector lifted into 3D with z = 0, as a new mutable vector.
val Vector3dc.v2: Vector2d This 3D double vector with its z dropped, as a new mutable 2D vector — a projection, not a copy.
val Vector2fc.v3: Vector3f This Vec2 lifted into 3D with z = 0, as a new mutable vector.
val Vector3fc.v2: Vector2f This Vec3 with its z dropped, as a new mutable Vec2 — flattening, not a perspective divide.

Vec4

typealias Vec4 = Vector4f

Field's 4D vector alias, backed by Field's generated mutable Vector4f.

Vec3

typealias Vec3 = Vector3f

Field's 3D vector alias, backed by Field's generated mutable Vector3f.

Vec2

typealias Vec2 = Vector2f

Field's 2D vector alias, backed by Field's generated mutable Vector2f.

Quat

typealias Quat = Quaternionf

Field's rotation alias, backed by the generated mutable Quaternionf — a rotation with no position, and no gimbal lock.

DualQuat

typealias DualQuat = DualQuaternionf

Field's rigid-transform alias, backed by the generated mutable DualQuaternionf — a rotation and a translation in one value.

MatrixProfile.kt

Matrix profiles of a single series: for every subsequence, the distance to its nearest neighbour. The result, MatrixProfile, yields motifs (repeated shapes) and discords (shapes that occur once).

matrixProfile computes a self-join or, given two series, an AB-join; joinBothWays returns both directions of a join, and distanceProfile the distances from one query to every subsequence of a series. Its motifs and discords return Motifs and Discords. The multichannel and contrast variants are in MultiDimensional.kt. Tests: MatrixProfileTest.

src/jvmMain/kotlin/afield/math/matrixprofile/MatrixProfile.kt — read the source

Used in: Motifs, Discords and Contrast: the Matrix Profile

Top-level functions

Declaration Summary
fun defaultExclusion(m: Int): Int Default trivial-match exclusion zone: a quarter of the window, as in STUMPY and SCAMP.
fun matrixProfile(series: List<Number>, m: Int, exclusion: Int = defaultExclusion(m), fraction: Double = 1.0, seed: Long = 0): MatrixProfile+2 overloads · also DoubleArray, FloatArray The self-join matrix profile of series with subsequence length m.
fun matrixProfile(a: List<Number>, b: List<Number>, m: Int, fraction: Double = 1.0, seed: Long = 0): MatrixProfile+1 overload · also DoubleArray The join matrix profile: for every subsequence of a, its nearest neighbour in b. There is no exclusion zone, since the two series are different. The profile of b against a comes out of the same pass; ask for it with joinBothWays.
fun joinBothWays(a: DoubleArray, b: DoubleArray, m: Int, fraction: Double = 1.0, seed: Long = 0): Pair<MatrixProfile, MatrixProfile> The join profiles of a against b and of b against a, from one pass over the distance matrix.
fun distanceProfile(query: DoubleArray, series: DoubleArray): DoubleArray The z-normalized Euclidean distance from query to every subsequence of series of the same length, by Mueen's MASS: one FFT convolution for all the dot products, so O(n log n).

MatrixProfile

class MatrixProfile internal constructor(val m: Int, val distances: DoubleArray, val indices: IntArray, val leftDistances: DoubleArray, val leftIndices: IntArray, val rightDistances: DoubleArray, val rightIndices: IntArray, val exclusion: Int, val series: DoubleArray, val other: DoubleArray, val fraction: Double)

The matrix profile of a series (Yeh, Keogh et al., 2016): for every subsequence of length m, the z-normalized Euclidean distance to its nearest neighbour somewhere else in the series (or, for a join, in another series) and where that neighbour is.

Declaration Summary
val m: Int The subsequence length.
val distances: DoubleArray Nearest-neighbour distance per subsequence start, length n - m + 1.
val indices: IntArray Start of the nearest neighbour per subsequence, or -1.
val leftDistances: DoubleArray Nearest neighbour that starts earlier (self-joins only; empty for a join).
val leftIndices: IntArray Index of leftDistances' neighbour, or -1.
val rightDistances: DoubleArray Nearest neighbour that starts later (self-joins only; empty for a join).
val rightIndices: IntArray Index of rightDistances' neighbour, or -1.
val exclusion: Int Half-width of the trivial-match exclusion zone used.
val series: DoubleArray The series the subsequences come from.
val other: DoubleArray For a join, the series the neighbours come from; for a self-join, series.
val fraction: Double Fraction of the distance matrix's diagonals evaluated (1.0 = exact).
val size: Int Number of subsequences.
val isSelfJoin: Boolean True for a self-join.
val profile: List<Double> distances as a list, ready for showSignals("mp" to mp.profile). Infinite values become NaN.
fun motifs(count: Int = 3, radius: Double = 2.0, maxMembers: Int = 10, maxDistance: Double = Double.POSITIVE_INFINITY): List<Motif> The count best motifs, best first. Each starts from the closest remaining pair in the profile; its members are every subsequence within radius × pair distance of the first (at most maxMembers, no two trivially overlapping). Members of a motif, and their exclusion zones, are not used again.
fun discords(count: Int = 3): List<Discord> The count most unusual subsequences, most unusual first, none trivially overlapping.
fun subsequence(i: Int): DoubleArray The subsequence starting at i.
override fun toString(): String

Motif

data class Motif(val indices: List<Int>, val distances: List<Double>, val pairDistance: Double, val seed: Int = indices.first())

A repeated shape: where it occurs (closest first), each occurrence's distance from seed, the distance of the pair it was found from, and the subsequence it was grown from.

Discord

data class Discord(val index: Int, val distance: Double, val neighbour: Int)

An unusual shape: where it starts, how far it is from its nearest neighbour, and where that is.

MultiDimensional.kt

Matrix profiles across several channels and between two classes of examples, built on the single-series MatrixProfile.

multiMatrixProfile finds motifs that appear in only some channels of a multichannel series and reports which channels share them (MultiMatrixProfile, MultiMotif). contrastProfile finds shapes that repeat in a set of positive examples and are absent from a set of negative ones (ContrastProfile, Plato). Tests: MatrixProfileTest.

src/jvmMain/kotlin/afield/math/matrixprofile/MultiDimensional.kt — read the source

Used in: Motifs, Discords and Contrast: the Matrix Profile

Top-level functions

Declaration Summary
fun multiMatrixProfile(channels: List<DoubleArray>, m: Int, exclusion: Int = defaultExclusion(m)): MultiMatrixProfile The multidimensional self-join matrix profile of channels (all the same length) with subsequence length m. Exact, O(d · n² + d log d · n²) — the per-channel covariance recurrence of matrixProfile run in lock-step over every channel, with the channels sorted per cell.
fun multiMatrixProfile(channels: List<List<Number>>, m: Int, exclusion: Int = defaultExclusion(m)): MultiMatrixProfile multiMatrixProfile for channels given as lists.
fun contrastProfile(positive: List<DoubleArray>, negative: List<DoubleArray>, m: Int, exclusion: Int = defaultExclusion(m)): ContrastProfile The ContrastProfile of the positive examples against the negative ones.
fun contrastProfile(positive: DoubleArray, negative: DoubleArray, m: Int, exclusion: Int = defaultExclusion(m)): ContrastProfile contrastProfile for one positive and one negative series.

MultiMatrixProfile

class MultiMatrixProfile internal constructor(val m: Int, val channels: List<DoubleArray>, private val dist: Array<DoubleArray>, private val idx: Array<IntArray>, val exclusion: Int)

The multidimensional matrix profile of a d-channel series (mSTAMP, Yeh, Kavantzas & Keogh, 2017). A motif in many channels is rarely in all of them: a gesture shows up in the wrist's x and z but not y, a fault in two of eight sensors. So instead of one profile there are d: profile k (1-based, profile) is, for every subsequence, the smallest mean distance to a neighbour over the best k channels, choosing the channels per pair.

Declaration Summary
val m: Int The subsequence length.
val channels: List<DoubleArray> The channels, each the same length.
val exclusion: Int Half-width of the trivial-match exclusion zone used.
val dimensions: Int Number of channels.
val size: Int Number of subsequences.
fun distances(k: Int): DoubleArray Distances of the k-channel profile (1..dimensions).
fun indices(k: Int): IntArray Neighbour indices of the k-channel profile.
fun profile(k: Int): List<Double> The k-channel profile as a plot-ready list (infinities become NaN).
fun asMatrixProfile(k: Int, channel: Int = 0): MatrixProfile The k-channel profile as a one-dimensional MatrixProfile over channel channel, so MatrixProfile.motifs-style questions can be asked of it — the distances are the k-channel ones.
fun subspace(k: Int, i: Int): IntArray The k channels, most similar first, in which subsequence i matches its k-channel neighbour.
fun motifs(k: Int, count: Int = 3, radius: Double = 2.0, maxMembers: Int = 10): List<MultiMotif> The count best k-channel motifs. Each grows from the closest remaining pair; its members are subsequences within radius × that pair's distance, measured as the mean distance over the pair's own subspace.
fun discords(k: Int, count: Int = 3): List<Discord> The count most unusual k-channel subsequences.
override fun toString(): String

MultiMotif

data class MultiMotif(val indices: List<Int>, val distances: List<Double>, val pairDistance: Double, val channels: List<Int>)

A motif in a subset of channels: occurrences, their distances, the seed pair's distance and the channels it lives in.

ContrastProfile

class ContrastProfile internal constructor(val m: Int, val contrast: DoubleArray, val self: MatrixProfile, val join: MatrixProfile, val segmentStarts: IntArray)

The contrast profile (Mercer, Alaee, Abdoli, Singh, Murillo, Keogh, 2021): which shapes occur in the positive examples and not in the negative ones.

Declaration Summary
val m: Int The subsequence length.
val contrast: DoubleArray Contrast per subsequence of the concatenated positives; NaN where undefined.
val self: MatrixProfile Self-join of the concatenated positives.
val join: MatrixProfile Join of the concatenated positives against the concatenated negatives.
val segmentStarts: IntArray Start of each positive example within the concatenation (examples are separated by one NaN).
val profile: List<Double> contrast as a plot-ready list.
val positive: DoubleArray The concatenated positives that contrast indexes.
fun platos(count: Int = 1): List<Plato> The count highest-contrast subsequences, best first, none trivially overlapping.
fun subsequence(i: Int): DoubleArray The positive subsequence starting at i.

Plato

data class Plato(val index: Int, val contrast: Double, val example: Int, val offset: Int, val positiveNeighbour: Int, val negativeNeighbour: Int)

A high-contrast subsequence: where it starts in the concatenated positives, its contrast, which positive example that is and where within it, and its nearest neighbours in the positives and the negatives.

LeastSquares.kt

A Levenberg–Marquardt nonlinear least-squares solver, pure Kotlin and self-contained.

leastSquares takes an initial parameter vector and a residuals: (DoubleArray) -> DoubleArray lambda and minimises half the squared residual norm. LeastSquaresOptions controls the iteration cap, the damping schedule and three convergence tolerances; LeastSquaresResult reports the parameters, final residuals, cost, iteration count, an rms, and a reason string — one of "gradient", "step", "improvement", "no accepted step" or "max iterations".

The Jacobian is central finite differences by default, so you never have to supply derivatives, but each iteration then costs 2 * parameters extra residual evaluations. Pass an explicit jacobian — or, easier, write the residuals in AD arithmetic and call leastSquaresAD, which derives an exact Jacobian by reverse-mode automatic differentiation — to trade those 2n evaluations for one backward sweep and get derivatives that are exact rather than truncated.

The damped normal equations are solved by Gaussian elimination with partial pivoting; a singular system or a non-finite candidate raises the damping and retries (up to 12 times per iteration) rather than failing. Non-finite initial residuals throw.

src/jvmMain/kotlin/afield/math/optimization/LeastSquares.kt — read the source

Top-level functions

Declaration Summary
fun leastSquares(initial: DoubleArray, options: LeastSquaresOptions = LeastSquaresOptions(), jacobian: ((DoubleArray) -> Array<DoubleArray>)? = null, residuals: (DoubleArray) -> DoubleArray): LeastSquaresResult Levenberg-Marquardt minimisation of half the squared residual norm.
fun leastSquaresAD(initial: DoubleArray, options: LeastSquaresOptions = LeastSquaresOptions(), residuals: (List<AD>) -> List<AD>): LeastSquaresResult leastSquares with an exact Jacobian, from residuals written once in AD arithmetic.

LeastSquaresOptions

data class LeastSquaresOptions(val maxIterations: Int = 64, val initialDamping: Double = 1e-3, val dampingUp: Double = 10.0, val dampingDown: Double = 0.35, val finiteDifferenceStep: Double = 1e-4, val gradientTolerance: Double = 1e-8, val stepTolerance: Double = 1e-9, val improvementTolerance: Double = 1e-12)

Declaration Summary
val maxIterations: Int
val initialDamping: Double
val dampingUp: Double
val dampingDown: Double
val finiteDifferenceStep: Double
val gradientTolerance: Double
val stepTolerance: Double
val improvementTolerance: Double

LeastSquaresResult

data class LeastSquaresResult(val x: DoubleArray, val residuals: DoubleArray, val cost: Double, val iterations: Int, val converged: Boolean, val reason: String)

Declaration Summary
val x: DoubleArray
val residuals: DoubleArray
val cost: Double
val iterations: Int
val converged: Boolean
val reason: String
val rms: Double

Improviser.kt

Playing a sequence memory back as a new performance: when to leave the passage being played, how to join the next one, and how to tell whether what came out still resembles the recording.

Improviser advances one sample at a time. It follows the recording, and once it has stayed on a passage for at least a phrase it may jump to another that shares context, joining the two with a correction that starts at the discontinuity and fades out. Every sample it emits is taken from the memory, and Performance records where from: the memory position behind each output sample, the size of the correction applied there, which channels the performer held, and a Segment wherever the source changed, carrying the Reason it changed.

Performance also carries the measurements, since whether a recombination still sounds like the recording is not something the player can answer about itself. reuse and coverage count memory positions; outputRepetition looks at the output alone, and so sees a loop assembled from copies of one gesture taken from different places in memory, which reuse cannot; joinRoughness and accelerationDistance compare the result against the original recording.

ContinuationJudge is declared here and implemented elsewhere: a second opinion that rates proposed continuations, which Improviser either folds into its own scores or, in veto mode, uses only to drop candidates. afield.math.temporal.ForecastJudge is the one that ships. A judge can change which passage is played next; it proposes no samples of its own.

Policy selects between the oracle and two baselines (jumping to the nearest match in recent history, with no oracle structure, and looping each episode unchanged), so a recombination can be compared against doing less. Tests: VariableMarkovOracleTest, SeveralTakesTest and ContinuationJudgeTest.

src/jvmMain/kotlin/afield/math/oracle/Improviser.kt — read the source

Used in: Continuing a Gesture: Sequence Memory and Forecasting

Policy

enum class Policy

How an Improviser chooses where to go next.

Constants: Loop, NearestHistory, Oracle.

ContinuationJudge

interface ContinuationJudge

Something that knows how a signal tends to move, asked to rate proposed futures — for an Improviser that recombines memory, a second opinion on which continuation is plausible. It sees every frame actually played (observe) and returns a loss for a proposed path (pathchannel`, starting at output sampleindex); lower is better, null for no opinion.afield.math.temporal.ForecastJudge` rates paths against a Timestral forecast.

Declaration Summary
fun observe(index: Long, frame: DoubleArray)
fun loss(index: Long, path: Array<DoubleArray>): Double?

JudgeMode

enum class JudgeMode

How an Improviser uses its ContinuationJudge.

Constants: Prefer, Veto.

Reason

enum class Reason

Why a Segment began. Hold repeats the last frame because no acceptable continuation exists; after holding for a phrase, Restart takes the closest match there is, whatever its distance or correction, rather than hold for ever.

Constants: Start, Continue, SuffixLink, EpisodeEnd, Cue, Takeover, Hold, Restart.

Segment

data class Segment(val outputStart: Int, val source: Int, val episode: Int, val reason: Reason, val distance: Double, val context: Int, val correction: Double)

One stretch of output played from one place in memory. outputStart is the output sample it began on; source the memory sample it began from; episode which demonstration that is. distance is the join distance (feature units) that admitted the jump, context the shared context length in samples, and correction the size of the join correction it needed.

Performance

class Performance(val output: Trajectory, val source: IntArray, val correction: DoubleArray, val owned: Array<BooleanArray>, val segments: List<Segment>)

What an improviser played: the output itself, and for every output sample the memory sample it came from, the size of the join correction applied, and which channels a performer owned.

Declaration Summary
val output: Trajectory
val source: IntArray Memory sample per output sample (-1 while holding).
val correction: DoubleArray Join-correction size per output sample: RMS over channels, in channel spreads (0 = played as recorded).
val owned: Array<BooleanArray> ownedchannel``: true where the performer drove that channel.
val segments: List<Segment>
fun sourceSeconds(): List<Double> The source trace in seconds of memory, ready to plot: a staircase of straight runs and jumps.
fun sourceReuse(window: Int = (output.sampleRate * 10).toInt()): Double Fraction of output samples whose source passage was played at least once before within window samples.
fun coverage(memoryLength: Int): Double Fraction of memory samples that appear somewhere in the output.
fun outputRepetition(window: Double = 2.0, threshold: Double = 0.25, channels: List<Int> = output.channels.indices.toList(), rate: Double = 10.0): Double How much the output repeats itself, whatever memory it came from: the fraction of window-second stretches of output that closely match some earlier stretch, at least one window back. "Closely" is an RMS difference below threshold channel spreads, measured on channels (all by default; pick a few for a wide trajectory), with the output resampled to rate Hz to keep the comparison cheap.
fun joinRoughness(memory: Trajectory): Double How rough the joins are, against the recording: the mean |second difference| within three samples of every jump, divided by the memory's mean |second difference|, averaged over channels. 1 means joins are as smooth as the recording is on average; above 1, the seams show. NaN with no jumps.
fun accelerationDistance(memory: Trajectory): Double Kolmogorov–Smirnov distance between the output's and the memory's distributions of |second difference| (how sharply things change), worst channel: 0 = indistinguishable, 1 = disjoint.
override fun toString()

Improviser

class Improviser(val oracle: VariableMarkovOracle, seed: Long = 1, val policy: Policy = Policy.Oracle, val minPhrase: Double = 1.0, val jumpProbability: Double = 0.2, val maxJoin: Double = 2.0, val temperature: Double = 1.0, val joinTime: Double = 0.15, val cueWindow: Double = 1.0, val cueInterval: Double = 0.25, val declineDistance: Double = 1.5, val maxCorrection: Double = 1.0, val takeoverMargin: Double = 0.7, val judge: ContinuationJudge? = null, val judgeWeight: Double = 2.0, val judgeAhead: Double = 2.0, val judgeMode: JudgeMode = JudgeMode.Prefer, val reuseWeight: Double = 1.0, val reuseMemory: Double = 10.0, val bridges: Boolean = true, val restartTolerance: Double = 2.0, val escapeReplays: Boolean = true, val vetoRatio: Double = 1.5, start: Int = 0)

Plays an oracle's memory forward one sample at a time, recombining it.

Declaration Summary
val oracle: VariableMarkovOracle
val policy: Policy
val minPhrase: Double
val jumpProbability: Double
val maxJoin: Double
val temperature: Double
val joinTime: Double
val cueWindow: Double
val cueInterval: Double
val declineDistance: Double
val maxCorrection: Double
val takeoverMargin: Double
val judge: ContinuationJudge?
val judgeWeight: Double
val judgeAhead: Double
val judgeMode: JudgeMode
val reuseWeight: Double
val reuseMemory: Double
val bridges: Boolean
val restartTolerance: Double
val escapeReplays: Boolean
val vetoRatio: Double
val segments Segments played so far (the most recent 10 000).
var judgedDecisions Jump decisions the judge scored.
var judgeChanged Judged decisions the judge altered: in JudgeMode.Prefer, its most likely choice differed from the unjudged one; in JudgeMode.Veto, it removed at least one candidate.
var judgeVetoed Candidate jumps the judge vetoed (JudgeMode.Veto).
var judgeStayed Judged decisions that ended with staying on the current passage.
val position: Int Memory sample of the last output (or -1 when holding).
var lastCorrection Correction magnitude applied to the last output.
fun perform(channel: Int, value: Double) Hands channel to the performer at value; call every sample while performing.
fun perform(name: String, value: Double) Hands the channel named name to the performer.
fun release(channel: Int) Gives channel back to the memory, fading from the performed value over joinTime.
fun release(name: String)
fun isPerformed(channel: Int) True while channel is performer-owned.
fun reset(seed: Long, at: Int = 0) Restart from memory sample at with a fresh random sequence.
fun step(): DoubleArray Advances one sample and returns the output frame.
fun take(samples: Int, script: (Improviser.(Int) -> Unit)? = null): Performance Runs samples steps and returns the Performance. script runs before each step with the step index, for scripted takeovers: { i -> if (i in 300..450) perform("x", 0.0) else if (i == 451) release("x") }.
fun take(seconds: Double, script: (Improviser.(Int) -> Unit)? = null): Performance Runs for seconds of output.

Trajectory.kt

Sequence memory for low-dimensional continuous time series: remember one or a few demonstrations, cue the memory with a recent trajectory, and let it continue in a related style while a performer takes over channels.

Trajectory holds uniformly sampled multichannel recordings with episode boundaries; GestureFeatures turns one into scaled value-and-velocity frames; VariableMarkovOracle (Wang and Dubnov's VMO, a Factor Oracle over thresholded continuous frames) indexes the repeated material; Improviser walks it, recording where every output sample came from. Tests: VariableMarkovOracleTest.

src/jvmMain/kotlin/afield/math/oracle/Trajectory.kt — read the source

Used in: Continuing a Gesture: Sequence Memory and Forecasting

Trajectory

class Trajectory(val sampleRate: Double, val channels: List<String>, val values: Array<DoubleArray>, val episodeStarts: IntArray = intArrayOf(0), val name: String = "")

A uniformly sampled multichannel recording: valueschannel` at [sampleRate`](math.md#trajectory-kt--trajectory--samplerate) samples per second, split into one or more episodes.

Declaration Summary
val sampleRate: Double Samples per second.
val channels: List<String> One name per channel.
val values: Array<DoubleArray> valueschannel``; every channel has the same length.
val episodeStarts: IntArray First sample of each episode, ascending, starting with 0.
val name: String Where this recording came from (free text, kept for provenance).
val length: Int Number of samples.
val width: Int Number of channels.
val duration: Double Duration in seconds.
fun channel(name: String): DoubleArray The samples of channel name.
fun frame(t: Int): DoubleArray All channels at sample t.
fun episodeOf(t: Int): Int Index of the episode containing sample t.
fun endsEpisode(t: Int): Boolean True when t is the last sample of its episode, so t + 1 is not its continuation.
fun startsEpisode(t: Int): Boolean True when t begins an episode.
fun episode(e: Int): Trajectory Episode e as a trajectory of its own.
fun series(prefix: String = ""): Array<Pair<String, List<Double>>> Every channel as a name to samples pair, ready for showSignals(*t.series()).
fun resample(rate: Double): Trajectory Resampled to rate by linear interpolation, episode by episode.
fun trimStill(threshold: Double = 0.25, pad: Double = 0.3): Trajectory Each episode with its still lead-in and tail removed: samples at the start and end where the speed (RMS over channels of the step, each channel in units of its own typical step) stays below threshold times the episode's median speed. pad seconds of the stillness are kept at each end, so a take still begins and ends at rest. An episode that is still throughout is kept whole.
override fun toString()

GestureFeatures

class GestureFeatures(val center: DoubleArray, val scale: DoubleArray, val velocityScale: DoubleArray, val velocityWeight: Double = 1.0, val channelWeights: DoubleArray = DoubleArray(center.size) { 1.0 }, val shapeOnly: Boolean = false)

Frozen per-channel scaling and the feature frames a VariableMarkovOracle compares.

Declaration Summary
val center: DoubleArray
val scale: DoubleArray
val velocityScale: DoubleArray
val velocityWeight: Double
val channelWeights: DoubleArray
val shapeOnly: Boolean
val width: Int Channels.
val dimension: Int Length of one feature frame.
fun frame(current: DoubleArray, previous: DoubleArray?, into: DoubleArray = DoubleArray(dimension)): DoubleArray Features of a single sample given its predecessor's values (previous = null at an episode start).
fun frames(t: Trajectory): Array<DoubleArray> Feature frames for every sample of t; velocity is 0 at each episode start.

VariableMarkovOracle.kt

Building a Variable Markov Oracle over a recording, and asking it where a performance could continue from.

VariableMarkovOracle indexes a Trajectory once, in its constructor, and then answers three kinds of question about it. Its suffix links (sfx) and repeated-suffix lengths (lrs) say where the material repeats itself, which is what Improviser walks. cue ranks positions by how well the samples before them match a window of recent history, over any subset of channels, which is how a performer's input finds a place in memory. bridges finds the nearest moments in other episodes, where suffix links rarely reach, so several separately recorded takes can be played as one memory.

The one parameter that matters is the threshold at which two frames count as the same symbol, and build chooses it rather than asking for it: it constructs an oracle at each of a range of candidate thresholds and keeps the one with the highest total information rate. That rate is near zero at both ends of the range, since a threshold too small finds no repeats and one too large makes everything a repeat, so the choice is a peak rather than a limit.

The construction is a Kotlin port of Cheng-i Wang's reference Python vmo package, kept in Kotlin so an oracle can be built and queried inside the frame loop. VmoReferenceTest checks the suffix links, the repeat lengths and the summed information rate against a fixture generated from the pinned package.

src/jvmMain/kotlin/afield/math/oracle/VariableMarkovOracle.kt — read the source

VariableMarkovOracle

class VariableMarkovOracle internal constructor(val trajectory: Trajectory, val features: GestureFeatures, val frames: Array<DoubleArray>, val threshold: Double)

A Variable Markov Oracle (Wang and Dubnov, 2014) over a Trajectory: a Factor Oracle (Allauzen, Crochemore and Raffinot, 1999) built on continuous frames, where two frames count as "the same symbol" when their GestureFeatures lie within threshold of each other.

Declaration Summary
val trajectory: Trajectory The remembered material.
val features: GestureFeatures The scaling and feature construction used for every comparison.
val frames: Array<DoubleArray> Feature frames, one per sample.
val threshold: Double Frames closer than this are the same symbol.
val sfx Suffix link per state; sfx[0] = -1.
val lrs Length of the repeated suffix per state.
val symbol Symbol (cluster of frames within threshold) per state; state 0 is symbol 0.
val size: Int Number of states (samples + 1).
fun transitions(i: Int): IntArray Forward transitions out of state i.
fun reverseSuffixes(i: Int): IntArray States whose suffix link points at i.
val alphabetSize: Int Number of distinct symbols: states whose suffix link is the start state.
fun informationRate(): DoubleArray Information rate per sample, in bits: the reference package's IR(ir_type='cum').
fun totalInformationRate(): Double Sum of informationRate over the oracle: the quantity the threshold search maximises.
fun cue(history: List<DoubleArray>, window: Int = min(history.size, (trajectory.sampleRate).toInt()), mask: BooleanArray = BooleanArray(features.width) { true }, count: Int = 5, separation: Int = window / 2): List<Cue> Ranked places in memory whose preceding window samples best match history, the most recent samples last (history[t](imaging.md#imaging-kt--channel), raw values).
fun bridges(sample: Int): IntArray Cross-episode transitions from sample sample: up to three samples in other episodes whose feature frames (value and velocity) are nearest to it, nearest first. The oracle's own suffix links rarely cross between separately recorded takes; these give an improviser a way across. Computed for every sample on first use (all pairs of samples, so a few seconds for a few thousand samples). Empty for a single episode.
fun matchDistance(history: List<DoubleArray>, position: Int, window: Int = min(history.size, (trajectory.sampleRate).toInt()), mask: BooleanArray = BooleanArray(features.width) { true }): Double How well history matches the window samples of memory ending at position (the same distance cue ranks by), or +∞ if that window crosses an episode boundary.
override fun toString()

Cue

data class Cue(val position: Int, val distance: Double, val context: Int)

A place in memory matching a cue: continue from sample position. Lower distance is better.

correlations.kt

Measures of statistical dependence between two equal-length DoubleArrays, batch and rolling.

StatsDependence carries the batch versions: pearson, spearman (Pearson on average-tie ranks), kendallTauB, chatterjeeXi (aliased as xi), distanceCorrelation and micTicNaive. Kendall's tau-b and distance correlation are both O(n²) in time, and distance correlation additionally materialises two n×n matrices, so neither suits long series.

RollingDistanceCorrelation holds a fixed window and updates its maintained row and grand sums in O(n) per push; RollingXiCorrelation only keeps the window and recomputes xi from scratch. All of it is pure Kotlin with no external dependency.

micTicNaive is an equipartition-grid approximation rather than either published algorithm: it bins x and y by rank into equal counts, scans every (xBins, yBins) whose product stays inside a budget that defaults to n^0.6, and takes the largest normalised mutual information it finds (the sum over the same grids is its TIC). That is MIC- and TIC-shaped without being either MICe/TICe or the MINE dynamic program, so its values will not match a reference implementation's. Use it for intuition and prototyping.

src/jvmMain/kotlin/afield/math/statistics/correlations.kt — read the source

Used in: Values over time: interpolants & keyframes, Measuring dependence between two signals

Top-level functions

Declaration Summary
fun main() A scratch demo rather than part of the API: prints every coefficient in this file for y = x² over an x grid symmetric about zero — the textbook case where pearson reads about 0 while the dependence is in fact total, so StatsDependence.chatterjeeXi and StatsDependence.distanceCorrelation read high.

StatsDependence

object StatsDependence

The batch dependence coefficients: hand any of these two equal-length DoubleArrays and get one number back per notion of "related" — pearson for linear association, spearman and kendallTauB for monotone association on ranks, chatterjeeXi for "is y a function of x", distanceCorrelation for dependence of any shape, micTicNaive for a grid mutual-information scan.

Declaration Summary
fun ranksAverageTies(values: DoubleArray): DoubleArray Ranks in 1..n, smallest value first, with tied values sharing the average of the positions they occupy: 3, 1, 4, 1, 5 comes back as 3.0, 1.5, 4.0, 1.5, 5.0, since the two 1s sit at positions 1 and 2. The result therefore always sums to n(n+1)/2 whatever the input looks like.
fun pearson(x: DoubleArray, y: DoubleArray): Double Pearson's product-moment correlation, in -1..1: 1 is a perfect increasing straight line, -1 a perfect decreasing one, and 0 means no linear relationship — which is emphatically not the same as no relationship, as the symmetric parabola in main demonstrates.
fun spearman(x: DoubleArray, y: DoubleArray): Double Spearman's rank correlation — pearson applied to each array's ranksAverageTies, which puts a monotone-but-curved relationship at ±1 where Pearson reads less: exactly +1 for any strictly increasing pair, exactly -1 for any strictly decreasing one, whatever curve connects them.
fun kendallTauB(x: DoubleArray, y: DoubleArray): Double Kendall's tau-b, in -1..1: concordant minus discordant pairs over all pairs, corrected for ties in either variable. Like spearman it measures monotone rather than linear association, and the two agree in sign throughout; tau-b is the more conservative of the pair in magnitude.
fun chatterjeeXi(x: DoubleArray, y: DoubleArray): Double Chatterjee's xi, which is asymmetric: it measures how nearly y is a function of x, so swapping the arguments asks a different question and returns a different number. Near 0 for independence, near 1 when y is a noiseless function of x — including a non-monotone one such as a parabola, where pearson reads 0 and kendallTauB not much more.
fun xi(x: DoubleArray, y: DoubleArray): Double Short alias for chatterjeeXi, for when the formula rather than the surname is what you have in mind. Same asymmetry: the first argument is the predictor.
fun distanceCorrelation(x: DoubleArray, y: DoubleArray): Double Székely–Rizzo distance correlation, in 0..1 — and unlike pearson it is 0 only for genuine independence, which is what makes it the one to reach for when you do not want to guess the shape of the relationship first. It is symmetric in its arguments and never negative, so it tells you how much dependence there is and nothing at all about direction.
data class MineLikeResult(val mic: Double, val tic: Double, val bestXBins: Int, val bestYBins: Int) What micTicNaive reports: the best (mic) and the summed (tic) normalized mutual-information score across every grid it scanned, plus the bestXBins × bestYBins grid that produced the maximum. Read the caveat on micTicNaive before trusting any of the four.
fun micTicNaive(x: DoubleArray, y: DoubleArray, bMax: Int? = null): MineLikeResult A MIC/TIC-style scan: bin x and y onto every grid whose cell count fits a budget, score normalized mutual information on each, and report the maximum and the sum. Unlike the correlation coefficients above it is blind to the shape of the relationship: a parabola or a sine scores about as high as a straight line.

RollingDistanceCorrelation

class RollingDistanceCorrelation(private val windowSize: Int)

Distance correlation over a sliding window, maintained incrementally: push a sample pair, get the current 0..1 coefficient back, at O(w) work per sample for a window of w rather than the O(w²) rebuild StatsDependence.distanceCorrelation would charge you.

Declaration Summary
fun push(x: Double, y: Double): Double? Push one (x,y) sample. If the window is full, evicts the oldest.
fun current(): Double? Current distance correlation (no mutation).

RollingXiCorrelation

class RollingXiCorrelation(private val windowSize: Int)

Chatterjee's xi over a sliding window, recomputed from scratch on every push — no incremental trickery, just StatsDependence.chatterjeeXi over the window's current contents, which costs O(w log w) per sample and is exactly as trustworthy as the batch answer.

Declaration Summary
fun push(x: Double, y: Double): Double? Push one (x, y) sample — evicting the oldest if the window is already full — and return xi over the window as it now stands, or null while fewer than two samples have arrived.
fun current(): Double? Xi over the window's current contents without pushing anything, or null while fewer than two samples have arrived. Copies the window out in arrival order first, though that order does not reach the answer: StatsDependence.chatterjeeXi sorts by x itself and resolves x-ties by expectation rather than by position, so the result depends only on which pairs are in the window and not on where in the ring buffer they happen to sit.
fun clear() Forget every buffered sample. The next push starts a fresh window and returns null again, exactly as it did on a new instance.

dirichlet.kt

2D multiplicatively weighted Voronoi (Apollonius) diagrams. Each site is a Vec3 of (x, y, weight) and cells are decided by the score |x - p|² / w².

CircularDirichlet.build returns a Diagram holding one CellBoundary per site, its pieces sorted angularly around that site. Because the bisector of two differently weighted sites is a circle — and a line only when the weights match — boundaries arrive as ArcPiece and LinePiece rather than polygon edges; call toPolyline / toPolylines on a piece to tessellate it, and pass an Aabb clip, since unbounded lines cannot be flattened without one and clipping an arc may split it into several polylines.

All weights must be > 0 (build requires it). The construction is triple-nested over the sites — every bisector is intersected against every other, and each candidate piece is kept only if a midpoint sample really is on the cell boundary — so it is aimed at modest site counts. Pure Kotlin on Field's Vec2/Vec3; the 3D counterpart is CircularDirichlet3d in voronoi.kt.

src/jvmMain/kotlin/afield/math/statistics/dirichlet.kt — read the source

CircularDirichlet

object CircularDirichlet

Declaration Summary
data class Aabb(val min: Vec2, val max: Vec2)
sealed interface SupportingCurve
data class SupportCircle(val center: Vec2, val radius: Float) : SupportingCurve
data class SupportLine(val point: Vec2, val direction: Vec2, val normal: Vec2, val c: Float) : SupportingCurve
sealed interface EdgePiece
data class ArcPiece(override val support: SupportCircle, val startAngle: Float, val sweepAngle: Float, override val startCorner: Vec2?, override val endCorner: Vec2?, private val sample: Vec2, override val neighborSite: Int) : EdgePiece
data class LinePiece(override val support: SupportLine, val t0: Float, val t1: Float, override val startCorner: Vec2?, override val endCorner: Vec2?, private val sample: Vec2, override val neighborSite: Int) : EdgePiece
data class CellBoundary(val siteIndex: Int, val pieces: List<EdgePiece>)
data class Diagram(val cells: List<CellBoundary>)
fun build(sites: List<Vec3>, tolerance: Float = 1e-4f): Diagram

randoms.kt

Box-scoped randomness. random, randomGaussian and setRandomSeed draw from a java.util.Random held as a property on the current Box, so re-running a box replays the same sequence; with no Box in context they fall back to one process-wide generator seeded with STDLIB_RANDOM_DEFAULT_SEED.

Alongside the seed each box carries a noise phase you advance yourself (noisePhase, noiseForward, setNoisePhase). That phase and seed drive noise1D, a small Perlin-style hashed 1D noise, and the perlinNoise extensions on Vector2f/Vector3f/Vector4f, which take one independent noise channel per component.

All the vector extensions — perlinNoise and the uniform-jitter noise(d) — mutate the receiver in place and return it rather than allocating. nextGaussian is a Box-Muller extension that fills the gap left by kotlin.random.Random, which has no Gaussian of its own.

src/jvmMain/kotlin/afield/math/statistics/randoms.kt — read the source

Used in: Flow Matching, State estimation: tracking what you cannot measure

Top-level functions

Declaration Summary
fun random(): Double A uniform sample from [0, 1) — zero possible, one not — drawn from the current box's generator.
fun randomGaussian(): Double A standard-normal sample (mean 0, standard deviation 1) from the current box's generator.
fun KotlinRandom.nextGaussian(): Double Standard-normal (Box-Muller) sample from any kotlin.random.Random. The Kotlin stdlib Random has no nextGaussian() of its own (unlike java.util.Random), so this fills the gap for code that carries its own seeded generator.
fun setRandomSeed(seed: Long? = null): Long Reseeds the current box's generator and resets its noise phase to 0, returning the seed used.
fun noisePhase(): Float Where along its noise curve this box currently is — the default phase noise1D samples at.
fun noiseForward(delta: Float): Float Advances this box's noise phase by delta and returns the new phase.
fun setNoisePhase(phase: Float): Float Jumps this box's noise phase straight to phase, returning it. Use to drive noise from a clock.
fun noise1D(phase: Float = currentStdlibPhase(), seedOffset: Long = 0L, frequency: Float = 1f, extraHash: Long = 0L): Float Repeatable 1D Perlin-style noise: smooth, continuous, and bounded by ±0.5.
fun Vector2f.perlinNoise(amplitude: Float, phaseAdvance: Float = 0f, frequency: Float = 1f, seedOffset: Long = 0L, extraHash: Long = 0L): Vector2f Adds smooth noise1D wander to this point in place, one independent channel per component, and returns the same instance.
fun Vector3f.perlinNoise(amplitude: Float, phaseAdvance: Float = 0f, frequency: Float = 1f, seedOffset: Long = 0L, extraHash: Long = 0L): Vector3f Adds smooth noise1D wander to this point in place — three independent channels, one per component — and returns the same instance. Conventions exactly as the Vector2f overload; this one consumes noise channels seedOffset through seedOffset + 2.
fun Vector4f.perlinNoise(amplitude: Float, phaseAdvance: Float = 0f, frequency: Float = 1f, seedOffset: Long = 0L, extraHash: Long = 0L): Vector4f Adds smooth noise1D wander to all four components in place, one independent channel each, and returns the same instance. Conventions as the Vector2f overload. Note that on a colour this wanders alpha too, which is rarely what you want — jitter the Vector3f and rebuild the alpha.
fun Vector2f.noise(d: Number) : Vector2f Jitters this point in place by an independent uniform draw per component and returns the same instance.
fun Vector3f.noise(d: Number) : Vector3f Jitters all three components in place by up to ±d/2 each, uniformly and independently, and returns the same instance. As the Vector2f overload: white noise, not smooth wander.
fun Vector4f.noise(d: Number) : Vector4f Jitters all four components in place by up to ±d/2 each, uniformly and independently, and returns the same instance. As the Vector2f overload — including alpha, if this is a colour.

Top-level values

Declaration Summary
const val STDLIB_RANDOM_DEFAULT_SEED: Long The seed every box's generator starts from, and the seed setRandomSeed undoes.

smile_interpolation_regression.kt

Two 1D fitters that wrap the Smile library: smileCubicSpline (cubic spline interpolation through the data) and smileGaussianProcess (Gaussian-process regression with a Gaussian kernel, configured by SmileGpOptions).

Both take parallel xs/ys arrays, copy them, and require at least two finite samples with xs strictly increasing — a violation throws rather than producing a bad fit with no complaint. The returned SmileCubicSpline1D / SmileGaussianProcess1D are callable via invoke and also expose predict for a single x or for a whole array.

GP fitting failures propagate. No interpolator is substituted for a failed probabilistic model. For 1D/2D/3D inference, composed kernels, heteroscedastic noise and coherent samples see afield.math.gp.GaussianProcess.

src/jvmMain/kotlin/afield/math/statistics/smile_interpolation_regression.kt — read the source

Top-level functions

Declaration Summary
fun smileCubicSpline(xs: DoubleArray, ys: DoubleArray): SmileCubicSpline1D
fun smileGaussianProcess(xs: DoubleArray, ys: DoubleArray, options: SmileGpOptions = SmileGpOptions()): SmileGaussianProcess1D

SmileGpOptions

data class SmileGpOptions(val lengthScale: Double = 0.15, val noise: Double = 1e-3, val normalize: Boolean = true, val tol: Double = 1e-4, val maxIter: Int = 500)

Declaration Summary
val lengthScale: Double
val noise: Double Smile uses noise VARIANCE in normalized output units when normalize is enabled. New GP APIs use noiseStd.
val normalize: Boolean
val tol: Double
val maxIter: Int

SmileCubicSpline1D

class SmileCubicSpline1D internal constructor(val xs: DoubleArray, val ys: DoubleArray, private val model: CubicSplineInterpolation1D)

Declaration Summary
val xs: DoubleArray
val ys: DoubleArray
fun predict(x: Double): Double
fun predict(xs: DoubleArray): DoubleArray
operator fun invoke(x: Double): Double

SmileGaussianProcess1D

class SmileGaussianProcess1D internal constructor(val xs: DoubleArray, val ys: DoubleArray, val options: SmileGpOptions, private val model: GaussianProcessRegression<DoubleArray>)

Declaration Summary
val xs: DoubleArray
val ys: DoubleArray
val options: SmileGpOptions
fun predict(x: Double): Double
fun predictWithVariance(x: Double): GpPrediction Latent posterior uncertainty in the original output units.
fun posterior(xs: DoubleArray): GpJoint Seedable coherent draws through Field's semidefinite-aware joint sampler.
fun predict(xs: DoubleArray): DoubleArray
operator fun invoke(x: Double): Double

stats.kt

Descriptive statistics, and the SVG/HTML renderings Field uses to put them in a box output.

buildSummaryStats reduces a sequence of doubles to a SummaryStats — count, quartiles, IQR, MAD, variance, skewness, excess kurtosis — dropping and counting non-finite values as it goes. buildHistogram picks a bin width as the smallest of the Freedman–Diaconis, Scott and Sturges estimates, clamped to at most 2000 bins. SummaryStatsBuffer wraps both behind a synchronized, capped sliding window you can add to sample by sample; there are parallel Vec2SummaryStats / Vec3SummaryStats / Vec4SummaryStats forms that carry per-component stats, vector length and the pairwise component correlations, each with their own buffer.

The rendering half — histogramToSvg, seriesPlotToSvg and summaryStatsBoxToHtml — builds SVG and HTML by string interpolation, with no drawing library involved, and defaults to Field's cyan-on-black look through a long list of colour and padding parameters. Pass the previous SummaryStats and changed numbers are tinted by the sign and relative size of the change. seriesPlotToSvg envelope-downsamples to maxSeriesPoints (500) before drawing.

src/jvmMain/kotlin/afield/math/statistics/stats.kt — read the source

Used in: Measuring dependence between two signals

Top-level functions

Declaration Summary
fun buildSummaryStats(rawValues: Iterable<Double>, droppedCount: Int = 0, onDrop: ((droppedCount: Int) -> Unit)? = null): SummaryStats Builds descriptive summary statistics from the finite values in rawValues.
fun buildVec2SummaryStats(rawValues: Iterable<Vec2>, droppedCount: Int = 0, onDrop: ((droppedCount: Int) -> Unit)? = null): Vec2SummaryStats Summarizes finite 2D vectors by component, length, angle, and X/Y correlation.
fun buildVec3SummaryStats(rawValues: Iterable<Vec3>, droppedCount: Int = 0, onDrop: ((droppedCount: Int) -> Unit)? = null): Vec3SummaryStats Summarizes finite 3D vectors by component, length, and pairwise correlations.
fun buildVec4SummaryStats(rawValues: Iterable<Vec4>, droppedCount: Int = 0, onDrop: ((droppedCount: Int) -> Unit)? = null): Vec4SummaryStats Summarizes finite 4D vectors by component, length, and pairwise component correlations.
fun buildHistogram(rawValues: Iterable<Double>, onDrop: ((droppedCount: Int) -> Unit)? = null): Histogram Bin rawValues into a Histogram, choosing the bin width itself: the smallest of the Freedman–Diaconis, Scott and Sturges estimates, floored at range/2000 so the bin count can never exceed 2000. There is no bin-count parameter — if you need a specific one, bin it yourself.
fun histogramToSvg(hist: Histogram, previousStats: SummaryStats? = null, widthPx: Int = 480, heightPx: Int = 240, padLeft: Int = 14, padRight: Int = 14, padTop: Int = 10, padBottom: Int = 22, fontFamily: String = "'Iosevka Web', Iosevka, monospace", title: String? = null, showCountsOnHover: Boolean = true, decimals: Int = 3, background: String = "#000000", textColor: String = "#0af", tickColor: String = "#0af", axisColor: String = "#0af", barFill: String = "#0af", barFillOpacity: Double = 0.2, barStroke: String = "#0af", barStrokeOpacity: Double = 0.5, barStrokeWidth: Double = 1.5, keyTextOpacity: Double = 0.5): String Render a histogram to an SVG string.
fun seriesPlotToSvg(rawValues: Iterable<Double>, previousStats: SummaryStats? = null, widthPx: Int = 480, heightPx: Int = 240, padLeft: Int = 40, padRight: Int = 64, padTop: Int = 10, padBottom: Int = 22, fontFamily: String = "'Iosevka Web', Iosevka, monospace", title: String? = null, decimals: Int = 3, background: String = "#000000", textColor: String = "#0af", tickColor: String = "#0af", axisColor: String = "#0af", seriesColor: String = "#0af", seriesOpacity: Double = 0.85, seriesStrokeWidth: Double = 1.5, refDash: String = "3,2", keyTextOpacity: Double = 0.5, minLabelSeparationPx: Double = 12.0, maxSeriesPoints: Int = 500): String Render a time-series plot to SVG.
fun summaryStatsBoxToHtml(summary: SummaryStatsBox, previousSummary: SummaryStatsBox? = null, widthPx: Int = 480, chartHeightPx: Int = 120, decimals: Int = 3, fontFamily: String = "'Iosevka Web', Iosevka, monospace", background: String = "#000000", textColor: String = "#0af", axisColor: String = "#0af", keyTextOpacity: Double = 0.5, showFrame: Boolean = false, showHeader: Boolean = true, histogramTitle: String? = "Histogram", seriesTitle: String? = "Series", chartPadLeft: Int = 28, chartPadRight: Int = 28, chartPadTop: Int = 22, chartPadBottom: Int = 26): String Render a whole SummaryStatsBox as one self-contained HTML card: a three-column grid of thirteen metrics, then a histogramToSvg and a seriesPlotToSvg of the same window, all inline. No <img>, no <script>, nothing to fetch — hand it to display and it draws.

HistogramBin

data class HistogramBin(val start: Double, val end: Double, val count: Int)

One bin of a Histogram: the half-open interval [start, end) and how many samples fell in it.

Histogram

data class Histogram(val bins: List<HistogramBin>, val min: Double, val q1: Double, val median: Double, val q3: Double, val max: Double, val binWidth: Double)

A histogram and the five-number summary of the same data, as buildHistogram returns them.

Declaration Summary
val bins: List<HistogramBin>
val min: Double
val q1: Double
val median: Double
val q3: Double
val max: Double
val binWidth: Double
val count: Int
val modeBin: HistogramBin?

SummaryStats

data class SummaryStats(val count: Int, val droppedCount: Int, val min: Double, val q1: Double, val median: Double, val q3: Double, val max: Double, val sum: Double, val mean: Double, val sampleVariance: Double, val sampleStdDev: Double, val populationVariance: Double, val populationStdDev: Double, val iqr: Double, val mad: Double, val skewness: Double, val kurtosisExcess: Double)

Seventeen descriptive numbers about one sample, as buildSummaryStats returns them.

Declaration Summary
val count: Int
val droppedCount: Int
val min: Double
val q1: Double
val median: Double
val q3: Double
val max: Double
val sum: Double
val mean: Double
val sampleVariance: Double
val sampleStdDev: Double
val populationVariance: Double
val populationStdDev: Double
val iqr: Double
val mad: Double
val skewness: Double
val kurtosisExcess: Double
val isEmpty: Boolean
val range: Double
val coeffOfVariation: Double

SummaryStatsBox

data class SummaryStatsBox(val values: List<Double>, val stats: SummaryStats, val histogram: Histogram, val windowSize: Int, val samplesSeen: Long, val droppedNonFinite: Long, val droppedByCapacity: Long)

Everything SummaryStatsBuffer.summaryBox knows in one value: the window's values in arrival order, their stats and histogram, and the buffer's three counters — so it is what summaryStatsBoxToHtml needs to render a whole card, and what you keep if you want to pass a previous card to it and get the changed numbers tinted.

Declaration Summary
val values: List<Double>
val stats: SummaryStats
val histogram: Histogram
val windowSize: Int
val samplesSeen: Long
val droppedNonFinite: Long
val droppedByCapacity: Long

Vec2SummaryStats

data class Vec2SummaryStats(val x: SummaryStats, val y: SummaryStats, val length: SummaryStats, val angleRadians: SummaryStats, val correlationXY: Double)

buildVec2SummaryStats's answer: a full SummaryStats per component, plus one for the vector length and one for the angleRadians (atan2(y, x), so in −π..π), plus the Pearson correlation between the two components.

Declaration Summary
val x: SummaryStats
val y: SummaryStats
val length: SummaryStats
val angleRadians: SummaryStats
val correlationXY: Double
val count: Int
val droppedCount: Int
val mean: Vec2
val median: Vec2
val min: Vec2
val max: Vec2

Vec3SummaryStats

data class Vec3SummaryStats(val x: SummaryStats, val y: SummaryStats, val z: SummaryStats, val length: SummaryStats, val correlationXY: Double, val correlationXZ: Double, val correlationYZ: Double)

buildVec3SummaryStats's answer: a SummaryStats per component and one for the vector length, plus the three pairwise component correlations. There is no angle summary here — Vec2SummaryStats has one because a 2D direction is a single number and a 3D one is not.

Declaration Summary
val x: SummaryStats
val y: SummaryStats
val z: SummaryStats
val length: SummaryStats
val correlationXY: Double
val correlationXZ: Double
val correlationYZ: Double
val count: Int
val droppedCount: Int
val mean: Vec3
val median: Vec3
val min: Vec3
val max: Vec3

Vec4SummaryStats

data class Vec4SummaryStats(val x: SummaryStats, val y: SummaryStats, val z: SummaryStats, val w: SummaryStats, val length: SummaryStats, val correlationXY: Double, val correlationXZ: Double, val correlationXW: Double, val correlationYZ: Double, val correlationYW: Double, val correlationZW: Double)

buildVec4SummaryStats's answer: a SummaryStats per component and one for the vector length, plus all six pairwise component correlations.

Declaration Summary
val x: SummaryStats
val y: SummaryStats
val z: SummaryStats
val w: SummaryStats
val length: SummaryStats
val correlationXY: Double
val correlationXZ: Double
val correlationXW: Double
val correlationYZ: Double
val correlationYW: Double
val correlationZW: Double
val count: Int
val droppedCount: Int
val mean: Vec4
val median: Vec4
val min: Vec4
val max: Vec4

Vec2SummaryStatsBox

data class Vec2SummaryStatsBox(val values: List<Vec2>, val stats: Vec2SummaryStats, val windowSize: Int, val samplesSeen: Long, val droppedNonFinite: Long, val droppedByCapacity: Long)

What Vec2SummaryStatsBuffer.summaryBox returns: the window's vectors in arrival order, their stats, and the buffer's three counters. The scalar twin, SummaryStatsBox, also carries a Histogram; these do not, because there is no single histogram of a vector — ask the buffer for Vec2SummaryStatsBuffer.lengthHistogram if that is what you want.

Declaration Summary
val values: List<Vec2>
val stats: Vec2SummaryStats
val windowSize: Int
val samplesSeen: Long
val droppedNonFinite: Long
val droppedByCapacity: Long

Vec3SummaryStatsBox

data class Vec3SummaryStatsBox(val values: List<Vec3>, val stats: Vec3SummaryStats, val windowSize: Int, val samplesSeen: Long, val droppedNonFinite: Long, val droppedByCapacity: Long)

What Vec3SummaryStatsBuffer.summaryBox returns — see Vec2SummaryStatsBox; the only difference is the stats type.

Declaration Summary
val values: List<Vec3>
val stats: Vec3SummaryStats
val windowSize: Int
val samplesSeen: Long
val droppedNonFinite: Long
val droppedByCapacity: Long

Vec4SummaryStatsBox

data class Vec4SummaryStatsBox(val values: List<Vec4>, val stats: Vec4SummaryStats, val windowSize: Int, val samplesSeen: Long, val droppedNonFinite: Long, val droppedByCapacity: Long)

What Vec4SummaryStatsBuffer.summaryBox returns — see Vec2SummaryStatsBox; the only difference is the stats type.

Declaration Summary
val values: List<Vec4>
val stats: Vec4SummaryStats
val windowSize: Int
val samplesSeen: Long
val droppedNonFinite: Long
val droppedByCapacity: Long

SummaryStatsBuffer

class SummaryStatsBuffer(maxSize: Int = 100)

Rolling numeric summary + histogram buffer: add samples one at a time, ask for a snapshot, a histogram, a whole summaryBox or a rendered summaryHtml whenever you like.

Declaration Summary
var maxSize: Int
var samplesSeen: Long
var droppedNonFinite: Long
var droppedByCapacity: Long
val size: Int
fun setWindow(size: Int): SummaryStatsBuffer
fun clear(resetCounters: Boolean = false): SummaryStatsBuffer
fun add(value: Number?): SummaryStatsBuffer+1 overload · also Double Adds a numeric sample and returns this buffer for chaining.
fun addAll(values: Iterable<Number?>): SummaryStatsBuffer
fun values(): List<Double>
fun snapshot(): SummaryStats
fun histogram(): Histogram
fun summaryBox(): SummaryStatsBox
fun histogramSvg(previousStats: SummaryStats? = null, widthPx: Int = 480, heightPx: Int = 240, padLeft: Int = 14, padRight: Int = 14, padTop: Int = 10, padBottom: Int = 22, fontFamily: String = "'Iosevka Web', Iosevka, monospace", title: String? = null, showCountsOnHover: Boolean = true, decimals: Int = 3, background: String = "#000000", textColor: String = "#0af", tickColor: String = "#0af", axisColor: String = "#0af", barFill: String = "#0af", barFillOpacity: Double = 0.2, barStroke: String = "#0af", barStrokeOpacity: Double = 0.5, barStrokeWidth: Double = 1.5, keyTextOpacity: Double = 0.5): String
fun seriesSvg(previousStats: SummaryStats? = null, widthPx: Int = 480, heightPx: Int = 240, padLeft: Int = 40, padRight: Int = 64, padTop: Int = 10, padBottom: Int = 22, fontFamily: String = "'Iosevka Web', Iosevka, monospace", title: String? = null, decimals: Int = 3, background: String = "#000000", textColor: String = "#0af", tickColor: String = "#0af", axisColor: String = "#0af", seriesColor: String = "#0af", seriesOpacity: Double = 0.85, seriesStrokeWidth: Double = 1.5, refDash: String = "3,2", keyTextOpacity: Double = 0.5, minLabelSeparationPx: Double = 12.0): String
fun summaryHtml(widthPx: Int = 480, chartHeightPx: Int = 120, decimals: Int = 3, fontFamily: String = "'Iosevka Web', Iosevka, monospace", background: String = "#000000", textColor: String = "#0af", axisColor: String = "#0af", keyTextOpacity: Double = 0.5, previousSummary: SummaryStatsBox? = null): String

Vec2SummaryStatsBuffer

class Vec2SummaryStatsBuffer(maxSize: Int = 100)

The Vec2 twin of SummaryStatsBuffer: add vectors one at a time into a capped, synchronized sliding window, then ask for a snapshot, a summaryBox or a lengthHistogram.

Declaration Summary
var maxSize: Int
var samplesSeen: Long
var droppedNonFinite: Long
var droppedByCapacity: Long
val size: Int
fun setWindow(size: Int): Vec2SummaryStatsBuffer
fun clear(resetCounters: Boolean = false): Vec2SummaryStatsBuffer
fun add(value: Vec2?): Vec2SummaryStatsBuffer
fun addAll(values: Iterable<Vec2?>): Vec2SummaryStatsBuffer
fun values(): List<Vec2>
fun snapshot(): Vec2SummaryStats
fun summaryBox(): Vec2SummaryStatsBox
fun lengthHistogram(): Histogram
fun lengthHistogramSvg(widthPx: Int = 480, heightPx: Int = 240, title: String? = "Vec2 length", decimals: Int = 3): String

Vec3SummaryStatsBuffer

class Vec3SummaryStatsBuffer(maxSize: Int = 100)

The Vec3 twin of SummaryStatsBuffer — see Vec2SummaryStatsBuffer for the counters and the all-components-must-be-finite rule. snapshot carries the three pairwise component correlations.

Declaration Summary
var maxSize: Int
var samplesSeen: Long
var droppedNonFinite: Long
var droppedByCapacity: Long
val size: Int
fun setWindow(size: Int): Vec3SummaryStatsBuffer
fun clear(resetCounters: Boolean = false): Vec3SummaryStatsBuffer
fun add(value: Vec3?): Vec3SummaryStatsBuffer
fun addAll(values: Iterable<Vec3?>): Vec3SummaryStatsBuffer
fun values(): List<Vec3>
fun snapshot(): Vec3SummaryStats
fun summaryBox(): Vec3SummaryStatsBox
fun lengthHistogram(): Histogram
fun lengthHistogramSvg(widthPx: Int = 480, heightPx: Int = 240, title: String? = "Vec3 length", decimals: Int = 3): String

Vec4SummaryStatsBuffer

class Vec4SummaryStatsBuffer(maxSize: Int = 100)

The Vec4 twin of SummaryStatsBuffer — see Vec2SummaryStatsBuffer for the counters and the all-components-must-be-finite rule. snapshot carries all six pairwise component correlations.

Declaration Summary
var maxSize: Int
var samplesSeen: Long
var droppedNonFinite: Long
var droppedByCapacity: Long
val size: Int
fun setWindow(size: Int): Vec4SummaryStatsBuffer
fun clear(resetCounters: Boolean = false): Vec4SummaryStatsBuffer
fun add(value: Vec4?): Vec4SummaryStatsBuffer
fun addAll(values: Iterable<Vec4?>): Vec4SummaryStatsBuffer
fun values(): List<Vec4>
fun snapshot(): Vec4SummaryStats
fun summaryBox(): Vec4SummaryStatsBox
fun lengthHistogram(): Histogram
fun lengthHistogramSvg(widthPx: Int = 480, heightPx: Int = 240, title: String? = "Vec4 length", decimals: Int = 3): String

timeseries.kt

Streaming, time-domain filters over Float signals — the sample-at-a-time counterpart to the frequency-domain code in spectral.kt.

Everything here implements the FloatFilter fun interface (one Float in, one Float out) and composes with then, so a chain reads as DCBlocker() then EMAFilter(0.1f) then NoiseGate(0.05f). Three families share that interface: smoothers and classic filters (EMAFilter, AlphaBetaFilter, OneEuroFilter, BiquadFilter with its LowPassFilter / HighPassFilter / BandPassFilter / BandRejectFilter constructors); rolling distribution fits — exponential, gamma, beta and Rayleigh — each paired with a Normalizer mapping the fitted CDF into a target range and an EventDetector firing on tail probability (ZScoreEventDetector instead thresholds on standard deviations from a rolling mean); and utility stages such as MedianFilter, SchmittTrigger, AGC, PIDController, LeakyIntegrator and DynamicRangeCompressor.

Pure Kotlin with no external dependency: the log-gamma, regularized incomplete gamma and regularized incomplete beta functions the distribution fits need are implemented privately at the top of this file. Filters are stateful objects, so use one instance per signal, and the window-based ones allocate their ring buffer up front from windowSize.

src/jvmMain/kotlin/afield/math/statistics/timeseries.kt — read the source

Used in: Signal examples: time and frequency

Top-level functions

Declaration Summary
fun LowPassFilter(frequency: Float, sampleRate: Float, q: Float = 0.707f) A BiquadFilter that passes what is below frequency (Hz) and rolls off above it at 12 dB/octave. sampleRate is the rate of the stream in Hz — both are needed, because only their ratio means anything. q defaults to the maximally-flat 0.707; raise it for a resonant bump at the corner.
fun HighPassFilter(frequency: Float, sampleRate: Float, q: Float = 0.707f) A BiquadFilter that passes what is above frequency (Hz) and rolls off below it at 12 dB/octave — the steeper, better-specified alternative to DCBlocker once you know the corner you actually want.
fun BandPassFilter(frequency: Float, sampleRate: Float, q: Float = 0.707f) A BiquadFilter that keeps a band around frequency (Hz) and rejects both sides of it. With a single biquad, q is the bandwidth (bandwidth ≈ frequency / q), so the 0.707 default is very wide indeed — push it to 4 or 8 for something that isolates a component.
fun BandRejectFilter(frequency: Float, sampleRate: Float, q: Float = 0.707f) A BiquadFilter that notches out frequency (Hz) and passes everything else — mains hum, a rig's resonance, one offending tone. Higher q gives a narrower notch; this is BiquadType.NOTCH under a friendlier name.

FloatFilter

fun interface FloatFilter

One Float in, one Float out — the shape every filter in this file takes, and the reason they chain. A filter is a stateful object called once per sample, in arrival order: it is not a pure function, and calling it is what advances its state, so never call one twice on the same sample just to read a number back out of it.

Declaration Summary
operator fun invoke(value: Float): Float Push one sample through and take this stage's output for it, advancing the filter's state.
infix fun then(next: FloatFilter): FloatFilter Series composition, left to right: a then b feeds a's output into b, so DCBlocker() then EMAFilter(0.1f) takes the offset out first and smooths what is left.

EMAFilter

class EMAFilter(var alpha: Float) : FloatFilter

Exponential moving average — the cheapest useful smoother, out = alpha * in + (1 - alpha) * out. One multiply-add and one field of state, which is why it is so often the first thing in a chain.

Declaration Summary
override fun invoke(value: Float): Float The smoothed value for value — on the very first call, value itself.

AlphaBetaFilter

class AlphaBetaFilter(var alpha: Float, var beta: Float, var dt: Float = 1f) : FloatFilter

A constant-velocity tracker with fixed gains — a Kalman filter's structure without its bookkeeping. It carries a position and a velocity, predicts one step ahead, then corrects both from the residual, so unlike EMAFilter it keeps up with a steadily moving signal instead of trailing behind it.

Declaration Summary
override fun invoke(value: Float): Float The corrected position estimate for value. The internal velocity is updated too, but is not exposed.

OneEuroFilter

class OneEuroFilter(var minCutoff: Float = 1.0f, var beta: Float = 0.0f, var dCutoff: Float = 1.0f, var sampleRate: Float? = null) : FloatFilter

The 1€ filter (Casiez, Roussel & Vogel): a low-pass whose cutoff rises with the signal's own speed, so a slow-moving hand gets smoothed hard while a fast gesture is barely delayed. The usual pick for pointer, touch or tracker input, where lag is more objectionable than jitter.

Declaration Summary
var minCutoff: Float Cutoff (Hz) used when the signal is standing still — lower is smoother and laggier.
var beta: Float How strongly measured speed raises the cutoff: cutoff = minCutoff + beta * \|speed\|. At 0 the adaptation is off and this is an ordinary one-pole low-pass at minCutoff.
var dCutoff: Float Cutoff (Hz) of the low-pass applied to the speed estimate itself. Raise it if the adaptation feels sluggish, lower it if noise in the speed makes the cutoff jitter.
var sampleRate: Float? The rate in Hz the samples were taken at. When set, dt is 1 / sampleRate on every call; when null, dt is measured from the wall clock between calls.
override fun invoke(value: Float): Float The filtered sample. Without sampleRate this reads the clock, so the amount of smoothing depends on when you call it, not only on what you pass in.

BiquadType

enum class BiquadType

Which of the RBJ audio-EQ-cookbook shapes a BiquadFilter realises. LOWPASS, HIGHPASS, BANDPASS and NOTCH are determined entirely by frequency and Q; PEAK, LOWSHELF and HIGHSHELF are the only three that read gainDb — the others accept it and ignore it.

Constants: LOWPASS, HIGHPASS, BANDPASS, NOTCH, PEAK, LOWSHELF, HIGHSHELF.

BiquadFilter

class BiquadFilter(val type: BiquadType, var frequency: Float, var sampleRate: Float, var q: Float = 0.707f, var gainDb: Float = 0f) : FloatFilter

A second-order IIR section — two poles, two zeros — run as a direct-form I recursion with coefficients from the RBJ audio-EQ cookbook. This is the machinery behind LowPassFilter, HighPassFilter, BandPassFilter and BandRejectFilter, and what to reach for when you want a filter with an actual corner frequency rather than a vague smoothing coefficient.

Declaration Summary
val type: BiquadType The response shape. Fixed at construction — build a new filter to change it.
var frequency: Float Corner (or centre) frequency in Hz. Call calculateCoefficients after changing it.
var sampleRate: Float The rate in Hz of the stream you will push through: the reference frequency is measured against, and not something the filter can discover for itself.
var q: Float Resonance. 0.707 is maximally flat (Butterworth); higher peaks at the corner and rings, and for BiquadType.BANDPASS / BiquadType.NOTCH higher also means a narrower band.
var gainDb: Float Boost (positive) or cut (negative) in dB — read only by BiquadType.PEAK, BiquadType.LOWSHELF and BiquadType.HIGHSHELF, and silently ignored by the other four.
fun calculateCoefficients() Re-derive the five coefficients from the current type, frequency, sampleRate, q and gainDb. Run once at construction; run it again yourself after mutating any of those, because nothing else will. It leaves the delay line alone, so a mid-stream retune stays continuous in the signal but can step the output audibly.
override fun invoke(value: Float): Float One step of y = b0·x + b1·x₋₁ + b2·x₋₂ − a1·y₋₁ − a2·y₋₂, shifting the two input and two output delays along afterwards.

RollingStats

class RollingStats(val windowSize: Int) : FloatFilter

A tap rather than a filter: every sample passes through unchanged, and the window's mean, variance, stdDev and the current sample's zScore are left behind for you to read. Drop it anywhere in a then chain to find out what the signal is doing at that point without disturbing it.

Declaration Summary
var mean: Float Arithmetic mean of the window, the sample just pushed included.
var variance: Float Sample (n−1) variance of the window; 0 until a second sample has arrived.
var stdDev: Float Square root of variance.
var zScore: Float How many standard deviations the sample just pushed sits from mean — signed, and 0 whenever stdDev is 0. Note the sample is itself in the window it is being compared against.
override fun invoke(value: Float): Float Records value into the window, refreshes all four statistics, and hands value straight back.

RollingExponentialStats

class RollingExponentialStats(val windowSize: Int, var epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Fits an exponential distribution to the last windowSize samples by moment matching (lambda = 1 / mean) and reports where the newest sample falls within it. A tap: the sample passes through unchanged.

Declaration Summary
val windowSize: Int How many samples the fit sees. Must be >= 2; the ring buffer is allocated up front.
var epsilon: Float Degeneracy floor. While the fitted mean or rate is at or below this the fit is abandoned rather than dividing by something near zero.
var mean: Float Mean of the window after negative clamping — the only statistic an exponential fit needs.
var lambda: Float Fitted rate 1 / mean, or 0 while the window mean is at or below epsilon.
var variance: Float Variance of the fitted distribution, 1 / lambda² — not the sample variance of the window.
var stdDev: Float Square root of variance, so also 1 / lambda.
var cdf: Float P(X <= x) under the fit for the sample just pushed, in 0..1.
var tailProbability: Float P(X >= x) = 1 - cdf for the sample just pushed: small means a surprisingly large sample. Sits at 1 during the degenerate case, so a silent signal reports nothing as unusual.
var clippedNegativeCount: Long How many negative samples have been clamped to zero so far. Anything but 0 means the exponential is the wrong model for this signal — take abs() first, or use RollingBetaStats.
override fun invoke(value: Float): Float Clamps value to >= 0, refits the window, updates cdf and tailProbability — and returns the original value, clamping and all left behind in the fit.

ExponentialNormalizer

class ExponentialNormalizer(windowSize: Int, var targetMin: Float = 0f, var targetMax: Float = 1f, epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Rescales a non-negative stream by rank rather than by magnitude: each sample's fitted exponential CDF, mapped onto targetMin..targetMax. What you want when an unbounded intensity has to drive something that expects a bounded control value, and "big for this signal lately" matters more than the units.

Declaration Summary
var targetMin: Float The value a CDF of 0 maps to.
var targetMax: Float The value a CDF of 1 maps to. May be set below targetMin to invert the mapping.
val stats The fit doing the work — read stats.lambda, stats.tailProbability or stats.clippedNegativeCount to see what the normaliser is basing its answer on.
override fun invoke(value: Float): Float Pushes value through the fit and returns its CDF mapped into targetMin..targetMax.

ExponentialEventDetector

class ExponentialEventDetector(val windowSize: Int, var tailProbabilityThreshold: Float = 0.01f, var influence: Float = 0f, var epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Fires when a sample is improbably large for the exponential distribution the preceding window fits — an onset / spike detector for non-negative streams, in the same family as ZScoreEventDetector but thresholding tail probability instead of standard deviations.

Declaration Summary
val windowSize: Int Samples of history behind the fit. Must be >= 2, and nothing can fire until this many have arrived.
var tailProbabilityThreshold: Float Fire when the sample's upper-tail probability falls to this or below — at 0.01, "in the top 1% of the fitted distribution". Clamped into 0..1 on use.
var influence: Float How much of an event's own sample is written into the window, 0..1 (clamped): 0 keeps events out of the fit entirely, 1 lets them recalibrate it so a sustained shift stops being news.
var epsilon: Float Degeneracy floor — while the window mean is at or below this, no events are reported at all.
var isEvent: Boolean Whether the most recent sample fired — the same news as a nonzero return from invoke.
var tailProbability: Float Upper-tail probability of the most recent sample; 1 during warm-up and while the fit is degenerate.
var lambda: Float Fitted exponential rate 1 / mean over the window, or 0 while degenerate.
var clippedNegativeCount: Long How many negative samples have been clamped to zero — anything but 0 means the wrong model for this signal.
override fun invoke(value: Float): Float 1f if value is an event, 0f otherwise — including for every sample of the warm-up.

RollingGammaStats

class RollingGammaStats(val windowSize: Int, var epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Fits a gamma distribution to the last windowSize samples by matching mean and variance (shape = mean²/variance, scale = variance/mean) and reports where the newest sample falls within it. A tap: the sample passes through unchanged.

Declaration Summary
val windowSize: Int How many samples the fit sees. Must be >= 2; the ring buffer is allocated up front.
var epsilon: Float Degeneracy floor for the window's mean and variance, and the lower bound clamped onto the fitted shape and scale.
var mean: Float Arithmetic mean of the window after negative clamping — half of the moment-matching fit.
var variance: Float Sample (n−1) variance of the window — a genuine sample statistic here, and the other half of the fit.
var stdDev: Float Square root of variance.
var shape: Float Fitted gamma shape mean²/variance (the k parameter): 1 would be an exponential, larger is more symmetric. 0 while the fit is degenerate.
var scale: Float Fitted gamma scale variance/mean (the θ parameter), in the units of your signal. 0 while degenerate.
var cdf: Float P(X <= x) under the fit for the sample just pushed, in 0..1.
var tailProbability: Float 1 - cdf: the share of the fitted distribution lying above the current sample. Small means surprisingly large.
var clippedNegativeCount: Long How many negative samples have been clamped to zero — anything but 0 means a gamma is the wrong model here.
override fun invoke(value: Float): Float Clamps value to >= 0, re-matches shape and scale over the window, updates cdf and tailProbability, and returns the original value untouched.

GammaNormalizer

class GammaNormalizer(windowSize: Int, var targetMin: Float = 0f, var targetMax: Float = 1f, epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Rescales a non-negative, skewed stream onto targetMin..targetMax by its fitted gamma CDF — the two-parameter sibling of ExponentialNormalizer, and the better pick whenever the signal's bulk sits somewhere away from zero rather than hugging it.

Declaration Summary
var targetMin: Float The value a CDF of 0 maps to.
var targetMax: Float The value a CDF of 1 maps to. May be set below targetMin to invert the mapping.
val stats The fit doing the work — read stats.shape and stats.scale to see what distribution the answers came from.
override fun invoke(value: Float): Float Pushes value through the fit and returns its CDF mapped into targetMin..targetMax.

GammaEventDetector

class GammaEventDetector(val windowSize: Int, var tailProbabilityThreshold: Float = 0.01f, var influence: Float = 0f, var epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Fires when a sample lands in the upper tail of the gamma distribution the preceding window fits. The general-purpose distribution-aware onset detector in this file: it copes with the skewed, non-negative streams — magnitudes, energies, abs() of a motion signal — where a z-score's assumption of symmetry makes it over-fire on the low side. This is the detector the published signal examples page (spectral_examples) reaches for.

Declaration Summary
val windowSize: Int Samples of history behind the fit. Must be >= 2, and nothing fires until this many have arrived.
var tailProbabilityThreshold: Float Fire when the sample's upper-tail probability falls to this or below — at 0.01, "in the top 1% of the fitted distribution". Clamped into 0..1 on use.
var influence: Float How much of an event's own sample enters the window, 0..1 (clamped): 0 keeps events out of the fit, 1 lets them recalibrate it.
var epsilon: Float Degeneracy floor for the window's mean and variance — below it, no events are reported.
var isEvent: Boolean Whether the most recent sample fired.
var cdf: Float P(X <= x) of the most recent sample under the fit; 0 during warm-up and while degenerate.
var tailProbability: Float 1 - cdf for the most recent sample — the quantity actually compared with tailProbabilityThreshold.
var shape: Float Fitted gamma shape over the window, or 0 while the fit is degenerate.
var scale: Float Fitted gamma scale over the window, in the units of your signal, or 0 while degenerate.
var clippedNegativeCount: Long How many negative samples have been clamped to zero — anything but 0 means the wrong model for this signal.
override fun invoke(value: Float): Float 1f if value is an event, 0f otherwise — including for every sample of the warm-up.

RollingBetaStats

class RollingBetaStats(val windowSize: Int, var supportMin: Float = 0f, var supportMax: Float = 1f, var epsilon: Float = 1e-4f) : FloatFilter

Fits a beta distribution to the last windowSize samples by moment matching and reports where the newest one falls within it. A tap: the sample passes through unchanged.

Declaration Summary
val windowSize: Int How many samples the fit sees. Must be >= 2; the ring buffer is allocated up front.
var supportMin: Float Bottom of your signal's assumed range; anything below maps to 0 and counts as clipped.
var supportMax: Float Top of your signal's assumed range; anything above maps to 1 and counts as clipped. Must be strictly greater than supportMin at construction.
var epsilon: Float How far inside the open interval (0, 1) stored samples are held, and the degeneracy floor for the moment fit. Defaults to 1e-4, looser than the other fits in this file.
var mean: Float Mean of the window in normalised 0..1 units, not in the units of your signal.
var variance: Float Sample (n−1) variance of the window, also in normalised units.
var stdDev: Float Square root of variance.
var alpha: Float Fitted beta α, the shape pulling mass toward 1; 0 when the fit fell back to min/max normalisation.
var beta: Float Fitted beta β, the shape pulling mass toward 0; 0 when the fit fell back to min/max normalisation.
var cdf: Float P(X <= x) under the fit for the sample just pushed, in 0..1 — or, when the fit is degenerate, simply that sample's normalised position within the support.
var tailProbabilityLow: Float Lower-tail probability of the current sample; equal to cdf. Small means unusually low for this window.
var tailProbabilityHigh: Float Upper-tail probability of the current sample, 1 - cdf. Small means unusually high.
var clippedToSupportCount: Long How many samples have fallen outside supportMin..supportMax and been clipped into it. A count that keeps climbing means the support is wrong.
var normalizedValue: Float The sample just pushed, mapped onto 0..1 across the support — clipped there but before the epsilon nudge, so unlike the value stored in the window this one can be exactly 0 or 1.
override fun invoke(value: Float): Float Normalises value into the support, refits the window, updates the CDF and both tails, and returns the original value unchanged.

BetaNormalizer

class BetaNormalizer(windowSize: Int, var targetMin: Float = 0f, var targetMax: Float = 1f, supportMin: Float = 0f, supportMax: Float = 1f, epsilon: Float = 1e-4f) : FloatFilter

Rescales a bounded stream onto targetMin..targetMax by its fitted beta CDF — histogram equalisation with a two-parameter distribution standing in for the histogram. Where RollingNormalizer merely stretches the window's extremes apart, this spreads samples out according to how they are actually distributed, so a signal that spends its life bunched in one corner of its range comes out using the whole target range.

Declaration Summary
var targetMin: Float The value a CDF of 0 maps to.
var targetMax: Float The value a CDF of 1 maps to. May be set below targetMin to invert the mapping.
val stats The fit doing the work. Also where the support lives after construction — and where clippedToSupportCount will tell you the support is wrong.
override fun invoke(value: Float): Float Pushes value through the fit and returns its CDF mapped into targetMin..targetMax.

BetaEventDetector

class BetaEventDetector(val windowSize: Int, var supportMin: Float = 0f, var supportMax: Float = 1f, var lowerTailThreshold: Float = 0.01f, var upperTailThreshold: Float = 0.01f, var influence: Float = 0f, var epsilon: Float = 1e-4f) : FloatFilter

A two-sided event detector for a bounded stream: it fires both when a sample is improbably high for the window's fitted beta and when it is improbably low, and the sign of the output says which. -1f for a lower-tail event, +1f for an upper-tail one, 0f for nothing — including for the whole windowSize-sample warm-up.

Declaration Summary
val windowSize: Int Samples of history behind the fit. Must be >= 2, and nothing fires until this many have arrived.
var supportMin: Float Bottom of your signal's assumed range; anything below is clipped to it and counted.
var supportMax: Float Top of your signal's assumed range; must be strictly greater than supportMin at construction.
var lowerTailThreshold: Float Fire low when the CDF falls to this or below — at 0.01, "in the bottom 1% of the fit". Clamped into 0..1; set above 0.5 and nearly everything becomes an event.
var upperTailThreshold: Float Fire high when 1 - cdf falls to this or below — the top 1% at 0.01. Clamped into 0..1.
var influence: Float How much of an event's own sample enters the window, 0..1 (clamped): 0 keeps events out of the fit, 1 lets a sustained shift recalibrate it.
var epsilon: Float Boundary nudge for stored samples and degeneracy floor for the moment fit; 1e-4 by default.
var isEvent: Boolean Whether the most recent sample fired at either end. It does not say which end — the sign of invoke's return does.
var cdf: Float CDF of the most recent sample under the fit, or its normalised position when degenerate; parked at 0.5 during warm-up.
var alpha: Float Fitted beta α over the window; 0 when the fit fell back to plain normalisation.
var beta: Float Fitted beta β over the window; 0 when the fit fell back to plain normalisation.
var clippedToSupportCount: Long How many samples have been clipped into supportMin..supportMax — a climbing count means the support is wrong.
override fun invoke(value: Float): Float -1f for a lower-tail event, +1f for an upper-tail one, 0f for neither and for the whole warm-up. If both thresholds somehow fire at once, the lower tail wins.

RollingRayleighStats

class RollingRayleighStats(val windowSize: Int, var epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Fits a Rayleigh distribution to the last windowSize samples and reports where the newest one falls within it. A tap: the sample passes through unchanged.

Declaration Summary
val windowSize: Int How many samples the fit sees. Must be >= 2; the ring buffer is allocated up front.
var epsilon: Float Degeneracy floor on the window's second moment and on sigma — below it, nothing is reported.
var sigma: Float Fitted Rayleigh scale, sqrt(mean(x²) / 2); 0 while the window's second moment is at or below epsilon.
var mean: Float Mean of the fitted distribution, sigma * sqrt(π/2) — derived from sigma, not averaged from the window.
var variance: Float Variance of the fitted distribution, ((4 - π)/2) * sigma².
var stdDev: Float Square root of variance.
var cdf: Float P(X <= x) = 1 - exp(-x² / 2sigma²) for the sample just pushed.
var tailProbability: Float 1 - cdf: the share of the fitted distribution above the current sample. Small means a surprisingly large magnitude.
var clippedNegativeCount: Long How many negative samples have been clamped to zero. A magnitude should never be negative, so anything but 0 means the wrong signal is arriving here.
override fun invoke(value: Float): Float Clamps value to >= 0, re-estimates sigma from the window's second moment, updates the fitted moments and the CDF, and returns the original value unchanged.

RayleighNormalizer

class RayleighNormalizer(windowSize: Int, var targetMin: Float = 0f, var targetMax: Float = 1f, epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Rescales a stream of magnitudes onto targetMin..targetMax by its fitted Rayleigh CDF. The one to use when the numbers arriving are lengths of 2-D vectors — cursor speed, optical-flow magnitude, gradient strength — because a single-parameter fit adapts on far less history than GammaNormalizer needs.

Declaration Summary
var targetMin: Float The value a CDF of 0 maps to.
var targetMax: Float The value a CDF of 1 maps to. May be set below targetMin to invert the mapping.
val stats The fit doing the work — stats.sigma is the single number the whole mapping rests on.
override fun invoke(value: Float): Float Pushes value through the fit and returns its CDF mapped into targetMin..targetMax.

RayleighEventDetector

class RayleighEventDetector(val windowSize: Int, var tailProbabilityThreshold: Float = 0.01f, var influence: Float = 0f, var epsilon: Float = DISTRIBUTION_EPSILON) : FloatFilter

Fires when a magnitude is improbably large for the Rayleigh distribution the preceding window fits. The detector to pair with a speed or vector-length signal: a sudden lunge, a flick, a burst of optical flow.

Declaration Summary
val windowSize: Int Samples of history behind the fit. Must be >= 2, and nothing fires until this many have arrived.
var tailProbabilityThreshold: Float Fire when the sample's upper-tail probability falls to this or below — at 0.01, "in the top 1% of the fitted distribution". Clamped into 0..1 on use.
var influence: Float How much of an event's own sample enters the window, 0..1 (clamped): 0 keeps events out of the fit, 1 lets them recalibrate it.
var epsilon: Float Degeneracy floor on the window's second moment and on sigma — below it, no events are reported.
var isEvent: Boolean Whether the most recent sample fired.
var sigma: Float Fitted Rayleigh scale over the window, sqrt(mean(x²) / 2), or 0 while degenerate.
var cdf: Float P(X <= x) of the most recent sample under the fit; 0 during warm-up and while degenerate.
var tailProbability: Float 1 - cdf for the most recent sample — the quantity compared with tailProbabilityThreshold.
var clippedNegativeCount: Long How many negative samples have been clamped to zero; a magnitude should never be negative.
override fun invoke(value: Float): Float 1f if value is an event, 0f otherwise — including for every sample of the warm-up.

ZScoreEventDetector

class ZScoreEventDetector(val windowSize: Int, var threshold: Float, var influence: Float) : FloatFilter

The classic smoothed z-score peak detector: it fires when the newest sample sits more than threshold standard deviations from the mean of the window, and its output carries the direction — +1f above, -1f below, 0f for no event and throughout the warm-up.

Declaration Summary
val windowSize: Int Samples of history behind the mean and standard deviation; nothing fires until this many have arrived.
var threshold: Float How many standard deviations from the window mean counts as an event — 3 to 4 is a conventional starting point. The test is strict and on \|z\|, so it fires in both directions.
var influence: Float How much of an event's own sample enters the window, nominally 0..1 but not clamped: 0 substitutes the previous sample, so a sustained level shift keeps being reported forever, while 1 lets the detector adapt to it and fall silent.
var isEvent: Boolean Whether the most recent sample fired, in either direction.
override fun invoke(value: Float): Float +1f or -1f for an event above or below the window, 0f for none — and 0f for the first windowSize samples while the window fills, and whenever the window is perfectly flat.

AdaptiveCompander

class AdaptiveCompander(var attack: Float = 0.01f, var release: Float = 0.1f, var threshold: Float = 0.5f, var ratio: Float = 2.0f, var isCompressor: Boolean = true) : FloatFilter

An envelope follower plus a static gain curve: it either squashes the loud parts toward threshold (compressor) or pushes the quiet parts further down (expander), depending on isCompressor. Everything is in linear units, which makes this the plain, cheap sibling of DynamicRangeCompressor — that one does the same job in dB, with a soft knee and real attack and release times.

Declaration Summary
var attack: Float Per-sample coefficient (0..1) used while the envelope is rising; larger reacts faster to a growing signal.
var release: Float Per-sample coefficient (0..1) used while the envelope is falling; larger lets the gain recover faster after a peak.
var threshold: Float The linear amplitude where the curve bends — above it for a compressor, below it for an expander. Not dB.
var ratio: Float How hard the curve bends: as a compressor the excess above threshold is divided by this; as an expander the shortfall below it is multiplied by it.
var isCompressor: Boolean true compresses (pull the loud parts in), false expands (push the quiet parts down). Switchable mid-stream.
override fun invoke(value: Float): Float Updates the envelope from abs(value) and returns value scaled by the gain the curve asks for at that envelope.

AGC

class AGC(var targetLevel: Float = 1.0f, var attack: Float = 0.01f, var release: Float = 0.001f, var maxGain: Float = 10.0f) : FloatFilter

Automatic gain control: follows the signal's envelope and scales the signal so that envelope sits at targetLevel. Reach for it when you care about the shape of a signal whose absolute scale you cannot predict — a sensor whose range depends on how it was mounted, a control stream you want to hand to something expecting 0..1.

Declaration Summary
var targetLevel: Float The envelope level the gain chases, in the same linear units as your signal.
var attack: Float Per-sample coefficient (0..1) used when the envelope rises and when the gain must increase; larger is faster.
var release: Float Per-sample coefficient (0..1) used when the envelope falls and when the gain must decrease. The default is ten times slower than attack, so the gain backs off gently.
var maxGain: Float Ceiling on the multiplier, so a near-silent stretch cannot be amplified without limit.
override fun invoke(value: Float): Float Updates the envelope and the smoothed gain, then returns value multiplied by that gain.

LeakyIntegrator

class LeakyIntegrator(var leakRate: Float = 0.99f) : FloatFilter

A running sum that forgets: sum = sum * leakRate + value. The signal-processing way to ask "how much has been going on lately" — feed it event flags or rectified magnitudes and it climbs with activity, then decays back down when things go quiet.

Declaration Summary
override fun invoke(value: Float): Float Leaks the accumulator once, adds value, and returns the new total. Starts from 0 and has no reset.

RollingNormalizer

class RollingNormalizer(val windowSize: Int, var targetMin: Float = -1f, var targetMax: Float = 1f) : FloatFilter

Stretches the last windowSize samples onto targetMin..targetMax using their own minimum and maximum — the simplest auto-ranging there is, and the one to use when you know nothing about the signal's distribution and just need something bounded. Note the default target is -1..1, not 0..1.

Declaration Summary
override fun invoke(value: Float): Float Records value, then returns it rescaled from the window's current min..max onto targetMin..targetMax — or the midpoint of that range while the window is flat.

RollingOneDollarNormalizer

class RollingOneDollarNormalizer(val windowSize: Int) : FloatFilter

Centres the stream on its window mean and divides by the largest absolute deviation in that window, so the output lands in -1..1 with ±1 reached exactly by whichever sample is currently furthest from the mean. A symmetric, zero-centred alternative to RollingNormalizer: "which side of typical" stays meaningful, at the price of letting one outlier set the scale for a whole window.

Declaration Summary
override fun invoke(value: Float): Float Records value and returns its deviation from the window mean divided by the window's largest absolute deviation; 0 while the window is constant.

AdaptiveThresholder

class AdaptiveThresholder(val windowSize: Int = 100, var stdDevMultiplier: Float = 2.0f, var clamp: Boolean = false) : FloatFilter

Despite the name this returns a continuous value, not a decision: the sample's distance from the window mean, measured in units of stdDevMultiplier standard deviations. A sample sitting exactly stdDevMultiplier sigma above the mean comes out as +1, and it is thresholding that downstream — or a SchmittTrigger — that turns it into a decision.

Declaration Summary
val windowSize: Int Samples of history behind the mean and standard deviation. The buffer is allocated up front.
var stdDevMultiplier: Float How many standard deviations map to an output of 1. Larger is a gentler scale.
var clamp: Boolean Coerce the output into -1..1 instead of letting it run past the ends.
override fun invoke(value: Float): Float Records value and returns its deviation from the window mean divided by stdDev * stdDevMultiplier, optionally clamped to -1..1.

DerivativeFilter

class DerivativeFilter : FloatFilter

First difference: this sample minus the previous one. The cheapest way to turn something position-shaped into something velocity-shaped, or to find the edges in a slow signal.

Declaration Summary
override fun invoke(value: Float): Float The change since the previous sample, or 0 on the first call.

IntegralFilter

class IntegralFilter : FloatFilter

A plain running total of everything it has ever been given — for accumulating an event count, or for turning a velocity-shaped signal back into a position-shaped one.

Declaration Summary
override fun invoke(value: Float): Float Adds value to the total and returns the new total.

DCBlocker

class DCBlocker(var r: Float = 0.995f) : FloatFilter

Removes whatever constant offset a signal is riding on, without waiting for a window to fill: a one-pole, one-zero high-pass, y = x - x₋₁ + r·y₋₁, parked just above DC. The standard thing to put at the head of a chain when you care how a sensor is changing rather than where its zero happens to sit.

Declaration Summary
override fun invoke(value: Float): Float One step of y = x - x₋₁ + r·y₋₁ — the input with its slow-moving offset taken out.

PeakDetector

class PeakDetector(var decay: Float = 0.99f) : FloatFilter

A peak-hold envelope: it jumps instantly to abs(value) whenever that exceeds the current peak, and otherwise decays toward zero by a factor of decay each sample. What you want for a level meter, a "how hard was that hit" reading, or the denominator of a hand-rolled normaliser.

Declaration Summary
override fun invoke(value: Float): Float The held peak after value: its magnitude if that is a new high, otherwise the previous peak decayed once — but never less than the magnitude in hand.

SchmittTrigger

class SchmittTrigger(var lowThreshold: Float, var highThreshold: Float) : FloatFilter

A comparator with hysteresis: it goes high once the input rises above highThreshold and only comes back down once the input falls below lowThreshold, so a signal loitering around a single threshold cannot chatter. Returns 1f while high and 0f while low.

Declaration Summary
override fun invoke(value: Float): Float 1f while latched high and 0f while low, after letting value set or clear the latch.

MedianFilter

class MedianFilter(val windowSize: Int) : FloatFilter

Rolling median of the last windowSize samples. The spike killer: unlike any moving average it removes an isolated outlier completely instead of spreading it over its neighbourhood, and it leaves real edges where they are instead of smearing them into ramps.

Declaration Summary
override fun invoke(value: Float): Float Records value and returns the median of the window, averaging the two middle samples when the count is even.

NoiseGate

class NoiseGate(var threshold: Float, var attack: Float = 0.01f, var release: Float = 0.1f) : FloatFilter

Passes the signal through untouched while it is loud enough and hard-zeroes it when it is not. The gate opens when the tracked envelope rises above threshold and closes only once the envelope falls below half that, so a signal hovering at the threshold does not flutter.

Declaration Summary
override fun invoke(value: Float): Float value unchanged while the gate is open, 0f while it is shut — after this sample has updated the envelope.

PIDController

class PIDController(var kp: Float, var ki: Float, var kd: Float) : FloatFilter

A textbook proportional-integral-derivative controller wired up as a filter: it reads the measured value and returns the control effort that should push it toward setpoint. In a box this is what to reach for when one thing should chase another — a camera trailing a target, a value easing toward the mouse — with overshoot you get to tune rather than inherit.

Declaration Summary
var setpoint Where the controller is trying to get the measured value to. Safe to move mid-stream, though the accumulated integral term does not know it moved.
override fun invoke(value: Float): Float The control output for a measurement of value: kp·e + ki·Σe + kd·Δe, where e = setpoint - value.

CooldownFilter

class CooldownFilter(val cooldownSamples: Int) : FloatFilter

A debouncer for a stream of event flags: it lets a positive sample through, then swallows everything for the next cooldownSamples samples. Put it after a detector to turn a burst of flags at one onset into a single flag.

Declaration Summary
override fun invoke(value: Float): Float value itself if it is positive and the cooldown has elapsed, otherwise 0f.

StreamingWhitener

class StreamingWhitener(var alpha: Float = 0.01f, var epsilon: Float = 1e-5f) : FloatFilter

A z-score with no window: it tracks the mean and the variance exponentially and returns how far the current sample sits from that running mean, in running standard deviations. The O(1)-memory alternative to RollingStats plus arithmetic, and the natural pre-stage for anything that wants a roughly zero-mean, unit-variance input.

Declaration Summary
override fun invoke(value: Float): Float The current sample's distance from the running mean in running standard deviations; 0 on the first call and whenever the signal is flat.

BimodalThresholder

class BimodalThresholder(var alpha: Float = 0.05f, var interpolate: Boolean = false) : FloatFilter

Online two-means clustering of a stream: it keeps two centroids, moves whichever is nearer toward each new sample, and reports which of the two the sample joined. For a signal that really does live at two levels — a switch, a contact sensor, a light that is on or off, a gesture held or not — this finds the two levels for you instead of making you guess a threshold, and it follows them as they drift.

Declaration Summary
override fun invoke(value: Float): Float -1f or +1f for the lower or upper cluster — or the interpolated position between the centroids when interpolate is set — and 0f until both centroids have been seeded.

PercentileThresholder

class PercentileThresholder(val windowSize: Int = 100, var mapToMinusOneToOne: Boolean = true) : FloatFilter

Reports where the current sample ranks within the last windowSize samples: the fraction of the rest of the window lying strictly below it. (The current sample is in the window but can never count itself, so the denominator is one less than the fill level and a new high reads exactly 1.0.) Completely distribution-free — no mean, no standard deviation, no fitted law — so a heavy-tailed or lopsided signal cannot mislead it, and one wild outlier moves the output by exactly one rank.

Declaration Summary
override fun invoke(value: Float): Float The fraction of the rest of the window strictly below value — mapped onto -1..1, or left as 0..1 when mapToMinusOneToOne is false.

DynamicRangeCompressor

class DynamicRangeCompressor(var thresholdDb: Float = -20f, var ratio: Float = 4f, var kneeWidthDb: Float = 10f, var attackMs: Float = 5f, var releaseMs: Float = 50f, var sampleRate: Float = 44100f, var makeupGainDb: Float = 0f) : FloatFilter

A proper dB-domain compressor: envelope follower, soft knee, attack and release in milliseconds, makeup gain. Where AdaptiveCompander works in linear amplitude with per-sample coefficients, this one is specified the way a compressor usually is, which makes it the one to use on anything audio-shaped, or on anything whose timing you would rather state in milliseconds than in samples.

Declaration Summary
var thresholdDb: Float Level (dB, full scale = 0) above which gain reduction begins. Negative values are the norm.
var ratio: Float Compression ratio above the knee: 4 means 4 dB in over threshold yields 1 dB out. 1 is no compression at all.
var kneeWidthDb: Float Total width (dB) of the soft knee straddling thresholdDb — compression eases in across this span instead of switching on abruptly. 0 gives a hard knee.
var attackMs: Float How quickly (ms) the envelope follows a rising signal. Converted to a per-sample coefficient using sampleRate.
var releaseMs: Float How quickly (ms) the envelope falls after a peak, and so how quickly the gain recovers. Also converted using sampleRate.
var sampleRate: Float The rate (Hz) at which samples arrive, which is what gives attackMs and releaseMs their meaning. The 44100 default is an audio assumption — set it to your frame rate for a control signal.
var makeupGainDb: Float Flat gain (dB) applied after the compression, to win back the headroom it just took. Applied at all levels, reduction or not.
override fun invoke(value: Float): Float Updates the envelope, reads the knee curve at that level, and returns value scaled by the resulting gain plus makeupGainDb.

ForecastJudge.kt

A forecaster used as a second opinion on a sequence memory's choices, and the one place where the two ways of continuing a gesture meet.

ForecastJudge implements afield.math.oracle.ContinuationJudge over any TemporalSession. It watches every frame the improviser actually plays, asks the session for a fresh forecast at a fixed interval in the background, and scores a proposed passage by its mean pinball loss against that forecast's quantiles, per channel and divided by the channel's spread. The score never reaches the output: the improviser still plays its memory, and the judge only changes which part of it.

What the score can and cannot say is set by the forecast it reads. The quantiles are per-step marginals, so the loss asks whether each step of a passage sits inside the forecast's fan at that step, not how likely the passage is as a whole. Until the first forecast arrives, and wherever a passage runs past the end of the latest one, the judge returns no opinion and the improviser decides alone. The first failure the session reports is recorded and no further forecasts are requested.

Forecasts are collected without blocking the frame that asked for them, so an interactive session's timing does not depend on the model's latency, which the judge also records. For an offline render that has to come out the same on any machine, lockstep waits for each request instead.

src/jvmMain/kotlin/afield/math/temporal/ForecastJudge.kt — read the source

ForecastJudge

class ForecastJudge(val session: TemporalSession, val sampleRate: Double, val channels: List<String>, private val scale: DoubleArray, val history: Double = 20.0, val horizon: Double = 4.0, val refresh: Double = 1.0, val lockstep: Boolean = false) : ContinuationJudge, AutoCloseable

A forecaster as a ContinuationJudge: the hybrid where the oracle proposes and the forecaster judges.

Declaration Summary
val session: TemporalSession
val sampleRate: Double
val channels: List<String>
val history: Double
val horizon: Double
val refresh: Double
val lockstep: Boolean
var forecasts Forecasts received.
val latencies Request-to-reply latency of every forecast, in seconds.
var failure: Throwable? First failure reported by the session; the judge then has no opinion.
override fun observe(index: Long, frame: DoubleArray)
override fun loss(index: Long, path: Array<DoubleArray>): Double?
override fun close()

TemporalSession.kt

Continuing a multichannel time series live, from any backend that can propose a future.

A TemporalSession answers ContinuationRequests (recent history in, proposed future out) asynchronously. RecedingHorizon turns a session into something a frame loop can step without ever waiting: it keeps a buffer of planned samples, commits only a short prefix of each plan, replans in the background, drops replies made stale by a takeover, and reports underruns instead of extrapolating. Two sessions ship: OracleSession (explicit sequence memory, afield.math.oracle) and TimestralSession (Mistral's pretrained forecaster, in a separate Python worker). Tests: RecedingHorizonTest.

src/jvmMain/kotlin/afield/math/temporal/TemporalSession.kt — read the source

Used in: Continuing a Gesture: Sequence Memory and Forecasting

Top-level functions

Declaration Summary
fun Continuation.quantileIndex(q: Double): Int Index of the quantile level nearest q (0.5 is the median). Throws without quantiles.
fun Continuation.medianFLine(channel: Int, xs: DoubleArray): afield.geometry.path.FLine The proposal on channel drawn as y(x), with xs (one x per horizon step) — the thick line of a forecast plot.
fun Continuation.quantileFLine(channel: Int, q: Double, xs: DoubleArray): afield.geometry.path.FLine The q quantile on channel drawn as y(x).
fun Continuation.bandFLine(channel: Int, lower: Double, upper: Double, xs: DoubleArray): afield.geometry.path.FLine The region between the lower and upper quantiles on channel, as one closed FLine ready to fill. Nest several (0.01..0.99, 0.1..0.9, 0.25..0.75) for a fan chart. Like a Gaussian process's interval, each band is pointwise: it describes one step at a time, and no single performance is expected to stay inside it.

TemporalCapabilities

data class TemporalCapabilities(val quantileForecast: Boolean = false, val sampledAlternatives: Boolean = false, val futureCovariates: Boolean = false, val maskedHistory: Boolean = false, val onlineMemoryAppend: Boolean = false)

What a TemporalSession can do, declared rather than discovered.

Declaration Summary
val quantileForecast: Boolean Returns a quantile envelope around the central proposal.
val sampledAlternatives: Boolean Can return several distinct alternatives (not quantiles).
val futureCovariates: Boolean Accepts covariates known over the future horizon.
val maskedHistory: Boolean Accepts history with individual samples marked missing.
val onlineMemoryAppend: Boolean Can add new demonstrations while running.

ContinuationRequest

class ContinuationRequest(val revision: Long, val history: Trajectory, val horizon: Int, val owned: BooleanArray = BooleanArray(history.width), val known: Array<DoubleArray>? = null)

A request for horizon samples continuing history. revision identifies the history the request was made from; owned marks performer-driven channels (owned[channel](imaging.md#imaging-kt--channel)), and known carries covariates over history + horizon for a session with futureCovariates.

Declaration Summary
val revision: Long
val history: Trajectory
val horizon: Int
val owned: BooleanArray
val known: Array<DoubleArray>?

Continuation

class Continuation(val revision: Long, val proposal: Array<DoubleArray>, val quantiles: Array<Array<DoubleArray>>? = null, val quantileLevels: DoubleArray? = null, val diagnostics: Map<String, Any?> = emptyMap())

A proposed future: proposalchannel for t in 0 until horizon, starting at the sample after the request's history. quantileschannelq` at [quantileLevels](math.md#temporalsession-kt--continuation--quantilelevels) when the session provides them — an uncertainty display per sample, not a set of joint performances. [diagnostics`](math.md#temporalsession-kt--continuation--diagnostics) is backend-specific (latency, source positions, model identity).

Declaration Summary
val revision: Long
val proposal: Array<DoubleArray>
val quantiles: Array<Array<DoubleArray>>?
val quantileLevels: DoubleArray?
val diagnostics: Map<String, Any?>
val horizon: Int

TemporalSession

interface TemporalSession : AutoCloseable

A backend that proposes continuations. Replies may arrive on any thread.

Declaration Summary
val capabilities: TemporalCapabilities
fun propose(request: ContinuationRequest): CompletableFuture<Continuation>
override fun close()

OracleSession

class OracleSession(val oracle: VariableMarkovOracle, private var seed: Long = 1, val configure: (VariableMarkovOracle, Long, Int) -> Improviser = { o, s, start -> Improviser(o, seed = s, start = start) }) : TemporalSession

The oracle as a TemporalSession: cue the memory with the request's history (only the performer's channels, if any are owned), then let an Improviser play forward from the best match for the horizon. Diagnostics carry source (memory sample per proposed sample) and cueDistance. Proposals are computed on the caller's thread and complete immediately.

Declaration Summary
override val capabilities
override fun propose(request: ContinuationRequest): CompletableFuture<Continuation>

Underflow

enum class Underflow

What RecedingHorizon does when the plan runs out before a new one arrives.

Constants: Hold, Stop.

RecedingHorizon

class RecedingHorizon(val session: TemporalSession, seed: Trajectory, val history: Double = 10.0, val horizon: Double = 4.0, val commit: Double = 1.0, val releaseTime: Double = 0.3, val underflow: Underflow = Underflow.Hold, val lockstep: Boolean = false) : AutoCloseable

Plays a TemporalSession in a frame loop without blocking on it.

Declaration Summary
val session: TemporalSession
val history: Double
val horizon: Double
val commit: Double
val releaseTime: Double
val underflow: Underflow
val lockstep: Boolean
var revision Bumped by every takeover, release and reset; replies from older revisions are dropped.
var underruns Steps on which no planned sample was available.
var staleReplies Replies discarded as stale.
val latencies Request-to-reply latency of every accepted plan, in seconds.
val currentPlan: Continuation? The most recent accepted plan, for plotting its envelope.
val currentPlanStart: Long Output sample index at which currentPlan starts.
var releaseCorrection Size of the fade applied on the last step (scaled by channel spread), 0 when none.
var failure: Throwable? First failure reported by the session, if any; the player then follows underflow.
fun perform(channel: Int, value: Double)
fun perform(name: String, value: Double)
fun release(channel: Int)
fun release(name: String)
fun reset() Drops the plan and anything in flight; the next step replans from the current history.
fun step(): DoubleArray Advances one sample; never blocks unless lockstep.
fun take(samples: Int, script: (RecedingHorizon.(Int) -> Unit)? = null): Played Runs samples steps (with an optional per-step script) and returns what was played.
fun take(seconds: Double, script: (RecedingHorizon.(Int) -> Unit)? = null)
override fun close()

Played

class Played(val output: Trajectory, val owned: Array<BooleanArray>, val band: Array<Array<DoubleArray>>, val underruns: Int, val staleReplies: Int, val latencies: List<Double>)

What a RecedingHorizon played. band[channel](imaging.md#imaging-kt--channel)0|1`` is the outer quantile pair of the plan each sample came from (NaN without quantiles) — the forecaster's uncertainty at that sample, not a bound on the output.

Declaration Summary
val output: Trajectory
val owned: Array<BooleanArray>
val band: Array<Array<DoubleArray>>
val underruns: Int
val staleReplies: Int
val latencies: List<Double>
fun latency(p: Double): Double The pth percentile of latencies, in seconds.

TimestralSession.kt

Mistral's pretrained Timestral forecaster behind this package's session interface, running in a Python worker that Field starts and owns.

TimestralSession has never seen the signal it is continuing and needs no training step: it reads the recent history and returns a median forecast with a quantile envelope around it. The model runs in a separate process under uv run --script, in an environment uv builds from the worker script's own pinned dependencies, so Torch never loads into Field or into the venv the Python boxes share. Field talks to the worker over length-prefixed frames on its standard input and output, one worker per session, and a session created inside a box run stops its worker when the box stops or re-runs.

Because the model is optional, this file also answers whether it can be used at all. status reports whether uv, the worker script and the checkpoint are all present, without starting anything, and names the step that would supply the first one missing; ProviderStatus is that answer. Nothing downloads until install is called.

The forecast's quantiles are per-sample marginals, so this session offers no sampled alternatives: drawing each step independently from its own quantile would invent a dependence between steps that the model never predicted. TimestralSessionTest exercises a real worker, and only when AFIELD_TIMESTRAL_TEST=1 and the checkpoint is already installed.

src/jvmMain/kotlin/afield/math/temporal/TimestralSession.kt — read the source

ProviderStatus

sealed class ProviderStatus

Where an optional provider stands, with the reason when it is not usable.

Declaration Summary
data class Installed(val detail: String) : ProviderStatus()
data class Missing(val cause: String, val fix: String) : ProviderStatus()
data class Failed(val cause: String) : ProviderStatus()

TimestralSession

class TimestralSession(val model: String = "mistralai/Timestral-28M", val device: String = "cpu", val symmetricAveraging: Boolean = true, val clipNonnegative: Boolean = false, val threads: Int = 0) : TemporalSession

Mistral's Timestral forecaster (Apache-2.0 code, Modified-MIT weights) as a TemporalSession, running zero-shot with no fine-tuning.

Declaration Summary
val model: String
val device: String
val symmetricAveraging: Boolean
val clipNonnegative: Boolean
val threads: Int
override val capabilities
val info: JsonObject What the worker reported at start: model, versions, device, load time.
val quantileLevels: DoubleArray Quantile levels of every envelope, lowest first.
override fun propose(request: ContinuationRequest): CompletableFuture<Continuation>
override fun close()
override fun toString()

units.kt

Physical units for Field's spreadsheet-style numbers. A Quantity pairs a KotUniL Expression (normalized to SI) with the display unit the user actually wrote, so 3 | mm still prints as "3 mm".

withUnit is what that | unit syntax lowers to, and the operator overloads here let a Quantity mix with plain Numbers and bare Expressions in either order under ordinary dimensional-analysis rules. The rest of the file is presentation: recognizeDerivedUnit matches a dimensional signature against a table of named SI derived units (N, J, W, Pa, Hz, V, Ω, ...), autoScaleExpression chooses an SI prefix, and formatUnitSuperscripts rewrites kg*m2/s2 as kg·m²/s². formatExpression runs those in that order.

The unit algebra itself is KotUniL's, not Field's. Two limits are visible in the code: only single-factor dimensions are auto-scaled (compound ones like m/s pass through unchanged), and the derived-unit table is a fixed list, so anything outside it falls back to raw SI symbols.

src/jvmMain/kotlin/afield/math/units/units.kt — read the source

Top-level functions

Declaration Summary
fun Number.withUnit(unit: Expression): Quantity Converts this numeric magnitude into a Quantity using unit as both the SI scaling expression and preferred display unit.
fun Expression.withUnit(unit: Expression): Quantity
fun Quantity.withUnit(unit: Expression): Quantity
fun Any?.withUnit(unit: Expression): Quantity
operator fun Number.plus(q: Quantity): Quantity
operator fun Number.minus(q: Quantity): Quantity
operator fun Number.times(q: Quantity): Quantity
operator fun Number.div(q: Quantity): Quantity
operator fun Expression.plus(q: Quantity): Quantity
operator fun Expression.minus(q: Quantity): Quantity
operator fun Expression.times(q: Quantity): Quantity
operator fun Expression.div(q: Quantity): Quantity
fun autoScaleExpression(expr: Expression): Pair<Double, String> Returns a pair of (scaled value, unit string) that picks the SI prefix making the number easiest to read.
fun formatUnitSuperscripts(sym: String): String Applies superscript formatting to any unit symbol string from KotUniL. Turns trailing digits in unit tokens into Unicode superscripts: "kgm2/s2" → "kg·m²/s²" Also replaces '' with center-dot '·'.
fun recognizeDerivedUnit(expr: Expression): Pair<Double, String>? Tries to express an Expression's dimensions as a recognized SI derived unit.
fun formatExpression(expr: Expression): Pair<Double, String> Formats an Expression for display, recognizing derived SI units and applying superscript formatting.
fun formatQuantityDisplay(q: Quantity): Pair<Double, String> Formats a Quantity for display, applying derived unit recognition to quantities without an explicit display unit, and superscripts everywhere.

Quantity

class Quantity(val expression: Expression, val displayUnit: Expression? = null) : Comparable<Quantity>

A numeric value with tracked physical units.

Declaration Summary
val expression: Expression
val displayUnit: Expression?
val displayValue: Double The numeric value expressed in the display unit (or SI if none).
val displaySymbol: String The unit symbol string for display purposes.
val value: Double The raw SI-normalized numeric value.
val unitSymbols: String The SI unit symbol string.
operator fun plus(other: Expression): Quantity+2 overloads · also Quantity, Number
operator fun minus(other: Expression): Quantity+2 overloads · also Quantity, Number
operator fun times(other: Expression): Quantity+2 overloads · also Quantity, Number
operator fun div(other: Expression): Quantity+2 overloads · also Quantity, Number
operator fun rem(other: Expression): Quantity+2 overloads · also Quantity, Number
operator fun unaryMinus(): Quantity
operator fun unaryPlus(): Quantity
override fun compareTo(other: Quantity): Int
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
override fun toString(): String
fun `in`(unit: Expression): Quantity Convert to a different (compatible) display unit.
fun to(unit: Expression): Quantity Convert to a different (compatible) display unit (alias).