Λs, running #
Every line below runs the checker at build time via #guard. If any result
were different, this file would not compile.
A note on what that does and does not establish. #guard executes the compiled
infer, so these are tests, not kernel-checked proofs: kernel reduction gets
stuck on the Rat instance chain that Mathlib's algebraic hierarchy is built
from, which is a known cost of building on it. The correctness guarantee does
not come from here: it comes from LambdaS.Typing, where check returns the
derivation (so soundness holds by construction) and check_eq is proved in
the kernel, saying nothing well-typed is rejected. These examples show the
verified checker running.
Five base units; note that three of them measure Length. That is deliberate: it is the configuration a "one named unit per dimension" restriction forbids, and the one conversion factors live in.
Instances For
Equations
- One or more equations did not get rendered due to their size.
The declared dimension of each base unit. Meter, foot, and yard collapse to the same dimension; nothing else does.
Note that dim lands in DExp Dim 0, a dimension expression, not a single
base dimension. That is what lets a derived unit be declared directly at a
compound dimension.
Equations
- One or more equations did not get rendered due to their size.
The empty dimension context: a closed term has no unit variables in scope.
Equations
Instances For
Terms and types of the running example, at the closed scope.
Instances For
Instances For
The type a closed term is assigned.
Equations
Instances For
The type a term is assigned in a non-empty value context.
Equations
Instances For
A base unit as a unit expression, at any scope.
Equations
Instances For
Instances For
Instances For
Instances For
Instances For
Instances For
Scalars #
5 s.
Equations
Instances For
The error that motivates the whole exercise #
Adding a length to a duration: the Mars Climate Orbiter failure, in one line.
Equations
Instances For
Roots, where ℚ exponents pay for themselves #
Kennedy's Λu and F# both type sqrt : float<'u^2> -> float<'u>, which to apply
at a volume requires solving 2·vec(u) = 3 over ℤ. There is no solution, so
F# rejects the square root of a volume. Over ℚ the root is total, and this
lands at m^(3/2).
Equations
Instances For
Spaces #
A state space whose components carry different units: position in meters,
momentum in kg·m/s. This is the non-uniform case F# cannot express at all,
since it parameterizes a type by a single unit.
Equations
Instances For
Linear maps #
Equations
Instances For
Unit polymorphism #
The reason Kennedy's calculus exists, now over spaces, with the dimension bound that lets a polymorphic function still convert.
∀u. τ is not primitive. It is ∀δ. ∀u:δ. τ: a unit variable bounded by a
dimension variable. That is what makes it genuinely unbounded, and the guards
below check both halves: that δ matches any concrete dimension on
instantiation, and that nothing concrete matches δ inside the binder.
The bound unit variable, inside one unit binder.
Equations
Instances For
The bound dimension variable, inside one dimension binder.
Equations
Instances For
Λδ. Λu:δ. λ(x : Q u). x * x: squaring, at any unit.
Equations
Instances For
Equations
Instances For
Equations
Instances For
Addition at any unit, but the same unit on both sides.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Polymorphism does not weaken the check. Λδ. Λu:δ. λ(x : Q u). x + 1 is
rejected, because u is rigid inside the binder and cannot be 1.
Equations
Instances For
Two distinct unit variables cannot be added either: nested binders really introduce fresh variables rather than shadowing.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Conversion under a binder #
This is what the dimension bound buys, and what an unbounded quantifier cannot express: a polymorphic function that converts its argument.
Λu:Length. λ(x : Q u). convert x u meter: take a length in any unit,
return it in meters.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The unbounded quantifier cannot convert. The same body under ∀δ. ∀u:δ.
is rejected: dimOf reports the dimension variable δ, which is not the
dimension of the meter, so SameDim fails. This is the rejection an unbounded
quantifier ought to give, and it is why the bound is a dimension variable rather
than the trivial dimension: under the trivial dimension u would be claimed
dimensionless and the conversion would wrongly be accepted.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Physics: where the exponents come from #
ħ = kg·m²/s
Equations
Instances For
G = m³/(kg·s²)
Equations
- One or more equations did not get rendered due to their size.
Instances For
c = m/s
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
The base-measure problem, as a type error #
A probability density over a space whose measure carries μ itself carries
μ⁻¹; that is what makes ∫ p dμ dimensionless. So log p is not
well-typed, which is the base-measure problem: a density means nothing except
relative to the measure it was taken against, and representing a distribution by
its density silently discards that measure.
Radul and Alexeev (arXiv:2010.09647) identify exactly this failure in probabilistic programming systems and fix it by library convention. Here the checker rejects it.
A probability density over a length-parameterized space: m⁻¹.
Instances For
A wavefunction is a half-density, at m^(-1/2). Squaring it gives a
weight-1 density, which is the normalization condition.
Equations
Instances For
Equations
Instances For
Conversion, and the ambiguity that never arises #
Comp 311's unit-conversion assignment has a latent bug: with units nameable in
terms of other units, convert walks a declared structure, and two routes from
u to v need not agree. Λs cannot exhibit it. Conversion is a ratio of one
valuation (LambdaS.Conversion), so convChain_eq makes path independence a
theorem rather than a proof obligation on declarations.
What the checker enforces is the side condition (that the two units measure the
same thing), and that is SameDim.
Declarations, and the conflict that cannot be declared away #
unit yard = 3 foot declares a generator and an equation. Three such
declarations give two routes from yard to meter, which is the Comp 311 bug. Here
the redundant declaration is either arithmetically right or the system has no
solution: there is never a choice of route to get wrong.
Base units of equal declared dimension are interchangeable, via
dimOf_ofBase rather than decide, since kernel reduction sticks on ℚ's
instance chain. The dimension equation is rfl for any two Length units.
Equations
- LambdaS.Examples.dYardFoot = { lhs := LambdaS.Examples.Base.yard, factor := 3, pos := LambdaS.Examples.dYardFoot._proof_1, rhs := LambdaS.Examples.ft }
Instances For
Equations
- LambdaS.Examples.dFootMeter = { lhs := LambdaS.Examples.Base.foot, factor := 3048 / 10000, pos := LambdaS.Examples.dFootMeter._proof_1, rhs := LambdaS.Examples.m }
Instances For
unit yard = 0.9144 meter: the redundant declaration, stated correctly.
Equations
- LambdaS.Examples.dYardMeter = { lhs := LambdaS.Examples.Base.yard, factor := 9144 / 10000, pos := LambdaS.Examples.dYardMeter._proof_1, rhs := LambdaS.Examples.m }
Instances For
unit yard = 0.9 meter: the same declaration, stated wrongly. This is the
one that would have made convert route-dependent.
Equations
- LambdaS.Examples.dYardMeterBad = { lhs := LambdaS.Examples.Base.yard, factor := 9 / 10, pos := LambdaS.Examples.dYardMeterBad._proof_1, rhs := LambdaS.Examples.m }
Instances For
The consistent set forces the redundant factor. Any valuation satisfying the first two determines the third, so the second route cannot disagree with the first; it is not free to.
The conflicting set has no valuation at all.
This is the Comp 311 bug, decided rather than papered over. The assignment's
convert had to walk a declared structure and could walk the wrong way; here the
configuration that would force a choice is exactly the configuration with no
solution, so it is rejected at declaration time.
Dimension abbreviations, elaborated #
Unit declarations constrain; dimension declarations abbreviate, and the
only check they need is scoping (DimAbbrev.elabDimDefs, in
LambdaS.Declare). The chain below
elaborates, with Accel landing at the vector length·time⁻². The
cyclic pair Velocity = Length/Time; Length = Velocity/Time is rejected at its
second line for rebinding the generator Length, and a forward
reference is rejected because an undefined name does not denote.
The base dimensions' surface names.
Equations
Instances For
Equations
Instances For
Velocity = Length/Time; Length = Velocity/Time: rebinds a generator.
Equations
Instances For
The valuation the declarations determine, and the evaluator using it #
yard_conflict says the bad set has no valuation; this is the other half,
exhibited rather than asserted: the consistent set has one, written down. Its
factors then reach the evaluator through evalC_convert_declared: the number
the evaluator multiplies by is the number the declaration names, with no route
through an informal reading of "3". The theorems below run eval at carrier
ℝ, with conv ψyd as the conversion oracle; LambdaS.Algorithms runs the
same evaluator at Float with the declared magnitudes transcribed.
The consistent set is satisfiable: the counterpart to yard_conflict,
with the witness constructed rather than assumed.
Cycles are constraints, not definitions #
Declarations carry no order and no acyclicity condition, and need none: each
is an equation, the set is a simultaneous system, and a cycle is just a
dependency the criterion decides. Nothing can reference an undeclared
generator either, since the base units are the parameter B. The benign
cycle below is satisfiable, by the same valuation as the straight set; the
vicious one forces 3 = 1 and no valuation exists.
unit foot = 1/3 yard, the converse of dYardFoot: a benign cycle.
Equations
- LambdaS.Examples.dFootYard = { lhs := LambdaS.Examples.Base.foot, factor := 1 / 3, pos := LambdaS.Examples.dFootYard._proof_1, rhs := LambdaS.Examples.yd }
Instances For
unit foot = yard, closing the cycle wrongly.
Equations
- LambdaS.Examples.dFootYardBad = { lhs := LambdaS.Examples.Base.foot, factor := 1, pos := LambdaS.Examples.dFootYardBad._proof_1, rhs := LambdaS.Examples.yd }
Instances For
A benign cycle is satisfiable: yard = 3 foot and foot = 1/3 yard
say the same thing, and the criterion sees a passing dependency, not a
loop.
A vicious cycle is rejected: yard = 3 foot and foot = yard force
3 = 1, so no valuation exists.
One yard is three feet, on the evaluator. Evaluating (1 yd) in ft at
carrier ℝ, with the conversion oracle the declarations determine, multiplies
by exactly the declared 3 and lands at ft: evalC_convert_declared,
instantiated. The binary runs the same eval at Float
(LambdaS.Algorithms).
And directly to meters, by the redundant declaration: the same number the
chain through feet produces, which is yard_forced made numeric.
The route through feet lands on the same number. ((1 yd) in ft) in m
multiplies by the declared 3 and then by the declared 0.3048, and
3 × 0.3048 = 0.9144 is exactly the magnitude the direct route produces:
evalC_convert_declared applied twice, the inner conversion's value read off
through Decl.conv_eq_factor.
The two routes agree: whatever fuel each needs, the evaluator returns
the same value for one yard in meters whether it converts directly or through
feet. This is path independence (conv_trans) at the evaluator, for the
declared factors.
Regression: substitution under nested binders #
Attempting the type-soundness proof turned up a genuine defect in Ty.subst.
Inside ∀u. τ, de Bruijn index 0 is the bound variable and 1 is the outer
one, so substituting for the outer variable must leave the binder alone. The
original definition recursed as τ.subst σ.weaken, which substitutes index 0
(the bound variable) and shifts the outer one down into its place. Ty.weaken
had the matching defect, inserting the fresh variable at index 0 rather than past
the binder.
Nothing here reached it. Every single-binder use is unaffected, and the one test
with nested unit binders (twoVars) is rejected by the checker before
substitution runs. The two definitions were also wrong in a way that canceled,
so subst_weaken (the only theorem about them) held regardless.
It took writing the soundness proof to surface it: the composition lemma for
substitution refused to hold, and the reason was that liftU had no counterpart
in the quantifier case. The fix was to define subst and weaken as instances
of a simultaneous substitution that lifts properly. These guards pin the
corrected behavior.
That is the argument for mechanizing a calculus rather than describing one. The defect is invisible to inspection, invisible to the examples, and invisible to the one property anybody would have thought to state about it.
Instances For
∀u. Q v₁: the body mentions the outer variable, not the bound one.
Equations
Instances For
∀u. Q v₀: the body mentions the bound variable.
Equations
Instances For
Algebraic units, in types and in measurements #
Two expressibility checks worth keeping visible, because a reader of the grammar can miss both. Unit expressions are the free abelian group: a type may be indexed by an algebraic combination of bound unit variables, and a measurement is a literal times a unit constant at any compound unit.
Λu:Length. Λv:Time. λx : Q (u·v). x: a polymorphic identity at a
compound unit built from two distinct bound variables. Inside the two binders,
de Bruijn variable 1 is u and 0 is v.
Equations
- One or more equations did not get rendered due to their size.
Instances For
5 m/s: a literal times the unit constant at a compound unit. The type is
Q (1 · m/s), and the exponent-vector representation makes that the same
index as Q (m/s); the #guard compares the vectors.
Equations
Instances For
The drift diagnostic, exercised #
Three programs, three answers. Converting meters to feet and back cancels:
unitDrift answers 1, and the result is unit-system independent. Converting
one way does not: the drift is m/ft, and the diagnostic names it. And a sum
whose branches share a variable carries the branches' shared drift.
x in ft + y in ft for distinct measurements x y : Q m is accepted
too, with the same drift m/ft. This is the frees-at-one assignment at work:
x and y are inputs, inputs rescale ideally, so each contributes the ratio
1 rather than an opaque atom, and the branch ratios are the same exponent
vector. An earlier design gave every free variable an atom and declined this
program, answering a stronger question than the certified theorems ask: the
theorems instantiate the inputs' ratios at 1 (oneTwEnv), and at that
instantiation the two branches agree.
Equations
Instances For
What add still declines at first order: a genuine drift disagreement.
x in ft + y with x : Q m and y : Q ft converts one branch and not the
other, so the branch drifts are m/ft and 1, distinct exponent vectors,
and the sum has no uniform drift; indeed the program is not scale-invariant.
By Tw.normEq_iff_eval_eq such disagreements are the only declines at
add between atom-free ratios, which is what a first-order program produces
once its internal applications are normalized away (hoSum below).
Equations
Instances For
A residue of internal abstraction. An abstraction applied inside a
first-order program leaves a redex behind: (λf. f x) (λy. y in ft) has the
ratio m/ft under every scaling, and unitDrift names it, because deciding
triviality normalizes the ratio fully (Tw.nfOne). But twistOf reduces one
β-step at construction, and the redex the outer application creates,
(λy. ...) 1, survives in the ratio as an app node, which Tw.flat treats
as an opaque atom. The ratio as built is not atom-free; its normal form is,
and the branch comparison runs on normal forms (Tw.normEq).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Summing hoApp with x in ft, two branches worth m/ft under every
scaling, is accepted at that drift: the comparison normalizes both branches
first, so the residue is reduced away and two unit expressions are compared.
Before the comparison ran on normal forms this sum was declined, an atom
against a unit.
Equations
Instances For
log ((x in ft)/(x in ft)): the argument's conversions cancel, so it is
drift-free, and a drift-free value at Q 1 is declaration-independent under
every rescaling. Its logarithm is declaration-independent too, and the
analysis now accepts it: log runs the add-style check against the literal
ratio 1 rather than declining unconditionally.
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
The λ-wrapped kernel: λx:Q m. λy:Q m. (x in ft) + (y in ft). Closed,
so unitDrift has no context to pin; unitDriftLam strips the two leading
binders, treats x and y as inputs, and reports the kernel's drift, the
same verdict addTwoVars receives as an open term: by
unitDriftLam_eq_unitDrift the two spellings' verdicts are one computation,
and unitDriftLam_spec makes the verdict exact for the applied kernel.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Branch ratios compare up to the unit algebra, not up to spelling. The two
branches below convert the same product of meters to feet, placed differently:
the first converts the product once at m·m, the second converts each factor
at m. Their ratio terms differ syntactically (the syntactic check Tw.beq
rejects exactly
this pair), but Tw.normEq flattens both to the unit vector m²/ft² with
atom exponent 1 on each of x and y, so the analysis answers, and the
answer is the drift the branches share.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The generic caster #
Under ∀δ. ∀u:δ, conversion out of u is not blocked wholesale: it can
target any expression of dimension δ, and in particular another variable
bounded by the same δ. The generic caster
Λδ. Λu:δ. Λv:δ. λx:Q u. convert x u v
typechecks, at the polymorphic cast type, and instantiating both unit
quantifiers at same-dimension ground units yields an ordinary cast. What
remains rejected is conversion from u to a concrete unit, since no
concrete unit has dimension δ. The caster is a term the two abstraction
theorems separate: coherent rescalings give u and v one shared factor
and leave it invariant; independent factors move it.
Λδ. Λu:δ. Λv:δ. λx:Q u. convert x u v.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Drift through the unit quantifiers #
The caster's ratio is a family: at bind shape it is indexed by the
instantiating unit, and its body's drift is the open exponent vector u/v.
The normalizer reads the family at chosen units, so reading it at two fresh
unit variables exhibits the open vector itself; and an instantiation is a
recorded uapp that twistOf performs on the spot (Tw.uappE).
Checker plus diagnostic, for the guards below: the drift of a
quantity-typed term over Γ, or none where either the checker or the
analysis declines.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The polymorphic round trip: Λδ. Λu:δ. Λv:δ. λx:Q u. (x in v) in u.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The coherent fundamental theorem, at a rescaling that moves a dimension #
fundamental quantifies over dimension rescalings Φ. This section
instantiates it at a Φ that is not the identity: it multiplies every Length
by 2 and fixes Mass and Time. The term is a conversion from meters to feet, so
the instantiation exercises the convert case of the theorem, the case that
requires coherence.
A dimension rescaling that doubles Length and fixes Mass and Time. In log
coordinates the Length entry is log 2, which is nonzero, so this rescaling
moves a dimension.
Equations
- One or more equations did not get rendered due to their size.
Instances For
ΦDouble is not the trivial rescaling: it moves the Length dimension.
The unit rescaling ΦDouble pulls back to: each base unit scales by the
factor of its dimension, so meter, foot, and yard all scale by 2 while
kilogram and second are fixed.
Equations
- One or more equations did not get rendered due to their size.
Instances For
λ(x : Q m). convert x m ft, closed and parametric: the conversion is the
only non-variable subterm, and Tm.Parametric admits convert.
Instances For
fundamental, instantiated at a rescaling that moves a dimension. The
conversion program is related to itself under ΦDouble, which rescales Length
by 2. Unfolding RelCo at the arrow type: for inputs related by
ψDouble.scale m, the outputs are related by ψDouble.scale ft. The convert
case of fundamental carries the content here, since the conversion factor
conv V m ft must be invariant under the rescaling, and coherence of
ψDouble (every Length unit scales by the same factor 2) is exactly what
makes it so.
Vector and matrix literals #
Until the introduction forms existed, vectors and matrices entered only as free
variables: the calculus could consume its data but not construct it. These
guards exercise the forms that close the gap: a State vector built one
component at a time, matrices built one row at a time, the rejections the rules
impose, and the zero-row width story that mnil's annotation exists to tell.
Equations
Instances For
A two-row map the other way, one row per component of State.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Row extraction #
mrow is the elimination form for Lin, dual to mcons as idx is to
vcons. Introduction and elimination meet on the nose: row 0 of
mcons w r M has exactly the type r was checked at. Without it a matrix
could be built and applied but never read, so no closed term could name a
matrix element, a column, or a transpose.
A transpose, at fixed arity. The transpose of a map V → W is the
map W* → V* on the dual spaces, whose units are the reciprocals. Here
fromTime : Lin W State, so its transpose lives at
Lin [1/m, 1/pmom] [1/sec], and the one row of the transpose is the one
column of fromTime, assembled entry by entry from row extractions. The
mcons rule checks that the entries m/sec and pmom/sec are exactly
(1/sec) / (1/m) and (1/sec) / (1/pmom), which is where the dual spaces
come from.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Drift for vectors and matrices #
The drift of a vector is a vector of drifts, and the drift of a matrix is a
matrix of drifts. A literal whose component converts one way is reported
with a drift vector naming the component's ratio, not declined; idx projects
the component's drift back out; and a matrix application carries the
add-style agreement condition per output row, across the summed index, so a
row whose summands carry genuinely different drifts is declined.
x : Q m, t : Q s ⊢ ⟨x in ft, t⟩: a vector literal whose first component
converts one way and whose second does not.
Equations
Instances For
Indexing the drifting component recovers exactly the scalar diagnosis the one-way conversion would get on its own.
Instances For
Equations
Instances For
A context for the matrix-application examples: a length, and a
dimensionless entry at exactly the unit ft/ft the rank-one row space
demands.
Equations
Instances For
A 1×2 matrix applied to a drifting vector, agreement holding: both argument components carry the same variable through the same conversion, and both row entries are the same variable, so the two products across the sum agree and the output row's drift is their common value.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
The same shape with distinct measurements in the argument components:
the mapp analogue of addTwoVars. Under the frees-at-one assignment both
components contribute the ratio 1 through their own variable, so the
products across the sum agree at the exponent vector m/ft and the
application is accepted; an earlier design gave x and y distinct atoms
and declined it.
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
What mapp still declines at first order: a genuine drift disagreement
across the summed index, the mapp analogue of addMixed. One argument
component converts (x in ft, drift m/ft) and the other is already in
feet (drift 1), so the row's two products carry distinct exponent vectors
and no uniform output drift exists.
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Case study: a mixed-unit ballistics kernel #
The drift diagnostic has so far run on one-line programs. This section runs it on a small straight-line kernel with the shape real conversion bugs have: field data arrives in imperial units, the physics is metric, and the report goes back out in imperial units.
Inputs: a distance in feet (variable 0) and a time in seconds (variable 1).
The shared pipeline converts the distance to meters, forms the velocity in
m/s, and computes the kinetic energy per unit mass v²/2 in m²/s². Four
reporting variants, four verdicts, all decided at build time:
kernelConsistentconverts the metric energy back toft²/s²for the report. The input conversion cancels against the output conversion through the arithmetic, andunitDriftcertifies the whole multi-step routine drift-free: byden_indep_of_driftFree, the reported number does not depend on the declared unit magnitudes.kernelOneWayreports in metric, so the input conversion is never undone. The diagnostic names the exact ratio,ft²/m²: the driftft/mof one foot-to-meter conversion, squared throughv².kernelMixedPathscomputesv²twice in one sum: once by converting the input and squaring the metric velocity, once by squaring the imperial velocity and converting the square atft²/s². The branch ratios differ syntactically (Tw.beqrejects the pair), butTw.normEqflattens both to the exponent vectorft²/m²with atom exponentsx² / t², so the sum is accepted and carries the branches' shared drift.kernelRoottakes the speed back out of the energy with a square root, and the analysis follows it through:Tw.qpowlifts the energy's driftft²/m²to the power1/2, and the diagnostic names the speed's driftft/mexactly. The exponent arithmetic is sound because ratio values are positive by type (SemScalar), the same positivityfundamentaluses throughrelQ_rpow.
Every routine typechecks by a #guard on the inferred type, and every drift
claim is a #guard on unitDrift, so a wrong claim fails the build.
The kernel's input context: a distance in feet and a time in seconds.
Equations
Instances For
Metric velocity, m/s.
Instances For
Imperial velocity, ft/s.
Equations
Instances For
Metric energy per unit mass, m²/s².
Equations
Instances For
Imperial energy per unit mass, ft²/s².
Equations
Instances For
The shared pipeline #
Real code shares subterms, so the variants do too: one converted distance, one metric velocity, one imperial velocity, each with its derivation.
Metric velocity: the converted distance over the raw time input.
Equations
Instances For
Imperial velocity: the raw inputs, no conversion.
Equations
Instances For
The one-way drift both drifted variants share: one foot-to-meter
conversion, squared through v².
Equations
Instances For
Variant 1: convert in, compute, convert out #
Equations
- One or more equations did not get rendered due to their size.
Instances For
Variant 2: convert in, report metric #
(1/2) · (velM · velM): the same computation, left in m²/s².
Equations
Instances For
Variant 3: the same quantity, two ways, in one sum #
(1/2) · (velM·velM + ((velF·velF) in m²/s²)): the left branch converts
the input then squares, the right squares the raw imperial velocity then
converts once at ft²/s². Averaging the two would divide by a further
literal; adding them keeps the branch comparison the interesting step.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Variant 4: the rooted variant, followed through the root #
√(2 · kernelOneWay): the speed recovered from the energy. Well-typed at
m/s (rational powers are total over ℚ exponents), and inside the analysis:
Tw.qpow lifts the energy's drift to the power.
Equations
Instances For
A Jacobi rotation, and what the units force #
The reason ifle was added. Section 5 classifies dimensioned maps, and
svd_entry_const says a singular value decomposition needs a uniform space
because sorting singular values requires a shared unit. That is a claim about
types, and until now nothing in the development ran an actual kernel against
it. This does.
The setting is the symmetric eigenvalue problem, the fourth Hart class:
A : V ⊸ dual V on a uniform V, so every entry carries 1 / (V j * V i)
and, uniformly, m⁻². A Jacobi sweep annihilates the off-diagonal entry by a
rotation whose angle is computed from the entries themselves.
A uniform two-component space.
Instances For
Its dual, carrying reciprocal units.
Equations
Instances For
The context the kernel runs in: one symmetric matrix.
Instances For
The tangent parameter of the rotation angle.
Every entry carries m⁻², so the difference is legal and the ratio is
dimensionless. The rotation is forced to be a pure number, and it is the
uniformity of the space that forces it: on a non-uniform space the numerator
below does not typecheck.
Equations
- LambdaS.Examples.tau = ((((LambdaS.Tm.var 0).mrow 1).idx 1).add ((LambdaS.Tm.lit (-1)).mul (((LambdaS.Tm.var 0).mrow 0).idx 0))).div ((LambdaS.Tm.lit 2).mul (((LambdaS.Tm.var 0).mrow 0).idx 1))
Instances For
t = sign(τ) / (|τ| + √(1 + τ²)), the standard stable form, written with
the two branches the sign requires. This is the term that could not be written
before ifle.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Cosine and sine of the rotation, both dimensionless.
Equations
Instances For
Instances For
The rotation itself, an endomorphism of the uniform space. Its entries
carry V j / V i = 1, so a rotation is a matrix of plain numbers, which is
entry_id_diag and entry_perm_prod seen from the term side.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Its transpose, assembled column by column with mrow. The rotation is
orthogonal, so this is also its inverse, and it lands at the dual spaces.
Equations
- One or more equations did not get rendered due to their size.
Instances For
One Jacobi sweep, the congruence Rᵀ A R. It needs both extensions at
once: mrow to build the transpose and ifle to choose the angle. The type is
preserved exactly, so sweeps compose.
Equations
Instances For
What the units refuse #
The rotation algebra above is not merely typeable on a uniform space; it is
typeable only there. On a non-uniform space the two diagonal entries carry
different units, so the numerator of tau is an addition at unequal units and
the term is rejected before any question of correctness arises. Hart's
uniformity condition is visible here as a typing constraint of this particular
Jacobi kernel. svd_entry_const supplies the unit identity; the checker guard
under ΓN below demonstrates the rejection. No typed general SVD algorithm
is mechanized.
Instances For
Equations
Instances For
Instances For
What the units say about the stopping test #
A convergence test compares the off-diagonal to a tolerance, and the tolerance must carry the entries' unit. Both forms below typecheck; parametricity distinguishes how their tolerances are supplied.
A relative tolerance scales a quantity already in hand by a dimensionless factor. It names no unit, so it stays inside the parametric fragment and Theorem 6.1 applies to the kernel.
The fixed absolute tolerance below is written using a named unit
constant ucon, which Tm.Parametric excludes. The kernel still typechecks
and runs, but the abstraction theorem no longer applies. An absolute tolerance
passed as an input could remain parametric and would rescale with the inputs;
absolute tolerances are not excluded in general.
Equations
- LambdaS.Examples.stopRelative = (((LambdaS.Tm.var 0).mrow 0).idx 1).ifle ((LambdaS.Tm.lit 1e-9).mul (((LambdaS.Tm.var 0).mrow 0).idx 0)) (LambdaS.Tm.var 0) LambdaS.Examples.sweep
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
The diagnostic at the sweep #
unitDriftGen takes an arbitrary context and a .lin result, so it applies
to the kernel exactly as written, matrix argument and matrix result. This is
the guard that says so.
What the experiment found #
Three things the kernel confirms, one of which took a generalization to reach.
Confirmed: uniformity is not a hypothesis but a precondition for writing the code at all, and the parametric fragment separates a relative tolerance from an absolute one. Both were predicted before the kernel was written.
Not confirmed at first: the drift analysis did not reach this program.
unitDrift is typed
HasTy Δ (scalarCtx us) e (.Q u) → Option (UExp B k),
so it wanted scalar arguments and a scalar result, and sweep has a matrix
argument and a matrix result. No rearrangement of the kernel helped, because
the restriction was on the shape of the judgment rather than on the term.
That was a coverage gap between two parts of the development rather than an
unsoundness, and the diagnosis said where to look: twistOf, the analysis
underneath, is indexed by Ctx.shapes and already handled vector and matrix
shapes, which is why Tw.agree needed cases at those shapes at all. Only the
wrapper was scalar-only. The gap mattered because it fell exactly between two
claims the development makes: LambdaS.Map classifies dimensioned linear
operators, the diagnostic decides invariance, and the kernels motivating the
first sat outside the second.
The wrapper did generalize, on both axes. unitDriftLin reports one ratio per
entry of a matrix result, and unitDriftGen drops the scalar-context
restriction as well, so it applies to sweep exactly as written. The guard
above runs it and finds every entry trivial, which is the answer the
congruence of rotations should have: the kernel converts nowhere, so nothing
drifts. Neither axis needed a new theorem, and Twist.scaling turned out to
have been fully general already.
The kernel restated over scalar arguments #
Written when the gap above was open, as the workaround: pass the entries
rather than the matrix, so that the context is a scalarCtx and only the
result shape stands between the kernel and the diagnostic. unitDriftGen
has since removed the need for it. It stays because it exercises a genuinely
different path, a scalar context with a matrix result, and because the same
rotation algebra written two ways is a check that the shapes are doing no
work the units are not.
The entry unit of a symmetric matrix on the uniform space.
Instances For
Three scalar arguments, which is a scalarCtx.
Equations
Instances For
The matrix assembled from them, symmetric by construction.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- LambdaS.Examples.tauS = ((LambdaS.Tm.var 2).add ((LambdaS.Tm.lit (-1)).mul (LambdaS.Tm.var 0))).div ((LambdaS.Tm.lit 2).mul (LambdaS.Tm.var 1))
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
The sweep over a scalar context. Everything unitDrift asks of the
context is satisfied here; only the result shape remains.