Skip to content

Function

The function algebra — Field's first-class, evolvable, graph-rewritable notion of a function. Build them, compose them, fit them, mutate them, and rewrite their graphs with user-authored patterns.

16 source file(s), 611 public symbol(s), 405 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.

autodiff_fn.kt

Differentiating the Fn graph itself — the bridge from autodiff.kt's scalar tape to the graphs Functions builds.

Functions.Fn is a closure over Double, so it cannot be differentiated by overloading its arithmetic. But every Fn the stdlib builds also carries an inspectable FnGraphNode: the op name, the role-tagged children, the scalar metadata. This file re-interprets that structure in AD arithmetic, so the exact derivative of a graph's output with respect to its numeric leaves — the same θ-vector extractParameters reads and applyParameters writes — falls out of one backward sweep instead of 2n extra evaluations.

with(Functions) {
    val f = sin(1.5, amplitude = 0.8, offset = 0.2)
    f.gradientAt(0.3)          // d f(0.3) / d theta, exact, in leaf order
}

Which ops, and the agreement check

The interpreter covers the smooth algebraic core — see FnAutoDiff.differentiableOps. That is a subset: the piecewise oscillators (saw, square), the table-backed interpolants, the noise generators and everything with baked point data are not here, and FnAutoDiff.supports answers correctly for any graph.

A second interpreter can silently compute the derivative of a different function from the one eval computes, so the two are compared instead of assumed to match: FnAutoDiff.agrees re-evaluates the interpreter against Functions.Fn.eval on the caller's own sample grid and reports the worst disagreement, and the fitters run that check before switching over. A mismatch demotes the run to finite differences rather than returning a confident wrong gradient.

Ops indexed by leaf, not by node

θ-indices are assigned by exactly the traversal Functions.Fn.mapGraphNumbers uses — post-order DFS over node.children in order, one index per node carrying numbers["value"] — so index i here is the same leaf as index i in extractParameters. FnAutoDiff.leafIndices is that map, keyed by the child-index path, and a test asserts the two agree leaf for leaf.

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

Used in: Differentiating Functions

Top-level functions

Declaration Summary
fun Functions.Fn.gradientAt(x: Double): ADGradient The exact value and gradient of f(x) with respect to this graph's numeric leaves, in the same order extractParameters reads them.
fun Functions.Fn.valueAndGradOverSamples(theta: DoubleArray, samples: DoubleArray, objective: (List<AD>) -> AD): ADGradient The exact value and gradient, with respect to the numeric leaves, of any scalar objective built from this graph's values at samples — the general primitive the exact fitters ride on.
fun fnLossGrid(domain: Functions.Domain, samples: Int): DoubleArray The even sample grid FnMetric.lossAgainst uses, exposed so the exact path samples the same points as the finite-difference path and the two are comparable.
fun exactFitObjective(target: Functions.Fn, seed: Functions.Fn, domain: Functions.Domain = Functions.Domain.DEFAULT, samples: Int = 128, metric: FnAdMetric = FnAdMetric.rmse, tolerance: Double = 1e-9): ((DoubleArray) -> ADGradient)? An exact θ → (loss, ∂loss/∂θ) function for fitting seed to target under metric, on the grid fnLossGrid — or null when the exact path does not apply.
fun exactFitRefusal(target: Functions.Fn, seed: Functions.Fn, domain: Functions.Domain = Functions.Domain.DEFAULT, samples: Int = 128, tolerance: Double = 1e-9): String? Why the exact gradient path was refused for this target/seed pair — one human-readable line, or null when it is available.

FnGradientMode

enum class FnGradientMode

How a fit obtains the gradient of its loss with respect to a graph's numeric leaves.

Constants: FINITE_DIFFERENCE, EXACT, AUTO.

FnAutoDiff

object FnAutoDiff

The op vocabulary the AD interpreter understands, plus the agreement checks around it.

Declaration Summary
val differentiableOps: Set<String> The ops the gradient can handle, lower-cased — derived, not restated: every op with an FnFormulas entry, plus the two numeric-leaf ops that are θ rather than a formula.
fun supports(fn: Functions.Fn): Boolean True when every node of fn's graph is in differentiableOps.
fun unsupportedOps(fn: Functions.Fn): List<String> The distinct ops in fn that the interpreter does not know, in first-seen order — the diagnostic behind the EXACT mode's error message.
fun leafIndices(fn: Functions.Fn): Map<List<Int>, Int> The θ-index of every numeric leaf, keyed by its child-index path from the root.
fun worstValueDisagreement(fn: Functions.Fn, samples: DoubleArray): Double The worst absolute disagreement between the AD interpreter's value and Functions.Fn.eval across samples. Zero-length input reports 0.
fun agrees(fn: Functions.Fn, samples: DoubleArray, tolerance: Double = 1e-9): Boolean Whether the interpreter reproduces Functions.Fn.eval on samples to within tolerance.
fun evalByFormula(fn: Functions.Fn, x: Double): Double Re-evaluates one node in plain Double arithmetic by walking the same FnFormulas entry — the reference the agreement test compares against Functions.Fn.eval bit for bit.

FnAdMetric

fun interface FnAdMetric

The AD counterpart of FnMetric: a distance between a target and a candidate that can itself be differentiated, so a fit can descend it exactly.

Declaration Summary
fun distance(xs: DoubleArray, target: DoubleArray, candidate: List<AD>): AD The tracked distance of candidate from target over the grid xs.

autodiff_formula.kt

One declaration of each Fn op's pointwise semantics, walked by two evaluators — so Fn.eval and the reverse-mode gradient cannot compute different functions.

One declaration, two walkers

The per-op formula lives in exactly one place — FnFormulas — as a tiny expression tree (FnExpr) over the op's role-addressed children and the sample position x. Two walkers consume it:

  • evalFormula in Double, transcribed to be bit-identical to the hand-written closure in Functions.kt (same operations, same order, same Math.pow / coerceIn calls);
  • evalFormulaAD in tracked AD arithmetic.

The AD side therefore has no per-op code at all. Adding an op means adding one FnExpr and the gradient follows, so a transcription error has one place to hide per FnExpr kind rather than one per op.

AutoDiffFnBridgeTest.theFormulaTableIsABitIdenticalTranscriptionOfFnEval is what holds the two walkers together: for every op with a formula it compares the Double walker's output to Functions' own eval as raw bits (toRawBits), over a 129-point grid at three parameter vectors. A formula that is not a faithful transcription fails in CI.

FnAutoDiff.agrees — the runtime value-agreement check the fitters run before switching to exact gradients — stays as well, because it catches what the table cannot express at all: an unbounded shape domain, an inverted clamp range.

The cost of the walk

Making the walk the only traversal would take one edit: every op in Functions builds its Fn through one private factory, graphFn(domain, name, op, …, eval: (Double) -> Double), so a formula-derived evaluator could go in there without touching the individual factories.

It is not done because the walk costs several times a closure call, and Fn.eval is where that lands hardest: it is the hottest call in the library, and one fitGradient at the defaults is 200 × (2·leaves + 1) × 128 whole-graph evaluations, with plotting, FnMetric, evolve and every sampler on the same path. The cost is the per-node when dispatch and the Child role lookup — a linear scan of node.children — against a closure the JIT inlines wholesale. FnFormulaBenchmarkTest prints the ratio for three graphs; it asserts only that the two paths accumulate bit-identical sums, so those timings are a report and not a contract.

The two costs to attack first would be that role lookup (pre-resolve Child(role) to a child index when the formula is built) and the recursive when (flatten to a post-order array of opcodes with a value stack).

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

Top-level functions

Declaration Summary
fun evalFormula(expr: FnExpr, x: Double, alpha: (Double) -> Double, childValue: (role: String, at: Double) -> Double): Double Walks a formula in plain Double arithmetic — the reference evaluator, transcribed to be bit-identical to Functions' own closures.
fun evalFormulaAD(expr: FnExpr, x: AD, alpha: (AD) -> AD, childValue: (role: String, at: AD) -> AD): AD Walks a formula in tracked AD arithmetic — the whole of the reverse-mode gradient for Fn graphs, with no per-op code.

FnExpr

sealed interface FnExpr

A node of an op's formula: an expression over the sample position, the op's role-addressed children, and literals.

Declaration Summary
data object X : FnExpr The sample position. Inside an AtX body this is the moved position.
data class Lit(val value: Double) : FnExpr A constant, carried through with no derivative.
data class Child(val role: String) : FnExpr The op's child in role, evaluated at the current position.
data object Alpha : FnExpr The normalised position within the op's own domain — the tracked twin of Domain.finiteSweep().alphaOf(x), which ramp and the windows sample.
data class Add(val a: FnExpr, val b: FnExpr) : FnExpr Sum.
data class Sub(val a: FnExpr, val b: FnExpr) : FnExpr Difference.
data class Mul(val a: FnExpr, val b: FnExpr) : FnExpr Product.
data class Div(val a: FnExpr, val b: FnExpr) : FnExpr Quotient.
data class Neg(val a: FnExpr) : FnExpr Negation.
data class Abs(val a: FnExpr) : FnExpr Absolute value — kotlin.math.abs.
data class Floor(val a: FnExpr) : FnExpr kotlin.math.floor — the greatest integer not above the value.
data class Sin(val a: FnExpr) : FnExpr sin in radians.
data class Cos(val a: FnExpr) : FnExpr cos in radians.
data class Exp(val a: FnExpr) : FnExpr Natural exponential.
data class Ln(val a: FnExpr) : FnExpr Natural logarithm.
data class Tanh(val a: FnExpr) : FnExpr Hyperbolic tangent.
data class Atan(val a: FnExpr) : FnExpr kotlin.math.atan — the principal arc tangent, in (-pi/2, pi/2).
data class Atan2(val y: FnExpr, val x: FnExpr) : FnExpr kotlin.math.atan2(y, x) — the quadrant-correct angle of the vector (x, y), in [-pi, pi].
data class Hypot(val a: FnExpr, val b: FnExpr) : FnExpr kotlin.math.hypot — √(a²+b²), computed without an intermediate overflow.
data class Pow(val a: FnExpr, val b: FnExpr) : FnExpr Math.pow(a, b) — spelled as the real call, so a literal exponent of 2.0 stays bit-identical to the hand-written .pow(2.0) rather than being rewritten as a * a.
data class Min(val a: FnExpr, val b: FnExpr) : FnExpr The smaller of two.
data class Max(val a: FnExpr, val b: FnExpr) : FnExpr The larger of two.
data class Clamp01(val a: FnExpr) : FnExpr coerceIn(0.0, 1.0) — the clamp01 the shapes and windows use.
data class CoerceIn(val a: FnExpr, val lo: FnExpr, val hi: FnExpr) : FnExpr coerceIn(lo, hi), which throws for an inverted range exactly as Fn.clamp does.
data class IfZero(val test: FnExpr, val then: FnExpr, val otherwise: FnExpr) : FnExpr if (test == 0.0) then else otherwise — the zero-sigma / zero-amount / zero-scale short-circuits, differentiated as taken.
data class Sqrt(val a: FnExpr) : FnExpr kotlin.math.sqrt — correctly rounded, so not Pow(a, 0.5), which differs in the last bit and at -0.0.
data class Rem(val a: FnExpr, val b: FnExpr) : FnExpr Kotlin's a % b on doubles — the truncated remainder, which keeps the sign of a and is computed exactly (no rounding). pulseTrain wraps its phase with ((local % p) + p) % p.
data class Hash(val seed: Long, val a: FnExpr) : FnExpr Functions.hashToUnit(seed, a.toLong()): a seeded hash of an integer position into [0, 1), the per-cycle jitter of pulseTrain. Its derivative is zero; its interval is exact over a handful of integers and [0, 1] otherwise.
data class IfEq(val a: FnExpr, val b: FnExpr, val then: FnExpr, val otherwise: FnExpr) : FnExpr if (a == b) then else otherwise.
data class IfLt(val a: FnExpr, val b: FnExpr, val then: FnExpr, val otherwise: FnExpr) : FnExpr if (a < b) then else otherwise.
data class AtX(val newX: FnExpr, val body: FnExpr) : FnExpr Evaluates body with the position replaced by newX — the shape of every x-domain transform.

FnFormulas

object FnFormulas

The formula table: one FnExpr per differentiable Fn op, keyed by lower-cased op name.

