Skip to content

BriefLowering.kt

src/jvmMain/kotlin/afield/lang/brief/BriefLowering.kt · 1379 lines

@file:afield.runtime.completion.CompletionCategory("Solvers")

package afield.lang.brief

import afield.math.optimization.ceres.AxisSet
import afield.math.optimization.ceres.CeresExpr
import afield.math.optimization.ceres.CeresExprVec
import afield.math.optimization.ceres.CeresLinearSolver
import afield.math.optimization.ceres.FfmCeresBackend
import afield.math.optimization.ceres.Colinear
import afield.math.optimization.ceres.ConstraintScope
import afield.math.optimization.ceres.c
import afield.math.optimization.ceres.GeometryModel
import afield.math.optimization.ceres.GeometryModelScope
import afield.math.optimization.ceres.GeometrySolveSession
import afield.math.optimization.ceres.InteractionPolicy
import afield.math.optimization.ceres.LineRef
import afield.math.optimization.ceres.PanelRef
import afield.math.optimization.ceres.PlaneRef
import afield.math.optimization.ceres.PointId
import afield.math.optimization.ceres.PointRef
import afield.math.optimization.ceres.RobustLoss
import afield.math.optimization.ceres.SolveOptions
import afield.math.optimization.ceres.Vec3
import afield.math.optimization.ceres.geometryModel
import afield.math.linalg.Vector3f
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.acos
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sqrt

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.

The compiled program

class BriefProgram internal constructor

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.

compileBrief returns one of these or throws BriefException; a program that compiled may still carry warnings in diagnostics.

class BriefProgram internal constructor(
    /** The program text as compiled, kept so a diagnostic's line and column still mean something. */
    val source: String,
    /** The parse this was resolved from, for a caller that wants the statements rather than a solve. */
    val syntax: BriefSyntax,
    /** Everything the lexer, parser and resolver said. Errors are impossible here, because an error throws. */
    val diagnostics: List<BriefDiagnostic>,
    /** Every point, in the order they became parameters — declared first, then invented. */
    val pointNames: List<String>,
    /** Points a relation named that nothing declared, seeded on the golden-angle circle. */
    val inventedPoints: List<String>,
    internal val seeds: Map<String, Vec3>,
    internal val anchors: List<AxisAnchor>,
) {

internal class AxisAnchor(val name: String, val at: Vec3, val axes: AxisSet)

A hidden point that turns p.z = 3 into a relation between two points, which the solver has.

val constraintCount: Int get() = syntax.constraints.size

How many statements the brief wrote, which is not how many residuals they lower to.

val warnings: List<BriefDiagnostic> get()

The subset of diagnostics worth showing a reader whose brief compiled.

    val warnings: List<BriefDiagnostic> get() = diagnostics.filter { it.severity == BriefSeverity.WARNING }

val statementLabels: List<String> get() = syntax.constraints.map { it.label }

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.

    override fun toString(): String =
        "BriefProgram(${pointNames.size} point(s), $constraintCount constraint(s)" +
            if (inventedPoints.isEmpty()) ")" else ", invented ${inventedPoints.joinToString(", ")})"
}

fun compileBrief(source: String): BriefProgram

Compile a brief. A pure function of text: no Box, no output, no server, no native library.

A leading #brief line is optional and is skipped as a comment either way, so the same string works as an embedded region and as a loose file. Throws BriefException carrying every error, because a brief with four misspellings should report four.

fun compileBrief(source: String): BriefProgram {
    val lexer = BriefLexer(source)
    val toks = lexer.lex()
    val syntax = BriefParser(toks).parse()
    val resolver = BriefResolver(source, syntax, lexer.diagnostics() + syntax.diagnostics)
    return resolver.resolve()
}

fun checkBrief(source: String): List<BriefDiagnostic>

compileBrief without the throw: the diagnostics, whether or not the program is usable.

fun checkBrief(source: String): List<BriefDiagnostic> =
    try {
        compileBrief(source).diagnostics
    } catch (e: BriefException) {
        e.diagnostics
    }

Resolution

internal class BriefResolver

Two passes and a check.

Pass one registers every declaration, so a constraint on line 3 can name a panel declared on line 30 — forward reference is not a feature bolted onto the grammar, it is the consequence of resolving after parsing rather than during. Pass two walks the constraints, resolving each name against the namespace its position demands and inventing points that nobody declared. The check is contradictions, which is the only place the notation second-guesses the author.

