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
How a fit obtains the gradient of its loss with respect to a graph's numeric leaves.
Constants: FINITE_DIFFERENCE, EXACT, AUTO.
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
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:
evalFormulainDouble, transcribed to be bit-identical to the hand-written closure inFunctions.kt(same operations, same order, sameMath.pow/coerceIncalls);evalFormulaADin trackedADarithmetic.
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
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
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.
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 ordinaryDoublealready brackets every rounded result in between. No outward rounding is needed, which is why a held constant certifies as exactly[1, 1]rather than as1 ± ulp.abs,floor,min,max,coerceInand 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 byTRANSCENDENTAL_ULPSulps each side, which also absorbs the rare last-bit disagreement between an interpreted and a JIT-intrinsicMath.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
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
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
The per-kind rules, as functions on FnRange. Public so a test can exercise one rule on its own.
FnEnclosure
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
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
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
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
Which extreme a branch-and-bound search was looking for.
Constants: MIN, MAX.
FnSearchPiece
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
The answer to a certified yes/no question about a function on an interval.
Constants: PROVEN, REFUTED, UNDECIDED.
FnVerdict
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
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
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
FnMutation
Seeded mutation operators and a bounded, deterministic search harness.
| Declaration | Summary |
|---|---|
fun mutate(fn: Functions.Fn, rng: Random): Functions.Fn |
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
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
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
FnGraphSweepPoint
data class FnGraphSweepPoint(val value: Double, val samples: DoubleArray)
One swept value of a numeric node and the final signal it produces.
FnGraphTraceNode
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
A whole graph sampled on one shared time-base: the abscissae, the finished signal, and one FnGraphTraceNode per visible occurrence.
FnRewriteStage
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
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.
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
FnGraphRule
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
Whether one rewrite pass uses the first matching rule or chains every matching rule.
Constants: FIRST_MATCH, ALL_MATCHES.
FnGraphRewriteStop
Why a bounded graph-rewrite run stopped.
Constants: ONE_PASS, STABLE, CYCLE, MAX_PASSES.
FnGraphRewriteEvent
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
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
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
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
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
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
Whether a diagnostic kills a rule or merely grumbles about it.
Constants: ERROR, WARNING.
FnRewriteDiagnostic
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.
FnRewriteProgram
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
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
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
FnRewriteSelfBoxReentry
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
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
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
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
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
Ordinary Functions.Fn values. Callers promise pure, deterministic, random-access evaluation.
StudyRevision
FunctionStudy
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.
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
| Declaration | Summary |
|---|---|
val expectedRevision: String |
|
val expectedSourceHash: String |
|
val source: String |
|
val description: String |
|
val previewStartSeconds: Double |
|
val previewEndSeconds: Double |
PatchFunctionStudy
| Declaration | Summary |
|---|---|
val edits: List<StudySourceEdit>, val description: String |
|
val previewStartSeconds: Double |
ExportFunctionStudy
| Declaration | Summary |
|---|---|
val filenames: List<String>, val sampleRateHz: Double |
|
val header: Boolean |
ObserveFunctionStudy
| Declaration | Summary |
|---|---|
val endSeconds: Double |
SampleFunctionStudy
data class SampleFunctionStudy(val revision: String, val timesSeconds: List<Double>)
SelectFunctionStudy
CompareFunctionStudy
| Declaration | Summary |
|---|---|
val changedStartSeconds: Double, val changedEndSeconds: Double, val allowedChannels: List<String>) |
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 |