Skip to content

Solvers

Nonlinear least squares, aimed at geometry. A constraint vocabulary — planarity, colinearity, fixed distances, perpendicularity, dihedral angles, panel areas, positions held hard or held softly — assembled into a program, compiled once and solved by Ceres across a foreign-function bridge; the architectural layer above it for gridshells and panel families; and the reuse-oriented half that re-solves the same program every frame while a user drags. Residuals you cannot express in the vocabulary can be written in Kotlin and differentiated exactly by Ceres through the expression VM. These entry points need the native Ceres library present, and nothing here draws — a solve returns points, and drawing them is your business. Start from CeresGeometry.kt, which is the vocabulary everything else is written in; ArchitecturalGeometry.kt is the layer most calls go through.

7 source file(s), 766 public symbol(s), 514 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.

Brief.kt

Brief is a text notation for weighted geometric constraints: points, lines, planes and panels, and the relations they should satisfy, each with a strength.

A brief is what an architect writes before there is a building: a statement of what has to be true, in no particular order, about things that do not exist yet. The vocabulary is the weighted constraint set of afield.math.optimization.ceres.ConstraintScope, which otherwise is reachable only from a Kotlin receiver lambda; Brief makes it available as text.

A program is a #brief-tagged region (a Kotlin triple-quoted string whose first line is #brief, the same Route-A shape RectSL uses) or any string handed to compileBrief:

#brief
point west  at (0, 0)
point east  at (9, 0)
fix west east

prefer spine at 1/3 along (west, east)
insist distance(west, spine) = 6

Weight is a number

Every constraint statement starts with a strength, and the slot takes either a word from a decade ladder (BriefStrength) or a bare number. prefer and insist differ by a factor of a thousand, and the solved answer moves accordingly. The ladder words are names for numbers, so a bare number is accepted in the same position.

Forward reference is the normal case

Lines, planes and panels resolve in a second pass, so a relation may cite geometry declared below it. Points are declared on use: naming a point that was never declared creates one, seeded on a deterministic golden-angle circle so a brief of pure relations starts from a non-degenerate spread rather than a pile at the origin. The invented points are listed in BriefProgram.inventedPoints.

Two kinds of relation, one statement shape

spine.length = 4 is a relation over a property; spine parallel ridge is a relation over elements. Both are strength relation, and a relation is an equation when a top-level =, >= or <= appears and a predicate otherwise. Properties and functions of elements are terms, so west.length = east.length and angle(spine, ridge) = 30deg sit in one grammar. planar, mirrors and fix have no scalar left-hand side and stay predicates. The grammar is written up in dev_docs/brief_notation.md.

Diagnostics are located and named

Every failure is a BriefDiagnostic with a line, a column, a span and, where a name was misspelled, a did-you-mean drawn from the names actually declared. compileBrief does not throw a bare Kotlin exception at a program that merely reads wrong.

compileBrief is a pure function of text, with no Box, output or server. toGeometryModel turns the result into a GeometryModel, and solve solves it.

src/jvmMain/kotlin/afield/lang/brief/Brief.kt — read the source

Brief

object Brief

The tag and language id that name Brief to the editor and to embedded-region detection.

Declaration Summary
const val TAG: String The first-line tag that marks a #brief embedded region.
const val ID: String The language id, shared by the editor extension and the Monaco language.

BriefSeverity

enum class BriefSeverity

Whether a diagnostic stops the compile.

Constants: ERROR, WARNING.

BriefDiagnostic

data class BriefDiagnostic(val severity: BriefSeverity, val line: Int, val column: Int, val span: Int, val message: String, val hint: String? = null)

One located complaint about a program.

Declaration Summary
val severity: BriefSeverity Whether this stops the compile.
val line: Int 1-based line in the source as handed to compileBrief, #brief tag line included.
val column: Int 1-based column of the offending text on that line.
val span: Int How many characters the offending text runs to, for underlining it.
val message: String What is wrong, with no position in it; format prepends that.
val hint: String? What to do about it, where there is something to say.
fun format(): String 3:8 error: unknown line 'spien' — did you mean 'spine'?, the form a box prints.
override fun toString(): String format, so printing a diagnostic gives the located form rather than the data class one.

BriefException

class BriefException(val diagnostics: List<BriefDiagnostic>)

Thrown by compileBrief when a program has errors, carrying all of them rather than the first.

BriefStrength

enum class BriefStrength(val word: String, val weight: Double)

The strength ladder: four words that are names for four numbers, three decades apart.

Constants: WHISPER, PREFER, INSIST, DEMAND.

BriefSpan

data class BriefSpan(val line: Int, val column: Int, val length: Int)

Where something is, for a diagnostic. 1-based, counting the source as handed to the compiler.

BriefName

data class BriefName(val text: String, val at: BriefSpan)

A name as written, with where it was written — so an unresolved name can point at itself.

Declaration Summary
override fun toString(): String The name as written, so a diagnostic can interpolate it without reaching for text.

BriefElementKind

enum class BriefElementKind(val noun: String)

The kinds of thing a brief can declare. The resolver keys its namespaces on this.

Constants: POINT, LINE, PLANE, PANEL.

BriefDeclaration

sealed interface BriefDeclaration

A declaration statement.

Declaration Summary
val name: BriefName The name this statement declares, located so a redeclaration can point at the second one.
val kind: BriefElementKind Which of the four namespaces the name goes into; each subtype fixes its own.
data class Point(override val name: BriefName, val x: Double, val y: Double, val z: Double) : BriefDeclaration point west at (0, 0) — the one declaration that carries coordinates. z defaults to 0.
data class Line(override val name: BriefName, val a: BriefName, val b: BriefName) : BriefDeclaration line spine from west to east — two endpoints, by name, resolved in the second pass.
data class Plane(override val name: BriefName, val a: BriefName, val b: BriefName, val c: BriefName) : BriefDeclaration plane roof through a b c — three points, by name.
data class Panel(override val name: BriefName, val a: BriefName, val b: BriefName, val c: BriefName, val d: BriefName) : BriefDeclaration panel bay corners a b c d — four corners, by name, which resolution requires to be distinct.

BriefComparison

enum class BriefComparison(val symbol: String)

The comparison in an equation.

Constants: EQ, AT_LEAST, AT_MOST.

BriefTerm

sealed interface BriefTerm

The left or right of an equation.

Declaration Summary
val at: BriefSpan Where the term was written, so an equation can underline the half that is wrong.
data class Literal(val value: Double, val radians: Boolean, override val at: BriefSpan) : BriefTerm A number. value is in radians when a deg suffix was written, and radians records that.
data class Property(val element: BriefName, val property: String, override val at: BriefSpan) : BriefTerm spine.length, roof.area, ridge.z.
data class Call(val function: String, val arguments: List<BriefName>, override val at: BriefSpan) : BriefTerm angle(spine, ridge), distance(a, b), dihedral(edge, a, b).

BriefRelation

sealed interface BriefRelation

The relation half of a constraint statement.

Declaration Summary
val at: BriefSpan Where the relation starts: the first token after the strength, if one was written.
data class Equation(val left: BriefTerm, val comparison: BriefComparison, val right: BriefTerm, override val at: BriefSpan) : BriefRelation left (comparison) right — a relation over properties.
data class Predicate(val verb: String, val subjects: List<BriefName>, val objects: List<BriefName>, val number: Double? = null, val vector: Triple<Double, Double, Double>? = null, val axes: String? = null, override val at: BriefSpan) : BriefRelation subjects verb objects… — a relation over elements.

BriefConstraint

data class BriefConstraint(val strength: Double, val strengthWritten: String?, val relation: BriefRelation, val at: BriefSpan)

One strength relation line. strengthWritten is null when the statement took the default.

Declaration Summary
val strength: Double The weight the statement's residuals carry, already resolved from the word or the number.
val strengthWritten: String? The strength exactly as typed, or null where the statement took the verb's default.
val relation: BriefRelation The relation half: an equation over properties, or a predicate over elements.
val at: BriefSpan Where the whole statement starts, strength included. BriefRelation.at skips it.
val label: String The source line, reconstructed enough for a residual report to name the statement.

BriefSettle

data class BriefSettle(val weight: Double?, val at: BriefSpan)

A settle directive: the whole-cloud prior weight, or null for "none".

BriefRobust

data class BriefRobust(val kind: String, val scale: Double, val at: BriefSpan)

A robust directive: the loss every residual is wrapped in.

BriefVerbs

object BriefVerbs

Every reserved word in the notation, grouped the way a diagnostic wants to talk about them.

Declaration Summary
val infix: Set<String> Verbs that come after their subjects: spine parallel ridge.
val prefix: Set<String> Verbs that come first: fix west east.
val declarations: Set<String> Words that introduce a declaration.
val directives: Set<String> Words that introduce a directive.
val losses: Set<String> The robust-loss kinds robust accepts, mapped to RobustLoss.Kind.
val functions: Set<String> Function names usable as an equation term.
val properties: Set<String> Properties reachable with element.property.
val glue: Set<String> Glue words that are reserved but carry no relation of their own.
val strengths: Set<String> The ladder words, from BriefStrength, so the parser and the editor read one list.
val all: Set<String> Everything a program may not use as a name.

BriefSyntax

data class BriefSyntax(val declarations: List<BriefDeclaration>, val constraints: List<BriefConstraint>, val settle: BriefSettle?, val robust: BriefRobust?, val diagnostics: List<BriefDiagnostic>)

The parsed, unresolved program: declarations, constraints and directives in source order.

Declaration Summary
val declarations: List<BriefDeclaration> The declaration statements, in source order, with nothing resolved against anything.
val constraints: List<BriefConstraint> The strength relation statements, in source order.
val settle: BriefSettle? The settle directive, or null for the vocabulary's own default. The last one written wins.
val robust: BriefRobust? The robust directive, or null, which leaves the brief on plain least squares.
val diagnostics: List<BriefDiagnostic> What the parser complained about. The lexer's complaints are joined in by compileBrief.

BriefEditorExtension.kt

The Brief editor extension's JavaScript, read from the same resource the browser loads.

The single source is the static resource src/commonMain/resources/brief.js, served to the browser as the AMD module bithero/brief (see bootstrapMonaco.js) and read verbatim here, so the node probe in afield.examples.brief.BriefEditorExtensionWorkout exercises the code that ships. The file installs itself under bare eval and registers an AMD module when a Monaco loader is present, which is what lets it be tested outside a browser.

src/jvmMain/kotlin/afield/lang/brief/BriefEditorExtension.kt — read the source

Top-level functions

Declaration Summary
fun briefEditorExtensionJavascript(): String BriefEditorExtension.javascript as a top-level function, for a caller holding no receiver.

BriefEditorExtension

object BriefEditorExtension

Access to brief.js and the vocabulary it declares.

Declaration Summary
const val id: String The language id the editor registers under, which is Brief.ID.
fun javascript(): String The text of brief.js, from the classpath if it is there and from the source tree otherwise.
fun declaredVocabulary(): Map<String, Set<String>> The vocabulary the .js declares, scraped out of it.

BriefLowering.kt

Resolution and lowering: a BriefSyntax becomes a BriefProgram, and a BriefProgram becomes an afield.math.optimization.ceres.GeometryModel solved through the real Ceres backend.

The two halves are separated because they fail differently. Resolution is where a reader is wrong — a misspelling, a plane used where a line was wanted, two statements that cannot both hold — and every failure there is a located BriefDiagnostic. Lowering is where the implementation would be wrong, and by then every name has been checked, so it throws error() on anything that gets through: an exception from this file is a bug in this file, never in the brief.

src/jvmMain/kotlin/afield/lang/brief/BriefLowering.kt — read the source

Top-level functions