internal class BriefResolver(
    private val source: String,
    private val syntax: BriefSyntax,
    private val inherited: List<BriefDiagnostic>,
) {
    private val diagnostics = inherited.toMutableList()

    private val points = linkedMapOf<String, Vec3>()
    private val lines = linkedMapOf<String, BriefDeclaration.Line>()
    private val planes = linkedMapOf<String, BriefDeclaration.Plane>()
    private val panels = linkedMapOf<String, BriefDeclaration.Panel>()
    private val invented = mutableListOf<String>()
    private val anchors = mutableListOf<BriefProgram.AxisAnchor>()

fun resolve(): BriefProgram

body — 76 lines, from line 138
    fun resolve(): BriefProgram {
        declarations()
        constraints()
        contradictions()

        if (diagnostics.any { it.severity == BriefSeverity.ERROR }) throw BriefException(diagnostics)

        return BriefProgram(
            source = source,
            syntax = syntax,
            diagnostics = diagnostics.toList(),
            pointNames = points.keys.toList() + anchors.map { it.name },
            inventedPoints = invented.toList(),
            seeds = points.toMap() + anchors.associate { it.name to it.at },
            anchors = anchors.toList(),
        )
    }

    private fun err(at: BriefSpan, message: String, hint: String? = null) {
        diagnostics += BriefDiagnostic(BriefSeverity.ERROR, at.line, at.column, at.length, message, hint)
    }

    private fun warn(at: BriefSpan, message: String, hint: String? = null) {
        diagnostics += BriefDiagnostic(BriefSeverity.WARNING, at.line, at.column, at.length, message, hint)
    }

    private fun declaredNames(): Set<String> = points.keys + lines.keys + planes.keys + panels.keys

    private fun kindOf(name: String): BriefElementKind? = when (name) {
        in points -> BriefElementKind.POINT
        in lines -> BriefElementKind.LINE
        in planes -> BriefElementKind.PLANE
        in panels -> BriefElementKind.PANEL
        else -> null
    }

    private fun declarations() {
        syntax.declarations.forEach { declaration ->
            val existing = kindOf(declaration.name.text)
            if (existing != null) {
                err(
                    declaration.name.at,
                    "'${declaration.name.text}' is already declared as a ${existing.noun}",
                    "every element name is used once, across all four kinds",
                )
                return@forEach
            }
            when (declaration) {
                is BriefDeclaration.Point -> points[declaration.name.text] =
                    Vec3(declaration.x, declaration.y, declaration.z)
                is BriefDeclaration.Line -> lines[declaration.name.text] = declaration
                is BriefDeclaration.Plane -> planes[declaration.name.text] = declaration
                is BriefDeclaration.Panel -> panels[declaration.name.text] = declaration
            }
        }
        // Second pass over the compound declarations' *members*, so `line spine from a to b` may
        // precede `point a at …` and may also invent both of its endpoints.
        lines.values.forEach { point(it.a); point(it.b) }
        planes.values.forEach { point(it.a); point(it.b); point(it.c) }
        panels.values.forEach { point(it.a); point(it.b); point(it.c); point(it.d) }
        lines.values.forEach {
            if (it.a.text == it.b.text) err(it.b.at, "a line's endpoints must be two different points")
        }
        planes.values.forEach { plane ->
            val names = listOf(plane.a, plane.b, plane.c)
            if (names.map { it.text }.distinct().size != 3) {
                err(plane.name.at, "a plane needs three different points")
            }
        }
        panels.values.forEach { panel ->
            val names = listOf(panel.a, panel.b, panel.c, panel.d)
            if (names.map { it.text }.distinct().size != 4) {
                err(panel.name.at, "a panel needs four different corners")
            }
        }
    }

Resolve a name in point position, declaring it on use if nothing did.

The seed is a golden-angle point on a widening spiral, which is the whole reason this is safe: every invented point is distinct from every other, so a brief made only of relations starts from a spread the solver can move rather than a coincident pile whose Jacobian is rank-deficient. The radius grows as sqrt(k+1) so the spread stays roughly uniform in area.

source — 156 lines, from line 223
    private fun point(name: BriefName): String {
        when (val kind = kindOf(name.text)) {
            BriefElementKind.POINT -> return name.text
            null -> Unit
            else -> {
                err(
                    name.at, "'${name.text}' is a ${kind.noun}, and a point was wanted here",
                    "the points are ${points.keys.sorted().joinToString(", ").ifEmpty { "none yet" }}",
                )
                return name.text
            }
        }
        val k = invented.size
        val theta = k * GOLDEN_ANGLE
        val radius = sqrt((k + 1).toDouble())
        points[name.text] = Vec3(radius * cos(theta), radius * kotlin.math.sin(theta), 0.0)
        invented += name.text
        return name.text
    }

    private fun expect(name: BriefName, kind: BriefElementKind): Boolean {
        if (kind == BriefElementKind.POINT) {
            point(name)
            return kindOf(name.text) == BriefElementKind.POINT
        }
        val actual = kindOf(name.text)
        if (actual == kind) return true
        if (actual == null) {
            err(
                name.at, "no ${kind.noun} called '${name.text}'",
                didYouMean(name.text, namesOf(kind))
                    ?: "declare it: ${declarationHint(kind, name.text)}",
            )
        } else {
            err(name.at, "'${name.text}' is a ${actual.noun}, and a ${kind.noun} was wanted here")
        }
        return false
    }

    private fun namesOf(kind: BriefElementKind): Set<String> = when (kind) {
        BriefElementKind.POINT -> points.keys
        BriefElementKind.LINE -> lines.keys
        BriefElementKind.PLANE -> planes.keys
        BriefElementKind.PANEL -> panels.keys
    }

    private fun declarationHint(kind: BriefElementKind, name: String): String = when (kind) {
        BriefElementKind.POINT -> "point $name at (0, 0)"
        BriefElementKind.LINE -> "line $name from a to b"
        BriefElementKind.PLANE -> "plane $name through a b c"
        BriefElementKind.PANEL -> "panel $name corners a b c d"
    }

    private fun constraints() {
        syntax.constraints.forEach { constraint ->
            when (val relation = constraint.relation) {
                is BriefRelation.Equation -> equation(constraint, relation)
                is BriefRelation.Predicate -> predicate(constraint, relation)
            }
        }
    }

    private fun termKind(term: BriefTerm): String = when (term) {
        is BriefTerm.Literal -> "a number"
        is BriefTerm.Property -> term.property
        is BriefTerm.Call -> term.function
    }

    private fun equation(constraint: BriefConstraint, equation: BriefRelation.Equation) {
        val left = equation.left
        val right = equation.right
        checkTerm(left)
        checkTerm(right)
        if (left is BriefTerm.Literal && right is BriefTerm.Literal) {
            err(equation.at, "an equation between two numbers constrains nothing")
            return
        }
        // Normalise so the element side is on the left, which is what the lowering assumes.
        val (element, other) = if (left is BriefTerm.Literal) right to left else left to right
        if (other is BriefTerm.Literal) {
            targetSanity(constraint, element, other)
            return
        }
        // element = element: only a matching pair of the same property is a relation the solver has.
        val a = termKind(element)
        val b = termKind(other)
        if (a != b) {
            err(
                equation.at, "brief cannot equate a '$a' to a '$b'",
                "the solver has no residual for it — state a number on one side",
            )
            return
        }
        when (a) {
            "length", "x", "y", "z", "distance" -> Unit
            // No opcode equates two areas, so this one is a runtime residual, and Ceres will not accept
            // a residual block that names one point twice. Two panels sharing a corner is therefore a
            // real refusal rather than a missing feature, and it is worth saying which corner.
            "area" -> {
                val left = (element as BriefTerm.Property).element.text
                val right = (other as BriefTerm.Property).element.text
                val a1 = panels[left]
                val b1 = panels[right]
                if (a1 != null && b1 != null) {
                    val shared = listOf(a1.a, a1.b, a1.c, a1.d).map { it.text }
                        .intersect(listOf(b1.a, b1.b, b1.c, b1.d).map { it.text }.toSet())
                    if (shared.isNotEmpty()) {
                        err(
                            equation.at,
                            "'$left' and '$right' share the corner(s) ${shared.sorted().joinToString(", ")}",
                            "an equal-area residual reads all eight corners at once and Ceres will not " +
                                "let one residual name a point twice — give each panel a number instead",
                        )
                    }
                }
            }
            else -> err(
                equation.at, "brief cannot equate two '$a' terms",
                "state a number on one side",
            )
        }
        if (equation.comparison != BriefComparison.EQ && a != "length" && a != "distance") {
            err(equation.at, "'${equation.comparison.symbol}' is only available for a length")
        }
    }

    private fun checkTerm(term: BriefTerm) {
        when (term) {
            is BriefTerm.Literal -> Unit
            is BriefTerm.Property -> when (term.property) {
                "length" -> expect(term.element, BriefElementKind.LINE)
                "area" -> expect(term.element, BriefElementKind.PANEL)
                "x", "y", "z" -> expect(term.element, BriefElementKind.POINT)
            }
            is BriefTerm.Call -> {
                val arity = CALL_ARITY.getValue(term.function)
                if (term.arguments.size != arity) {
                    err(
                        term.at,
                        "'${term.function}' takes $arity argument(s), not ${term.arguments.size}",
                    )
                    return
                }
                when (term.function) {
                    "distance" -> term.arguments.forEach { expect(it, BriefElementKind.POINT) }
                    "angle", "cos-angle", "dot", "raw-dot" ->
                        term.arguments.forEach { expect(it, BriefElementKind.LINE) }
                    "dihedral" -> {
                        expect(term.arguments[0], BriefElementKind.LINE)
                        expect(term.arguments[1], BriefElementKind.POINT)
                        expect(term.arguments[2], BriefElementKind.POINT)
                    }
                }
            }
        }
    }

Complain about a number that cannot be what it says: a negative length, a cosine past one.

source — 178 lines, from line 381
    private fun targetSanity(constraint: BriefConstraint, element: BriefTerm, literal: BriefTerm.Literal) {
        val kind = termKind(element)
        when (kind) {
            "length", "distance", "area" ->
                if (literal.value < 0.0) {
                    err(literal.at, "a $kind cannot be negative")
                } else if (literal.value == 0.0 && kind != "area") {
                    warn(
                        literal.at, "a $kind of zero welds the two points together",
                        "write 'coincident' if that is what you mean",
                    )
                }
            "cos-angle", "dot", "dihedral" ->
                if (abs(literal.value) > 1.0) {
                    err(literal.at, "a cosine has to be between -1 and 1, not ${trimNumber(literal.value)}")
                }
            "angle" -> {
                if (!literal.radians && abs(literal.value) > PI * 2) {
                    warn(
                        literal.at, "'${trimNumber(literal.value)}' is being read as radians",
                        "write '${trimNumber(literal.value)}deg' for degrees",
                    )
                }
                if (literal.value < 0.0 || literal.value > PI) {
                    warn(
                        literal.at, "an angle target is a cosine underneath, so it is unsigned and " +
                            "folds into 0..180deg",
                    )
                }
            }
        }
        if (kind in setOf("x", "y", "z")) {
            val axis = when (kind) {
                "x" -> AxisSet.X
                "y" -> AxisSet.Y
                else -> AxisSet.Z
            }
            val subject = (element as BriefTerm.Property).element
            val seed = points[subject.text] ?: Vec3(0.0, 0.0, 0.0)
            val at = when (axis) {
                AxisSet.X -> Vec3(literal.value, seed.y, seed.z)
                AxisSet.Y -> Vec3(seed.x, literal.value, seed.z)
                else -> Vec3(seed.x, seed.y, literal.value)
            }
            anchors += BriefProgram.AxisAnchor("_at:${subject.text}.$kind:${anchors.size}", at, axis)
        }
        if (constraint.strength == 0.0) {
            warn(constraint.at, "a strength of 0 makes this statement free, so it says nothing")
        }
    }

    private fun predicate(constraint: BriefConstraint, predicate: BriefRelation.Predicate) {
        val subjects = predicate.subjects
        val objects = predicate.objects
        when (predicate.verb) {
            "parallel", "perpendicular", "same-vector" -> {
                requireSubjects(predicate, 1, 1, BriefElementKind.LINE)
                expect(objects[0], BriefElementKind.LINE)
                if (subjects.firstOrNull()?.text == objects[0].text) {
                    err(predicate.at, "'${predicate.verb}' between a line and itself says nothing")
                }
            }
            "along" -> {
                requireSubjects(predicate, 1, 0, BriefElementKind.LINE)
                val v = predicate.vector ?: return
                if (v.first == 0.0 && v.second == 0.0 && v.third == 0.0) {
                    err(predicate.at, "'along' needs a direction, and (0, 0, 0) is not one")
                }
            }
            "toward", "vanishes-at" -> {
                requireSubjects(predicate, 1, 1, BriefElementKind.LINE)
                expect(objects[0], BriefElementKind.POINT)
            }
            "faces" -> {
                requireSubjects(predicate, 1, 0, BriefElementKind.PLANE)
                val v = predicate.vector ?: return
                if (v.first == 0.0 && v.second == 0.0 && v.third == 0.0) {
                    err(predicate.at, "'faces' needs a direction, and (0, 0, 0) is not one")
                }
                predicate.number?.let {
                    if (abs(it) > 1.0) err(predicate.at, "a target cosine has to be between -1 and 1")
                }
            }
            "planar" -> {
                if (subjects.size == 1 && kindOf(subjects[0].text) == BriefElementKind.PANEL) return
                if (subjects.size < 3) {
                    err(
                        predicate.at, "'planar' needs at least three points, or one panel",
                        "it is only a constraint past three",
                    )
                    return
                }
                subjects.forEach { expect(it, BriefElementKind.POINT) }
                if (subjects.size == 3) {
                    warn(predicate.at, "three points are always coplanar, so this costs nothing")
                }
            }
            "colinear" -> {
                if (subjects.size < 3) {
                    err(predicate.at, "'colinear' needs at least three points")
                    return
                }
                subjects.forEach { expect(it, BriefElementKind.POINT) }
            }
            "level", "same-x", "same-y", "same-z", "same-xy", "same-xz", "same-yz" -> {
                if (subjects.size < 2) {
                    err(predicate.at, "'${predicate.verb}' needs at least two points")
                    return
                }
                subjects.forEach { expect(it, BriefElementKind.POINT) }
            }
            "on", "inside" -> {
                requireSubjects(predicate, 1, 1, BriefElementKind.POINT)
                expect(objects[0], BriefElementKind.PLANE)
            }
            "coincident" -> {
                requireSubjects(predicate, 1, 1, BriefElementKind.POINT)
                expect(objects[0], BriefElementKind.POINT)
                if (subjects.firstOrNull()?.text == objects[0].text) {
                    err(predicate.at, "a point is already coincident with itself")
                }
            }
            "mirrors" -> {
                requireSubjects(predicate, 1, 2, BriefElementKind.POINT)
                expect(objects[0], BriefElementKind.POINT)
                expect(objects[1], BriefElementKind.PLANE)
            }
            "at" -> {
                requireSubjects(predicate, 1, 2, BriefElementKind.POINT)
                objects.forEach { expect(it, BriefElementKind.POINT) }
                val all = (subjects + objects).map { it.text }
                if (all.distinct().size != all.size) {
                    err(
                        predicate.at, "'at … along' needs three different points",
                        "one residual block may not name the same point twice",
                    )
                }
                val f = predicate.number
                if (f != null && (f < 0.0 || f > 1.0)) {
                    warn(
                        predicate.at, "a fraction outside 0..1 puts the point beyond the span",
                        "which is legal, and rarely meant",
                    )
                }
            }
            "fix", "hold" -> {
                if (subjects.isEmpty()) {
                    err(predicate.at, "'${predicate.verb}' needs at least one point")
                    return
                }
                subjects.forEach { expect(it, BriefElementKind.POINT) }
            }
            "all-loose", "all-above-ground" -> Unit
            else -> err(predicate.at, "'${predicate.verb}' is not lowered yet")
        }
        if (constraint.strength == 0.0) {
            warn(constraint.at, "a strength of 0 makes this statement free, so it says nothing")
        }
    }

    private fun requireSubjects(
        predicate: BriefRelation.Predicate,
        subjects: Int,
        objects: Int,
        subjectKind: BriefElementKind,
    ) {
        if (predicate.subjects.size != subjects) {
            err(
                predicate.at,
                "'${predicate.verb}' takes $subjects ${subjectKind.noun}(s) before it, " +
                    "not ${predicate.subjects.size}",
            )
        }
        if (predicate.objects.size != objects) {
            err(predicate.at, "'${predicate.verb}' takes $objects thing(s) after it, not ${predicate.objects.size}")
        }
        predicate.subjects.forEach { expect(it, subjectKind) }
    }

The only place the notation argues with the author: two statements that cannot both hold.

Deliberately narrow. A least-squares system is supposed to absorb disagreement — that is what the weights are for — so flagging every pair that pulls in different directions would be noise. What is flagged is a pair that agrees on the statement and disagrees on the number, where both are at insist or above: at that strength the author has said "this one wins" twice, and only one of them can.

    private fun contradictions() {
        val hard = syntax.constraints.filter { it.strength >= BriefStrength.INSIST.weight }
        val byTarget = mutableMapOf<String, MutableList<Pair<BriefConstraint, Double>>>()
        hard.forEach { constraint ->
            val relation = constraint.relation as? BriefRelation.Equation ?: return@forEach
            if (relation.comparison != BriefComparison.EQ) return@forEach
            val literal = (relation.right as? BriefTerm.Literal) ?: (relation.left as? BriefTerm.Literal)
                ?: return@forEach
            val element = if (relation.left is BriefTerm.Literal) relation.right else relation.left
            byTarget.getOrPut(describeTerm(element)) { mutableListOf() } += constraint to literal.value
        }
        byTarget.forEach { (target, statements) ->
            val values = statements.map { it.second }.distinct()
            if (values.size > 1) {
                val where = statements.first().first
                err(
                    where.at,
                    "'$target' is insisted to be both ${values.sorted().joinToString(" and ") { trimNumber(it) }}",
                    "lines ${statements.map { it.first.at.line }.sorted().joinToString(", ")} — " +
                        "lower one of them, or the solver will split the difference and satisfy neither",
                )
            }
        }
    }

    private companion object {
        /** 2.399963… radians — the angle that makes successive points maximally unaligned. */
        const val GOLDEN_ANGLE = 2.399963229728653

        val CALL_ARITY = mapOf(
            "distance" to 2, "angle" to 2, "cos-angle" to 2, "dot" to 2, "raw-dot" to 2, "dihedral" to 3,
        )
    }
}