Declaration Summary
fun of(op: String): FnExpr? The formula for op, or null when this op has no declared pointwise semantics.
fun valueOf(op: String): FnExpr? The formula that defines op's value — of, or else a valueOnlyOps entry. What the Double and interval walkers read; never what the AD walk reads.
fun of(node: FnGraphNode): FnExpr? The formula for node: a node-parameterised one (FnShapedFormulas — ease, tween, pulseTrain, whose shape is chosen by the node's strings, booleans and seed) or else of by op.
fun valueOf(node: FnGraphNode): FnExpr? valueOf for a node, with the same node-parameterised ops first.
val ops: Set<String> Every op with a declared formula, lower-cased — including the node-parameterised ones.
val valueOnlyOps: Set<String> Ops with a declared value formula and a deliberately refused gradient, lower-cased.

FnOpStatus

data class FnOpStatus(val reason: String, val closable: Boolean, val missing: String = "")

Why an Fn op has no derivative, and whether that is permanent.

Declaration Summary
val reason: String One line, in prose, for a diagnostic or a docs table.
val closable: Boolean True when a derivative could be written; false when there genuinely is not one.
val missing: String What is missing, when closable. Empty otherwise.

FnNonDifferentiable

object FnNonDifferentiable

Every op in OpRegistry that FnAutoDiff cannot differentiate, with a declared reason.

Declaration Summary
fun statusOf(op: String): FnOpStatus? The declared status of op, or null when this op has a formula (or is unknown to the registry).
val ops: Set<String> Every op declared non-differentiable, lower-cased.
val closable: List<String> Ops whose derivative could be written but has not been — the real backlog.
fun explain(op: String): String A one-line explanation for a diagnostic: either the declared reason, or a note that the op is differentiable, or that nothing is known about it.

FnInterval.kt

Certified bounds for an Fn over an interval — the third walker of FnFormulas, in interval arithmetic, and what turns a sampled envelope into a guarantee.

with(Functions) {
    val f = sin(0.025, amplitude = 0.2, offset = 0.45) + gaussian(1234.57, 0.002, 0.5, 0.0..3600.0)
    f.enclose(1234.0, 1235.0)          // lo..hi that every f(x) on that interval lies inside
    f.certifiedRange(0.0, 3600.0)      // the global max, with the x that reaches it
    f.certifiedEnvelope(0.0, 3600.0, 1000)  // per-bin bounds a plot cannot slip between
}

Why an enclosure, not a sampled envelope

SignalViewer bins a function by evaluating it on a grid and keeping each bin's min and max. A feature narrower than the grid spacing can sit between two samples and never be drawn, and nothing in the picture says so — a limit public/docs/function_conversation.md states about its own plots. An enclosure is a different kind of object: [lo, hi] such that every value the function takes on the interval lies inside, whether or not any sample landed near it. A spike 10 ms wide in an hour-long signal cannot hide from it, because nothing is sampled.

One declaration, three walkers

evalFormulaInterval walks the same FnExpr tree that evalFormula walks in Double and evalFormulaAD in tracked arithmetic, so the enclosure is of the function Fn.eval computes and not of a third transcription of it. There is still no per-op code: every op with an FnFormulas entry is enclosed, and an op without one is refused by name (FnEnclosure.refusal) — never bounded by a guess. Most of the hand-written fn { x -> … } lambdas in the wild are therefore refused; that is the cost of a guarantee, and giving an op a formula is how to lift it (it gains an exact gradient at the same time).

What exactly is contained

The contract is the Double values Fn.eval returns for every double x in [a, b] — the values a sampler draws — not the real-number function those doubles approximate. That choice is what lets most of the arithmetic go unwidened:

  • + − × ÷ are correctly rounded, and round-to-nearest is monotone, so evaluating an interval's endpoints (or a product's four corners) in ordinary Double already brackets every rounded result in between. No outward rounding is needed, which is why a held constant certifies as exactly [1, 1] rather than as 1 ± ulp.
  • abs, floor, min, max, coerceIn and the branch kinds are exact.
  • The library transcendentals (sin, exp, pow, tanh, atan2, …) are not correctly rounded: the Javadoc allows up to 1–2.5 ulps, and promises only that they are semi-monotonic. Their endpoint values are widened outward by TRANSCENDENTAL_ULPS ulps each side, which also absorbs the rare last-bit disagreement between an interpreted and a JIT-intrinsic Math.sin.

NaN is tracked separately (FnRange.maybeNaN) rather than widened into the range: log of a range reaching below zero may produce NaN, and saying so is more useful than an infinite bound.

Tightness and branch-and-bound

Plain interval arithmetic is sound but loose: x - x over [0, 1] encloses as [-1, 1], because the walker does not know the two xs are the same number (the dependency problem). The bound converges to the true range as the interval shrinks, so everything above Functions.Fn.enclose works by subdividing: certifiedRange is a best-first branch-and-bound on the maximum and the minimum, the certified… predicates split until each piece is decided, and certifiedEnvelope refines each bin until its bound is within a tolerance of what that bin's samples show. FnIntervalTest measures how loose the one-shot bound is and ratchets the number.

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

Used in: Proving a Function's Range

Top-level functions

Declaration Summary
fun evalFormulaInterval(expr: FnExpr, x: FnRange, alpha: (FnRange) -> FnRange, childRange: (role: String, at: FnRange) -> FnRange, known: Map<FnExpr, FnRange> = emptyMap()): FnRange Walks a formula in interval arithmetic: the FnRange of values the formula can take for any x in x — the third walker of the same FnExpr tree evalFormula and evalFormulaAD walk.
fun Functions.Fn.enclose(a: Double, b: Double): FnEnclosure The certified bound on this function over [a, b] — see FnIntervals.enclose.
fun Functions.Fn.certifiedRange(a: Double, b: Double, tolerance: Double = 1e-6, maxEnclosures: Int = 200_000, onEnclosure: ((FnSearchPiece) -> Unit)? = null): FnCertifiedRange The certified global minimum and maximum of this function over [a, b], each to within tolerance: the true extreme is at least as far out as the value reached at the witness, and no further out than the proven bound.
fun Functions.Fn.certifiedNeverExceeds(limit: Double, a: Double, b: Double, slack: Double = 0.0, maxEnclosures: Int = 200_000): FnVerdict Certifies that f(x) <= limit + slack for every x in [a, b] (and never NaN), or refutes that same claim with a witness.
fun Functions.Fn.certifiedNeverBelow(limit: Double, a: Double, b: Double, slack: Double = 0.0, maxEnclosures: Int = 200_000): FnVerdict Certifies that f(x) >= limit - slack for every x in [a, b] (and never NaN), or refutes that claim with a witness.
fun Functions.Fn.certifiedEquals(value: Double, a: Double, b: Double, maxEnclosures: Int = 200_000): FnVerdict Certifies that f(x) == value exactly for every x in [a, b], or refutes it with a witness.
fun Functions.Fn.certifiedReaches(level: Double, a: Double, b: Double, maxEnclosures: Int = 200_000): FnVerdict Certifies that f(x) >= level somewhere in [a, b] — PROVEN with the witness that reaches it, REFUTED when the proven maximum is below level.
fun Functions.Fn.certifiedEnvelope(a: Double, b: Double, bins: Int, tolerance: Double = 1e-3, maxDepth: Int = 8): FnCertifiedEnvelope Certified per-bin bounds for this function over [a, b] in bins equal bins.

Top-level values

Declaration Summary
const val TRANSCENDENTAL_ULPS: Int Ulps each side a transcendental's endpoint values are widened by. See the file KDoc.

FnRange

data class FnRange(val lo: Double, val hi: Double, val maybeNaN: Boolean = false)

A set of Double values: every number in [lo, hi], plus NaN when maybeNaN. Empty (no non-NaN value at all) when lo > hi, which is how log of an all-negative range is represented.

Declaration Summary
val isEmpty: Boolean True when no non-NaN value is possible.
val width: Double hi - lo, or 0 when empty.
val isPoint: Boolean True when this is one number, with no NaN.
operator fun contains(value: Double): Boolean Whether value is a member — NaN is a member exactly when maybeNaN.
fun hull(other: FnRange): FnRange The smallest range holding both.
fun including(value: Double): FnRange This range with value added (NaN sets maybeNaN).
override fun toString(): String

FnIntervalRefusal

class FnIntervalRefusal(message: String) : RuntimeException(message)

Thrown inside a walk when no sound bound exists — an op with no formula, an unbounded shape domain, a clamp whose range may invert (which throws in Fn.eval itself). Caught by FnIntervals.enclose and reported as FnEnclosure.refusal.

FnIntervalRules

object FnIntervalRules

The declared rule for each FnExpr kind — one line saying how the kind is enclosed and whether the bound is exact at the endpoints, widened, or a hull.

Declaration Summary
val rules: Map<String, String>

FnRangeMath

object FnRangeMath

The per-kind rules, as functions on FnRange. Public so a test can exercise one rule on its own.

Declaration Summary
fun widen(r: FnRange, ulps: Int = TRANSCENDENTAL_ULPS): FnRange r widened outward by ulps ulps at each finite end.
fun add(a: FnRange, b: FnRange): FnRange
fun neg(a: FnRange): FnRange
fun sub(a: FnRange, b: FnRange): FnRange
fun mul(a: FnRange, b: FnRange): FnRange
fun square(a: FnRange): FnRange v * v for one value drawn from a — tighter than mul of a with itself.
fun div(a: FnRange, b: FnRange): FnRange
fun abs(a: FnRange): FnRange
fun floor(a: FnRange): FnRange
fun sqrt(a: FnRange): FnRange sqrt: monotone and correctly rounded, so the endpoints are exact.
fun rem(a: FnRange, b: FnRange): FnRange Kotlin's a % b. Exact (fmod does not round), with the sign of a and magnitude below \|b\| and no larger than \|a\|. When b is one number and [lo, hi] holds no multiple of it on one side of zero, a % b is increasing across the interval, so its endpoints bound it — and the test for that is lo % b <= hi % b with the width below \|b\|, since a wrap would reverse them.
fun hash(seed: Long, a: FnRange): FnRange hashToUnit(seed, a.toLong()): exact over a few integers, [0, 1] beyond.
fun fract(a: FnRange): FnRange t - floor(t) for one value drawn from a — the fractional part, which sub of a and floor of a cannot see: over [0, 10] those give [-10, 10].
fun sin(a: FnRange): FnRange
fun cos(a: FnRange): FnRange
fun exp(a: FnRange): FnRange
fun ln(a: FnRange): FnRange
fun tanh(a: FnRange): FnRange
fun atan(a: FnRange): FnRange
fun atan2(y: FnRange, x: FnRange): FnRange
fun hypot(a: FnRange, b: FnRange): FnRange
fun pow(a: FnRange, b: FnRange): FnRange
fun cubicSmoothstep(t: FnRange): FnRange t·t·(3 − 2t) evaluated as Fn.eval evaluates it, for t drawn from t.
fun quinticSmootherstep(u: FnRange, generic: () -> FnRange): FnRange u·u·u·(10 + u·(−15 + 6u)), the quintic smootherstep, evaluated as Fn.eval evaluates that graph, for u drawn from u — intersected with generic, the bound the ordinary rules give.
fun min(a: FnRange, b: FnRange): FnRange
fun max(a: FnRange, b: FnRange): FnRange
fun clamp01(a: FnRange): FnRange
fun coerceIn(a: FnRange, lo: FnRange, hi: FnRange): FnRange

FnEnclosure

data class FnEnclosure(val a: Double, val b: Double, val lo: Double, val hi: Double, val maybeNaN: Boolean, val certified: Boolean, val refusal: String? = null, val unsupportedOps: List<String> = emptyList())

A certified bound on an Fn over [a, b], or a refusal saying why there is none.

Declaration Summary
val a: Double
val b: Double
val lo: Double
val hi: Double
val maybeNaN: Boolean
val certified: Boolean
val refusal: String?
val unsupportedOps: List<String>
operator fun contains(value: Double): Boolean Whether value is inside the certified bound. False for every value when not certified.
val width: Double hi - lo, or NaN when refused.
fun requireCertified(): FnEnclosure This enclosure, or an exception carrying its refusal.
override fun toString(): String

FnIntervals

object FnIntervals

The interval walk over an Fn graph: support checks and the one-shot enclosure.

Declaration Summary
fun unsupportedOps(fn: Functions.Fn): List<String> The distinct ops in fn that have no formula, in first-seen order — exactly the ops an enclosure refuses. A node carrying numbers["value"] is a constant and always supported.
fun supports(fn: Functions.Fn): Boolean True when every node of fn has a formula or is a constant.
fun range(fn: Functions.Fn, x: FnRange): FnRange The enclosure of fn over the position range x. Throws FnIntervalRefusal when no sound bound exists; enclose is the non-throwing form.
fun enclose(fn: Functions.Fn, a: Double, b: Double): FnEnclosure The certified bound on fn over [a, b], or a refusal naming why there is none.

FnExtremum

data class FnExtremum(val value: Double, val witness: Double, val bound: Double, val certified: Boolean, val enclosures: Int)