Declaration Summary
fun compileBrief(source: String): BriefProgram Compile a brief. A pure function of text: no Box, no output, no server, no native library.
fun checkBrief(source: String): List<BriefDiagnostic> compileBrief without the throw: the diagnostics, whether or not the program is usable.
fun BriefProgram.toGeometryModel(bindings: Map<String, Vector3f> = emptyMap()): GeometryModel Build the GeometryModel this brief describes.
fun settleOptions(iterations: Int = 400): SolveOptions What a brief means by "solve it": run it out, not to the interactive default.
fun BriefProgram.solve(iterations: Int = 400, bindings: Map<String, Vector3f> = emptyMap(), options: SolveOptions = settleOptions(iterations)): BriefSolution Solve the brief through the real Ceres backend and measure every statement.
fun BriefProgram.openSession(bindings: Map<String, Vector3f> = emptyMap(), dragWeight: Double = BriefStrength.INSIST.weight): BriefSession Open a BriefSession over this program — the transition from "what has to be true" to "and now let me push on it".

BriefProgram

class BriefProgram internal constructor(val source: String, val syntax: BriefSyntax, val diagnostics: List<BriefDiagnostic>, val pointNames: List<String>, val inventedPoints: List<String>, internal val seeds: Map<String, Vec3>, internal val anchors: List<AxisAnchor>)

A resolved brief: a checked syntax tree, a point order, and the list of points the notation invented because a relation named them before anything declared them.

Declaration Summary
val source: String The program text as compiled, kept so a diagnostic's line and column still mean something.
val syntax: BriefSyntax The parse this was resolved from, for a caller that wants the statements rather than a solve.
val diagnostics: List<BriefDiagnostic> Everything the lexer, parser and resolver said. Errors are impossible here, because an error throws.
val pointNames: List<String> Every point, in the order they became parameters — declared first, then invented.
val inventedPoints: List<String> Points a relation named that nothing declared, seeded on the golden-angle circle.
val constraintCount: Int How many statements the brief wrote, which is not how many residuals they lower to.
val warnings: List<BriefDiagnostic> The subset of diagnostics worth showing a reader whose brief compiled.
val statementLabels: List<String> The statement labels, in source order — the names a residual report uses.
override fun toString(): String Point count, statement count, and the invented names: what a box prints when it compiles one.

BriefResidual

data class BriefResidual(val label: String, val line: Int, val strength: Double, val residual: Double)

One statement's own residual, in its own units, measured from the solved geometry.

Declaration Summary
val label: String The statement, reconstructed from the parse, as in insist distance(west, spine) = 6.
val line: Int The 1-based source line the statement was written on.
val strength: Double The weight the statement was written with, the strength half of cost.
val residual: Double The statement's residual in its natural units, or NaN where there is no independent one.
val cost: Double What the statement contributes to the objective, strength * residual².
override fun toString(): String One report line: the source line, the residual, and the statement it came from.

BriefSolution

class BriefSolution internal constructor(val ok: Boolean, val totalCost: Double, val points: Map<String, Vec3>, val residuals: List<BriefResidual>)

A solved brief: the geometry, and every statement's residual measured back off it.

Declaration Summary
val ok: Boolean Whether the solve converged. The points are populated either way.
val totalCost: Double Ceres's own final cost, or NaN when metrics were not requested.
val points: Map<String, Vec3> Every point by the name the brief used, hidden axis anchors included.
val residuals: List<BriefResidual> One entry per statement, in source order. worst and report sort them by cost.
operator fun get(name: String): Vec3 A point by name, which throws and lists the names it does have rather than returning null.
val worst: BriefResidual? The statement with the largest weighted cost — "what is still fighting".
val statedCost: Double The sum of strength * residual² over the statements the author wrote.
fun report(limit: Int = 8): String A readable dump: the points, then the statements worst-first.

BriefSession

class BriefSession internal constructor(val program: BriefProgram, private val session: GeometrySolveSession, private val refs: Map<String, PointRef>) : AutoCloseable

A brief held open for interaction: the compiled model, a live point cloud, and the gesture verbs addressed by the names the brief used.

Declaration Summary
val program: BriefProgram The brief this session was opened over, for its statement labels and its point names.
operator fun get(name: String): Vec3 The current position of a named point. Overwritten by every solve.
fun positions(): Map<String, Vec3> Every named point's current position, hidden anchors excluded.
fun drag(name: String, target: Vec3, strength: Double = BriefStrength.INSIST.weight): Boolean Pull a named point towards target at strength and re-solve — one frame of a gesture.
fun release(name: String): Boolean End a gesture: drop the point's prior back to resting and settle.
fun pin(name: String) Hold a point where it is now, re-asserted before every solve — stronger than a heavy prior.
fun unpin(name: String) Undo pin: the point goes back to being governed by the brief.
fun solve(iterations: Int = 64): Boolean Re-solve from the current state.
fun residuals(): List<BriefResidual> Every statement's residual, measured off the current geometry the way BriefSolution does.
override fun close() Release the native problem. Every accessor here is invalid afterwards.

ArchitecturalGeometry.kt

A named, typed DSL over the raw constraint vocabulary in CeresGeometry.kt, plus the interactive machinery for solving it while a user drags. geometryModel opens a GeometryModelScope where you declare PointRefs, LineRefs, PlaneRefs and PanelRefs (points can be created from Vector3fc values or bound to live Vector3fs via GeometryModelScope.bindPoint) and then state relations inside constraints { } — ConstraintScope supplies length, parallelTo, angleRadiansTo, planar, on, inside, lock, weak, mirror, dihedral and the ceq infix equality.

GeometryModel.openSession (or session) compiles the program once and returns a GeometrySolveSession holding a mutable point cloud, per-point prior weights and a reusable solve report; GeometrySolveSession.drag / DragHandle / pin / release are the interaction verbs, weighted by InteractionPolicy.

liveGeometry wraps a model block rather than a model: each LiveGeometryProgram.step re-runs the block, reuses the existing session when point names, point count and program hash are all unchanged, reads every bound point back in, solves, and writes the results out through the bindings. LiveGeometryProgram.polishLocked does a separate hard-locked settle pass.

Point arithmetic (PointExpr, a + b, 0.5 * c) creates synthetic "trace" points that are recomputed from their expression before every solve. The default backend is FfmCeresBackend, so these entry points need the native Ceres library present.

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

Top-level functions

Declaration Summary
fun geometryModel(block: GeometryModelScope.() -> Unit): GeometryModel The way in: declare points and the relations between them, get back an immutable GeometryModel that can be solved and re-solved.
fun GeometryModel.session(backend: CeresSolverBackend = FfmCeresBackend(), baselinePrior: Double = 1e-6, metricCapacity: Int = 128, policy: InteractionPolicy = InteractionPolicy(preserveWeight = baselinePrior, releaseWeight = baselinePrior, metricCapacity = metricCapacity)): GeometrySolveSession GeometryModel.openSession as an extension, for the reading order model.session { }. Same parameters and the same caveat: baselinePrior and metricCapacity are ignored if you pass an explicit policy.
fun liveGeometry(backend: CeresSolverBackend = FfmCeresBackend(), policy: InteractionPolicy = InteractionPolicy(), block: GeometryModelScope.() -> Unit): LiveGeometryProgram Wrap a model block rather than a model, so the declarations can be re-run — the live-coding entry point.
operator fun PointRef.plus(other: PointRef): PointExpr The point arithmetic, outside constraints { }. These are the operators ConstraintScope carries, for building an expression before there is a scope to state it in.
operator fun PointRef.plus(other: PointExpr): PointExpr Add a point into an existing expression.
operator fun PointRef.minus(other: PointRef): PointExpr The difference of two points, which is the edge vector between them.
operator fun PointRef.minus(other: PointExpr): PointExpr Subtract an expression from a point.
operator fun PointRef.times(scale: Number): PointExpr Scale a point's position. Nothing normalises, so 0.5 * (a + b) is the midpoint and a + b is not.
operator fun Number.times(point: PointRef): PointExpr The same scaling with the number on the left.
operator fun Number.times(expr: PointExpr): PointExpr The same, scaling a whole expression.

Top-level values

Declaration Summary
val PointRef.expr: PointExpr A point as a one-term PointExpr, so arithmetic can start from it. a + b and 0.5 * a go through this.

PointRef

class PointRef internal constructor(val id: PointId, val name: String?)

A handle on one point of a model — what the DSL passes around instead of an index.

Declaration Summary
val index: Int The raw index, for reaching the underlying PointCloud or MutablePointCloud directly.
override fun equals(other: Any?): Boolean By id alone, so a reference from another model compares equal at the same index.
override fun hashCode(): Int id's hash, to match equals.
override fun toString(): String The declared name, or p and the index — p12 — where the scope was given none.

LineRef

data class LineRef(val a: PointRef, val b: PointRef, val name: String? = null)

Two points named as a segment. Carries no constraint of its own — it is the receiver the length, angle and direction verbs hang off, and the two endpoints stay ordinary free points.

PlaneRef

data class PlaneRef(val a: PointRef, val b: PointRef, val c: PointRef, val name: String? = null)

Three points named as a plane, which therefore moves with them.

PanelRef

data class PanelRef(val a: PointRef, val b: PointRef, val c: PointRef, val d: PointRef, val name: String? = null)

Four points named as a quadrilateral, in perimeter order — area triangulates on the a-c diagonal, so a transposed pair describes a bow tie rather than a panel.

Declaration Summary
val points: List<PointRef> The four corners in perimeter order, for a caller that wants to iterate rather than name them.

PointExpr

class PointExpr internal constructor(internal val terms: Map<PointRef, Double>, internal val constant: Vec3 = Vec3(0.0, 0.0, 0.0))

An affine combination of points — a + b, 0.5 * (a + b), a - b — as an expression you can constrain with ConstraintScope.ceq.

Declaration Summary
operator fun plus(other: PointExpr): PointExpr Add two expressions, summing the coefficient of any point they share. Not a midpoint.
operator fun plus(other: PointRef): PointExpr Add a bare point into this expression, as a term with coefficient one.
operator fun minus(other: PointExpr): PointExpr Subtract an expression, which for two points is the edge vector between them.
operator fun minus(other: PointRef): PointExpr Subtract a bare point from this expression.
operator fun unaryMinus(): PointExpr Negate every coefficient and the constant.
operator fun times(scale: Number): PointExpr Scale every coefficient and the constant. The scale must be finite.
override fun toString(): String The combination written out, as in west + east*0.5, so a trace point can be named.

DotExpr

class DotExpr internal constructor(val a: LineRef, val b: LineRef, val normalized: Boolean = true)

A pending dot-product statement, waiting for ConstraintScope.ceq to give it a target. Normalised by default, so the target is a cosine; ConstraintScope.raw makes it a raw dot product in length squared.

Declaration Summary
val a: LineRef The first segment. Its direction is b - a of that line.
val b: LineRef The second segment, dotted against a.
val normalized: Boolean Whether the target is a cosine. False makes it a raw dot product, in length squared.

GeometryTrace

class GeometryTrace internal constructor(val target: PointRef, val expression: PointExpr)

A synthetic point and the PointExpr it is recomputed from before every solve.

Declaration Summary
val target: PointRef The synthetic point the expression is written into, which is a free parameter like any other.
val expression: PointExpr What target is recomputed from at the start of each solve.

PointBinding

class PointBinding internal constructor(val point: PointRef, val reader: () -> Vec3, val writer: (Vec3) -> Unit, val dragTolerance: Double, val dragWeight: Double?, internal val sourceIdentity: Int? = null)

The link between a model point and a value that lives outside the model: a reader, a writer, and the tolerance that decides when a change in the read value counts as a drag.

Declaration Summary
val point: PointRef The model point this value stands for.
val reader: () -> Vec3 Called before each step to see where the outside world has put the point.
val writer: (Vec3) -> Unit Called after each solve with the answer, which is how a bound Vector3f is updated in place.
val dragTolerance: Double How far a read value may move between steps before the change counts as user input.
val dragWeight: Double? The prior weight such a change is given, or null for the policy's own drag weight.

GeometryModel

class GeometryModel internal constructor(val pointNames: List<String?>, private val initialValues: DoubleArray, val program: CeresProgram, internal val traces: List<GeometryTrace>, internal val bindings: List<PointBinding>)

A finished, immutable model: the named points, their seed positions, the compiled-ready CeresProgram, and the trace expressions and external bindings that go with them.