Lowering to the Kotlin DSL

internal class BriefEnv

The resolved handles a lowering walks against.

internal class BriefEnv(
    val points: Map<String, PointRef>,
    val lines: Map<String, LineRef>,
    val planes: Map<String, PlaneRef>,
    val panels: Map<String, PanelRef>,
) {

fun point(name: BriefName): PointRef

    fun point(name: BriefName): PointRef = points[name.text] ?: error("unresolved point ${name.text}")

fun line(name: BriefName): LineRef = lines[name.text] ?: error("unresolved line ${name.text}")

fun plane(name: BriefName): PlaneRef

    fun plane(name: BriefName): PlaneRef = planes[name.text] ?: error("unresolved plane ${name.text}")

fun panel(name: BriefName): PanelRef

    fun panel(name: BriefName): PanelRef = panels[name.text] ?: error("unresolved panel ${name.text}")
}

fun BriefProgram.toGeometryModel(bindings: Map<String, Vector3f> = emptyMap()): GeometryModel

Build the GeometryModel this brief describes.

bindings is the host seam and the answer to "a Route-A notation cannot see Kotlin values": a name in this map becomes a GeometryModelScope.bindPoint rather than a literal point, so the solver reads the Vector3f on the way in and writes the answer back into it on the way out. That is how a brief constrains geometry that lives outside it, and it is the reason the notation is embedded in Kotlin rather than promoted to a box language.