One side of a certified range: the true extreme lies between value (reached, at witness) and bound (proven). certified when that gap is within the requested tolerance.

Declaration Summary
val value: Double The best value actually returned by Fn.eval, at witness.
val witness: Double An x at which Fn.eval returns value.
val bound: Double The proven bound: no value on the interval is beyond it.
val certified: Boolean \|bound - value\| is within the tolerance asked for.
val enclosures: Int How many enclosures the search computed.
val gap: Double How far apart the reached value and the proven bound are.

FnCertifiedRange

data class FnCertifiedRange(val a: Double, val b: Double, val min: FnExtremum?, val max: FnExtremum?, val maybeNaN: Boolean, val refusal: String? = null)

The global minimum and maximum of an Fn over [a, b], each bracketed between a reached value and a proven bound. refusal is set (and both sides are null) when the function cannot be enclosed at all.

Declaration Summary
val a: Double
val b: Double
val min: FnExtremum?
val max: FnExtremum?
val maybeNaN: Boolean Some x in the interval may evaluate to NaN; the extremes are of the non-NaN values.
val refusal: String?
val certified: Boolean True when both extremes are certified to the tolerance.
override fun toString(): String

FnSearchSide

enum class FnSearchSide

Which extreme a branch-and-bound search was looking for.

Constants: MIN, MAX.

FnSearchPiece

data class FnSearchPiece(val lo: Double, val hi: Double, val range: FnRange, val side: FnSearchSide, val kept: Boolean)

One enclosure a certifiedRange search computed: every value on [lo, hi] is in range. kept when the piece could still hold a better extreme than the one already reached, so the search queued it for splitting; a piece that is not kept was pruned by its bound.

FnVerdictKind

enum class FnVerdictKind

The answer to a certified yes/no question about a function on an interval.

Constants: PROVEN, REFUTED, UNDECIDED.

FnVerdict

data class FnVerdict(val kind: FnVerdictKind, val claim: String, val witness: Double? = null, val witnessValue: Double? = null, val enclosures: Int = 0, val refusal: String? = null)

A certified verdict, with the witness that refutes it (or proves an existence claim).

Declaration Summary
val kind: FnVerdictKind
val claim: String
val witness: Double? The x that decides it, when there is one.
val witnessValue: Double? Fn.eval(witness).
val enclosures: Int
val refusal: String?
val proven: Boolean
override fun toString(): String

FnCertifiedEnvelope

data class FnCertifiedEnvelope(val start: Double, val end: Double, val low: DoubleArray, val high: DoubleArray, val certified: Boolean, val refusal: String? = null, val enclosures: Int = 0, val unconverged: Int = 0)