Declaration Summary
val pointNames: List<String?> One entry per point, in index order, null where the scope declared the point unnamed.
val program: CeresProgram The constraints as the backend wants them, ready to compile but not yet compiled.
val pointCount: Int How many points the model declares, trace points included.
fun initialPointCloud(): PointCloud A copy of the declared positions. Copies, so the model's own seed cannot be edited through it.
fun mutablePoints(): MutablePointCloud A fresh mutable cloud at the declared positions, carrying the point names. What a session solves into.
fun openSession(backend: CeresSolverBackend = FfmCeresBackend(), baselinePrior: Double = 1e-6, metricCapacity: Int = 128, policy: InteractionPolicy = InteractionPolicy(preserveWeight = baselinePrior, releaseWeight = baselinePrior, metricCapacity = metricCapacity)): GeometrySolveSession Compile the program and open a live GeometrySolveSession over it — the transition from description to solving.

GeometryModelScope

class GeometryModelScope

The declaration half of the DSL: everything you can say inside a geometryModel block before, and around, the constraints { } that states the relations.

Declaration Summary
fun robustLoss(loss: RobustLoss) Choose the loss every residual is wrapped in. Read this before tuning a single weight.
fun point(name: String, x: Double, y: Double, z: Double): PointRef A named point at explicit coordinates: the name-first spelling of point.
fun point(name: String, value: Vector3fc): PointRef A named point seeded from a Vector3fc, memoised by object identity like the unnamed form.
fun point(value: Vector3fc, name: String? = null): PointRef A point seeded from a Vector3fc, memoised by object identity: the same vector always yields the same PointRef, two vectors that merely hold equal coordinates yield two points.
val Vector3fc.c: PointRef v.c — the terse form of point for a read-only vector, memoised by identity in the same way.
val Vector3f.point: PointRef v.point — a point bound to a mutable Vector3f, read and written back on every solve.
fun point(name: String, reader: () -> Vec3, writer: (Vec3) -> Unit = {}, dragTolerance: Double = 1e-5, dragWeight: Double? = null): PointRef A named bound point: as bindPoint, for when the name is not optional.
fun bindPoint(name: String? = null, reader: () -> Vec3, writer: (Vec3) -> Unit = {}, dragTolerance: Double = 1e-5, dragWeight: Double? = null, sourceIdentity: Int? = null): PointRef A point whose value lives outside the model: reader supplies it, writer receives the solved result.
fun bindPoint(name: String? = null, value: Vector3f, dragTolerance: Double = 1e-5, dragWeight: Double? = null): PointRef Bind a mutable Vector3f directly: the solver reads it and writes the answer back into it, in place.
fun point(x: Double, y: Double, z: Double, name: String? = null): PointRef A free point at a literal position. The seed matters: it is where the solve starts from, where ConstraintScope.lock will lock to, and — for a non-convex problem — which of several answers you get.
fun line(a: PointRef, b: PointRef, name: String? = null): LineRef Name a segment. Declares nothing about it — a LineRef is a handle you then hang ConstraintScope.length, ConstraintScope.parallelTo and the rest on.
fun line(a: Vector3fc, b: Vector3fc, name: String? = null): LineRef As line, declaring a point for each Vector3fc end.
fun line(a: PointRef, b: Vector3fc, name: String? = null): LineRef As line, declaring a point for the Vector3fc end.
fun line(a: Vector3fc, b: PointRef, name: String? = null): LineRef As line, declaring a point for the Vector3fc end.
infix fun PointRef.lineTo(other: PointRef): LineRef a lineTo b — line as an infix, for reading a declaration left to right.
infix fun PointRef.lineTo(other: Vector3fc): LineRef As above, declaring a point for the Vector3fc end.
infix fun Vector3fc.lineTo(other: PointRef): LineRef As above, declaring a point for the Vector3fc end.
infix fun Vector3fc.lineTo(other: Vector3fc): LineRef As above, declaring a point for both ends.
fun plane(a: PointRef, b: PointRef, c: PointRef, name: String? = null): PlaneRef Name a plane by three of the model's own points, so it moves with them.
fun plane(a: Vector3fc, b: Vector3fc, c: Vector3fc, name: String? = null): PlaneRef As plane, declaring a point for each Vector3fc corner. The order still fixes the normal.
fun plane(a: PointRef, b: Vector3fc, c: Vector3fc, name: String? = null): PlaneRef As plane, declaring points for the Vector3fc corners.
fun plane(a: Vector3fc, b: PointRef, c: Vector3fc, name: String? = null): PlaneRef As plane, declaring points for the Vector3fc corners.
fun plane(a: Vector3fc, b: Vector3fc, c: PointRef, name: String? = null): PlaneRef As plane, declaring points for the Vector3fc corners.
fun panel(a: PointRef, b: PointRef, c: PointRef, d: PointRef, name: String? = null): PanelRef Name a quadrilateral. Pass the corners around the perimeter — ConstraintScope.area triangulates on the a-c diagonal, so a transposed pair silently constrains a bow tie.
fun panel(a: Vector3fc, b: Vector3fc, c: Vector3fc, d: Vector3fc, name: String? = null): PanelRef As panel, declaring a point for each corner. Perimeter order still matters.
fun constraints(block: ConstraintScope.() -> Unit) State the relations. Runs immediately, appending to this model; may be called more than once, and constraints accumulate across calls.
fun defaultInteractionPriors(weight: Double = 1e-6) Set the weight of the whole-cloud prior that build appends. Raise it to make the model stiffer against being dragged; the default 1e-6 is meant to be invisible except where the geometry leaves a point free.
fun noDefaultInteractionPriors() Suppress the automatic whole-cloud prior, leaving the model constrained by nothing but what you wrote.
fun build(): GeometryModel Freeze the declarations into a GeometryModel, appending the whole-cloud weak prior unless one was written by hand or noDefaultInteractionPriors was called.

ConstraintScope

class ConstraintScope internal constructor(private val model: GeometryModelScope)

The verbs: what you can say about the points, lines, planes and panels a GeometryModelScope has declared.

Declaration Summary
fun add(constraint: GeometryConstraint) Add a raw GeometryConstraint — the escape hatch to anything this scope has no verb for.
fun expression(points: List<PointRef>, weight: Double = 1.0, build: (List<CeresExprVec>) -> List<CeresExpr>) A residual written here in Kotlin and differentiated exactly by Ceres (CeresExpressionResidual) — the way past this scope's vocabulary without a native rebuild.
val Vector3fc.c: PointRef v.c — declare a point at this vector's value, inside constraints { }. Read once, not watched.
val Vector3f.point: PointRef v.point — declare a point bound to this mutable vector, so the solve writes back into it.
fun point(value: Vector3fc, name: String? = null): PointRef Declare a point from inside constraints { }, for geometry the model block did not name.
fun line(a: PointRef, b: PointRef, name: String? = null): LineRef Name a segment from inside constraints { }. The endpoints must be distinct.
fun line(a: Vector3fc, b: Vector3fc, name: String? = null): LineRef As above, declaring a point for each Vector3fc end.
fun line(a: PointRef, b: Vector3fc, name: String? = null): LineRef As above, declaring a point for the Vector3fc end.
fun line(a: Vector3fc, b: PointRef, name: String? = null): LineRef As above, declaring a point for the Vector3fc end.
infix fun PointRef.lineTo(other: PointRef): LineRef a lineTo b — line as an infix.
infix fun PointRef.lineTo(other: Vector3fc): LineRef As above, declaring a point for the Vector3fc end.
infix fun Vector3fc.lineTo(other: PointRef): LineRef As above, declaring a point for the Vector3fc end.
infix fun Vector3fc.lineTo(other: Vector3fc): LineRef As above, declaring a point for both ends.
infix fun LineRef.dot(other: LineRef): DotExpr Begin a dot-product statement: (a lineTo b) dot (c lineTo d) ceq 0.5. Normalised, so the target is a cosine.
fun DotExpr.raw(): DotExpr Make a dot statement unnormalised, so the target is a raw dot product in length squared and the lengths are constrained too. Rarely what you want; angleCosTo is the angle-only form.
operator fun PointRef.plus(other: PointRef): PointExpr Start a PointExpr from two points. A sum of positions, not a midpoint; scale it yourself.
operator fun PointRef.plus(other: PointExpr): PointExpr Add a point into an existing expression.
operator fun PointRef.minus(other: PointRef): PointExpr The difference of two points, which is the edge vector between them.
operator fun PointRef.minus(other: PointExpr): PointExpr Subtract an expression from a point.
operator fun PointRef.times(scale: Number): PointExpr Scale a point's position, as in 0.5 * (a + b).
operator fun Number.times(point: PointRef): PointExpr The same scaling with the number on the left, so 0.5 * a reads as it would on paper.
operator fun Number.times(expr: PointExpr): PointExpr The same, scaling a whole expression, as in 0.5 * (a + b).
infix fun DotExpr.ceq(target: Number) Drive a dot statement to a value. Weight is fixed at 1.0; use angleCosTo if you need to set it.
infix fun PointRef.ceq(other: PointRef) Two points coincident, as three same-coordinate constraints at weight 1.0.
infix fun PointRef.ceq(other: Vector3fc) As PointRef.ceq, against a position; the vector is declared as a point first.
infix fun PointRef.ceq(other: PointExpr) PointExpr.ceq with the operands the other way round; the same one-way coupling applies.
infix fun Vector3fc.ceq(other: PointRef) As PointRef.ceq, with the vector on the left; it is declared as a point first.
infix fun PointExpr.ceq(other: PointRef) Tie a point to an affine combination of others — (a + b) * 0.5 ceq mid for a midpoint.
infix fun PointExpr.ceq(other: Vector3fc) As PointExpr.ceq, against a position; the vector is declared as a point first.
fun weakAll(weight: Double = 1e-6) A weak "stay near where you are" prior on every point, at a weight meant to be invisible except where the geometry leaves a point free.
fun noNegativeZAll(weight: Double = 1.0) Penalise negative z on every point: a ground plane written as a residual, not as a bound.
fun Iterable<PointRef>.planar(weight: Double = 1.0) All these points coplanar, on a plane the solver introduces as free variables (Planar).
fun PanelRef.planar(weight: Double = 1.0) A flat panel: planar over its four corners.
fun PanelRef.area(target: Double, weight: Double = 1.0) A panel of a prescribed area, triangulated on the a-c diagonal (PanelArea).
fun PointRef.on(plane: PlaneRef, offset: Double = 0.0, weight: Double = 1.0) This point on that plane, or offset away from it along the normal (PointOnPlane).
fun PointRef.inside(halfspace: PlaneRef, minSignedDistance: Double = 0.0, weight: Double = 1.0) This point on the normal side of that plane, with minSignedDistance of clearance (PointInHalfspace).
fun PointRef.lock(axes: AxisSet = AxisSet.XYZ, weight: Double = 1e6) Hold this point where it was declared. See lock for what "hold" and "where" actually mean.
fun lock(points: Iterable<PointRef>, axes: AxisSet = AxisSet.XYZ, weight: Double = 1e6) Hold points at the coordinates they were declared with, on the given axes, at a heavy default weight.
fun PointRef.weak(axes: AxisSet = AxisSet.XYZ, weight: Double = 1e-6) A light preference for this point staying put, refreshed from its current position on every solve (WeakPosition).
fun LineRef.length(distance: Double, weight: Double = 1.0) A fixed length (DistanceEquals) — the constraint everything else's weight is usually scaled against.
fun LineRef.minLength(distance: Double, weight: Double = 1.0) A minimum length (DistanceAtLeast): a barrier, costing exactly nothing while it is respected.
fun LineRef.maxLength(distance: Double, weight: Double = 1.0) A maximum length (DistanceAtMost): the mirror barrier, free while short enough.
fun LineRef.sameLengthAs(other: LineRef, weight: Double = 1.0) Two segments the same length, without saying what (EqualDistance). Use sameVectorAs if you also mean parallel.
fun LineRef.sameVectorAs(other: LineRef, weight: Double = 1.0) One segment a translate of another — same length, same direction, same sense (EqualEdgeVector). Strictly stronger than sameLengthAs plus parallelTo; the shape a repeated bay or module usually wants.
fun LineRef.parallelTo(other: LineRef, weight: Double = 1.0) Parallel, in either sense — antiparallel satisfies it too (LineParallel). Endpoints must all be distinct.
fun LineRef.angleCosTo(other: LineRef, targetCos: Double, weight: Double = 1.0) A prescribed angle given as its cosine (LineAngle). Unsigned: it cannot tell +t from -t.
fun LineRef.angleRadiansTo(other: LineRef, radians: Double, weight: Double = 1.0) A prescribed angle in radians, converted to a cosine here. The readable form of angleCosTo — and, being a cosine underneath, equally unsigned and equally blind to a full turn.
fun LineRef.perpendicularTo(other: LineRef, weight: Double = 1.0) A right angle: angleCosTo at zero. Says nothing about either length.
fun LineRef.direction(direction: Vec3, weight: Double = 1.0) Align a segment with a world direction — vertical, level, on a grid axis (LineDirection).
fun LineRef.toward(point: PointRef, weight: Double = 1.0) Point this segment's line at another point (LineTowardPoint) — radial members, a set of lines converging.
fun LineRef.vanishesAt(point: PointRef, weight: Double = 1.0) toward, named for the perspective case — a family of segments sharing one vanishing point.
fun PlaneRef.normal(direction: Vec3, targetCos: Double = 1.0, weight: Double = 1.0) Turn a plane to face a given direction (PlaneNormalDirection).
fun dihedral(edge: LineRef, sideA: PointRef, sideB: PointRef, targetCos: Double, weight: Double = 1.0) A fold angle across an edge, given as its cosine (DihedralAngle).
fun mirror(point: PointRef, mirror: PointRef, across: PlaneRef, weight: Double = 1.0) Two points reflections of each other across a plane (MirrorAcrossPlane) — the symmetry statement of the DSL.
fun same(points: Iterable<PointRef>, axes: AxisSet, weight: Double = 1.0) Points sharing one or two coordinates — a level row, a shared height (SameCoordinate).
fun sameXYZ(a: PointRef, b: PointRef, weight: Double = 1.0) Two points coincident, as three single-axis SameCoordinate constraints. What ceq is written in terms of.