fun BriefProgram.toGeometryModel(bindings: Map<String, Vector3f> = emptyMap()): GeometryModel {
    val unknown = bindings.keys - pointNames.toSet()
    require(unknown.isEmpty()) {
        "brief has no point(s) named ${unknown.sorted().joinToString(", ")}; it declares " +
            pointNames.filterNot { it.startsWith("_at:") }.joinToString(", ")
    }
    return geometryModel {
        val env = declare(this, this@toGeometryModel, bindings)
        syntax.settle?.let { settle ->
            if (settle.weight == null) noDefaultInteractionPriors() else defaultInteractionPriors(settle.weight)
        }
        // Plain least squares unless the brief asks otherwise. See BriefRobust: the underlying
        // CeresProgram defaults to Huber(1e-3), under which a weight ratio decides a fight outright
        // instead of splitting it, and a notation built on a strength ladder cannot mean that.
        robustLoss(
            syntax.robust?.let { RobustLoss(lossKind(it.kind), it.scale) } ?: RobustLoss.NONE
        )
        constraints {
            anchors.forEach { anchor ->
                // Hold the hidden anchor overwhelmingly harder than any statement can be written, so
                // `p.z = 3` behaves as a relation to the number rather than a negotiation with it. All
                // three axes, not just the constrained one: the other two are read by nothing, and a
                // parameter with an identically-zero Jacobian column is exactly the free-wandering
                // direction `settle none` exists to forbid.
                lock(listOf(env.point(BriefName(anchor.name, BriefSpan(0, 0, 0)))), AxisSet.XYZ, ANCHOR_WEIGHT)
            }
            syntax.constraints.forEach { emit(this, env, it) }
        }
    }
}

1e9 — three decades above demand, so an axis anchor is never the thing that loses.

source — 61 lines, from line 662
private const val ANCHOR_WEIGHT = 1e9

private fun lossKind(name: String): RobustLoss.Kind = when (name) {
    "none" -> RobustLoss.Kind.NONE
    "huber" -> RobustLoss.Kind.HUBER
    "soft-l1" -> RobustLoss.Kind.SOFT_L_ONE
    "cauchy" -> RobustLoss.Kind.CAUCHY
    else -> error("unhandled loss $name")
}

private fun declare(
    scope: GeometryModelScope,
    program: BriefProgram,
    bindings: Map<String, Vector3f>,
): BriefEnv {
    val points = linkedMapOf<String, PointRef>()
    program.pointNames.forEach { name ->
        val seed = program.seeds.getValue(name)
        val bound = bindings[name]
        points[name] = if (bound == null) {
            scope.point(seed.x, seed.y, seed.z, name)
        } else {
            scope.bindPoint(name = name, value = bound)
        }
    }
    val lines = linkedMapOf<String, LineRef>()
    val planes = linkedMapOf<String, PlaneRef>()
    val panels = linkedMapOf<String, PanelRef>()
    program.syntax.declarations.forEach { declaration ->
        when (declaration) {
            is BriefDeclaration.Point -> Unit
            is BriefDeclaration.Line -> lines[declaration.name.text] = scope.line(
                points.getValue(declaration.a.text), points.getValue(declaration.b.text), declaration.name.text,
            )
            is BriefDeclaration.Plane -> planes[declaration.name.text] = scope.plane(
                points.getValue(declaration.a.text),
                points.getValue(declaration.b.text),
                points.getValue(declaration.c.text),
                declaration.name.text,
            )
            is BriefDeclaration.Panel -> panels[declaration.name.text] = scope.panel(
                points.getValue(declaration.a.text),
                points.getValue(declaration.b.text),
                points.getValue(declaration.c.text),
                points.getValue(declaration.d.text),
                declaration.name.text,
            )
        }
    }
    return BriefEnv(points, lines, planes, panels)
}

private fun axisSet(name: String): AxisSet = when (name.lowercase()) {
    "x" -> AxisSet.X
    "y" -> AxisSet.Y
    "z" -> AxisSet.Z
    "xy" -> AxisSet.XY
    "xz" -> AxisSet.XZ
    "yz" -> AxisSet.YZ
    else -> AxisSet.XYZ
}

One statement, lowered.

Every branch is a call into ConstraintScope, which is the whole claim of this package: the notation adds no residual of its own, so a brief cannot mean anything the Kotlin DSL could not already say. BriefCoverageTest is what keeps that true in the other direction.

source — 93 lines, from line 731
private fun emit(scope: ConstraintScope, env: BriefEnv, constraint: BriefConstraint) {
    val w = constraint.strength
    when (val relation = constraint.relation) {
        is BriefRelation.Equation -> emitEquation(scope, env, relation, w)
        is BriefRelation.Predicate -> emitPredicate(scope, env, relation, w)
    }
}