Per-bin certified bounds: bin i covers [edge(i), edge(i+1)] (closed, so neighbours share an edge) and every value the function takes there lies in [low[i](workspace.md#stdlib-kt--i), high[i](workspace.md#stdlib-kt--i)].

Declaration Summary
val start: Double
val end: Double
val low: DoubleArray
val high: DoubleArray
val certified: Boolean
val refusal: String?
val enclosures: Int Enclosures computed in total.
val unconverged: Int Bins still wider than observed + tolerance when the depth cap was reached.
val bins: Int
fun edge(i: Int): Double The left edge of bin i; edge(bins) is end.
override fun equals(other: Any?): Boolean
override fun hashCode(): Int

FnMetric.kt

The objective shared by both halves of the function-search family: FnMetric, a fun interface scoring a candidate signal against a target on one even sample grid (lower is better), plus a companion of ready-made metrics — rmse, mae, lp, maxError, huber, logCosh, correlation — and the fromResidual / window builders behind them.

FnMetric.lossAgainst turns a metric into the (Fn) -> Double closure the gradient fitters in FunctionFit.kt descend, sampling the target once and reusing it across the finite-difference inner loop; the same metrics are what matchFitness in FunctionEvolve.kt negates, so structural search and numeric fitting can run under one notion of "close". Any metric — including a hand-written lambda — must ignore indices where either side is non-finite and return Double.POSITIVE_INFINITY when no index is jointly finite, which callers read as maximally bad.

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

Top-level functions

Declaration Summary
fun FnMetric.lossAgainst(target: Functions.Fn, domain: Functions.Domain = Functions.Domain.DEFAULT, samples: Int = 128): (Functions.Fn) -> Double Builds a loss closure (lower is better) that scores a candidate Fn against target under this metric, sampling both on an even grid of samples points across domain with the fitters' NaN-safe outside = NaN convention. The target is sampled once and reused, so the returned closure is cheap to call in the finite-difference inner loop.

FnMetric

fun interface FnMetric

A pluggable distance metric between a target signal and a candidate, evaluated on a shared even sample grid. Lower is better; 0.0 means the candidate matches the target at every sampled point. This is the objective the differentiable fitters (fitGradient, fitBest, fitHybrid) descend; rmse is the default and one metric among the family.

Declaration Summary
fun distance(xs: DoubleArray, target: DoubleArray, candidate: DoubleArray): Double The scalar distance of candidate from target over the grid xs. Lower is better; returns Double.POSITIVE_INFINITY when the two share no jointly-finite sample.

FnShapedFormulas.kt

Formulas for the three ops whose shape is chosen by data on the node rather than by the op name. ease and tween read a Functions.Easing out of strings["easing"]; pulseTrain reads a Functions.PulseEdge, a Functions.PulseProfile, booleans["bipolar"] and longs["seed"].

Each is a transcription of the matching closure in Functions.kt (shapeAlpha, bounceOut, edgeRamp, pulseProfileValue, pulseShape, and pulseTrain's own body), operation for operation and in Kotlin's association order, which FnExpr's operator overloads reproduce: c3 * t * t * t here is ((c3·t)·t)·t there. FnShapedFormulaAgreementTest pins every variant to Fn.eval's own bits. Formulas are built once per variant and cached.

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

FunctionEvolve.kt

Search over the structure of Fn graphs — the genetic counterpart to the gradient fitting of a fixed structure's numbers in FunctionFit.kt.

FnMutations holds the seeded mutation operators (jitter and nudge numeric leaves, transmute an op within its OpRegistry family, prune, duplicate or graft a subtree), each expressed over the graph copy and query primitives rather than a private traversal. evolve runs them as a bounded (µ+λ) hill-climb and returns an FnEvolveRun listing every accepted improvement. Fitness is the caller's: matchFitness scores closeness to a target under any FnMetric, less an optional complexity penalty from graphMetrics; noveltyFitness instead scores distance from an archive of FnBehaviour descriptors — what a graph does when sampled, not how it is built — and evolveVariety chains slots of novelty search into a behaviourally spread set of results rather than one best match.

Both runners are bounded by an iteration count so a search cannot disappear into an animation loop, and both are deterministic: traversal is stable and every draw comes from one seeded Random, with each evolveVariety slot deriving rngSeed * 31 + slot. Set normalize on Functions.Fn.behaviour (and hence on novelty search) to compare shape rather than amplitude.

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

Used in: Functions to FLine examples (Field graphics)

Top-level functions

Declaration Summary
fun evolve(seed: Functions.Fn, iterations: Int = 64, lambda: Int = 6, rngSeed: Long = 0L, mutations: List<Pair<String, FnMutation>> = FnMutations.defaults(), onIteration: ((iteration: Int, total: Int) -> Unit)? = null, fitness: (Functions.Fn) -> Double): FnEvolveRun A bounded (1+λ) hill-climb over Fn graphs.
fun matchFitness(target: Functions.Fn, domain: Functions.Domain = Functions.Domain.DEFAULT, samples: Int = 128, complexityWeight: Double = 0.0, metric: FnMetric = FnMetric.rmse): (Functions.Fn) -> Double A ready-made fitness: how closely a candidate matches target on a shared time-base, minus an optional complexity penalty from graphMetrics. Higher is better.
fun Functions.Fn.behaviour(domain: Functions.Domain = Functions.Domain.DEFAULT, samples: Int = 32, normalize: Boolean = false): FnBehaviour Characterizes what this does over domain into an FnBehaviour descriptor.
fun noveltyFitness(archive: List<FnBehaviour>, domain: Functions.Domain = Functions.Domain.DEFAULT, samples: Int = 32, neighbours: Int = 3, validityWeight: Double = 0.25, normalize: Boolean = false): (Functions.Fn) -> Double A fitness that rewards behavioural novelty relative to an archive of already-seen behaviours, plus a small validityWeight bonus for evaluating to finite values.
fun evolveVariety(seed: Functions.Fn, count: Int = 6, iterations: Int = 48, lambda: Int = 6, rngSeed: Long = 0L, domain: Functions.Domain = Functions.Domain.DEFAULT, samples: Int = 32, neighbours: Int = 3, validityWeight: Double = 0.25, normalize: Boolean = false, mutations: List<Pair<String, FnMutation>> = FnMutations.defaults()): FnVarietyRun Evolves a diverse set of Fn graphs instead of a single best match.

FnMutation

fun interface FnMutation

Seeded mutation operators and a bounded, deterministic search harness.

Declaration Summary
fun mutate(fn: Functions.Fn, rng: Random): Functions.Fn

FnMutations

object FnMutations

Library of seeded structural mutations, each expressed over existing graph primitives.

Declaration Summary
fun jitterNumbers(scale: Double = 0.2): FnMutation Perturb every numeric leaf by a bounded multiplicative jitter.
fun nudgeNumbers(amount: Double = 0.1): FnMutation Additively nudge every numeric leaf by a bounded amount.
fun substituteOp(): FnMutation Transmute one random node to another op in the same family (oscillator, noise, …).
fun pruneSubtree(): FnMutation Replace one random non-root subtree with zero (parsimony pressure).
fun duplicateSubtree(): FnMutation Replace one random subtree with subtree + subtree, growing structure.
fun graftSubtree(donor: Functions.Fn): FnMutation Graft a copy of donor onto one random non-root site (structural crossover).
fun defaults(): List<Pair<String, FnMutation>> A reasonable default palette for evolve when the caller has no donor material.

FnEvolveStep

data class FnEvolveStep(val iteration: Int, val mutation: String, val score: Double)

One accepted improvement during a run.

Declaration Summary
val iteration: Int
val mutation: String
val score: Double

FnEvolveRun

data class FnEvolveRun(val best: Functions.Fn, val bestScore: Double, val initialScore: Double, val steps: List<FnEvolveStep>, val iterations: Int, val evaluations: Int)

Result and provenance of an evolve run, mirroring FnGraphRewriteRun's shape.

Declaration Summary
val best: Functions.Fn
val bestScore: Double
val initialScore: Double
val steps: List<FnEvolveStep>
val iterations: Int
val evaluations: Int
val improved: Boolean

FnGenomeOps

class FnGenomeOps(mutationList: List<Pair<String, FnMutation>> = FnMutations.defaults()) : GenomeOps<Functions.Fn>

The Fn graph, presented to the generic search core as a genome.

Declaration Summary
override val mutations: List<Variation<Functions.Fn>>
override fun random(rng: Random): Functions.Fn

FnBehaviour

data class FnBehaviour(val samples: DoubleArray, val domain: Functions.Domain, val finiteFraction: Double)

A fixed-length behavioural descriptor: what an Fn does, not how it is built.

Declaration Summary
val samples: DoubleArray
val domain: Functions.Domain
val finiteFraction: Double
fun distanceTo(other: FnBehaviour): Double Root-mean-square difference between two equal-length descriptors.
override fun equals(other: Any?): Boolean
override fun hashCode(): Int

FnVarietyRun

data class FnVarietyRun(val results: List<Functions.Fn>, val behaviours: List<FnBehaviour>, val runs: List<FnEvolveRun>)

Result and provenance of an evolveVariety run.

Declaration Summary
val results: List<Functions.Fn>
val behaviours: List<FnBehaviour>
val runs: List<FnEvolveRun>
val meanPairwiseDistance: Double Mean pairwise behavioural distance across behaviours — the spread of the set.

FunctionFit.kt

Gradient-based fitting of Fn graphs — the differentiable counterpart to the genetic evolve in FunctionEvolve.kt.

Where evolve searches over structure (seeded mutation of the graph), this file tunes the numbers: the numeric leaves of a fixed-structure graph are the θ-vector, and Adam descends the RMSE loss against a target Fn. The two are complementary — fitHybrid runs evolve to discover a shape, then polishes its constants here.

A lone fitGradient is local and will stall in a frequency local-minimum when the seed's oscillator frequencies are far from the target's (RMSE-vs-frequency is periodic, not convex); fitBest wraps it in deterministic jittered restarts and is the entry to reach for when the starting numbers may be wrong; its own KDoc has the shape of that landscape.

The θ extract/apply primitives ride on Functions.Fn.mapGraphNumbers, whose post-order DFS is stable and deterministic with preserveSharing = false: the i-th numeric leaf seen while reading is the i-th leaf written while applying, so a plain index counter is a sound θ↔graph bijection. Everything is deterministic given a seed: Adam itself has no randomness, and only an optional FnFitConfig init-jitter draws from a single Random, so a fit re-run with the same seed comes back identical. FnEvolveRun holds to the same rule.

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

Used in: Functions to FLine examples (Field graphics), Differentiating Functions, Patterns: writing rhythm as a function of time

Top-level functions

Declaration Summary
fun Functions.Fn.extractParameters(): DoubleArray Reads the numeric leaves of this into a θ-vector in stable post-order-DFS order.
fun Functions.Fn.parameterCount(): Int The number of tunable numeric leaves in this graph — the dimensionality of θ.
fun Functions.Fn.applyParameters(theta: DoubleArray): Functions.Fn Rebuilds this graph with its numeric leaves replaced, left-to-right, by theta.
fun rmseLossAgainst(target: Functions.Fn, domain: Functions.Domain = Functions.Domain.DEFAULT, samples: Int = 128): (Functions.Fn) -> Double An RMSE loss (lower is better) of a candidate against target over an even grid across domain. A named convenience, equivalent to FnMetric.rmse.lossAgainst(...) — which is the general form the fitters take for any FnMetric.
fun fitGradient(target: Functions.Fn, seed: Functions.Fn, domain: ClosedRange<Double> = Functions.Domain.DEFAULT, iterations: Int = 200, lr: Double = 0.05, beta1: Double = 0.9, beta2: Double = 0.999, epsilon: Double = 1e-8, fdStep: Double = 1e-4, samples: Int = 128, metric: FnMetric = FnMetric.rmse, rngSeed: Long = 0L, initJitter: Double = 0.0, gradient: FnGradientMode = FnGradientMode.FINITE_DIFFERENCE, adMetric: FnAdMetric? = null, onIteration: ((iteration: Int, total: Int) -> Unit)? = null): FnFitRun Fits the numeric leaves of seed to target by Adam descent on the RMSE loss.
fun fitBest(target: Functions.Fn, seed: Functions.Fn, domain: ClosedRange<Double> = target.domain.takeIf { it.isFinite } ?: Functions.Domain.DEFAULT, restarts: Int = 12, iterations: Int = 300, lr: Double = 0.05, beta1: Double = 0.9, beta2: Double = 0.999, epsilon: Double = 1e-8, fdStep: Double = 1e-4, samples: Int = 128, metric: FnMetric = FnMetric.rmse, restartJitter: Double = 0.6, rngSeed: Long = 0L, gradient: FnGradientMode = FnGradientMode.FINITE_DIFFERENCE, adMetric: FnAdMetric? = null): FnFitRun Robust fit by multiple jittered restarts — the recommended entry point when the seed's numbers might be far from the target's.
fun fitHybrid(target: Functions.Fn, seed: Functions.Fn, domain: ClosedRange<Double> = Functions.Domain.DEFAULT, evolveIterations: Int = 64, evolveLambda: Int = 6, fitIterations: Int = 200, lr: Double = 0.05, beta1: Double = 0.9, beta2: Double = 0.999, epsilon: Double = 1e-8, fdStep: Double = 1e-4, samples: Int = 128, complexityWeight: Double = 0.0, metric: FnMetric = FnMetric.rmse, rngSeed: Long = 0L, mutations: List<Pair<String, FnMutation>> = FnMutations.defaults(), gradient: FnGradientMode = FnGradientMode.FINITE_DIFFERENCE, adMetric: FnAdMetric? = null): FnHybridRun Structure-then-numbers fitting: run evolve to discover a graph shape that roughly matches target, then hand its numeric leaves to fitGradient for a precise polish.
fun Functions.Fn.normalizeNumericLeaves(): Functions.Fn Rewrites every numeric leaf of this graph as a const, whether or not its value changed — so a fitted graph and its seed have the same leaf representation and can be compared structurally.
fun fitEvolveRounds(target: Functions.Fn, seed: Functions.Fn, domain: ClosedRange<Double> = Functions.Domain.DEFAULT, rounds: Int = 4, evolveIterations: Int = 40, evolveLambda: Int = 6, fitIterations: Int = 120, lr: Double = 0.05, fdStep: Double = 1e-4, samples: Int = 128, complexityWeight: Double = 0.0, metric: FnMetric = FnMetric.rmse, gradient: FnGradientMode = FnGradientMode.AUTO, adMetric: FnAdMetric? = null, rngSeed: Long = 0L, mutations: List<Pair<String, FnMutation>> = FnMutations.defaults()): FnRefineRun Alternates structural search and numeric refinement for rounds rounds — the marriage the two halves of this package were always heading for, and the thing fitHybrid only does once.

Top-level values

Declaration Summary
const val FN_FREE_PARAMETER_PREFIX: String The name-node prefix a lowering gives a number that is meant to be solved for (RectSL's free-param, Kotlin's FreeParamTrack). A graph carrying one is a track with declared unknowns, and fitGradient refuses it: it would move every number, key times and clamp bounds included.

FnFitStep

data class FnFitStep(val iteration: Int, val loss: Double)

One descent step's record: the loss (the metric distance) after applying the step.

Declaration Summary
val iteration: Int
val loss: Double

FnFitConfig

data class FnFitConfig(val iterations: Int, val learningRate: Double, val beta1: Double, val beta2: Double, val epsilon: Double, val fdStep: Double, val samples: Int, val domain: Functions.Domain, val rngSeed: Long, val initJitter: Double, val gradient: FnGradientMode = FnGradientMode.FINITE_DIFFERENCE)

Immutable configuration + seed for a fitGradient run, kept for provenance.

Declaration Summary
val iterations: Int
val learningRate: Double
val beta1: Double
val beta2: Double
val epsilon: Double
val fdStep: Double
val samples: Int
val domain: Functions.Domain
val rngSeed: Long
val initJitter: Double
val gradient: FnGradientMode Which gradient the run was asked for; FnFitRun.usedExactGradient says what it got.

FnFitRun

data class FnFitRun(val fitted: Functions.Fn, val parameters: DoubleArray, val initialParameters: DoubleArray, val steps: List<FnFitStep>, val losses: List<Double>, val initialError: Double, val finalError: Double, val iterations: Int, val evaluations: Int, val config: FnFitConfig, val usedExactGradient: Boolean = false)

Result and provenance of a fitGradient run, mirroring the shape of FnEvolveRun.

Declaration Summary
val fitted: Functions.Fn
val parameters: DoubleArray
val initialParameters: DoubleArray
val steps: List<FnFitStep>
val losses: List<Double>
val initialError: Double
val finalError: Double
val iterations: Int
val evaluations: Int
val config: FnFitConfig
val usedExactGradient: Boolean Whether the run really used the exact reverse-mode gradient from autodiff_fn.kt.
val improved: Boolean True when the fit ended strictly closer to the target than it started.
override fun equals(other: Any?): Boolean
override fun hashCode(): Int

FnHybridRun

data class FnHybridRun(val evolveRun: FnEvolveRun, val fitRun: FnFitRun)

Result and provenance of a fitHybrid run: the structural search then the polish.

Declaration Summary
val evolveRun: FnEvolveRun
val fitRun: FnFitRun
val best: Functions.Fn The final polished graph.
val seedError: Double RMSE at the very start (seed), after evolve, and after the gradient polish.
val evolvedError: Double
val finalError: Double

FnRefineRound

data class FnRefineRound(val round: Int, val evolveRun: FnEvolveRun, val fitRun: FnFitRun, val usedExactGradient: Boolean, val refusal: String?)

One round of fitEvolveRounds: what the structural search found, and what the fit did to it.

Declaration Summary
val round: Int 1-based round index.
val evolveRun: FnEvolveRun The structural search for this round.
val fitRun: FnFitRun The numeric refinement of evolveRun's best.
val usedExactGradient: Boolean Whether this round's refinement really got the exact gradient, or fell back.
val refusal: String? Why the exact path was refused this round, when it was. Null when it was used.
val error: Double The error after this round's refinement.

FnRefineRun

data class FnRefineRun(val best: Functions.Fn, val bestError: Double, val seedError: Double, val rounds: List<FnRefineRound>)

Result and provenance of a fitEvolveRounds run.

Declaration Summary
val best: Functions.Fn The best graph found across all rounds, with its numeric leaves normalised.
val bestError: Double The error of best.
val seedError: Double The error of the original seed, for the improvement ratio.
val rounds: List<FnRefineRound> Every round, in order.
val exactRounds: Int How many rounds got the exact gradient rather than falling back to finite differences.
val errors: List<Double> The error after each round, so the alternation is plottable.

FunctionGraph.kt

The data model and the copy engine underneath Field's graph-rewritable notion of a function: FnGraphNode / FnGraphEdge (an op name, role-tagged children, scalar metadata and an optional rebuild recipe) plus Functions.Fn.deepCopy and Functions.Fn.rewriteGraph, a bottom-up copy that offers every occurrence to an FnGraphEdit. Every other file in this family runs on that single traversal, as do the edit primitives declared here — Functions.Fn.mapGraphNumbers, Functions.Fn.replaceOps, Functions.Fn.pruneWhere, Functions.Fn.graftWhere, Functions.Fn.duplicateWhere, Functions.Fn.slideCopy. The read-only renderings live here too: Functions.Fn.graphSnapshot, Functions.Fn.graphText, Functions.Fn.graphSvg and Functions.Fn.graphFLineMap.

Two constraints show up everywhere downstream. A node whose rebuild is null cannot be reconstructed, so a copy keeps the original evaluator and drops the recipe. And preserveSharing decides whether a shared subgraph stays shared (memoized by identity) or is expanded into a tree — Functions.Fn.deepCopy defaults it to true, while the rewriting entry points default it to false so each occurrence can be edited independently.

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

Used in: Functions to FLine examples (Field graphics), Patterns: writing rhythm as a function of time

Top-level functions

Declaration Summary
fun Functions.Fn.graphNode(): FnGraphNode This function's graph node — or a synthetic "opaque" node, tagged opaque, when it has no graph at all.
fun Functions.Fn.deepCopy(preserveSharing: Boolean = true, edit: FnGraphEdit = FnGraphEdit { _, copy -> copy }): Functions.Fn Rebuilds this function from its graph, bottom-up, offering every node to edit on the way — the single traversal every other rewrite in this family is written in terms of.
fun Functions.Fn.rewriteGraph(preserveSharing: Boolean = false, edit: FnGraphEdit): Functions.Fn deepCopy under the name that says what you are doing, and with sharing off by default so each occurrence of a repeated subgraph is offered to edit separately and can be changed independently.
fun Functions.Fn.mapGraphNumbers(preserveSharing: Boolean = false, transform: (value: Double, context: FnGraphCopyContext, key: String) -> Double): Functions.Fn Rewrites the numbers in the graph: every node carrying a scalar under the key "value" — which is how a constant is represented — is replaced by a constant holding whatever transform returns.
fun Functions.Fn.replaceOps(replacements: Map<String, String>, preserveSharing: Boolean = false): Functions.Fn Transmutes ops in place: every node whose op is a key of replacements is rebuilt as the corresponding op, reusing its role-addressed children and its scalar parameters.
fun Functions.Fn.replaceOp(from: String, to: String, preserveSharing: Boolean = false): Functions.Fn replaceOps for a single op. Same quiet no-op if the transmutation is not viable.
fun Functions.Fn.pruneWhere(preserveSharing: Boolean = false, replacement: (FnGraphCopyContext) -> Functions.Fn = { Functions.zero(it.domain) }, predicate: (FnGraphCopyContext) -> Boolean): Functions.Fn Cuts every node matching predicate out of the graph, putting replacement in its place — zero, by default.
fun Functions.Fn.graftWhere(graft: Functions.Fn, preserveSharing: Boolean = false, predicate: (FnGraphCopyContext) -> Boolean): Functions.Fn Substitutes graft for every node matching predicate — transplanting one expression into another wherever it fits.
fun Functions.Fn.duplicateWhere(preserveSharing: Boolean = false, duplicate: (Functions.Fn) -> Functions.Fn, merge: (original: Functions.Fn, duplicated: Functions.Fn) -> Functions.Fn, predicate: (FnGraphCopyContext) -> Boolean): Functions.Fn At every node matching predicate, makes a second copy of that subtree, passes it through duplicate, and combines the two with merge.
fun Functions.Fn.slideCopy(amount: Number, preserveSharing: Boolean = true): Functions.Fn This function plus a shifted copy of itself: f + f.shift(amount) — the one-line canon or echo.
fun Functions.Fn.graphSnapshot(maxDepth: Int = 32): FnGraphSnapshot A flattened, serialisable FnGraphSnapshot of this graph — ids, ops, metadata and role-labelled edges, with no live Fn references.
fun Functions.Fn.graphText(maxDepth: Int = 12): String The graph as indented text — one line per node with its op, name, domain and scalars — for printing into a box output.
fun Functions.Fn.graphSvg(width: Int = 960, nodeWidth: Double = 168.0, nodeHeight: Double = 54.0, xGap: Double = 215.0, yGap: Double = 84.0, maxDepth: Int = 10, sparklines: Boolean = true): String The graph drawn as a dark-themed SVG: one card per node, laid out left-to-right by depth, with curved edges and — by default — a sparkline of what that sub-expression actually evaluates to behind each card.
fun Functions.Fn.graphFLineMap(nodeWidth: Double = 168.0, nodeHeight: Double = 54.0, xGap: Double = 215.0, yGap: Double = 84.0, maxDepth: Int = 10): FLineMap The graph as an FLineMap of drawable boxes and connectors — the same tree layout as graphSvg, but as Field geometry you can restyle, animate or hit-test.

FnGraphEdge

data class FnGraphEdge(val role: String, val fn: Functions.Fn)

One child of a FnGraphNode, tagged with the role it plays in its parent rather than a position.

Declaration Summary
val role: String
val fn: Functions.Fn

FnGraphRebuilder

typealias FnGraphRebuilder = (List<Functions.Fn>) -> Functions.Fn

A node's recipe for putting itself back together: given the children in role order, return the equivalent Functions.Fn.

FnGraphNode

data class FnGraphNode(val op: String, val label: String = op, val children: List<FnGraphEdge> = emptyList(), val numbers: Map<String, Double> = emptyMap(), val strings: Map<String, String> = emptyMap(), val booleans: Map<String, Boolean> = emptyMap(), val longs: Map<String, Long> = emptyMap(), val tags: Set<String> = emptySet(), val rebuild: FnGraphRebuilder? = null)

One node of the graph hanging off a Functions.Fn: which op it is, its role-tagged children, its scalar metadata, and — if it can — how to rebuild itself.

Declaration Summary
val op: String
val label: String
val children: List<FnGraphEdge>
val numbers: Map<String, Double>
val strings: Map<String, String>
val booleans: Map<String, Boolean>
val longs: Map<String, Long>
val tags: Set<String>
val rebuild: FnGraphRebuilder?
fun child(role: String): Functions.Fn? The child in this role, or null if there is none — the role-addressed accessor rules use. First match wins.

FnGraphPathElement

data class FnGraphPathElement(val node: FnGraphNode, val role: String?, val index: Int, val name: String? = null)

One step on the path from the root down to a node: the node, the role it was reached by, its index among its siblings, and its Fn's name if it had one.

Declaration Summary
val node: FnGraphNode
val role: String?
val index: Int
val name: String?
val op: String The op at this step of the path.

FnGraphCopyContext

data class FnGraphCopyContext(val original: Functions.Fn, val node: FnGraphNode, val path: List<Int>, val depth: Int, val role: String? = null, val parent: FnGraphNode? = null, val ancestors: List<FnGraphPathElement> = emptyList())

Everything a rewrite is told about the node it is currently being offered: the original Fn, its graph node, where it sits in the tree, and its immediate parent and full ancestry.

Declaration Summary
val original: Functions.Fn
val node: FnGraphNode
val path: List<Int>
val depth: Int
val role: String?
val parent: FnGraphNode?
val ancestors: List<FnGraphPathElement>
val op: String The op name of the node being offered — the first thing most predicates test.
val name: String? The original function's name, if it had one. Null for the great majority of nodes.
val domain: Functions.Domain The original function's domain, which the replacement is usually expected to match.
val parentOp: String? The op of the immediate parent, or null at the root — "am I inside a sum?" in one property.
val occurrence: List<FnGraphPathElement> The whole path from the root to this node inclusive, as FnGraphPathElements — the addressable identity of this occurrence, as opposed to this op in general.

FnGraphEdit

fun interface FnGraphEdit

The rewrite hook: for each node of the graph, look at FnGraphCopyContext and either return a replacement or leave the copy alone.

Declaration Summary
fun edit(context: FnGraphCopyContext, copy: Functions.Fn): Functions.Fn? Return a replacement for this node, or copy/null to keep it.

FnGraphSnapshotNode

data class FnGraphSnapshotNode(val id: Int, val op: String, val label: String, val name: String?, val domain: Functions.Domain, val numbers: Map<String, Double>, val strings: Map<String, String>, val booleans: Map<String, Boolean>, val longs: Map<String, Long>, val tags: Set<String>)

One node in a flattened, read-only FnGraphSnapshot: an integer id and the node's op, label, name, domain, scalars and tags, with no reference back to the live Fn.

Declaration Summary
val id: Int
val op: String
val label: String
val name: String?
val domain: Functions.Domain
val numbers: Map<String, Double>
val strings: Map<String, String>
val booleans: Map<String, Boolean>
val longs: Map<String, Long>
val tags: Set<String>

FnGraphSnapshotEdge

data class FnGraphSnapshotEdge(val from: Int, val to: Int, val role: String)

A parent-to-child link in an FnGraphSnapshot, by node id, carrying the role the child was addressed by.

Declaration Summary
val from: Int
val to: Int
val role: String

FnGraphSnapshot

data class FnGraphSnapshot(val nodes: List<FnGraphSnapshotNode>, val edges: List<FnGraphSnapshotEdge>, val root: Int)

A flattened, Fn-free picture of a graph: a node list, an edge list and the root's id.

Declaration Summary
val nodes: List<FnGraphSnapshotNode>
val edges: List<FnGraphSnapshotEdge>
val root: Int

FunctionGraphBrowser.kt

The looking-at-it layer for the Fn graph family: sample a graph on one shared time-base, then render it as a self-contained interactive HTML browser.

Functions.Fn.graphTrace samples every visible occurrence three ways — local (that subgraph alone), muted (the whole root with the occurrence replaced by a neutral constant: 0.0, or 1.0 for a divisor) and effect (final - muted) — plus optional value sweeps for numeric nodes, and Functions.Fn.graphBrowseHtml / Functions.Fn.browseGraph wrap that in a canvas UI. Functions.Fn.graphRewriteTrace is the same idea for rewriting: it applies rules cumulatively — either FnGraphRules or the meaningful lines of a string-DSL spec — recording before, after and delta per stage for FnRewriteTrace.rewriteBrowseHtml and Functions.Fn.browseRewrite.

effect is a counterfactual, not an additive decomposition: it includes every transformation between the occurrence and the root. Tracing is bounded by maxDepth/maxNodes and sampling is NaN-safe (out-of-domain and throwing evaluations become NaN and are simply left undrawn). The browse* functions display into the current box and throw without an active Box.context; the *Html functions are pure strings and work anywhere.

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

Used in: Functions to FLine examples (Field graphics)

Top-level functions

Declaration Summary
fun Functions.Fn.graphTrace(sampleDomain: Functions.Domain? = null, samples: Int = 512, maxDepth: Int = 12, maxNodes: Int = 32, sensitivitySteps: Int = 0, sensitivitySpan: Double = 1.0): FnGraphTrace Samples every visible graph occurrence on one root time-base.
fun Functions.Fn.graphRewriteTrace(rules: List<FnGraphRule>, sampleDomain: Functions.Domain? = null, samples: Int = 512): FnRewriteTrace Samples the signal before and after each rule, applied cumulatively, so a rewrite can be watched step by step on one time-base. Works with pattern rules (fnRule) and programmatic FnGraphRules alike.
fun Functions.Fn.graphRewriteTrace(spec: String, sampleDomain: Functions.Domain? = null, samples: Int = 512): FnRewriteTrace String-DSL convenience: one stage per meaningful rule line, applied cumulatively with Functions.Fn.rewrite.
fun Functions.Fn.graphBrowseHtml(sampleDomain: Functions.Domain? = null, samples: Int = 512, heightPx: Int = 520, title: String = name ?: "Fn graph", maxDepth: Int = 12, maxNodes: Int = 32, sensitivitySteps: Int = 9): String Interactive occurrence browser with final, local, muted, and contextual-effect traces.
fun Functions.Fn.browseGraph(channel: String = "graph", sampleDomain: Functions.Domain? = null, samples: Int = 512, heightPx: Int = 520, title: String = name ?: "Fn graph", maxDepth: Int = 12, maxNodes: Int = 32): Functions.Fn Builds the occurrence browser and displays it in the current box, returning the function unchanged so it can sit mid-chain.
fun Functions.Fn.graphBrowseHtml(sampleDomain: ClosedRange<*>, samples: Int = 512, heightPx: Int = 520, title: String = name ?: "Fn graph", maxDepth: Int = 12, maxNodes: Int = 32): String graphBrowseHtml taking a Kotlin range — 0.0..2.0 — instead of a Functions.Domain, for the common case of plotting over something other than the function's own domain.
fun Functions.Fn.browseGraph(sampleDomain: ClosedRange<*>, channel: String = "graph", samples: Int = 512, heightPx: Int = 520, title: String = name ?: "Fn graph", maxDepth: Int = 12, maxNodes: Int = 32): Functions.Fn browseGraph over an explicit range: f.browseGraph(0.0..2.0).
fun FnRewriteTrace.rewriteBrowseHtml(heightPx: Int = 460, title: String = "Fn rewrite"): String Steppable before/after/delta lanes for a rewrite, all on the shared time-base.
fun Functions.Fn.browseRewrite(spec: String, channel: String = "rewrite", sampleDomain: Functions.Domain? = null, samples: Int = 512, heightPx: Int = 460, title: String = name?.let { "rewrite $it" } ?: "Fn rewrite"): Functions.Fn Compute and display before/after rewrite lanes for a plain-DSL spec in the current box.
fun Functions.Fn.browseRewrite(rules: List<FnGraphRule>, channel: String = "rewrite", sampleDomain: Functions.Domain? = null, samples: Int = 512, heightPx: Int = 460, title: String = name?.let { "rewrite $it" } ?: "Fn rewrite"): Functions.Fn Display before/after rewrite lanes for programmatic rules in the current box.

FnGraphSweepPoint

data class FnGraphSweepPoint(val value: Double, val samples: DoubleArray)

One swept value of a numeric node and the final signal it produces.

Declaration Summary
val value: Double The value substituted at the swept node — the node is replaced by a plain constant holding it.
val samples: DoubleArray The whole root sampled with that substitution in place, on the trace's shared abscissae.

FnGraphTraceNode

data class FnGraphTraceNode(val path: List<Int>, val role: String?, val op: String, val name: String?, val domain: Functions.Domain, val local: DoubleArray, val muted: DoubleArray, val effect: DoubleArray, val numeric: Boolean = false, val value: Double? = null, val sensitivity: List<FnGraphSweepPoint> = emptyList())

One occurrence of the graph, sampled three ways on the root's time-base — and, if it is a number and you asked for sweeps, once more per swept value.

Declaration Summary
val path: List<Int> Child indices from the root; empty is the root itself, which the trace does include.
val role: String? The role of the edge this occurrence hangs from, or null at the root.
val op: String The op name, as the sidebar labels it.
val name: String? The occurrence's own function name, if it had one.
val domain: Functions.Domain The occurrence's own domain, which can be narrower than the domain actually plotted.
val local: DoubleArray This subgraph sampled directly at the root's abscissae, ignoring everything above it.
val muted: DoubleArray The rebuilt root with just this occurrence replaced by a neutral constant — 0.0, or 1.0 when it is the right-hand side of a divide. Muting the root itself therefore flatlines the whole trace.
val effect: DoubleArray final - muted, index by index, and non-finite wherever either side was. The contextual effect.
val numeric: Boolean True when this node carries a numeric value and is tagged as a number — the sweepable nodes.
val value: Double? That numeric value, or null for anything else.
val sensitivity: List<FnGraphSweepPoint> The value sweep behind the browser's sensitivity view. Empty unless the node is numeric and the caller asked for sensitivitySteps above zero — the trace does not sweep by default, though Functions.Fn.graphBrowseHtml does.

FnGraphTrace

data class FnGraphTrace(val domain: Functions.Domain, val xs: DoubleArray, val final: DoubleArray, val nodes: List<FnGraphTraceNode>)

A whole graph sampled on one shared time-base: the abscissae, the finished signal, and one FnGraphTraceNode per visible occurrence.

Declaration Summary
val domain: Functions.Domain The domain actually plotted, after an infinite or empty one was widened into something finite.
val xs: DoubleArray The shared abscissae every array in the trace is indexed by.
val final: DoubleArray The unmodified root sampled at xs — the gray curve the browser always keeps on screen.
val nodes: List<FnGraphTraceNode> One entry per sampled occurrence, root first.

FnRewriteStage

data class FnRewriteStage(val label: String, val before: DoubleArray, val after: DoubleArray, val delta: DoubleArray, val changed: Boolean)

One rewrite step: the signal before and after applying a single rule, on the shared axis.

Declaration Summary
val label: String What the step is called: the rule's name, or the raw spec line for the string-DSL form.
val before: DoubleArray The signal as it stood before this rule — i.e. after every earlier stage, since they accumulate.
val after: DoubleArray The signal after this rule.
val delta: DoubleArray after - before, non-finite wherever either side was.
val changed: Boolean Whether the rule really changed the graph, judged structurally rather than by the samples — a rule that matched nothing, or whose transmutation was refused, reports false and the stepper labels it no-op. A structural change with a flat delta is perfectly possible, and interesting.

FnRewriteTrace

data class FnRewriteTrace(val domain: Functions.Domain, val xs: DoubleArray, val original: DoubleArray, val finalSamples: DoubleArray, val stages: List<FnRewriteStage>)

A rewrite watched one rule at a time: the shared abscissae, the signal before anything ran, the signal at the end, and a FnRewriteStage per rule.

Declaration Summary
val domain: Functions.Domain The domain actually plotted, after an infinite or empty one was widened into something finite.
val xs: DoubleArray The shared abscissae every array here is indexed by.
val original: DoubleArray The receiver sampled before any rule ran.
val finalSamples: DoubleArray The function every stage ended on, sampled again at xs — the same signal the last stage's after holds.
val stages: List<FnRewriteStage> One stage per rule, in the order they were applied.

FunctionGraphRules.kt

The query and rule-runner layer over the graph copier in FunctionGraph.kt.

Queries come first, and none of them copy or evaluate the function: Functions.Fn.graphMatches and Functions.Fn.firstGraphMatch visit a shared node once per incoming path, so role, ancestry, depth and path stay meaningful for editing; Functions.Fn.graphMetrics separates unique DAG nodes from occurrences and charges a caller-supplied cost; Functions.Fn.graphSignature reduces structure to a deterministic string with evaluator closures and rebuild lambdas excluded. On top of those, FnGraphRule pairs a named predicate with a replacement, and Functions.Fn.rewriteWithRules (one bottom-up pass) and Functions.Fn.rewriteUntilStable (repeated passes) apply them, returning an FnGraphRewriteRun that records every edit that actually changed a node's signature.

Fixed-point rewriting is bounded: Functions.Fn.rewriteUntilStable compares signatures each pass and reports FnGraphRewriteStop.STABLE, FnGraphRewriteStop.CYCLE or FnGraphRewriteStop.MAX_PASSES rather than letting a non-terminating rule set hide inside an animation loop. Rules default to FnGraphRuleMode.FIRST_MATCH at each node, so ordering is explicit; FnGraphRuleMode.ALL_MATCHES chains every matching rule on the same copy.

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

Used in: Functions to FLine examples (Field graphics)

Top-level functions

Declaration Summary
fun Functions.Fn.graphMatches(maxDepth: Int = 64, predicate: (FnGraphCopyContext) -> Boolean): List<FnGraphMatch> Finds occurrence-sensitive matches without copying or evaluating the function.
fun Functions.Fn.firstGraphMatch(maxDepth: Int = 64, predicate: (FnGraphCopyContext) -> Boolean): FnGraphMatch? Returns the first occurrence-sensitive match in depth-first order.
fun Functions.Fn.graphMetrics(maxDepth: Int = 64, nodeCost: (FnGraphNode) -> Double = { 1.0 }): FnGraphMetrics Measures both unique DAG nodes and path-sensitive occurrences.
fun Functions.Fn.graphSignature(maxDepth: Int = 64): String A deterministic structural signature for convergence checks and regression tests. Evaluator closures and rebuild lambdas are excluded, so two copies of one graph sign alike.
fun Functions.Fn.rewriteWithRules(rules: List<FnGraphRule>, preserveSharing: Boolean = false, mode: FnGraphRuleMode = FnGraphRuleMode.FIRST_MATCH): FnGraphRewriteRun Applies one bottom-up pass of named rules and records every effective edit.
fun Functions.Fn.rewriteWithRules(vararg rules: FnGraphRule, preserveSharing: Boolean = false, mode: FnGraphRuleMode = FnGraphRuleMode.FIRST_MATCH): FnGraphRewriteRun Vararg convenience for a single bottom-up rule pass.
fun Functions.Fn.rewriteUntilStable(rules: List<FnGraphRule>, maxPasses: Int = 16, preserveSharing: Boolean = false, mode: FnGraphRuleMode = FnGraphRuleMode.FIRST_MATCH): FnGraphRewriteRun Repeats bottom-up rule passes until structure is stable, cycles, or reaches maxPasses. The bound is what makes a non-terminating rule set fail visibly instead of spinning inside an animation loop.
fun Functions.Fn.rewriteUntilStable(vararg rules: FnGraphRule, maxPasses: Int = 16, preserveSharing: Boolean = false, mode: FnGraphRuleMode = FnGraphRuleMode.FIRST_MATCH): FnGraphRewriteRun Vararg convenience for bounded fixed-point rewriting.

FnGraphRule

data class FnGraphRule(val name: String, val matches: (FnGraphCopyContext) -> Boolean, val replace: (context: FnGraphCopyContext, copy: Functions.Fn) -> Functions.Fn)

A named, programmatic rewrite rule over one occurrence in an Functions.Fn graph.

Declaration Summary
val name: String
val matches: (FnGraphCopyContext) -> Boolean
val replace: (context: FnGraphCopyContext, copy: Functions.Fn) -> Functions.Fn

FnGraphRuleMode

enum class FnGraphRuleMode

Whether one rewrite pass uses the first matching rule or chains every matching rule.

Constants: FIRST_MATCH, ALL_MATCHES.

FnGraphRewriteStop

enum class FnGraphRewriteStop

Why a bounded graph-rewrite run stopped.

Constants: ONE_PASS, STABLE, CYCLE, MAX_PASSES.

FnGraphRewriteEvent

data class FnGraphRewriteEvent(val pass: Int, val rule: String, val path: List<Int>, val role: String?, val beforeOp: String, val afterOp: String, val beforeName: String?, val afterName: String?)

One effective rule application. Paths are child indices from the graph root.

Declaration Summary
val pass: Int
val rule: String
val path: List<Int>
val role: String?
val beforeOp: String
val afterOp: String
val beforeName: String?
val afterName: String?

FnGraphRewriteRun

data class FnGraphRewriteRun(val function: Functions.Fn, val events: List<FnGraphRewriteEvent>, val passes: Int, val stop: FnGraphRewriteStop)

Result and provenance from Functions.Fn.rewriteWithRules or Functions.Fn.rewriteUntilStable.

Declaration Summary
val function: Functions.Fn
val events: List<FnGraphRewriteEvent>
val passes: Int
val stop: FnGraphRewriteStop
val stable: Boolean
val changed: Boolean

FnGraphMatch

data class FnGraphMatch(val function: Functions.Fn, val context: FnGraphCopyContext)

A matched graph occurrence. Shared nodes may appear more than once through different context paths.

Declaration Summary
val function: Functions.Fn
val context: FnGraphCopyContext

FnGraphMetrics

data class FnGraphMetrics(val uniqueNodes: Int, val occurrences: Int, val edges: Int, val maxDepth: Int, val opaqueNodes: Int, val rebuildableNodes: Int, val cost: Double)

Small structural measurements suitable for diagnostics and optimizer cost functions.

Declaration Summary
val uniqueNodes: Int
val occurrences: Int
val edges: Int
val maxDepth: Int
val opaqueNodes: Int
val rebuildableNodes: Int
val cost: Double

FunctionOps.kt

The op vocabulary for the whole Fn graph family, held in one object, OpRegistry: which op names the stdlib actually emits into a graph node, which of them are aliases of each other, and which can be rebuilt from an arbitrary node's role-addressed children — those rebuildable ones each carrying a family label (oscillator, noise, transform, …).

Everything that has to reason about ops consults it instead of keeping a private list — the rewrite DSL's op= validator and its hint text, the pattern matcher's alias-aware op comparison, rebuildAsOp, and the "another op in the same family" draw behind the substituteOp mutation. The Monaco fnrewrite.js extension mirrors it, and a JVM test asserts that mirror never drifts.

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

OpRegistry

object OpRegistry

Single source of truth for the Fn graph op vocabulary and op= transmutation.

Declaration Summary
data class OpSpec(val canonical: String, val family: String, val aliases: Set<String> = emptySet(), val build: (OpSource) -> Functions.Fn?) A transmutation target: how to rebuild canonical from an arbitrary matched node.
class OpSource internal constructor(val self: Functions.Fn) Read-only view of the node being transmuted: children by role, plus scalar metadata.
val knownOps: Set<String> Every op string the stdlib actually emits (from node(...)/graphFn(...)), so validation and selectors never treat a real op as unknown even when it is not a transmutation target. Kept in sync with Functions.kt by FunctionsGraphRewriteTest.
val transmutableOps: List<String> Canonical op names that op= can rebuild to, grouped by family, for docs and diagnostics.
fun knows(name: String): Boolean
fun isTransmutable(name: String): Boolean
fun canonical(name: String): String?
fun family(name: String): String? The family of a transmutable op (e.g. "oscillator", "noise", "transform").
fun opsInFamily(family: String): List<String> Canonical transmutable ops in family, useful for within-family mutation.
fun rebuild(fn: Functions.Fn, targetOp: String): Functions.Fn? Rebuild fn as targetOp, or null if the target is not a viable transmutation of this node.
fun transmutableHint(): String One-line description of the transmutation vocabulary, for validator hints.

FunctionRewriteDsl.kt

The selector => action string rewrite DSL, compiled and applied end to end. compileFnRewrite parses a spec into an FnRewriteProgram carrying line/column FnRewriteDiagnostics; Functions.Fn.rewrite, Functions.Fn.previewRewrite (which also reports match paths and which rules did nothing) and Functions.Fn.rewriteChecked (which refuses an invalid program) run it over the graph copier. Selector matching lives here — op, role, name, tag and depth tokens, parent=, [key>=n] predicates and slash-separated path selectors with * and ** wildcards — and so does action application: op=, name=, domain=, drop, and the numeric =, *=, /=, +=, -= forms. op= targets are checked against OpRegistry; an unknown op is reported as a warning, not an error.

The file does not depend on the live-code compiler. A Kotlin right-hand side is parsed out of its @{...} (or dollar-brace) wrapper and then handed to FnRewriteOptions.kotlinNumberContext; when no caller supplied one, the action is simply not applied. FunctionRewriteKotlin.kt is what plugs a box's Kotlin engine into that hook, and FunctionRewritePattern.kt is the separate structural pattern-to-template surface.

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

Used in: Functions to FLine examples (Field graphics)

Top-level functions

Declaration Summary
fun compileFnRewrite(spec: String): FnRewriteProgram Parses a rewrite spec and reports on it, without needing a function graph or touching one.
fun Functions.Fn.rewrite(spec: String, options: FnRewriteOptions = FnRewriteOptions()): Functions.Fn Compiles spec and applies it, returning a new function and discarding everything the attempt learned — the forgiving one-liner for sketching and performance.
fun Functions.Fn.previewRewrite(spec: String, options: FnRewriteOptions = FnRewriteOptions()): FnRewritePreview Compiles and applies spec, retaining diagnostics, match paths, and effective-change information.
fun Functions.Fn.rewriteChecked(spec: String, options: FnRewriteOptions = FnRewriteOptions()): Functions.Fn Functions.Fn.rewrite, but a malformed spec throws instead of applying whatever rules parsed.
fun Functions.Fn.rewrite(spec: String, preserveSharing: Boolean): Functions.Fn rewrite when the only thing you want to vary is sharing — no evaluator, no other options.

FnRewriteNumberContext

data class FnRewriteNumberContext(val expression: String, val current: Double, val graph: FnGraphCopyContext)

Everything one matched node can tell a right-hand side about itself.

Declaration Summary
val expression: String The Kotlin source of the right-hand side, already unwrapped from its @{...} or dollar-brace delimiters, and trimmed. An expression that carried no delimiters at all arrives verbatim.
val current: Double The numeric value the action is about to change: the matched node's value metadata entry.
val graph: FnGraphCopyContext The matched occurrence itself — op, role, path, ancestry, domain and the node's scalar maps.
val value: Double current under the name a snippet actually writes, as in *= @{value * 1.1}.
val it: Double current again, so a snippet can read like a lambda: = @{it * 2}. A plain property, not a real lambda parameter — nothing is shadowing it, and it inside a nested lambda wins as usual.
val op: String The op name of the matched node, from OpRegistry's vocabulary.
val role: String? The role of the edge this node was reached by ("frequency", "amplitude", …), or null at the root.
val parameter: String? Alias for role, for rules that read better as "which parameter am I".
val parentOp: String? The immediate parent's op, or null at the root — "am I inside an fbm?" in one property.
val depth: Int How many edges below the root this occurrence sits; the root itself is 0.
val path: List<Int> Child indices from the root down to this node — the same path a preview reports.
val ancestors: List<FnGraphPathElement> The path elements from the root down to this node's parent, root first. Empty at the root.
val occurrence: List<FnGraphPathElement> ancestors plus this node — the whole root-to-here path, which is what a path selector matches against. Rebuilt on every read, so bind it to a local rather than indexing it repeatedly.
val name: String? The matched function's own name, if it was given one. Null for the great majority of nodes.
val domain: Functions.Domain The matched function's own domain, which can be narrower than the root's.
val numbers: Map<String, Double> This node's numeric metadata — its own parameters, keyed by name. A constant carries value.
val strings: Map<String, String> This node's string metadata, for the ops that carry names or modes rather than numbers.
val booleans: Map<String, Boolean> This node's boolean metadata — flags such as a wrap or clamp mode.
val longs: Map<String, Long> This node's long metadata, typically seeds and counts.
fun number(key: String): Double? One numeric metadata entry, or null if this node has no such key.
fun string(key: String): String? One string metadata entry, or null if this node has no such key.
fun boolean(key: String): Boolean? One boolean metadata entry, or null if this node has no such key.
fun long(key: String): Long? One long metadata entry, or null if this node has no such key.

FnRewriteOptions

data class FnRewriteOptions(val kotlinNumber: ((expression: String, current: Double, context: FnGraphCopyContext) -> Double?)? = null, val preserveSharing: Boolean = false, val kotlinNumberContext: ((FnRewriteNumberContext) -> Double?)? = null)

How a rewrite should be run: who evaluates a Kotlin right-hand side, and whether shared subgraphs stay shared.

Declaration Summary
val kotlinNumber: ((expression: String, current: Double, context: FnGraphCopyContext) -> Double?)? The older, flatter evaluator hook, taking the raw expression text. Consulted only when kotlinNumberContext is absent or returns null, so it also works as a fallback under it.
val preserveSharing: Boolean Whether a subgraph reached by two paths is copied once and shared, or expanded so each occurrence can be edited on its own. False — expand — because that is what makes path-sensitive selectors behave the way you would guess; see Functions.Fn.deepCopy.
val kotlinNumberContext: ((FnRewriteNumberContext) -> Double?)? The preferred hook: given the matched node wrapped as an FnRewriteNumberContext, return the new number, or null to leave the value alone. Returning null falls through to kotlinNumber.

FnRewriteSeverity

enum class FnRewriteSeverity

Whether a diagnostic kills a rule or merely grumbles about it.

Constants: ERROR, WARNING.

FnRewriteDiagnostic

data class FnRewriteDiagnostic(val severity: FnRewriteSeverity, val line: Int, val column: Int, val message: String, val hint: String? = null)

One complaint from the rewrite parser, addressed to a line and column of the spec so an editor or an agent can point at it.

Declaration Summary
val severity: FnRewriteSeverity Fatal for FnRewriteProgram.valid, or advisory. See FnRewriteSeverity.
val line: Int One-based line within the spec. Rules separated by ; on one line all report that line.
val column: Int One-based column. Mostly 1, because most checks are about the statement as a whole; an empty action is reported just past the arrow.
val message: String What is wrong, as a sentence.
val hint: String? An optional nudge toward the fix — accepted arrow forms, valid domain syntax, viable ops.
override fun toString(): String severity at line:column: message (hint) — the one-line form that goes into a report.

FnRewriteRuleSpec

data class FnRewriteRuleSpec(val line: Int, val selector: String, val actions: List<String>)

One parsed rule: the line it came from, the selector text on the left of the arrow, and the actions on the right.

Declaration Summary
val line: Int One-based line of the spec this rule was written on.
val selector: String The raw selector text, trimmed — everything before the arrow.
val actions: List<String> The actions, in application order, each already trimmed and non-empty.

FnRewriteProgram

data class FnRewriteProgram(val source: String, val rules: List<FnRewriteRuleSpec>, val diagnostics: List<FnRewriteDiagnostic>)

A compiled rewrite spec: the source you handed in, the rules that survived parsing, and every diagnostic raised along the way.

Declaration Summary
val source: String The spec text exactly as it was passed in.
val rules: List<FnRewriteRuleSpec> The rules that parsed, in source order. This is the order they are tried at each node.
val diagnostics: List<FnRewriteDiagnostic> Every error and warning, in source order.
val valid: Boolean True when nothing fatal was found. Warnings — an unknown op= target — do not make a program invalid.
fun diagnosticText(): String The diagnostics as printable lines, or a one-line "no diagnostics" note with the rule count.

FnRewriteApplication

data class FnRewriteApplication(val rule: Int, val line: Int, val selector: String, val actions: List<String>, val path: List<Int>, val role: String?, val beforeOp: String, val afterOp: String, val changed: Boolean)

One rule firing at one occurrence: which rule, where it landed, and whether it actually changed anything.

Declaration Summary
val rule: Int Zero-based index into FnRewriteProgram.rules. A summary prints it one-based, as #1.
val line: Int The one-based spec line of the rule, for reporting back at the source.
val selector: String The rule's selector text, copied here so an application reads on its own.
val actions: List<String> The rule's actions, in the order they were applied to this node.
val path: List<Int> Child indices from the graph root to the matched node; empty means the root itself.
val role: String? The role of the edge the matched node hangs from, or null at the root.
val beforeOp: String The op before this rule's actions ran here.
val afterOp: String The op afterwards. Usually the same: a naming edit, a numeric edit or a refused transmutation all leave it.
val changed: Boolean Whether the node's structural signature actually moved. False is the correct report for a transmutation the OpRegistry refused, a numeric action on a non-numeric node, a Kotlin right-hand side with no evaluator installed, and *= 1.0.

FnRewritePreview

data class FnRewritePreview(val function: Functions.Fn, val program: FnRewriteProgram, val applications: List<FnRewriteApplication>)

The result of a rewrite with its receipts: the rewritten function, the compiled program, and one FnRewriteApplication per match.

Declaration Summary
val function: Functions.Fn The rewritten function. Always a fresh graph; the receiver of the rewrite is untouched.
val program: FnRewriteProgram The compiled spec, including the diagnostics that did not stop it.
val applications: List<FnRewriteApplication> Every rule/occurrence pair that fired, in traversal order (bottom-up, so children first).
val matchedRules: Int How many distinct rules found at least one node. Compare with program.rules.size.
val matchCount: Int Total matches across all rules — one per rule per occurrence, so easily larger than the rule count.
val changeCount: Int How many of those matches changed the graph. The gap between this and matchCount is the no-ops.
val unmatchedRules: List<FnRewriteRuleSpec> The rules that never matched anything — the one place a mistyped selector becomes visible, since the parser does not check selectors. Treat these as hypotheses to reconsider, not as success.
fun summary(): String Compact deterministic report suitable for logs, chat, and agent transcripts.

FunctionRewriteKotlin.kt

The bridge that gives the string rewrite DSL a real Kotlin right-hand side.

FunctionRewriteDsl.kt parses @{...} expressions but never compiles them; this file supplies the FnRewriteOptions.kotlinNumberContext hook it leaves open, compiling each expression as a FnRewriteNumberContext -> Any? lambda inside a live box's coding context so a snippet can read value, op, parentOp, depth and the rest as plain properties. SourceBackedFnRewriteNumberExpression stores only source plus a box id and hydrates on first use; SourceBackedFnRewriteNumberEvaluator caches one per distinct expression string. The entry points are Functions.Fn.rewriteKotlin, Functions.Fn.previewRewriteKotlin, Functions.Fn.rewriteKotlinChecked and the Box.rewriteFn / Box.previewRewriteFn receiver forms — each is the corresponding plain-DSL call with that evaluator wired in.

Hydration requires the originating box to still be loaded and the compiled lambda to return a Number?; anything else throws, naming the box or the offending source. When there is no box in scope no evaluator is installed at all, so Kotlin right-hand sides are left unapplied; the compiler-free rewrite(...) path never needs one.

Do not call rewriteKotlin (or thisBox.rewriteFn) from the body of the box the snippets compile in. Hydration evaluates on that box's own engine, which re-instantiates its snippet chain and so re-runs the body that is already running — once per matched node, until the stack goes. It is guarded now: the second hydration for a box on one thread throws FnRewriteSelfBoxReentry, whose message names the ways out. Same hazard as afield.study.source.SourceBackedBlock, reached by a second route; see dev_docs/FN_REWRITE_SELF_BOX_REENTRY.md.

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

Used in: Functions to FLine examples (Field graphics)

Top-level functions

Declaration Summary
fun fnRewriteNumberExpression(source: String, box: Box? = Box.context.get()): SourceBackedFnRewriteNumberExpression Wraps a snippet of Kotlin source as a rewrite-number evaluator bound to box, compiled lazily on first use.
fun fnRewriteNumber(block: FnRewriteNumberContext.() -> Number?): SourceBackedFnRewriteNumberExpression A rewrite-number evaluator written as an ordinary Kotlin lambda over FnRewriteNumberContext, with no compilation involved — the typed alternative to a string of source.
fun Functions.Fn.rewriteKotlin(spec: String, preserveSharing: Boolean = false, box: Box? = Box.context.get()): Functions.Fn Functions.Fn.rewrite with the live box's Kotlin evaluator wired in — the version to use in Field code, and the only one where a @{...} right-hand side actually runs.
fun Functions.Fn.previewRewriteKotlin(spec: String, preserveSharing: Boolean = false, box: Box? = Box.context.get()): FnRewritePreview Functions.Fn.previewRewrite with the live box's Kotlin evaluator wired in: the same rewrite rewriteKotlin performs, but keeping the diagnostics, matched paths and no-ops.
fun Functions.Fn.rewriteKotlinChecked(spec: String, preserveSharing: Boolean = false, box: Box? = Box.context.get()): Functions.Fn Functions.Fn.rewriteChecked with the live box's Kotlin evaluator wired in — throws for a spec whose parse produced any error, rather than applying whatever rules happened to parse.
fun Box.rewriteFn(fn: Functions.Fn, spec: String, preserveSharing: Boolean = false): Functions.Fn Functions.Fn.rewriteKotlin with the box named explicitly instead of looked up: box.rewriteFn(f, spec).
fun Box.previewRewriteFn(fn: Functions.Fn, spec: String, preserveSharing: Boolean = false): FnRewritePreview Functions.Fn.previewRewriteKotlin with the box named explicitly: the reporting form of rewriteFn, for the same off-context callers.

FnRewriteSelfBoxReentry

class FnRewriteSelfBoxReentry(box: Box, source: String) : IllegalStateException("This rewrite is compiling '@{$source}' in box '${box.name}' (${box.id}) — the box that is running it, " + "whose body it therefore re-runs, once per matched node, until the stack overflows. " + "Run the rewrite somewhere that is not this box's own body: " + "(1) from another box — `other.rewriteFn(fn, spec)`, e.g. `parents.last().rewriteFn(fn, spec)`, " + "where `other` is idle; " + "(2) from a frame loop or a control callback, after this body has finished; or " + "(3) with a typed evaluator instead of a source string — `fnRewriteNumber { value * 2.0 }` " + "carries a live lambda and never compiles anything. " + "See dev_docs/FN_REWRITE_SELF_BOX_REENTRY.md.")

Thrown when a @{...} right-hand side would be compiled in a box whose body is already running — the failure that used to be 172 nested body runs and a StackOverflowError. The in-flight set it consults is afield.study.source.hydratingBoxes, shared with SourceBackedBlock's identical guard.

SourceBackedFnRewriteNumberExpression

class SourceBackedFnRewriteNumberExpression(val boxId: String, val source: String, private val block: ((FnRewriteNumberContext) -> Any?)? = null) : (FnRewriteNumberContext) -> Double?

Source-backed Kotlin evaluator for @{...} fragments inside the Fn rewrite DSL.

Declaration Summary
val boxId: String The box whose live-coding context the source will be compiled in. Empty when there was no box in scope at construction, which makes the first call fail rather than fall back to anything.
val source: String The Kotlin source of the right-hand side, already stripped of its @{...} wrapper. Compiled inside a with(context) block, so it reads the FnRewriteNumberContext members as bare names, and its last expression is the result.
fun invalidate() Forget a compiled lambda or rejected compile after changing the originating box's context.
override fun invoke(context: FnRewriteNumberContext): Double? Evaluates the expression for one matched node, returning the new number or null to leave the value alone.
override fun toString(): String The box id and the first 80 characters of the source, newlines flattened — safe to log.

SourceBackedFnRewriteNumberEvaluator

class SourceBackedFnRewriteNumberEvaluator(val boxId: String) : (FnRewriteNumberContext) -> Double?

The evaluator a whole rewrite runs through: one per box, compiling and caching a SourceBackedFnRewriteNumberExpression per distinct expression string it is asked for.

Declaration Summary
val boxId: String The box every expression will be compiled in.
fun clearCache() Forget cached successes and compiler rejections; the next rewrite hydrates against the current box.
override fun invoke(context: FnRewriteNumberContext): Double? Compiles FnRewriteNumberContext.expression if this is the first sighting of it, then evaluates it.

FunctionRewritePattern.kt

The third rewriting surface in the family, and the algebraic one. FunctionRewriteDsl.kt is selector => action and edits one matched node in place; FnGraphRule in FunctionGraphRules.kt is a hand-written predicate plus a hand-written replacement; here both sides of a rule are graph shapes with metavariables (FnPattern), so add(?x, 0) => ?x reads as an identity rather than an instruction. fnRule compiles a spec into an ordinary FnGraphRule, which is what lets pattern rules reuse the existing traced, bounded, cycle-detecting runners; Functions.Fn.rewritePatterns is the fixed-point convenience over a set of specs.

Matching and the parser are both here. A ?name binds a subtree and, if repeated, must bind a structurally equal one (compared by graphSignature()); a bare number matches a numeric node's value to within 1e-12; op names are compared through OpRegistry's alias table; op(a, b, …) constrains only the leading children. The string template side is narrower than the pattern side: a bare op transmutes the matched node, only add/subtract/multiply/divide accept an explicit child list, and any other op(...) template throws — the fnRule overload taking a Kotlin builder over FnPatternMatch is the general escape.

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

Used in: Functions to FLine examples (Field graphics)

Top-level functions

Declaration Summary
fun fnRule(spec: String): FnGraphRule Compile "lhs => rhs" into a traced FnGraphRule usable with the existing rewrite runners.
fun fnRule(name: String, lhs: String, build: Functions.(FnPatternMatch) -> Functions.Fn): FnGraphRule Compile a pattern LHS with a Kotlin builder RHS. This is the fully general surface: the pattern captures structure, the builder writes the replacement in ordinary Kotlin over the whole Functions stdlib.
fun Functions.Fn.rewritePatterns(vararg specs: String, maxPasses: Int = 16, preserveSharing: Boolean = false): FnGraphRewriteRun Convenience: rewrite to a fixed point with a set of pattern rules.

FnPattern

sealed interface FnPattern

Structural pattern → template rewriting: the term-rewriting surface on top of the graph copier.

Declaration Summary
data class Var(val name: String) : FnPattern ?name — binds (or, if already bound, must structurally re-match) a subtree.
data object Wild : FnPattern _ — matches anything without binding.
data class Num(val value: Double) : FnPattern A numeric literal — matches a numbers["value"] node (const/param).
data class Op(val op: String, val children: List<FnPattern>, val constrained: Boolean) : FnPattern op (children unconstrained) or op(child, …) (leading children must match).

FnPatternMatch

class FnPatternMatch internal constructor(val matched: Functions.Fn, private val bindings: Map<String, Functions.Fn>)

The result of matching a pattern: metavariable bindings plus the matched node.

Declaration Summary
val matched: Functions.Fn
fun sub(name: String): Functions.Fn The subtree bound to ?name.
fun value(name: String): Double? The numeric value of ?name, if it bound a numeric node.
fun has(name: String): Boolean

FunctionStudy.kt

A function study: a set of Functions.Fn channels written as Kotlin in a box, kept as checked revisions an AI conversation can inspect, edit, observe and export.

The box's source returns a StudyComposition: the channels, their names, declared StudyEvent landmarks and the StudyChoices behind them. FunctionStudy compiles each candidate, validates it, and only then publishes it as a new StudyRevision; the tools that drive it are in FunctionStudyTools.kt.

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

Used in: Talking a function into existence

StudyEvent

data class StudyEvent(val id: String, val start: Double, val rise: Double, val hold: Double = 30.0, val fall: Double = 60.0)

Stable, declared event landmarks in seconds. These are metadata, not a second function language.

StudyChoice

data class StudyChoice(val statement: String, val provenance: String)

The source declares its choices; checks never silently promote an assumption to a requirement.

StudySourceEdit

data class StudySourceEdit(val before: String, val after: String)

Exact, unique replacement in the inspected live source. All matches use the same base.

StudyComposition

data class StudyComposition(val channels: List<Functions.Fn>, val events: List<StudyEvent> = emptyList(), val choices: List<StudyChoice> = emptyList(), val duration: Double = 3600.0, val channelNames: List<String> = listOf("x", "y", "z"), val maxFrequencyHz: Double = 2.0, val seed: Long = 0)

Ordinary Functions.Fn values. Callers promise pure, deterministic, random-access evaluation.

Declaration Summary
val channels: List<Functions.Fn>
val events: List<StudyEvent>
val choices: List<StudyChoice>
val duration: Double
val channelNames: List<String>
val maxFrequencyHz: Double
val seed: Long

StudyRevision

data class StudyRevision(val id: String, val parent: String?, val description: String, val source: String, val composition: StudyComposition)

FunctionStudy

class FunctionStudy(private val directory: File, private val readSource: () -> String, replaceSource: (expected: String, replacement: String) -> Boolean, private val compiler: (String) -> StudyComposition = ::compileStudy)

Experimental observation/transaction layer over Fn. No model transport and no alternate evaluator. Source is authoritative. A candidate is compiled on a fresh Field Kotlin engine and checked before compare-and-swap publishes it. This is trusted local Kotlin, NOT an isolation/security boundary. Cancellation invalidates pending publication; arbitrary non-cooperative Kotlin cannot be killed here.

Declaration Summary
val revisions: SourceStudy<StudyComposition> The revision history this study publishes through: studies/<box id>/<id>.kt and <id>.json.
var editorLine: () -> Int? The line the person's editor cursor is on in this study's box, or null — see BoxStudies.personAt.
fun cancel(): String
fun currentId(): String
fun revision(id: String = "current"): StudyRevision A revision by id, or the active one for "current" or a blank id — as a model often sends.
fun resume(): String Resume only when the saved source still agrees. A manual edit remains explicitly dirty.
fun inspect(id: String = "current"): StudyReport
fun select(ids: List<String>, start: Double, end: Double): StudyReport
fun patch(base: String, sourceHash: String, edits: List<StudySourceEdit>, description: String): StudyReport Two guards: checked revision AND the current editable source, including uncompiled manual edits. See SourceStudy.patch.
fun edit(base: String, sourceHash: String, source: String, description: String): StudyReport See SourceStudy.edit: stale guards, a failed compile or check, or a concurrent edit change nothing.
fun previewEdit(report: StudyReport, start: Double = 0.0, end: Double = 25.0): StudyReport Preview a committed edit without turning a preview error into a misleading failed edit.
fun exportCsv(id: String, expectedSourceHash: String, outputs: List<File>, sampleRate: Double = 100.0, scale: Double = 100.0, header: Boolean = false, overwrite: Boolean = false): StudyReport Export a checked immutable revision, staging and verifying every file before replacing destinations.
fun sample(id: String, times: List<Double>): StudyReport
fun observe(id: String, start: Double, end: Double, compareTo: String = ""): StudyReport Revision-consistent pixels + landmarks + measured holds/joins. Same samples feed image and extrema.

FunctionStudyTools.kt

The tools an AI4 conversation gets when it is opened on a function study's code.

A function study is a box whose Kotlin returns a StudyComposition and whose aiStudy property is "function"; the "Make a Function Study" action sets it. The request classes here (inspect, edit, patch, export, observe, sample, select, compare) are the tool schemas, and installFunctionStudyTools registers them. Each tool works on the focused box's own study, so nothing here is per document; a document can still add tools of its own from a box, scoped to it.

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

Used in: Talking a function into existence

Top-level functions

Declaration Summary
fun installFunctionStudyTools() Register the function study tools once, for every conversation opened on a function study's code.
fun installFunctionStudyWatcher() Show each function study's viewer when its document opens (or a box is marked), without waiting for a tool call.
fun installFunctionStudyMark() Keep the aiStudy mark with the document.
fun installFunctionStudyAction(root: Box) "Make a Function Study": mark the selected boxes as function studies, seeding an empty one with FunctionStudies.STARTER.

FunctionStudyGuide

data class FunctionStudyGuide(val unused: String = "")

InspectFunctionStudy

data class InspectFunctionStudy(override val revision: String = "current") : StudyRequest.Inspect

EditFunctionStudy

data class EditFunctionStudy(val expectedRevision: String, val expectedSourceHash: String, @JsonPropertyDescription("Complete Kotlin source with imports. Last expression returns afield.math.functions.StudyComposition. No filesystem/network/process effects; no mutable or stateful evaluators.") val source: String, val description: String, val previewStartSeconds: Double = 0.0, val previewEndSeconds: Double = 25.0)

Declaration Summary
val expectedRevision: String
val expectedSourceHash: String
val source: String
val description: String
val previewStartSeconds: Double
val previewEndSeconds: Double

PatchFunctionStudy

data class PatchFunctionStudy(val expectedRevision: String, val expectedSourceHash: String, val edits: List<StudySourceEdit>, val description: String, val previewStartSeconds: Double = 0.0, val previewEndSeconds: Double = 25.0)

Declaration Summary
val edits: List<StudySourceEdit>, val description: String
val previewStartSeconds: Double

ExportFunctionStudy

data class ExportFunctionStudy(val revision: String, val expectedSourceHash: String, val filenames: List<String>, val sampleRateHz: Double = 100.0, val scale: Double = 100.0, val header: Boolean = false, val overwrite: Boolean = false)

Declaration Summary
val filenames: List<String>, val sampleRateHz: Double
val header: Boolean

ObserveFunctionStudy

data class ObserveFunctionStudy(val revision: String = "current", val startSeconds: Double = 0.0, val endSeconds: Double = 3600.0, val compareTo: String = "")

Declaration Summary
val endSeconds: Double

SampleFunctionStudy

data class SampleFunctionStudy(val revision: String, val timesSeconds: List<Double>)

SelectFunctionStudy

data class SelectFunctionStudy(val eventIds: List<String>, val startSeconds: Double, val endSeconds: Double)

CompareFunctionStudy

data class CompareFunctionStudy(val beforeRevision: String, val afterRevision: String, val changedStartSeconds: Double, val changedEndSeconds: Double, val allowedChannels: List<String>)

Declaration Summary
val changedStartSeconds: Double, val changedEndSeconds: Double, val allowedChannels: List<String>)

FunctionStudies

object FunctionStudies

Function studies, one per box: a box whose aiStudy property is "function" and whose Kotlin returns a StudyComposition. The study keeps its revisions in studies/<box id> and shows its signal viewer in the box's own output.

Declaration Summary
const val MARK The property that marks a box as a study, and the value that says which kind.
const val KIND
val STARTER The source a new function study starts from: three flat channels over an hour.
val guide: String by lazy How to work on a function study: what FunctionStudyGuide returns.
fun isStudy(box: Box, property: String): Boolean Whether a conversation focused on box's property is about a function study.
fun study(box: Box): FunctionStudy box's study, created on first use.
fun ready(box: Box): FunctionStudy study, with the box's current source checked in as the first revision if there is none, and its viewer showing.
fun showSignal(box: Box, revisionId: String = "current", comparisonId: String = "", channel: String = "overview") Show revisionId in the box's signal viewer, optionally with comparisonId dashed beneath it.
fun finishEdit(box: Box, report: StudyReport, parent: String, start: Double, end: Double): StudyReport An accepted edit's preview, with the viewer moved to the new revision (compared with its parent).
fun observe(box: Box, request: ObserveFunctionStudy): StudyReport FunctionStudy.observe, with the local maxima of each channel when the interval is short enough to resolve them.
fun compare(box: Box, request: CompareFunctionStudy): StudyReport Sampled preservation between two revisions outside the declared interval and channels; see CompareFunctionStudy.

FunctionStudyGuideText

data class FunctionStudyGuideText(val text: String) : afield.ai.tools.SummarisesForToolLog

The FunctionStudyGuide tool's answer.

Declaration Summary
override fun toolLogSummary(): String