GeometrySolveSession

class GeometrySolveSession internal constructor(model: GeometryModel, backend: CeresSolverBackend, val policy: InteractionPolicy) : AutoCloseable

A compiled model plus the mutable state a gesture needs: the live points, the per-point priors, the reusable report, and the pins.

Declaration Summary
val policy: InteractionPolicy The weights and iteration counts this session's drag, release and settle passes use.
var model: GeometryModel The model compiled into the native problem. A live program swaps it for one with the same point count and program hash, which is how a step reuses the session instead of recompiling.
val points: MutablePointCloud The live geometry, overwritten by every solve. Read it for results; snapshot it before you need an undo.
val priors: MutablePriorWeights The live prior weights — this is where a drag, a pin and a release are expressed.
val report: MutableSolveReport Filled by every solve that was asked for metrics; reused, so read it before the next one.
val quickOptions: SolveOptions The cheap per-frame options from the policy. The default for solve.
val metricOptions: SolveOptions The longer settle-with-diagnostics options from the policy. The default for release.
fun solve(options: SolveOptions = quickOptions): Boolean Re-solve from the current state, in place. Returns whether the answer is usable — a weak claim, not convergence.
fun solve(maxIterations: Int, metrics: Boolean = false): Boolean Solve for a given number of iterations, sizing the metric request to this session's report. The convenient form when you want more effort than the policy's default and nothing else changed.
fun set(point: PointRef, value: Vec3) Move a point directly, without touching its prior.
fun set(point: PointRef, x: Double, y: Double, z: Double) set from loose coordinates. A starting position, not a constraint; see the overload above.
fun get(point: PointRef): Vec3 The point's current solved position.
fun drag(point: PointRef, policy: InteractionPolicy = this.policy): DragHandle Begin a gesture: a DragHandle that holds this point heavily until released.
fun drag(point: PointRef, target: Vec3, strength: Double = policy.dragWeight, options: SolveOptions = quickOptions): Boolean Move a point to target with a heavy prior and re-solve in one call — the stateless form of a drag frame.
fun release(point: PointRef, weight: Double = policy.releaseWeight, options: SolveOptions = metricOptions): Boolean End a drag: drop the point's prior back to resting and run the longer settle.
fun pin(point: PointRef, weight: Double = policy.pinnedWeight) Fix a point where it is now, and keep putting it back there before every solve.
fun unpin(point: PointRef, weight: Double = policy.preserveWeight) Release a pin and return the point to a resting prior. Leaves it where it is; it is simply free again.
fun isPinned(point: PointRef): Boolean Whether pin is currently holding this point.
fun resetPriors(weight: Double = policy.preserveWeight) Return every prior to weight, then restore the pinned points' own weights — the "forget the last gesture" call.
override fun close() Release the native problem. The session, its points and its priors are unusable afterwards.

DragHandle

class DragHandle internal constructor(private val session: GeometrySolveSession, val point: PointRef, val policy: InteractionPolicy) : AutoCloseable

One gesture on one point: to while it moves, release when it stops.

Declaration Summary
val point: PointRef The point this gesture holds.
val policy: InteractionPolicy The weights and iteration counts to, settle and release draw their defaults from.
fun to(target: Vec3, solve: Boolean = true): Boolean Move the dragged point and, unless solve is false, re-solve at drag effort. The per-frame call of a gesture.
fun to(x: Double, y: Double, z: Double, solve: Boolean = true): Boolean to from loose coordinates.
fun settle(options: SolveOptions = policy.dragOptions): Boolean Re-solve without moving the point — more iterations at the same target, while still holding the drag.
fun release(options: SolveOptions = policy.releaseOptions): Boolean End the gesture: drop the prior to resting and run the settle. Idempotent — a second call reports the last result without solving again.
override fun close() Releases, which means closing a handle solves. Intentional: use should not leave a point weighted.

LivePointMove

class LivePointMove internal constructor(val binding: PointBinding, val previous: Vec3, val read: Vec3, val distance: Double, val explicit: Boolean)

One bound point that changed between steps: where it was, what its reader now says, and whether the caller said so (explicit) or the move was inferred from exceeding the binding's drag tolerance.

Declaration Summary
val binding: PointBinding The binding that noticed, which names the point and carries the tolerance that was exceeded.
val previous: Vec3 Where the point stood after the previous step.
val read: Vec3 What the binding's reader returns now.
val distance: Double How far it moved: the distance compared against the binding's drag tolerance.
val explicit: Boolean True when the caller named this point as dragged, false when the move was inferred.

LiveGeometryStep

class LiveGeometryStep internal constructor(val ok: Boolean, val rebuilt: Boolean, val moved: List<LivePointMove>, val pointCount: Int, val programHash: UInt, val report: MutableSolveReport)

What one LiveGeometryProgram.step did: whether the solve was usable, whether the session had to be rebuilt, which bound points moved, and the model identity that was solved.

Declaration Summary
val ok: Boolean Whether this step's solve was usable.
val rebuilt: Boolean Whether the session had to be recompiled, which drops pins and the state they stood on.
val moved: List<LivePointMove> The bound points that changed since the last step, explicitly or by exceeding a tolerance.
val pointCount: Int The point count of the model that was solved; one of the three things a reuse depends on.
val programHash: UInt The program hash of the model that was solved; another, alongside the point names.
val report: MutableSolveReport The session's own report, not a copy. Read it before the next step overwrites it.

LiveGeometryProgram

class LiveGeometryProgram(private val backend: CeresSolverBackend = FfmCeresBackend(), private val policy: InteractionPolicy = InteractionPolicy(), private val block: GeometryModelScope.() -> Unit) : AutoCloseable

A model block held across frames: re-declares, reuses the compiled session where it can, reads the bound values in, solves, writes them back.

Declaration Summary
val currentSession: GeometrySolveSession? The live session, or null before the first step. Replaced whenever the model changes shape.
fun step(options: SolveOptions? = null, dragged: Iterable<PointRef> = emptyList(), draggedVectors: Iterable<Vector3f> = emptyList()): LiveGeometryStep One frame: re-run the block, reuse or rebuild the session, read every bound point, solve, and write the results back through the bindings.
fun step(dragged: PointRef, vararg moreDragged: PointRef, options: SolveOptions? = null): LiveGeometryStep step naming the dragged points as varargs, so a caller need not build a collection.
fun step(dragged: Vector3f, vararg moreDragged: Vector3f, options: SolveOptions? = null): LiveGeometryStep step naming the dragged bound vectors as varargs, matched by object identity.
fun polishLocked(locked: Iterable<PointRef> = emptyList(), lockedVectors: Iterable<Vector3f> = emptyList(), weight: Double = policy.pinnedWeight, options: SolveOptions = policy.settleOptions): LiveGeometryStep A settle pass with some points nailed down: build a throwaway model with hard locks on locked, solve it to convergence, and write the result out through the bindings.
fun polishLocked(locked: PointRef, vararg moreLocked: PointRef, weight: Double = policy.pinnedWeight, options: SolveOptions = policy.settleOptions): LiveGeometryStep polishLocked naming the locked points as varargs.
fun polishLocked(locked: Vector3f, vararg moreLocked: Vector3f, weight: Double = policy.pinnedWeight, options: SolveOptions = policy.settleOptions): LiveGeometryStep polishLocked naming the locked bound vectors as varargs, matched by object identity.
override fun close() Release the compiled session. A later step compiles a fresh one rather than failing.

CeresExpression.kt

Residuals written in Kotlin, compiled to a little register machine, and differentiated exactly by Ceres.

Every other constraint in this vocabulary is a C++ functor with a matching opcode, which means a new kind of residual costs a native rebuild, a Kotlin change, and a redeployed dylib. This is the way out: build an expression here, CeresExpressionResidual encodes it, and the native EXPR opcode evaluates it. The evaluator is templated on the scalar type, so Ceres runs it on ceres::Jet and gets analytic derivatives — not finite differences — for a residual it has never seen.

// "these two points are exactly 2.5 apart", written from scratch
val program = buildCeresProgram {
    expression(a, b) { (pa, pb) ->
        listOf((pa - pb).length() - 2.5.c)
    }
}

What it costs

A hand-written functor is faster: the VM interprets, allocates a slot vector per evaluation, and Ceres cannot specialise the parameter-block sizes the way [ceres::AutoDiffCostFunction] does with a fixed arity. Reach for a real opcode when a residual is hot and settled; reach for this while you are still finding out what the residual should be, or when it is caller-specific and will never belong in the wrapper.

What to watch

  • abs, min and max are not differentiable at their kinks. Ceres will hand you a subgradient there, which is usually what you want for a one-sided barrier and is not what you want in the middle of a smooth objective.
  • Nothing checks for division by zero or a square root of a negative. A residual that produces a NaN will make the solve fail rather than corrupt anything, but it will not tell you where.
  • Ceres forbids one residual block naming the same point twice, so expression rejects a repeated point rather than letting the native side discover it.

Sharing

The expression is a DAG, not a tree: CeresExpr instances are hash-consed as you build them, so writing d / d evaluates d once. That is why the machine is register-based — with named destination slots, sharing is free.

Free variables

An expression can also read unknowns of its own — quantities Ceres solves for that are not points. CeresProgramBuilder.variable declares one, with an initial value and an optional CeresManifold, and hands back a CeresVariableRef that any number of expressions can name:

// fit a sphere: the centre and the radius are unknowns, not points
val centre = variable("centre", Vec3(0.0, 0.0, 0.0))
val radius = variable("radius", 1.0)
points.forEach { i ->
    expression(listOf(i), listOf(centre, radius)) { (p), (c, r) ->
        listOf(p.distanceTo(c.vec()) - r.scalar)
    }
}

This is the mechanism PLANAR_HESSE hardcodes in C++ — a plane as a unit normal on a SphereManifold plus a scalar offset — made available to a residual written at runtime. It is worth the machinery: on kkC the plane-as-a-free-variable formulation converges in 12 iterations to tangency 3e-08 where the same objective with the plane eliminated inside the residual does not converge at all. Well-conditioned auxiliary unknowns are not overhead; they are frequently what makes a problem tractable.

The solved values come back in SolveResult.variables, in declaration order.

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

Top-level functions