private fun emitEquation(scope: ConstraintScope, env: BriefEnv, equation: BriefRelation.Equation, w: Double) {
    val flipped = equation.left is BriefTerm.Literal
    val element = if (flipped) equation.right else equation.left
    val other = if (flipped) equation.left else equation.right
    val comparison = if (!flipped) equation.comparison else when (equation.comparison) {
        BriefComparison.EQ -> BriefComparison.EQ
        BriefComparison.AT_LEAST -> BriefComparison.AT_MOST
        BriefComparison.AT_MOST -> BriefComparison.AT_LEAST
    }
    with(scope) {
        if (other is BriefTerm.Literal) {
            val target = other.value
            when (element) {
                is BriefTerm.Property -> when (element.property) {
                    "length" -> {
                        val line = env.line(element.element)
                        when (comparison) {
                            BriefComparison.EQ -> line.length(target, w)
                            BriefComparison.AT_LEAST -> line.minLength(target, w)
                            BriefComparison.AT_MOST -> line.maxLength(target, w)
                        }
                    }
                    "area" -> env.panel(element.element).area(target, w)
                    "x", "y", "z" -> {
                        val axis = axisSet(element.property)
                        val subject = env.point(element.element)
                        val anchor = env.points.keys.first { it.startsWith("_at:${element.element.text}.${element.property}:") }
                        same(listOf(subject, env.points.getValue(anchor)), axis, w)
                    }
                    else -> error("unhandled property ${element.property}")
                }
                is BriefTerm.Call -> when (element.function) {
                    "distance" -> {
                        val line = line(env.point(element.arguments[0]), env.point(element.arguments[1]))
                        when (comparison) {
                            BriefComparison.EQ -> line.length(target, w)
                            BriefComparison.AT_LEAST -> line.minLength(target, w)
                            BriefComparison.AT_MOST -> line.maxLength(target, w)
                        }
                    }
                    "angle" -> env.line(element.arguments[0])
                        .angleRadiansTo(env.line(element.arguments[1]), target, w)
                    "cos-angle" -> env.line(element.arguments[0])
                        .angleCosTo(env.line(element.arguments[1]), target, w)
                    "dot" -> (env.line(element.arguments[0]) dot env.line(element.arguments[1])) ceq target
                    "raw-dot" -> (env.line(element.arguments[0]) dot env.line(element.arguments[1])).raw() ceq target
                    "dihedral" -> dihedral(
                        env.line(element.arguments[0]),
                        env.point(element.arguments[1]),
                        env.point(element.arguments[2]),
                        target,
                        w,
                    )
                    else -> error("unhandled function ${element.function}")
                }
                is BriefTerm.Literal -> error("two literals should have been refused")
            }
            return
        }
        // element = element
        when {
            element is BriefTerm.Property && other is BriefTerm.Property && element.property == "length" ->
                env.line(element.element).sameLengthAs(env.line(other.element), w)
            element is BriefTerm.Property && other is BriefTerm.Property && element.property == "area" -> {
                val p = env.panel(element.element)
                val q = env.panel(other.element)
                expression(p.points + q.points, w) { corners ->
                    listOf(quadAreaExpr(corners.take(4)) - quadAreaExpr(corners.drop(4)))
                }
            }
            element is BriefTerm.Property && other is BriefTerm.Property ->
                same(listOf(env.point(element.element), env.point(other.element)), axisSet(element.property), w)
            element is BriefTerm.Call && other is BriefTerm.Call && element.function == "distance" ->
                line(env.point(element.arguments[0]), env.point(element.arguments[1]))
                    .sameLengthAs(line(env.point(other.arguments[0]), env.point(other.arguments[1])), w)
            element is BriefTerm.Call && other is BriefTerm.Property ->
                line(env.point(element.arguments[0]), env.point(element.arguments[1]))
                    .sameLengthAs(env.line(other.element), w)
            element is BriefTerm.Property && other is BriefTerm.Call ->
                env.line(element.element)
                    .sameLengthAs(line(env.point(other.arguments[0]), env.point(other.arguments[1])), w)
            else -> error("unhandled equation between ${describeTerm(element)} and ${describeTerm(other)}")
        }
    }
}

A quadrilateral's area as a residual expression, triangulated on the a-c diagonal so it agrees with PanelArea — the constraint a brief reaches when it states an area as a number. The two spellings of "area" have to be the same function or a brief could satisfy one and not the other.

source — 71 lines, from line 830
private fun quadAreaExpr(corners: List<CeresExprVec>): CeresExpr {
    val (a, b, c, d) = corners
    return ((b - a) cross (c - a)).length() * 0.5.c + ((c - a) cross (d - a)).length() * 0.5.c
}

private fun emitPredicate(scope: ConstraintScope, env: BriefEnv, predicate: BriefRelation.Predicate, w: Double) {
    with(scope) {
        when (predicate.verb) {
            "parallel" -> env.line(predicate.subjects[0]).parallelTo(env.line(predicate.objects[0]), w)
            "perpendicular" -> env.line(predicate.subjects[0]).perpendicularTo(env.line(predicate.objects[0]), w)
            "same-vector" -> env.line(predicate.subjects[0]).sameVectorAs(env.line(predicate.objects[0]), w)
            "along" -> {
                val v = predicate.vector!!
                env.line(predicate.subjects[0]).direction(Vec3(v.first, v.second, v.third), w)
            }
            "toward" -> env.line(predicate.subjects[0]).toward(env.point(predicate.objects[0]), w)
            "vanishes-at" -> env.line(predicate.subjects[0]).vanishesAt(env.point(predicate.objects[0]), w)
            "faces" -> {
                val v = predicate.vector!!
                env.plane(predicate.subjects[0])
                    .normal(Vec3(v.first, v.second, v.third), predicate.number ?: 1.0, w)
            }
            "planar" ->
                if (predicate.subjects.size == 1 && predicate.subjects[0].text in env.panels) {
                    env.panel(predicate.subjects[0]).planar(w)
                } else {
                    predicate.subjects.map { env.point(it) }.planar(w)
                }
            "colinear" -> {
                val all = predicate.subjects.map { env.point(it) }
                add(Colinear(all[0].id, all[1].id, all.drop(2).map { it.id }, w))
            }
            "level", "same-z" -> same(predicate.subjects.map { env.point(it) }, AxisSet.Z, w)
            "same-x" -> same(predicate.subjects.map { env.point(it) }, AxisSet.X, w)
            "same-y" -> same(predicate.subjects.map { env.point(it) }, AxisSet.Y, w)
            "same-xy" -> same(predicate.subjects.map { env.point(it) }, AxisSet.XY, w)
            "same-xz" -> same(predicate.subjects.map { env.point(it) }, AxisSet.XZ, w)
            "same-yz" -> same(predicate.subjects.map { env.point(it) }, AxisSet.YZ, w)
            "on" -> env.point(predicate.subjects[0])
                .on(env.plane(predicate.objects[0]), predicate.number ?: 0.0, w)
            "inside" -> env.point(predicate.subjects[0])
                .inside(env.plane(predicate.objects[0]), predicate.number ?: 0.0, w)
            "coincident" -> sameXYZ(env.point(predicate.subjects[0]), env.point(predicate.objects[0]), w)
            "mirrors" -> mirror(
                env.point(predicate.subjects[0]),
                env.point(predicate.objects[0]),
                env.plane(predicate.objects[1]),
                w,
            )
            // Three residuals through `ConstraintScope.expression`, not `PointExpr.ceq`. `ceq`
            // materialises a trace point the minimiser can move instead of the geometry, and fixes the
            // weight at 1.0, so a `prefer`/`insist` distinction would have been silently lost — which
            // is exactly the thing this notation exists to make audible.
            "at" -> {
                val f = predicate.number!!
                val p = env.point(predicate.subjects[0])
                val u = env.point(predicate.objects[0])
                val v = env.point(predicate.objects[1])
                expression(listOf(p, u, v), w) { (pe, ue, ve) ->
                    val want = ue * (1.0 - f) + ve * f
                    listOf(pe.x - want.x, pe.y - want.y, pe.z - want.z)
                }
            }
            "fix" -> lock(predicate.subjects.map { env.point(it) }, axisSet(predicate.axes ?: "xyz"), w)
            "hold" -> predicate.subjects.forEach { env.point(it).weak(axisSet(predicate.axes ?: "xyz"), w) }
            "all-loose" -> weakAll(w)
            "all-above-ground" -> noNegativeZAll(w)
            else -> error("unhandled predicate ${predicate.verb}")
        }
    }
}

