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
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
Whether a diagnostic stops the compile.
Constants: ERROR, WARNING.
BriefDiagnostic
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.
BriefComparison
enum class BriefComparison(val symbol: String)
The comparison in an equation.
Constants: EQ, AT_LEAST, AT_MOST.
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
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
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
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
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
BriefProgram
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
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
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
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
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
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
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
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.
GeometryModel
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
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
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
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
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.
LiveGeometryStep
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.
LiveGeometryProgram
A model block held across frames: re-declares, reuses the compiled session where it can, reads the bound values in, solves, writes them back.
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,minandmaxare 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
expressionrejects 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
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
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
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
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
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
Unused by this vocabulary; the constraints that take an axis take an AxisSet.
Constants: X, Y, Z.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
A fluent front end for the constraint classes, taking plain Int point indices instead of PointIds.
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
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
Why the minimizer stopped. Mirrors ceres::TerminationType.
Constants: CONVERGENCE, NO_CONVERGENCE, FAILURE, USER_SUCCESS, USER_FAILURE, UNKNOWN.
CeresSolveSummary
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
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
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
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
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
The answer: always a full cloud, plus whatever diagnostics were asked for.
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
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
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
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
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. |