Declaration Summary
fun constant(value: Double): CeresExpr A literal. 2.5.c reads better than constant(2.5) inside an expression.
fun sqrt(x: CeresExpr): CeresExpr Square root. No guard: the native evaluator will happily take the root of a negative and produce a NaN, which fails the solve without saying where. Prefer CeresExprVec.length, which keeps a small epsilon under the root precisely so the derivative at zero stays finite.
fun square(x: CeresExpr): CeresExpr x * x as one instruction, and one node — cheaper and clearer than multiplying an expression by itself.
fun reciprocal(x: CeresExpr): CeresExpr 1 / x as one instruction. Nothing checks for zero; see sqrt for what a NaN costs you.
fun exp(x: CeresExpr): CeresExpr e to the power x, as one instruction. The inverse of ln.
fun ln(x: CeresExpr): CeresExpr Natural log. Undefined at and below zero, and unguarded.
fun sin(x: CeresExpr): CeresExpr Sine, in radians — as everything angular here is. CERES_PI is on hand for the literal.
fun cos(x: CeresExpr): CeresExpr Cosine, in radians.
fun tan(x: CeresExpr): CeresExpr Tangent, in radians. Unbounded at the odd multiples of pi/2, and unguarded.
fun abs(x: CeresExpr): CeresExpr Not differentiable at zero; Ceres will use a subgradient.
fun min(a: CeresExpr, b: CeresExpr): CeresExpr Not differentiable where the arguments cross.
fun max(a: CeresExpr, b: CeresExpr): CeresExpr Not differentiable where the arguments cross. The building block of a one-sided barrier.
fun pow(base: CeresExpr, exponent: CeresExpr): CeresExpr base to the power exponent, both expressions — so the exponent may itself be solved for. For a fixed integer power prefer square or a product; this is the general and slowest case.
fun atan2(y: CeresExpr, x: CeresExpr): CeresExpr Two-argument arctangent, in radians, y first. The only way to get a signed angle out of this vocabulary.
fun barrierBelow(x: CeresExpr, floor: CeresExpr): CeresExpr max(0, floor - x): zero while x >= floor, and a penalty below it.
fun barrierAbove(x: CeresExpr, ceiling: CeresExpr): CeresExpr max(0, x - ceiling): zero while x <= ceiling, and a penalty above it. The mirror of barrierBelow.
fun hessePlaneOf(points: List<Vec3>): CeresHessePlane The total-least-squares plane through points, in Hesse normal form: the centroid, and the smallest eigenvector of the scatter matrix about it.
fun CeresProgramBuilder.planarHesseExpression(points: List<Int>, pose: DoubleArray, weight: Double = 1.0, name: String = "plane"): CeresProgramBuilder PLANAR_HESSE, written as an expression: this face's corners against a free unit normal and a free scalar offset.

Top-level values

Declaration Summary
val Double.c: CeresExpr A literal, as a property. Also the fix for 2.0 * expr, which does not compile — write 2.0.c * expr.
val Int.c: CeresExpr A literal, as a property.
const val CERES_PI: Double Handy when an expression wants a literal angle; every trigonometric op here is in radians.

CeresExpr

sealed interface CeresExpr

One node of a residual expression: a constant, a single coordinate of one of the residual's points, or an operation over other nodes. Immutable, and hash-consed as you build, so an identical subexpression written twice is one node and is evaluated once.

Declaration Summary
operator fun plus(other: CeresExpr): CeresExpr+1 overload · also Double Sum. The Double overload wraps the number as a constant node first.
operator fun minus(other: CeresExpr): CeresExpr+1 overload · also Double Difference. The Double overload wraps the number as a constant node first.
operator fun times(other: CeresExpr): CeresExpr+1 overload · also Double Product. The Double overload wraps the number as a constant node first.
operator fun div(other: CeresExpr): CeresExpr+1 overload · also Double Quotient, with no check for a zero divisor. The Double overload wraps the number first.
operator fun unaryMinus(): CeresExpr Negation, as one instruction rather than a multiply by minus one.

CeresExprVec

data class CeresExprVec(val x: CeresExpr, val y: CeresExpr, val z: CeresExpr)

One of the expression's points, as three scalar expressions — the vector algebra you actually want to write a residual in.

Declaration Summary
operator fun plus(o: CeresExprVec) Componentwise sum: three scalar additions, written once.
operator fun minus(o: CeresExprVec) Componentwise difference, which is how an edge or an offset is written.
operator fun times(k: CeresExpr)+1 overload · also Double Scale all three components. The Double overload goes through Double.c.
operator fun div(k: CeresExpr) Divide all three components. There is no Double overload, so write vec / 2.0.c.
operator fun unaryMinus() Negate all three components, reversing the direction.
infix fun dot(o: CeresExprVec): CeresExpr Dot product — five instructions, and the basis of every angle and projection residual here.
infix fun cross(o: CeresExprVec) Cross product. Three residuals whose magnitude is an area (or, between unit vectors, a sine), so it makes a good scale-free "these are parallel" test without any square root.
fun lengthSquared(): CeresExpr Squared length. Prefer this to length() when you only need to compare.
fun length(epsilon: Double = 1e-20): CeresExpr Length, with epsilon added under the root so the derivative stays finite at the origin. A plain sqrt(0) has an infinite derivative and will poison the Jacobian.
fun distanceTo(o: CeresExprVec, epsilon: Double = 1e-20): CeresExpr Distance between two points, epsilon-guarded like length. The most-reached-for residual in the vocabulary.

CeresManifold

enum class CeresManifold(val code: Int)

The space a free variable lives in — which is to say, how Ceres is allowed to step it.

Constants: NONE, SPHERE, QUATERNION.

CeresVariableDecl

data class CeresVariableDecl(val name: String, val initial: List<Double>, val manifold: CeresManifold = CeresManifold.NONE, val bounds: Map<Int, ClosedFloatingPointRange<Double>> = emptyMap())

An unknown Ceres solves for that is not a point: dimension scalars with an initial value and a manifold.

Declaration Summary
val name: String A label for this side only; it never crosses the ABI. Blank is rejected.
val initial: List<Double> The starting value, one entry per component, and what fixes dimension.
val manifold: CeresManifold The geometry the solver steps the block on. Euclidean unless you say otherwise.
val bounds: Map<Int, ClosedFloatingPointRange<Double>> Per-component box constraints, component to (lower .. upper). A component may appear once.
val dimension: Int How many scalars the block holds, which is initial.size and is capped by the wrapper.

CeresVariableRef

data class CeresVariableRef(val index: Int, val declaration: CeresVariableDecl)

A declared variable, as the program refers to it.

Declaration Summary
val name: String The declared name, forwarded from declaration.
val dimension: Int The declared component count, forwarded from declaration.

CeresExprVariable

class CeresExprVariable internal constructor(internal val block: Int, val declaration: CeresVariableDecl)

One of an expression's free variables, bound to its position in that residual's variable list.

Declaration Summary
val declaration: CeresVariableDecl What was declared at program scope: the name, the initial value and the manifold.
val name: String The declared name, forwarded from declaration.
val dimension: Int How many components get will accept, forwarded from declaration.
operator fun get(component: Int): CeresExpr Component component of this variable.
val scalar: CeresExpr The variable itself, when it is a single scalar.
fun vec(): CeresExprVec The first three components, as a vector.
fun rotate(v: CeresExprVec): CeresExprVec A four-component variable read as a rotation, applied to v — the quaternion sandwich written out, so no trigonometry and no normalisation appear in the residual.

CeresExpressionResidual

data class CeresExpressionResidual(val points: List<PointId>, val expressions: List<CeresExpr>, override val weight: Double = 1.0, val variables: List<CeresVariableRef> = emptyList()) : GeometryConstraint

A residual block whose value is computed by expressions, one residual each, over points.

Declaration Summary
val points: List<PointId> The cloud points the expressions may read, distinct, and ALL_POINTS is not one of them.
val expressions: List<CeresExpr> One expression per residual in the block. At least one is required.
override val weight: Double Square-rooted into the functor like every other weight here, so the cost scales by weight.
val variables: List<CeresVariableRef> The free variables these expressions read, in the order the expressions' CeresExprVariable handles were bound. Declared at program scope, so a variable named here may also be named by other residuals.
override fun encodeLegacy(out: MutableList<Double>) Append the Expr opcode and the whole compiled program (point and variable indices, residual slots, slot count, constants and instructions) to the bytecode being built.
fun compile(): CompiledCeresExpression The compiled program, exposed so a caller can see what their expression became.

CompiledCeresExpression

data class CompiledCeresExpression(val code: List<Int>, val constants: List<Double>, val residualSlots: List<Int>, val slotCount: Int, val variables: List<CeresVariableDecl> = emptyList())

The register-machine program an expression compiles to: straight-line code, no branches, no stack.

Declaration Summary
val code: List<Int> Four ints per instruction: op, dst, a, b.
val constants: List<Double> The constant pool the CONST instructions index, deduplicated by the compiler.
val residualSlots: List<Int> Which slot each residual reads, in the order the expressions were given.
val slotCount: Int Slots the evaluator must allocate — one per instruction, since each writes a fresh one.
val variables: List<CeresVariableDecl> The free variables this residual reads, in local (parameter-block) order.
val instructionCount: Int code.size / 4: the cost of one evaluation, and what disassemble prints a line each for.
fun disassemble(): String A readable disassembly. The point of an expression VM is that you can look at the program.

CeresHessePlane

data class CeresHessePlane(val normal: Vec3, val offset: Double)

A plane as { x : x . normal == offset }, with normal a unit vector.

CeresGeometry.kt

The constraint vocabulary for Field's Ceres point solver, and the encoding that ships it to a backend. Geometry is a flat xyz PointCloud addressed by PointId; a solve is described by a CeresProgram of GeometryConstraints — Planar, Colinear, DistanceEquals, Perpendicular, LockPosition, WeakPosition, PointOnPlane, DihedralAngle, PanelArea and the rest — most conveniently assembled with buildCeresProgram / CeresProgramBuilder.

Every constraint serialises itself into one flat DoubleArray of opcodes (CeresOpcode) via encodeLegacyBytecode; the actual solving happens behind CeresSolverBackend / CompiledCeresSolver (see FfmCeresBackend.kt for the native one). CachedCeresSolvers keys compiled solvers by point count plus program hash so an unchanged program is compiled once.

Constructors validate aggressively: coordinates and weights must be finite, weights non-negative, point indices distinct where the opcode requires it, and the -1/PointId.ALL_POINTS sentinel may only be used alone and only where a constraint allows it.

Weights are square-rooted, and compiling captures a pose

Weights are square-rooted. Every native functor stores s = sqrt(weight) and multiplies its residual by s, so Ceres minimises weight * residual^2. A residual weight of k in the usual "residual = k * (quantity - target)" sense is a weight of k^2 here. Weights are also only comparable between constraints whose residuals share units — a length, a dimensionless cosine and an area are three different scales.

Compiling captures a pose. CeresSolverBackend.compile takes seed points, and several constraints bake that pose in permanently: LockPosition pins to the seed coordinates, GaugeRotation measures rotation relative to them, and Planar seeds each face's plane variables from them. Points passed to a later solve move the answer, not those targets. The one exception is WeakPosition, whose target is refreshed from the input cloud on every solve — which is what makes it a "stay near where you are" regulariser rather than a "return to the seed" one.

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

Top-level functions

Declaration Summary
fun buildCeresProgram(block: CeresProgramBuilder.() -> Unit): CeresProgram The way in. Describe a solve in a CeresProgramBuilder block and get an immutable CeresProgram back.
fun buildCeresProgram(loss: RobustLoss, block: CeresProgramBuilder.() -> Unit): CeresProgram As buildCeresProgram, choosing the loss the native solver wraps every residual in.
fun metricMap(groupCosts: DoubleArray): SolveMetrics Wrap a raw per-opcode cost array — the shape the native side fills — as a SolveMetrics.
fun fnv1a64BitDoubles(values: DoubleArray): UInt FNV-1a over the raw bytes of a DoubleArray, folding the length in at the end. The program identity behind CeresProgram.legacyHash and the solver cache.

PointId

value class PointId(val value: Int)

An index into a PointCloud, counting points rather than doubles: point k occupies values[3k .. 3k+2].