Solving, and the per-statement residual report

data class BriefResidual

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

data class BriefResidual(
    /** The statement, reconstructed from the parse, as in `insist distance(west, spine) = 6`. */
    val label: String,
    /** The 1-based source line the statement was written on. */
    val line: Int,
    /** The weight the statement was written with, the `strength` half of [cost]. */
    val strength: Double,
    /** The statement's residual in its natural units, or NaN where there is no independent one. */
    val residual: Double,
) {

val cost: Double get() = if (residual.isNaN()) Double.NaN else strength * residual * residual

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.

    override fun toString(): String =
        "line $line  ${"%.6g".format(residual)}  (${label})"
}

class BriefSolution internal constructor

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

The residuals are computed here, in Kotlin, from the solved point positions — not read out of Ceres. That is the point of them. MutableSolveReport.groupCosts is indexed by opcode, so a model with six length constraints reports one number for all six, and the question a brief's author has — "which of my statements is still fighting?" — cannot be answered from it. Measuring independently also means the report is a check on the solve rather than a restatement of it.

class BriefSolution internal constructor(
    /** Whether the solve converged. The points are populated either way. */
    val ok: Boolean,
    /** Ceres's own final cost, or NaN when metrics were not requested. */
    val totalCost: Double,
    /** Every point by the name the brief used, hidden axis anchors included. */
    val points: Map<String, Vec3>,
    /** One entry per statement, in source order. [worst] and [report] sort them by cost. */
    val residuals: List<BriefResidual>,
) {

operator fun get(name: String): Vec3

A point by name, which throws and lists the names it does have rather than returning null.

    operator fun get(name: String): Vec3 =
        points[name] ?: error("no point '$name' in this solution; it has ${points.keys.sorted()}")

val worst: BriefResidual? get()

The statement with the largest weighted cost — "what is still fighting".

    val worst: BriefResidual? get() = residuals.filterNot { it.cost.isNaN() }.maxByOrNull { it.cost }

val statedCost: Double get() = residuals.filterNot { it.cost.isNaN() }.sumOf { it.cost }

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.

    fun report(limit: Int = 8): String = buildString {
        append(if (ok) "solved" else "NOT SOLVED").append(", stated cost ")
        append("%.6g".format(statedCost)).append('\n')
        points.filterKeys { !it.startsWith("_") }.forEach { (name, at) ->
            append("  ").append(name).append(" = (")
            append("%.4f, %.4f, %.4f".format(at.x, at.y, at.z)).append(")\n")
        }
        residuals.filterNot { it.cost.isNaN() }.sortedByDescending { it.cost }.take(limit).forEach {
            append("  residual ").append("%- 12.6g".format(it.residual)).append("  ").append(it.label)
            append("  [line ").append(it.line).append("]\n")
        }
    }
}

fun settleOptions(iterations: Int = 400): SolveOptions

What a brief means by "solve it": run it out, not to the interactive default.

SolveOptions' own defaults are for a drag frame — eight iterations and functionTolerance = 1e-6, a relative decrease, so on a problem whose cost is a few thousand the solver stops while the answer is still 1e-4 from the minimum. That is right for something redrawn sixty times a second and wrong for a statement of intent, where the question is "what does this brief actually mean" and the answer should not depend on how many iterations were affordable. Hence tolerances near machine epsilon and DENSE_QR, which is the well-conditioned choice on the small dense systems a brief produces and matters because a demand next to a whisper is a condition number of 1e9.

fun settleOptions(iterations: Int = 400): SolveOptions = SolveOptions(
    maxIterations = iterations,
    metrics = true,
    linearSolver = CeresLinearSolver.DENSE_QR,
    functionTolerance = 1e-14,
    gradientTolerance = 1e-16,
    parameterTolerance = 1e-14,
)

fun BriefProgram.solve

Solve the brief through the real Ceres backend and measure every statement.

Needs the native library — ./gradlew buildCeresRedistributable once, per afield.math.optimization.ceres.CeresNativeLibrary.HOW_TO_BUILD. bindings are read on the way in and written back on the way out, so a brief can move Vector3fs the rest of a box is drawing.

fun BriefProgram.solve(
    iterations: Int = 400,
    bindings: Map<String, Vector3f> = emptyMap(),
    options: SolveOptions = settleOptions(iterations),
): BriefSolution {
    val model = toGeometryModel(bindings)
    // The backend directly rather than `GeometryModel.openSession`, because the session's fast path is
    // the allocation-free per-frame solve and it refuses a `SolveOptions` that needs the configurable
    // entry point (`FfmCeresBackend.kt:304`) — which tightened tolerances do. A brief has no traces and
    // no pins, so the session's other services are not wanted here; `InteractiveSolving` is still the
    // route for a drag.
    FfmCeresBackend().compile(model.initialPointCloud(), model.program).use { compiled ->
        val result = compiled.solve(model.initialPointCloud(), null, options)
        val solved = pointNames.mapIndexed { index, name -> name to result.points.get(index) }.toMap()
        bindings.forEach { (name, target) ->
            val at = solved.getValue(name)
            target.set(at.x.toFloat(), at.y.toFloat(), at.z.toFloat())
        }
        return BriefSolution(
            ok = result.ok,
            totalCost = result.metrics?.totalCost ?: Double.NaN,
            points = solved,
            residuals = measure(this, solved),
        )
    }
}

residual measurement

source — 169 lines, from line 1025
private fun measure(program: BriefProgram, at: Map<String, Vec3>): List<BriefResidual> =
    program.syntax.constraints.map { constraint ->
        BriefResidual(
            label = constraint.label,
            line = constraint.at.line,
            strength = constraint.strength,
            residual = residualOf(program, constraint, at),
        )
    }