Vec3

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

A bare xyz triple, used for both positions and directions.

Axis

enum class Axis

Unused by this vocabulary; the constraints that take an axis take an AxisSet.

Constants: X, Y, Z.

AxisSet

enum class AxisSet

Which coordinates a lock, prior or same-coordinate constraint acts on.

Constants: X, Y, Z, XY, XZ, YZ, XYZ.

CeresOpcode

enum class CeresOpcode(val code: Int, val metricName: String)

The wire vocabulary: one entry per residual family the native wrapper knows how to build.

Constants: Planar, OnLine, Distance, SameX, SameY, SameZ, SameXY, SameXZ, SameYZ, PerpendicularAtOrigin, LockX, LockY, LockZ, LockXY, LockXZ, LockYZ, Prior3, PriorX, PriorY, PriorZ, MinDistance, EqualDistance, MaxDistance, NoNegativeZ, DotAtOrigin, VectorDot, PointOnPlane, LineParallel, LineAngle, DihedralAngle, EqualEdgeVector, MirrorAcrossPlane, PanelArea, PointInHalfspace, LineDirection, PlaneNormalDirection, LineTowardPoint, SetLoss, EdgeTangency, TangencyCentroid, Centroid, RadiusAboutCentroid, PlanarHesse, PlanarFit, GaugeRotation, Expr, Variable.

GeometryConstraint

sealed interface GeometryConstraint

One geometric condition, as a thing that can validate itself, name the backend feature it needs, and serialise itself into the opcode stream.

Declaration Summary
val weight: Double
fun encodeLegacy(out: MutableList<Double>) Append this constraint's opcodes and operands to the program being built.

Planar

data class Planar(val points: List<PointId>, override val weight: Double = 1.0) : GeometryConstraint

All of points on one common plane, which the solver introduces as free variables: an origin O and a unit normal n on a sphere manifold, seeded from a PCA fit of the seed cloud. One residual per point, (P - O) . n, in length units.

Declaration Summary
val points: List<PointId>
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

Colinear

data class Colinear(val lineA: PointId, val lineB: PointId, val points: List<PointId>, override val weight: Double = 1.0) : GeometryConstraint

Every point in points on the infinite line through lineA and lineB — three residuals each, cross(unit(B - A), P - A), so the magnitude is the perpendicular distance from the line and does not grow with the line's length.

Declaration Summary
val lineA: PointId
val lineB: PointId
val points: List<PointId>
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

DistanceEquals

data class DistanceEquals(val a: PointId, val b: PointId, val distance: Double, override val weight: Double = 1.0) : GeometryConstraint

A fixed separation: one residual, |a - b| - distance, in length units. The workhorse of the vocabulary and the one whose weight everything else is usually scaled against.

Declaration Summary
val a: PointId
val b: PointId
val distance: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

DistanceAtLeast

data class DistanceAtLeast(val a: PointId, val b: PointId, val minimum: Double, override val weight: Double = 1.0) : GeometryConstraint

A one-sided floor: max(0, minimum - |a - b|). Contributes exactly nothing while the points are far enough apart, which is what makes it safe to sprinkle over a problem you have already tuned.

Declaration Summary
val a: PointId
val b: PointId
val minimum: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

DistanceAtMost

data class DistanceAtMost(val a: PointId, val b: PointId, val maximum: Double, override val weight: Double = 1.0) : GeometryConstraint

A one-sided ceiling: max(0, |a - b| - maximum), zero while the points are close enough. The mirror of DistanceAtLeast, with the same non-differentiable kink at the limit.

Declaration Summary
val a: PointId
val b: PointId
val maximum: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

EqualDistance

data class EqualDistance(val a: PointId, val b: PointId, val c: PointId, val d: PointId, override val weight: Double = 1.0) : GeometryConstraint

Two segments the same length, without saying what that length is: one residual, |a - b| - |c - d|. The building block of "all these edges are equal" without committing to a scale.

Declaration Summary
val a: PointId
val b: PointId
val c: PointId
val d: PointId
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

SameCoordinate

data class SameCoordinate(val points: List<PointId>, val axes: AxisSet, override val weight: Double = 1.0) : GeometryConstraint

Points that agree on one or two coordinates — a shared height, a vertical alignment, a row.

Declaration Summary
val points: List<PointId>
val axes: AxisSet
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

Perpendicular

data class Perpendicular(val origin: PointId, val a: PointId, val b: PointId, override val weight: Double = 1.0) : GeometryConstraint

A right angle at origin between the rays to a and to b: one residual, the dot product of the two normalised directions, so it is a dimensionless cosine and its weight is not on the same scale as a distance constraint's.

Declaration Summary
val origin: PointId
val a: PointId
val b: PointId
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

LockPosition

data class LockPosition(val points: List<PointId>, val axes: AxisSet, override val weight: Double = 1e6) : GeometryConstraint

Hold points at the coordinates they had when the solver was compiled — not the ones you pass to solve. One residual per locked axis per point, in length units, at a default weight of 1e6.

Declaration Summary
val points: List<PointId>
val axes: AxisSet
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

WeakPosition

data class WeakPosition(val points: List<PointId>, val axes: AxisSet = AxisSet.XYZ, override val weight: Double = 1e-6) : GeometryConstraint

A prior pulling each point towards the position it had on entry to this solve, refreshed every call. The regulariser that makes an underdetermined problem have one answer instead of a family of them, and at the default 1e-6 it is cheap enough to leave on everywhere.

Declaration Summary
val points: List<PointId>
val axes: AxisSet
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

NoNegativeZ

data class NoNegativeZ(val points: List<PointId>, override val weight: Double = 1.0) : GeometryConstraint

Keep points out of negative z: a hinge, max(0, -z), costing nothing at or above the ground plane.

Declaration Summary
val points: List<PointId>
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

DotAtOrigin

data class DotAtOrigin(val origin: PointId, val a: PointId, val b: PointId, val target: Double = 0.0, val normalized: Boolean = true, override val weight: Double = 1.0) : GeometryConstraint

A prescribed angle at origin: one residual, dir(a) . dir(b) - target, i.e. Perpendicular generalised to any cosine.

Declaration Summary
val origin: PointId
val a: PointId
val b: PointId
val target: Double
val normalized: Boolean
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

VectorDot

data class VectorDot(val a0: PointId, val a1: PointId, val b0: PointId, val b1: PointId, val target: Double = 0.0, val normalized: Boolean = true, override val weight: Double = 1.0) : GeometryConstraint

An angle between two segments that need not touch: one residual, dir(a0 -> a1) . dir(b0 -> b1) - target. The four-point form of DotAtOrigin, and the same normalised/raw distinction applies.

Declaration Summary
val a0: PointId
val a1: PointId
val b0: PointId
val b1: PointId
val target: Double
val normalized: Boolean
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

PointOnPlane

data class PointOnPlane(val point: PointId, val planeA: PointId, val planeB: PointId, val planeC: PointId, val offset: Double = 0.0, override val weight: Double = 1.0) : GeometryConstraint

point at a signed distance offset from the plane through planeA, planeB and planeC — one residual in length units, zero by default meaning "on the plane".

Declaration Summary
val point: PointId
val planeA: PointId
val planeB: PointId
val planeC: PointId
val offset: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

LineParallel

data class LineParallel(val a0: PointId, val a1: PointId, val b0: PointId, val b1: PointId, override val weight: Double = 1.0) : GeometryConstraint

Two segments parallel: three residuals, the cross product of the two unit directions, so the magnitude is the sine of the angle between them and does not depend on either length.

Declaration Summary
val a0: PointId
val a1: PointId
val b0: PointId
val b1: PointId
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

LineAngle

data class LineAngle(val a0: PointId, val a1: PointId, val b0: PointId, val b1: PointId, val targetCos: Double, override val weight: Double = 1.0) : GeometryConstraint

A prescribed angle between two segments: one residual, the dot product of the two unit directions minus targetCos. Always normalised, so the residual is dimensionless — this is VectorDot's normalised case with a name that says what it is for.

Declaration Summary
val a0: PointId
val a1: PointId
val b0: PointId
val b1: PointId
val targetCos: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

DihedralAngle

data class DihedralAngle(val edgeA: PointId, val edgeB: PointId, val sideA: PointId, val sideB: PointId, val targetCos: Double, override val weight: Double = 1.0) : GeometryConstraint

The fold angle across an edge: one residual, the dot product of the unit normals of triangles (edgeA, edgeB, sideA) and (edgeA, edgeB, sideB), minus targetCos.

Declaration Summary
val edgeA: PointId
val edgeB: PointId
val sideA: PointId
val sideB: PointId
val targetCos: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

EqualEdgeVector

data class EqualEdgeVector(val a: PointId, val b: PointId, val c: PointId, val d: PointId, override val weight: Double = 1.0) : GeometryConstraint

One segment a translate of another: three residuals, (b - a) - (d - c), in length units.

Declaration Summary
val a: PointId
val b: PointId
val c: PointId
val d: PointId
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

MirrorAcrossPlane

data class MirrorAcrossPlane(val point: PointId, val mirror: PointId, val planeA: PointId, val planeB: PointId, val planeC: PointId, override val weight: Double = 1.0) : GeometryConstraint

point and mirror reflections of each other in the plane through planeA, planeB, planeC.

Declaration Summary
val point: PointId
val mirror: PointId
val planeA: PointId
val planeB: PointId
val planeC: PointId
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

PanelArea

data class PanelArea(val a: PointId, val b: PointId, val c: PointId, val d: PointId, val targetArea: Double, override val weight: Double = 1.0) : GeometryConstraint

A quadrilateral panel of a prescribed area: one residual, the summed areas of triangles abc and acd minus targetArea.

Declaration Summary
val a: PointId
val b: PointId
val c: PointId
val d: PointId
val targetArea: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

PointInHalfspace

data class PointInHalfspace(val point: PointId, val planeA: PointId, val planeB: PointId, val planeC: PointId, val minSignedDistance: Double = 0.0, override val weight: Double = 1.0) : GeometryConstraint

Keep point on one side of a plane: a hinge, max(0, minSignedDistance - signed distance), costing nothing once the point is far enough over. A clearance or a keep-out zone, and free while respected.

Declaration Summary
val point: PointId
val planeA: PointId
val planeB: PointId
val planeC: PointId
val minSignedDistance: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

LineDirection

data class LineDirection(val a: PointId, val b: PointId, val direction: Vec3, override val weight: Double = 1.0) : GeometryConstraint

A segment aligned with a world direction — vertical columns, level beams. Three residuals, the cross product of the segment's unit direction with the normalised direction, so a sine and dimensionless.

Declaration Summary
val a: PointId
val b: PointId
val direction: Vec3
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

PlaneNormalDirection

data class PlaneNormalDirection(val a: PointId, val b: PointId, val c: PointId, val normal: Vec3, val targetCos: Double = 1.0, override val weight: Double = 1.0) : GeometryConstraint

A face turned to face a given way: one residual, the dot product of unit(cross(b - a, c - a)) with the normalised normal, minus targetCos.

Declaration Summary
val a: PointId
val b: PointId
val c: PointId
val normal: Vec3
val targetCos: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

LineTowardPoint

data class LineTowardPoint(val a: PointId, val b: PointId, val target: PointId, override val weight: Double = 1.0) : GeometryConstraint

target on the infinite line through a and b — three residuals, the same cross(unit(b - a), target - a) the native side uses for Colinear, reported under its own metric group.

Declaration Summary
val a: PointId
val b: PointId
val target: PointId
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

EdgeTangency

data class EdgeTangency(val a: PointId, val b: PointId, val radius: Double, override val weight: Double = 1.0) : GeometryConstraint

Distance from the origin to the infinite line through a and b, driven to radius: every edge tangent to one sphere about the origin. Local — two points per residual.

Declaration Summary
val a: PointId
val b: PointId
val radius: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

TangencyCentroid

data class TangencyCentroid(val edges: List<Pair<PointId, PointId>>, override val weight: Double = 1.0) : GeometryConstraint

The mean, over edges, of each edge's nearest point to the origin, driven to the origin — three residuals over every endpoint at once.

Declaration Summary
val edges: List<Pair<PointId, PointId>>
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

Centroid

data class Centroid(val points: List<PointId>, val target: Vec3 = Vec3(0.0, 0.0, 0.0), override val weight: Double = 1.0) : GeometryConstraint

The mean of points driven to target; three residuals. Pass PointId.ALL_POINTS alone for the whole cloud.

Declaration Summary
val points: List<PointId>
val target: Vec3
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

RadiusAboutCentroid

data class RadiusAboutCentroid(val points: List<PointId>, val radius: Double, override val weight: Double = 1.0) : GeometryConstraint

Every point in points the same distance radius from the running mean of that same set — a circumsphere whose centre is not a free variable. One residual per point, all in one block.

Declaration Summary
val points: List<PointId>
val radius: Double
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

RawProgramFragment

data class RawProgramFragment(val values: List<Double>, override val weight: Double = 1.0) : GeometryConstraint

A literal opcode stream, spliced into the program as-is.

Declaration Summary
val values: List<Double>
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

PlanarHesse

data class PlanarHesse(val points: List<PointId>, override val weight: Double = 1.0) : GeometryConstraint

Face planarity with the plane in Hesse normal form: { x : x . n == d }, n on a sphere manifold and d a scalar.

Declaration Summary
val points: List<PointId>
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

PlanarFit

data class PlanarFit(val points: List<PointId>, override val weight: Double = 1.0) : GeometryConstraint

Face planarity against the face's own Newell best-fit plane, computed inside the residual — no auxiliary plane variables at all.

Declaration Summary
val points: List<PointId>
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

GaugeRotation

data class GaugeRotation(val points: List<PointId>, override val weight: Double = 1.0) : GeometryConstraint

Remove the infinitesimal-rotation null space: three residuals holding sum(reference_i x p_i) at zero, where the reference pose is the one the problem was compiled with.

Declaration Summary
val points: List<PointId>
override val weight: Double
override fun encodeLegacy(out: MutableList<Double>)

CeresProgramBuilder

class CeresProgramBuilder

A fluent front end for the constraint classes, taking plain Int point indices instead of PointIds.

Declaration Summary
fun add(constraint: GeometryConstraint): CeresProgramBuilder Append an already-constructed constraint — the escape hatch for one this class has no method for.
fun variable(declaration: CeresVariableDecl): CeresVariableRef Declare a free variable: an unknown Ceres solves for that is not a point.
fun variable(name: String, initial: Double): CeresVariableRef A free scalar.
fun variable(name: String, initial: Vec3): CeresVariableRef A free position or offset — three components, no manifold.
fun unitVariable(name: String, initial: Vec3): CeresVariableRef A free direction, held on the unit sphere.
fun rotationVariable(name: String): CeresVariableRef A free rotation, held on the quaternion manifold. Starts at the identity.
fun planar(vararg points: Int, weight: Double = 1.0)
fun colinear(lineA: Int, lineB: Int, vararg points: Int, weight: Double = 1.0)
fun colinear(points: List<Int>, weight: Double = 1.0): CeresProgramBuilder
fun distance(a: Int, b: Int, distance: Double, weight: Double = 1.0)
fun minDistance(a: Int, b: Int, minimum: Double, weight: Double = 1.0)
fun maxDistance(a: Int, b: Int, maximum: Double, weight: Double = 1.0)
fun equalDistance(a: Int, b: Int, c: Int, d: Int, weight: Double = 1.0)
fun same(points: List<Int>, axes: AxisSet, weight: Double = 1.0)
fun perpendicular(origin: Int, a: Int, b: Int, weight: Double = 1.0)
fun dotAtOrigin(origin: Int, a: Int, b: Int, target: Double = 0.0, normalized: Boolean = true, weight: Double = 1.0)
fun vectorDot(a0: Int, a1: Int, b0: Int, b1: Int, target: Double = 0.0, normalized: Boolean = true, weight: Double = 1.0)
fun pointOnPlane(point: Int, planeA: Int, planeB: Int, planeC: Int, offset: Double = 0.0, weight: Double = 1.0)
fun lineParallel(a0: Int, a1: Int, b0: Int, b1: Int, weight: Double = 1.0)
fun lineAngle(a0: Int, a1: Int, b0: Int, b1: Int, targetCos: Double, weight: Double = 1.0)
fun dihedralAngle(edgeA: Int, edgeB: Int, sideA: Int, sideB: Int, targetCos: Double, weight: Double = 1.0)
fun equalEdgeVector(a: Int, b: Int, c: Int, d: Int, weight: Double = 1.0)
fun mirrorAcrossPlane(point: Int, mirror: Int, planeA: Int, planeB: Int, planeC: Int, weight: Double = 1.0)
fun panelArea(a: Int, b: Int, c: Int, d: Int, targetArea: Double, weight: Double = 1.0)
fun pointInHalfspace(point: Int, planeA: Int, planeB: Int, planeC: Int, minSignedDistance: Double = 0.0, weight: Double = 1.0)
fun lineDirection(a: Int, b: Int, direction: Vec3, weight: Double = 1.0)
fun planeNormalDirection(a: Int, b: Int, c: Int, normal: Vec3, targetCos: Double = 1.0, weight: Double = 1.0)
fun lineTowardPoint(a: Int, b: Int, target: Int, weight: Double = 1.0)
fun lock(points: List<Int>, axes: AxisSet, weight: Double = 1e6)
fun weak(points: List<Int>, axes: AxisSet = AxisSet.XYZ, weight: Double = 1e-6)
fun weakAll(weight: Double = 1e-6)
fun noNegativeZ(points: List<Int>, weight: Double = 1.0)
fun noNegativeZAll(weight: Double = 1.0)
fun edgeTangency(a: Int, b: Int, radius: Double, weight: Double = 1.0)
fun tangencyCentroid(edges: List<Pair<Int, Int>>, weight: Double = 1.0)
fun centroid(points: List<Int>, target: Vec3 = Vec3(0.0, 0.0, 0.0), weight: Double = 1.0)
fun centroidAll(target: Vec3 = Vec3(0.0, 0.0, 0.0), weight: Double = 1.0)
fun radiusAboutCentroid(points: List<Int>, radius: Double, weight: Double = 1.0)
fun radiusAboutCentroidAll(radius: Double, weight: Double = 1.0)
fun planarHesse(points: List<Int>, weight: Double = 1.0)
fun planarFit(points: List<Int>, weight: Double = 1.0)
fun gaugeRotationAll(weight: Double = 1.0)
fun expression(points: List<Int>, weight: Double = 1.0, build: (List<CeresExprVec>) -> List<CeresExpr>): CeresProgramBuilder A residual you write here, in Kotlin, differentiated exactly by Ceres — no native rebuild.
fun expression(vararg points: Int, weight: Double = 1.0, build: (List<CeresExprVec>) -> List<CeresExpr>): CeresProgramBuilder As expression, for the common case of naming the points inline.
fun expression(points: List<Int>, variables: List<CeresVariableRef>, weight: Double = 1.0, build: (List<CeresExprVec>, List<CeresExprVariable>) -> List<CeresExpr>): CeresProgramBuilder A residual over points and variables — unknowns Ceres solves for that are not points.
fun raw(vararg values: Double) Splice literal opcodes into the program. See RawProgramFragment — unvalidated, and a miscounted operand list corrupts everything after it.
fun build(): CeresProgram Snapshot the constraints so far, with the wrapper's historic Huber(1e-3) loss.
fun build(loss: RobustLoss): CeresProgram Snapshot the constraints so far under an explicit loss. RobustLoss.NONE is what a problem whose residuals are meant to reach zero together wants.

RobustLoss

data class RobustLoss(val kind: Kind = Kind.HUBER, val scale: Double = 1e-3)

The loss the native solver wraps every residual in.

Declaration Summary
enum class Kind(val code: Int) Which of Ceres' loss functions to wrap residuals in, in increasing order of how hard they discount a large residual: NONE (plain least squares), HUBER, SOFT_L_ONE, CAUCHY.

CeresProgram

data class CeresProgram(val constraints: List<GeometryConstraint>, val loss: RobustLoss = RobustLoss.DEFAULT, val variables: List<CeresVariableDecl> = emptyList())

A whole solve, described independently of any point data: an ordered list of constraints plus the loss they are wrapped in.

Declaration Summary
val constraints: List<GeometryConstraint>
val loss: RobustLoss
val variables: List<CeresVariableDecl> Free variables, in declaration order — the order the native side registers them and the order SolveResult.variables reports their solved values in.
val variableComponentCount: Int Every free-variable component, summed — the size of the array a solve reports back.
fun encodeLegacyBytecode(): DoubleArray Flatten the whole program to the DoubleArray the native wrapper parses — opcodes, operands and weights in one stream, with a leading SET_LOSS when the loss is not the default.
fun legacyHash(): UInt The cache key: an FNV-1a hash of the encoded bytecode, so two programs that encode identically are the same compiled solver.

PointCloud

data class PointCloud(val values: DoubleArray)

The geometry: a flat [x, y, z, x, y, z, …] array, immutable in intent, validated finite on construction.

Declaration Summary
val pointCount: Int Points, not doubles: values.size / 3.
operator fun get(index: Int): Vec3 Bounds-checked; throws rather than returning a garbage triple. Takes a point index, not an offset.
fun withPoint(index: Int, value: Vec3): PointCloud A copy with one point moved. Copies the whole array — fine for an edit, wasteful in a per-frame loop.
fun copyValues(): DoubleArray A defensive copy of the backing array — the safe way to hand these coordinates to anything else.

DynamicPriorWeights

data class DynamicPriorWeights(val values: DoubleArray)

Per-point, per-axis override of the WeakPosition prior weights, supplied at solve time rather than baked into the program.

Declaration Summary
val pointCount: Int
fun copyValues(): DoubleArray

CeresLinearSolver

enum class CeresLinearSolver(val code: Int)

Which linear solver Ceres uses inside each trust-region step.

Constants: DENSE_QR, DENSE_NORMAL_CHOLESKY, SPARSE_NORMAL_CHOLESKY, CGNR, DENSE_SCHUR, ITERATIVE_SCHUR.

CeresTermination

enum class CeresTermination

Why the minimizer stopped. Mirrors ceres::TerminationType.

Constants: CONVERGENCE, NO_CONVERGENCE, FAILURE, USER_SUCCESS, USER_FAILURE, UNKNOWN.

CeresSolveSummary

data class CeresSolveSummary(val initialCost: Double, val finalCost: Double, val successfulSteps: Int, val unsuccessfulSteps: Int, val iterations: Int, val termination: CeresTermination, val seconds: Double, val residualCount: Int, val effectiveParameterCount: Int, val parameterBlockCount: Int, val residualBlockCount: Int, val linearSolverUsed: CeresLinearSolver?)

What the minimizer actually did. Null when SolveOptions.summary is false.

Declaration Summary
val initialCost: Double
val finalCost: Double
val successfulSteps: Int
val unsuccessfulSteps: Int
val iterations: Int
val termination: CeresTermination
val seconds: Double
val residualCount: Int Includes residuals the caller never wrote — Planar's two stabilisers per face, for instance.
val effectiveParameterCount: Int Parameters after manifolds are taken into account, so a plane normal on a sphere counts 2, not 3. Compare it with 3 * pointCount to see how much of the problem is auxiliary variables rather than geometry.
val parameterBlockCount: Int
val residualBlockCount: Int
val linearSolverUsed: CeresLinearSolver? What Ceres used — not necessarily what you asked for, if the build lacks that backend. Null when Ceres picked something this ABI has no name for.
val converged: Boolean Ceres hit one of its tolerances, rather than running out of iterations. Strictly stronger than SolveResult.ok, which only says the answer is usable.
override fun toString(): String

SolveOptions

data class SolveOptions(val maxIterations: Int = 8, val metrics: Boolean = false, val metricGroupCapacity: Int = 64, val linearSolver: CeresLinearSolver = CeresLinearSolver.DENSE_QR, val threads: Int = 1, val functionTolerance: Double = 1e-6, val gradientTolerance: Double = 1e-10, val parameterTolerance: Double = 1e-8, val nonmonotonicSteps: Boolean = false, val summary: Boolean = false, val progressToStdout: Boolean = false)