private fun residualOf(program: BriefProgram, constraint: BriefConstraint, at: Map<String, Vec3>): Double {
    val lines = program.syntax.declarations.filterIsInstance<BriefDeclaration.Line>().associateBy { it.name.text }
    val planes = program.syntax.declarations.filterIsInstance<BriefDeclaration.Plane>().associateBy { it.name.text }
    val panels = program.syntax.declarations.filterIsInstance<BriefDeclaration.Panel>().associateBy { it.name.text }
    fun p(name: BriefName): Vec3 = at.getValue(name.text)
    fun edge(name: BriefName): Pair<Vec3, Vec3> = lines.getValue(name.text).let { p(it.a) to p(it.b) }
    fun len(name: BriefName): Double = edge(name).let { (a, b) -> (b - a).norm() }

    return when (val relation = constraint.relation) {
        is BriefRelation.Equation -> {
            val flipped = relation.left is BriefTerm.Literal
            val element = if (flipped) relation.right else relation.left
            val other = if (flipped) relation.left else relation.right
            val actual = evaluateTerm(element, ::p, ::len, ::edge, planes, panels, at)
            val target = evaluateTerm(other, ::p, ::len, ::edge, planes, panels, at)
            if (actual.isNaN() || target.isNaN()) Double.NaN else when (relation.comparison) {
                BriefComparison.EQ -> actual - target
                BriefComparison.AT_LEAST -> max(0.0, target - actual)
                BriefComparison.AT_MOST -> max(0.0, actual - target)
            }
        }
        is BriefRelation.Predicate -> when (relation.verb) {
            "parallel" -> {
                val (a, b) = edge(relation.subjects[0])
                val (c, d) = edge(relation.objects[0])
                sinBetween(b - a, d - c)
            }
            "perpendicular" -> {
                val (a, b) = edge(relation.subjects[0])
                val (c, d) = edge(relation.objects[0])
                cosBetween(b - a, d - c)
            }
            "same-vector" -> {
                val (a, b) = edge(relation.subjects[0])
                val (c, d) = edge(relation.objects[0])
                ((b - a) - (d - c)).norm()
            }
            "along" -> {
                val (a, b) = edge(relation.subjects[0])
                val v = relation.vector!!
                sinBetween(b - a, Vec3(v.first, v.second, v.third))
            }
            "toward", "vanishes-at" -> {
                val (a, b) = edge(relation.subjects[0])
                distancePointToLine(p(relation.objects[0]), a, b)
            }
            "faces" -> {
                val plane = planes.getValue(relation.subjects[0].text)
                val n = planeNormal(p(plane.a), p(plane.b), p(plane.c))
                val v = relation.vector!!
                cosBetween(n, Vec3(v.first, v.second, v.third)) - (relation.number ?: 1.0)
            }
            "planar" -> {
                val pts = if (relation.subjects.size == 1 && relation.subjects[0].text in panels) {
                    panels.getValue(relation.subjects[0].text).let { listOf(p(it.a), p(it.b), p(it.c), p(it.d)) }
                } else {
                    relation.subjects.map { p(it) }
                }
                planarityDeviation(pts)
            }
            "colinear" -> {
                val pts = relation.subjects.map { p(it) }
                pts.drop(2).maxOfOrNull { distancePointToLine(it, pts[0], pts[1]) } ?: 0.0
            }
            "level", "same-z" -> spread(relation.subjects.map { p(it).z })
            "same-x" -> spread(relation.subjects.map { p(it).x })
            "same-y" -> spread(relation.subjects.map { p(it).y })
            "same-xy" -> max(spread(relation.subjects.map { p(it).x }), spread(relation.subjects.map { p(it).y }))
            "same-xz" -> max(spread(relation.subjects.map { p(it).x }), spread(relation.subjects.map { p(it).z }))
            "same-yz" -> max(spread(relation.subjects.map { p(it).y }), spread(relation.subjects.map { p(it).z }))
            "on" -> {
                val plane = planes.getValue(relation.objects[0].text)
                signedDistanceToPlane(p(relation.subjects[0]), p(plane.a), p(plane.b), p(plane.c)) -
                    (relation.number ?: 0.0)
            }
            "inside" -> {
                val plane = planes.getValue(relation.objects[0].text)
                val signed = signedDistanceToPlane(p(relation.subjects[0]), p(plane.a), p(plane.b), p(plane.c))
                max(0.0, (relation.number ?: 0.0) - signed)
            }
            "coincident" -> (p(relation.subjects[0]) - p(relation.objects[0])).norm()
            "mirrors" -> {
                val plane = planes.getValue(relation.objects[1].text)
                val reflected = reflect(p(relation.subjects[0]), p(plane.a), p(plane.b), p(plane.c))
                (reflected - p(relation.objects[0])).norm()
            }
            "at" -> {
                val f = relation.number!!
                val u = p(relation.objects[0])
                val v = p(relation.objects[1])
                (p(relation.subjects[0]) - (u * (1.0 - f) + v * f)).norm()
            }
            // Only the axes the statement named. Measuring all three would report the drift of a
            // coordinate that `fix … in xy` deliberately left free, and at a lock's weight that number
            // dominates every real residual and makes the "what is still fighting" readout useless.
            "fix" -> {
                val axes = relation.axes ?: "xyz"
                relation.subjects.maxOf { subject ->
                    val now = p(subject)
                    val seed = program.seeds.getValue(subject.text)
                    var worst = 0.0
                    if ('x' in axes) worst = max(worst, abs(now.x - seed.x))
                    if ('y' in axes) worst = max(worst, abs(now.y - seed.y))
                    if ('z' in axes) worst = max(worst, abs(now.z - seed.z))
                    worst
                }
            }
            // A weak prior's target follows the point, so its residual is identically zero and saying
            // so is more honest than reporting a number that means nothing.
            "hold", "all-loose" -> Double.NaN
            "all-above-ground" -> at.values.maxOfOrNull { max(0.0, -it.z) } ?: 0.0
            else -> Double.NaN
        }
    }
}

private fun evaluateTerm(
    term: BriefTerm,
    p: (BriefName) -> Vec3,
    len: (BriefName) -> Double,
    edge: (BriefName) -> Pair<Vec3, Vec3>,
    planes: Map<String, BriefDeclaration.Plane>,
    panels: Map<String, BriefDeclaration.Panel>,
    at: Map<String, Vec3>,
): Double = when (term) {
    is BriefTerm.Literal -> term.value
    is BriefTerm.Property -> when (term.property) {
        "length" -> len(term.element)
        "area" -> panels.getValue(term.element.text).let { quadArea(p(it.a), p(it.b), p(it.c), p(it.d)) }
        "x" -> p(term.element).x
        "y" -> p(term.element).y
        else -> p(term.element).z
    }
    is BriefTerm.Call -> when (term.function) {
        "distance" -> (p(term.arguments[1]) - p(term.arguments[0])).norm()
        "angle" -> {
            val (a, b) = edge(term.arguments[0])
            val (c, d) = edge(term.arguments[1])
            acos(cosBetween(b - a, d - c).coerceIn(-1.0, 1.0))
        }
        "cos-angle", "dot" -> {
            val (a, b) = edge(term.arguments[0])
            val (c, d) = edge(term.arguments[1])
            cosBetween(b - a, d - c)
        }
        "raw-dot" -> {
            val (a, b) = edge(term.arguments[0])
            val (c, d) = edge(term.arguments[1])
            (b - a).dot(d - c)
        }
        "dihedral" -> {
            val (e0, e1) = edge(term.arguments[0])
            val n1 = (e1 - e0).cross(p(term.arguments[1]) - e0)
            val n2 = (e1 - e0).cross(p(term.arguments[2]) - e0)
            cosBetween(n1, n2)
        }
        else -> Double.NaN
    }
}

small vector helpers, local so this file has no dependency beyond Vec3

source — 44 lines, from line 1197
private operator fun Vec3.minus(other: Vec3) = Vec3(x - other.x, y - other.y, z - other.z)
private operator fun Vec3.plus(other: Vec3) = Vec3(x + other.x, y + other.y, z + other.z)
private operator fun Vec3.times(s: Double) = Vec3(x * s, y * s, z * s)
private fun Vec3.dot(other: Vec3) = x * other.x + y * other.y + z * other.z
private fun Vec3.cross(other: Vec3) =
    Vec3(y * other.z - z * other.y, z * other.x - x * other.z, x * other.y - y * other.x)