What to ask of the minimiser for one solve. Per-call, not baked into the compiled solver, so a drag can be cheap and the settle on release expensive.

Declaration Summary
val maxIterations: Int Trust-region iterations, not a time budget. The default of 8 is an interactive number.
val metrics: Boolean Fill SolveResult.metrics with per-opcode costs. Costs an extra problem evaluation per group.
val metricGroupCapacity: Int Length of the per-opcode cost array; opcodes numbered at or above it are not reported at all.
val linearSolver: CeresLinearSolver Ceres falls back to a dense solve, silently, when the loaded wrapper was built without the sparse backend an option asks for — CeresSolveSummary.linearSolverUsed is what tells you.
val threads: Int Ceres parallelises residual and Jacobian evaluation.
val functionTolerance: Double
val gradientTolerance: Double
val parameterTolerance: Double
val nonmonotonicSteps: Boolean Let the trust region accept a worse step occasionally; helps on narrow valleys.
val summary: Boolean Fill SolveResult.summary.
val progressToStdout: Boolean Ceres' own per-iteration table, on stdout. Debugging only.

SolveRequest

data class SolveRequest(val points: PointCloud, val program: CeresProgram, val dynamicPriorWeights: DynamicPriorWeights? = null, val options: SolveOptions = SolveOptions())

One solve, bundled: geometry, program, optional prior weights and options. The unit CachedCeresSolvers.solve takes.

Declaration Summary
val points: PointCloud
val program: CeresProgram
val dynamicPriorWeights: DynamicPriorWeights?
val options: SolveOptions
val legacyBytecode: DoubleArray by lazy
val legacyHash: UInt by lazy

SolveMetrics

data class SolveMetrics(val totalCost: Double?, val byOpcode: Map<CeresOpcode, Double>, val rawByOpcode: Map<Int, Double>)

Where the remaining cost is, broken down by opcode — the "which constraint family is fighting" readout.

Declaration Summary
val totalCost: Double?
val byOpcode: Map<CeresOpcode, Double>
val rawByOpcode: Map<Int, Double>
fun top(limit: Int = 5): List<Pair<String, Double>> The limit costliest groups, largest first, as metricName to cost. Zero and NaN entries are dropped, so the result can be shorter than limit, and empty when no group has any cost left to report.

CeresVariableValue

data class CeresVariableValue(val name: String, val manifold: CeresManifold, val values: DoubleArray)

What a free variable was solved to.

Declaration Summary
val name: String
val manifold: CeresManifold
val values: DoubleArray
val scalar: Double
fun vec3(): Vec3 The first three components, when the variable is a direction or a position.
override fun toString(): String
override fun equals(other: Any?): Boolean
override fun hashCode(): Int

CeresVariableValues

data class CeresVariableValues(val flat: DoubleArray, val blocks: List<CeresVariableValue>)

The solved values of a program's free variables.

Declaration Summary
val flat: DoubleArray
val blocks: List<CeresVariableValue>
val size: Int
operator fun get(index: Int): CeresVariableValue
fun all(name: String): List<CeresVariableValue> Every block with this name, in declaration order — names need not be unique.
fun first(name: String): CeresVariableValue The first block with this name.
fun scalar(name: String): Double
fun vec3(name: String): Vec3
override fun equals(other: Any?): Boolean
override fun hashCode(): Int

SolveResult

data class SolveResult(val ok: Boolean, val points: PointCloud, val metrics: SolveMetrics? = null, val summary: CeresSolveSummary? = null, val variables: CeresVariableValues = CeresVariableValues.EMPTY)

The answer: always a full cloud, plus whatever diagnostics were asked for.

Declaration Summary
val ok: Boolean
val points: PointCloud
val metrics: SolveMetrics?
val summary: CeresSolveSummary?
val variables: CeresVariableValues The solved free variables, in declaration order. Empty when the program declared none.

CompiledCeresSolver

interface CompiledCeresSolver : AutoCloseable

A CeresProgram built into a native ceres::Problem, ready to be solved repeatedly against changing point data.

Declaration Summary
val pointCount: Int The point count this solver was compiled for; a cloud of any other size is refused.
val programHash: UInt CeresProgram.legacyHash of the program built into it — the identity a cache keys on.
fun solve(points: PointCloud, dynamicPriorWeights: DynamicPriorWeights? = null, options: SolveOptions = SolveOptions()): SolveResult Solve for new positions, returning a fresh cloud and leaving the input untouched.
override fun close()

CeresSolverBackend

interface CeresSolverBackend

Something that can turn a program plus a seed cloud into a CompiledCeresSolver — the seam the native library sits behind.

Declaration Summary
fun compile(seedPoints: PointCloud, program: CeresProgram): CompiledCeresSolver Build the problem. seedPoints is not just a starting guess — it is the pose LockPosition locks to, GaugeRotation measures against, and Planar fits its plane variables from.

CachedCeresSolvers

class CachedCeresSolvers(private val backend: CeresSolverBackend, private val maxEntries: Int = 32) : AutoCloseable

An LRU of compiled solvers keyed by point count plus program hash, so re-solving an unchanged program compiles once.

Declaration Summary
fun solve(request: SolveRequest): SolveResult Compile if necessary, then solve. The one call a caller who does not want to manage solvers needs.
fun getOrCompile(seedPoints: PointCloud, program: CeresProgram): CompiledCeresSolver The cached solver for this program and point count, compiling one on a miss.
override fun close()

InteractiveSolving.kt

The mutable, reuse-oriented half of the Ceres solver API, for solving the same program over and over at interactive rates. MutablePointCloud, MutablePriorWeights and MutableSolveReport are in-place counterparts to the immutable PointCloud / DynamicPriorWeights / SolveMetrics in CeresGeometry.kt, so a drag loop can call ReusableCompiledCeresSolver.solveInPlace without allocating a new point cloud or result per frame.

InteractionPolicy collects the weights and iteration counts that distinguish the three phases of a drag — a cheap dragOptions pass while the mouse is down, a longer releaseOptions settle on release, and pinnedWeight for points held fixed — and precomputes the matching SolveOptions. MutableSolveReport.top names the highest-cost opcode groups from the last solve.

All point and weight arrays are flat xyz triples; coordinates must be finite and weights finite and non-negative, and a report's group capacity must be at least the metric capacity a solve asks for.

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

InteractionPolicy

data class InteractionPolicy(val preserveWeight: Double = 1e-2, val dragWeight: Double = 1e3, val releaseWeight: Double = preserveWeight, val pinnedWeight: Double = 1e8, val dragIterations: Int = 8, val settleIterations: Int = 24, val metricCapacity: Int = 128, val metricsDuringDrag: Boolean = false, val metricsOnRelease: Boolean = true)

The numbers that turn a constraint solve into a direct-manipulation gesture: how hard a dragged point is held, how hard everything else resists, and how many iterations each phase of the gesture gets.

Declaration Summary
val preserveWeight: Double What an untouched point's prior is worth — resistance to being moved by its neighbours.
val dragWeight: Double The point under the cursor: five orders of magnitude above preserveWeight, so it goes where you put it.
val releaseWeight: Double What the dragged point falls back to once the gesture ends; the same as preserveWeight by default.
val pinnedWeight: Double For points the user has explicitly fixed — heavy enough to win against any geometry in the program.
val dragIterations: Int Iterations per frame while dragging. Small: a late frame is worse than an unsettled one.
val settleIterations: Int Iterations for the settle pass, where latency no longer matters.
val metricCapacity: Int Length of the per-opcode cost array; 128 clears every opcode, unlike SolveOptions' default of 64.
val metricsDuringDrag: Boolean Whether a drag frame asks for per-opcode costs. Off, because nothing reads them mid-gesture.
val metricsOnRelease: Boolean Whether the settle pass asks for them, which is where a "what is still fighting" readout comes from.
val dragOptions: SolveOptions The cheap per-frame solve: dragIterations, metrics only if metricsDuringDrag.
val releaseOptions: SolveOptions The settle after the mouse goes up: settleIterations, metrics per metricsOnRelease.
val settleOptions: SolveOptions As releaseOptions but with metrics forced on — for the diagnostic pass, whatever the policy says.

MutablePointCloud

class MutablePointCloud(val values: DoubleArray, val names: List<String?> = List(values.size / 3) { null })

A point cloud you edit in place: the same flat xyz layout as PointCloud, with no copy on read or write.

Declaration Summary
val values: DoubleArray The live array, 3 * pointCount long. Held, not copied: a solve writes its answer over it.
val names: List<String?> One optional label per point, for tools and diagnostics. The solver never reads it.
val pointCount: Int Points, not doubles: a third of values.size, fixed for the life of the cloud.
fun x(point: PointId): Double One coordinate, without building a Vec3.
fun y(point: PointId): Double One coordinate, without building a Vec3.
fun z(point: PointId): Double One coordinate, without building a Vec3.
fun get(point: PointId): Vec3 A point as a Vec3. Allocates, so a per-frame loop usually wants x, y, z instead.
fun set(point: PointId, x: Double, y: Double, z: Double) Move a point. Both the index and the three coordinates are checked.
fun set(point: PointId, value: Vec3) Move a point, from a Vec3.
fun copyPoint(from: PointId, into: DoubleArray, offset: Int) Copy one point's three coordinates into an arbitrary array. For handing a point to a vertex buffer or a transform without allocating a Vec3.
fun setPointFrom(point: PointId, source: DoubleArray, offset: Int) The inverse of copyPoint: read three coordinates out of an arbitrary array into a point.
fun copyFrom(points: PointCloud) Overwrite every point from an immutable cloud of the same size — how a saved state is restored.
fun snapshot(): PointCloud A detached copy, safe to keep while this one keeps being solved into. The undo point of a gesture.

MutablePriorWeights

class MutablePriorWeights private constructor(val values: DoubleArray)

The in-place counterpart of DynamicPriorWeights: per-point, per-axis WeakPosition weights, rewritten between frames without allocating.

Declaration Summary
val pointCount: Int How many points this covers; it must match the cloud the compiled solver was built for.
fun fill(weight: Double) One weight over every point and axis — the baseline a gesture then departs from.
fun set(point: PointId, x: Double, y: Double, z: Double) One point's three axis weights, which is how a drag is expressed on one axis and not another.
fun set(point: PointId, weight: Double) One point, the same weight on all three axes, which is the usual form of a drag or a release.
fun asImmutable(): DynamicPriorWeights A detached copy for the allocating CompiledCeresSolver.solve path.

MutableSolveReport

class MutableSolveReport(groupCapacity: Int = 64)

A reusable, caller-owned place for a solve to put its diagnostics: the SolveMetrics of the in-place path, without the allocation.

Declaration Summary
var ok: Boolean Whether the last solve produced a usable answer. Weaker than convergence; see SolveResult.ok.
var totalCost: Double Final cost of the last solve, or NaN when that solve was not asked for metrics.
var groupCount: Int How many group slots the last solve filled.
val groupCosts: DoubleArray Costs indexed by CeresOpcode.code, zero-filled where nothing was reported. Live; the next solve overwrites.
fun cost(opcode: CeresOpcode): Double The last solve's cost for one opcode. Returns 0.0 for an opcode beyond the report's capacity, which is indistinguishable from an opcode that genuinely cost nothing.
fun top(limit: Int = 5): List<Pair<String, Double>> The limit costliest opcode groups, largest first, as metricName to cost — the "what is still fighting" readout. Zero and NaN entries are dropped, so it can be shorter than asked.

ReusableCompiledCeresSolver

interface ReusableCompiledCeresSolver : CompiledCeresSolver

A CompiledCeresSolver that can also solve without allocating: same compiled problem, a per-frame path that reuses its native buffers.

Declaration Summary
val reusableMetricCapacity: Int The largest metric-group array this solver has buffers for. A solve asking for more is refused.
fun solveInPlace(points: MutablePointCloud, dynamicPriorWeights: MutablePriorWeights? = null, options: SolveOptions = SolveOptions(), report: MutableSolveReport? = null): Boolean Solve in place: points is read for the starting positions and overwritten with the answer, and nothing is allocated on the way. Returns whether the result is usable — the same weak guarantee as SolveResult.ok.