private fun Vec3.norm() = sqrt(dot(this))

private fun Vec3.unit(): Vec3 {
    val n = norm()
    return if (n < 1e-12) Vec3(0.0, 0.0, 0.0) else this * (1.0 / n)
}

private fun cosBetween(a: Vec3, b: Vec3): Double {
    val na = a.norm()
    val nb = b.norm()
    return if (na < 1e-12 || nb < 1e-12) Double.NaN else a.dot(b) / (na * nb)
}

private fun sinBetween(a: Vec3, b: Vec3): Double {
    val na = a.norm()
    val nb = b.norm()
    return if (na < 1e-12 || nb < 1e-12) Double.NaN else a.cross(b).norm() / (na * nb)
}

private fun planeNormal(a: Vec3, b: Vec3, c: Vec3): Vec3 = (b - a).cross(c - a)

private fun signedDistanceToPlane(p: Vec3, a: Vec3, b: Vec3, c: Vec3): Double =
    planeNormal(a, b, c).unit().dot(p - a)

private fun reflect(p: Vec3, a: Vec3, b: Vec3, c: Vec3): Vec3 {
    val n = planeNormal(a, b, c).unit()
    return p - n * (2.0 * n.dot(p - a))
}

private fun distancePointToLine(p: Vec3, a: Vec3, b: Vec3): Double {
    val d = b - a
    val n = d.norm()
    return if (n < 1e-12) (p - a).norm() else (p - a).cross(d).norm() / n
}

private fun quadArea(a: Vec3, b: Vec3, c: Vec3, d: Vec3): Double =
    0.5 * (b - a).cross(c - a).norm() + 0.5 * (c - a).cross(d - a).norm()

Deviation from the best-fit plane, as the largest signed distance — the geometric residual.

private fun planarityDeviation(points: List<Vec3>): Double {
    if (points.size < 4) return 0.0
    val centroid = points.fold(Vec3(0.0, 0.0, 0.0)) { acc, p -> acc + p } * (1.0 / points.size)
    // The normal that minimises squared deviation is the smallest eigenvector of the scatter matrix.
    // For the sizes a brief produces, a few power iterations on the inverse are overkill; take the
    // best of the candidate normals through the centroid, which is exact for a quad and tight above.
    var best = Double.MAX_VALUE
    for (i in points.indices) {
        for (j in points.indices) {
            for (k in points.indices) {
                if (i >= j || j >= k) continue
                val n = planeNormal(points[i], points[j], points[k])
                if (n.norm() < 1e-12) continue
                val u = n.unit()
                val worst = points.maxOf { abs(u.dot(it - centroid)) }
                if (worst < best) best = worst
            }
        }
    }
    return if (best == Double.MAX_VALUE) 0.0 else best
}

private fun spread(values: List<Double>): Double =
    if (values.isEmpty()) 0.0 else (values.max() - values.min())

Dragging a brief

class BriefSession internal constructor

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

The reason this exists rather than "call solve again with different numbers": compiling is the expensive half and GeometrySolveSession builds the native problem once, so a drag re-solves without rebuilding anything. The reason it is a wrapper rather than the session itself is that a GeometrySolveSession speaks PointRef, and a brief's author only ever wrote names — asking them to carry around handles to points they declared in text would give the notation a second, worse API.

What a drag means here is worth stating, because it is the notation's own vocabulary applied to a gesture: drag gives the point a heavy prior at the target and re-solves, so the answer is the weighted compromise between where you are pulling and everything the brief says — exactly as if the gesture were one more statement, at a strength you choose. A demand in the text will fight a default drag and win; that is not a bug to be worked around, it is the brief being believed.

Owns native memory. Close it, or open it with use.

class BriefSession internal constructor(
    /** The brief this session was opened over, for its statement labels and its point names. */
    val program: BriefProgram,
    private val session: GeometrySolveSession,
    private val refs: Map<String, PointRef>,
) : AutoCloseable {

operator fun get(name: String): Vec3 = session.get(ref(name))

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 positions(): Map<String, Vec3> =
        refs.filterKeys { !it.startsWith("_at:") }.mapValues { session.get(it.value) }

    private fun ref(name: String): PointRef =
        refs[name] ?: error(
            "this brief has no point '$name'; it declares " +
                refs.keys.filterNot { it.startsWith("_at:") }.sorted().joinToString(", ")
        )

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.

strength is on the same ladder the notation writes, so BriefStrength.INSIST.weight here means what insist means there. It stays heavy afterwards, so a gesture built out of these ends with release.

    fun drag(name: String, target: Vec3, strength: Double = BriefStrength.INSIST.weight): Boolean =
        session.drag(ref(name), target, strength)

fun release(name: String): Boolean = session.release(ref(name))

End a gesture: drop the point's prior back to resting and settle.

fun pin(name: String) = session.pin(ref(name))

Hold a point where it is now, re-asserted before every solve — stronger than a heavy prior.

fun unpin(name: String) = session.unpin(ref(name))

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 solve(iterations: Int = 64): Boolean = session.solve(maxIterations = iterations, metrics = true)

fun residuals(): List<BriefResidual> = measure(program, positions() + hiddenPositions())

Every statement's residual, measured off the current geometry the way BriefSolution does.

    private fun hiddenPositions(): Map<String, Vec3> =
        refs.filterKeys { it.startsWith("_at:") }.mapValues { session.get(it.value) }

override fun close() = session.close()

Release the native problem. Every accessor here is invalid afterwards.

fun BriefProgram.openSession

Open a BriefSession over this program — the transition from "what has to be true" to "and now let me push on it".

Note the deliberate asymmetry with solve: a session runs the interactive SolveOptions, because that is what a session is for, and so its answers are not the machine-epsilon ones settleOptions produces. Ask a session for meaning and you will get a figure a fraction of a percent off; ask it for a gesture and it is right. Call solve when the question is what the brief means.

fun BriefProgram.openSession(
    bindings: Map<String, Vector3f> = emptyMap(),
    dragWeight: Double = BriefStrength.INSIST.weight,
): BriefSession {
    // `settle none` and dragging are incompatible, and the failure is silent, so refuse it here.
    //
    // A drag is expressed by raising one point's entry in `MutablePriorWeights`, and those weights
    // *override the weights of the model's `WeakPosition` constraints*. They do not add a constraint.
    // So a program with no whole-cloud prior has nothing for the drag to modulate: `drag` returns true,
    // the solve succeeds, and the point does not move. That cost an hour to find, and a caller with a
    // brief that happens to say `settle none` would have found it the same way.
    require(syntax.settle?.let { it.weight != null } != false) {
        "this brief says `settle none`, so it has no whole-cloud prior — and a drag is a *raised prior*, " +
            "not an added constraint, so there would be nothing for it to raise and the point would " +
            "not move. Give it a `settle <weight>` (the default, 1e-6, is invisible except where the " +
            "geometry leaves a point free), or call solve() instead of openSession()."
    }
    val model = toGeometryModel(bindings)
    val session = model.openSession(
        policy = InteractionPolicy(
            preserveWeight = 1e-6,
            releaseWeight = 1e-6,
            dragWeight = dragWeight,
        ),
    )
    val refs = pointNames.mapIndexed { index, name -> name to PointRef(PointId(index), name) }.toMap()
    return BriefSession(this, session, refs)
}