A real computation: quantum mechanics, end to end #
This module runs quantum mechanics through the calculus (the paper cites
its twoStateChecks as the fuel example and leaves the rest here): the
particle in a box exercises the scalar fragment and forces the rational
exponents, the two-state system exercises spaces, linear maps, and the
compiled path down to BLAS, and one example thus crosses four layers of the
development: rational exponents in the statics, fuel in the dynamics,
parametricity at the interface, and erasure on the path to the machine.
The particle in a box #
The evaluator from LambdaS.Dynamics is
generic in its numeric carrier, so the same definition that
unit_soundness_total and erasure_correct are proved about is instantiated
here at Float and
executed by #eval, and compiled to C by lake build.
What the type checker is asked to prove #
Two things, and both are physics rather than bookkeeping.
The uncertainty product is a pure number. ΔxΔp/ħ has type Q 1. Nothing in
the term says so; it follows from ħ being kg·m²/s, Δp being kg·m/s and
Δx being m, and the checker deriving it. Had any exponent been wrong the term
would not typecheck at all, so the printed value cannot be a dimensionally
meaningless number.
A wavefunction carries a half-power of length. In one dimension ψ has unit
m^(-1/2), so that |ψ|² is a probability density at m⁻¹ and |ψ|²dx is
dimensionless. This is the example that forces ℚ exponents: m^(-1/2) is not
expressible in F#, in Data.Dimensional, or in the Isabelle ISQ development,
all of which fix exponents to ℤ. pow is primitive here for the reason
LambdaS.NonDef.sqrt_not_definable gives, and over ℚ it is total.
Numbers #
CODATA values, written as exact rationals and converted to Float once per
literal by Num.ofRat. The box is 1 nm wide and holds an electron; the ground
state comes out near 0.376 eV, which is the textbook answer.
Units of the problem #
The joule, kg·m²/s².
Equations
Instances For
Action, kg·m²/s, the unit of ħ.
Equations
Instances For
Momentum, kg·m/s.
Equations
Instances For
Terms #
A dimensioned literal: a magnitude times a unit constant.
Equations
- LambdaS.QM.qty v u = (LambdaS.Tm.lit v).mul (LambdaS.Tm.ucon u)
Instances For
A dimensionless literal.
Equations
Instances For
Subtraction, as addition of a negated literal multiple. Λs has no primitive
subtraction; a − b is a + (−1)·b, and the unit rule for add still forces
both sides to agree.
Equations
- LambdaS.QM.sub a b = LambdaS.Tm.add a (LambdaS.Tm.mul (LambdaS.QM.num (-1)) b)
Instances For
Reduced Planck constant, 1.054571817e-34 kg·m²/s.
Equations
- LambdaS.QM.hbar = LambdaS.QM.qty (1054571817 / 10 ^ 43) LambdaS.QM.action
Instances For
Electron mass, 9.1093837015e-31 kg.
Equations
- LambdaS.QM.mass = LambdaS.QM.qty (91093837015 / 10 ^ 41) LambdaS.Examples.kg
Instances For
Box width, one nanometer.
Equations
- LambdaS.QM.width = LambdaS.QM.qty (1 / 10 ^ 9) LambdaS.Examples.m
Instances For
π, to fourteen places. Dimensionless, as every literal in Λs is.
Equations
- LambdaS.QM.pi = LambdaS.QM.num (314159265358979 / 10 ^ 14)
Instances For
The ground state #
E₁ = π²ħ² / (2mL²).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Uncertainties #
For the ground state of an infinite square well,
Δp = πħ/L and Δx = L·√(1/12 − 1/(2π²)).
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
The physics statement is the type. ΔxΔp/ħ is dimensionless, and the
checker proves it from the units of ħ, Δp and Δx alone.
Equations
Instances For
The wavefunction, where ℚ exponents are forced #
ψ(x) = √(2/L)·sin(πx/L). The amplitude carries m^(-1/2).
The normalization amplitude √(2/L), at unit m^(-1/2).
Equations
- LambdaS.QM.amplitude = LambdaS.Tm.pow (1 / 2) ((LambdaS.Tm.lit 2).div LambdaS.QM.width)
Instances For
|ψ|² is a probability density, at m⁻¹.
Instances For
|ψ|²·dx is dimensionless, which is what makes the normalization integral
a pure number, and what makes log of it well-typed while log |ψ|² is not.
Equations
Instances For
Running it #
eval is the evaluator of LambdaS.Dynamics (proved sound in
LambdaS.Soundness and total in LambdaS.Normalization), instantiated at
Float. No conversions occur here, so the conversion-factor argument is the
constant 1.
Evaluate a closed term to a magnitude.
Equations
- LambdaS.QM.run e = match LambdaS.evalC (fun (x x_1 : LambdaS.UExp LambdaS.Examples.Base 0) => 1.0) 1000 [] e with | some (LambdaS.Val.scalar x) => some x.mag | x => none
Instances For
Evaluate, keeping the unit the value carries at runtime, which
unit_soundness_total guarantees equals the one the checker predicted.
Equations
- LambdaS.QM.runMeas e = LambdaS.evalC (fun (x x_1 : LambdaS.UExp LambdaS.Examples.Base 0) => 1.0) 1000 [] e
Instances For
(-8)^(1/3): a non-integer power of a negative argument, an undefined
point of the classical operation. The Float carrier's npow is C's pow,
which returns NaN there; the ℝ semantics reads through Real.rpow, whose
conventional value there is 1, not a root. The two carriers part ways at this
point as they do at division by zero, and no theorem reaches the compiled
number (LambdaS.Num). The check pins the loud answer: the evaluator does not
get stuck (the term is well typed and pow is total), and what it returns is
NaN.
Equations
- LambdaS.QM.negBase = LambdaS.Tm.pow (1 / 3) (LambdaS.Tm.lit (-8))
Instances For
A two-state system #
The particle in a box is closed-form scalars. This exercises the other half of the calculus: spaces, linear maps, and the rank-one condition that makes their units cheap.
The system is a symmetric two-level system (an ammonia molecule, a spin in a transverse field, a qubit) with Hamiltonian
H = [ 0 -A ] A = 10⁻⁴ eV
[ -A 0 ]
Amplitudes are dimensionless, so the state space is [1, 1]; the Hamiltonian
maps it to [J, J], and by LambdaS.Map's rank-one condition its entries carry
J / 1 = J. Applying it to a state therefore yields energies, and
⟨ψ|H|ψ⟩ : Q J; the physics statement is the type.
Energies.
Instances For
The context: a Hamiltonian and a state, supplied as data.
Matrices and state vectors arrive through the environment for the same reason
unit constants do (LambdaS.Fundamental): a program that could name its data
would not be scale-invariant. A numeric program takes its operators as input.
Equations
Instances For
(H|ψ⟩)ᵢ: an energy.
Equations
- LambdaS.QM.ket i = ((LambdaS.Tm.var 0).mapp (LambdaS.Tm.var 1)).idx i
Instances For
⟨ψ|H|ψ⟩, summed over the two basis states. Real amplitudes, so no
conjugation is needed; see the note on carriers in LambdaS.Num.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The data #
The symmetric eigenstate (|1⟩ + |2⟩)/√2, eigenvalue −A.
Equations
- LambdaS.QM.statePlus = LambdaS.Val.vector [0.7071067811865476, 0.7071067811865476] LambdaS.QM.St
Instances For
The antisymmetric eigenstate (|1⟩ − |2⟩)/√2, eigenvalue +A.
Equations
- LambdaS.QM.stateMinus = LambdaS.Val.vector [0.7071067811865476, -0.7071067811865476] LambdaS.QM.St
Instances For
Literals, and where parametricity draws its line #
The introduction forms of LambdaS.Syntax let the eigenstate be written down
rather than supplied: its amplitudes are dimensionless literals, so the state
literal is parametric. A Hamiltonian literal is not: its entries are energies,
so each is a multiple of 1_J, and writing the operator down names the joule,
which is precisely the unit constant the parametric fragment excludes
(LambdaS.Fundamental). That is why this file supplies H and ψ through
the environment above.
1/√2, to sixteen places.
Equations
- LambdaS.QM.invSqrt2 = 7071067811865476 / 10 ^ 16
Instances For
The symmetric eigenstate (|1⟩ + |2⟩)/√2 as a vector literal.
Equations
Instances For
H = [[0, −A], [−A, 0]] as a matrix literal, entry by entry a multiple of
1_J.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Evaluate a scalar term in an environment.
Equations
- LambdaS.QM.runIn ρ e = match LambdaS.evalC (fun (x x_1 : LambdaS.UExp LambdaS.Examples.Base 0) => 1.0) 1000 ρ e with | some (LambdaS.Val.scalar x) => some x.mag | x => none
Instances For
The same with an explicit fuel bound, so exhaustion is observable.
Equations
- LambdaS.QM.runFuel n ρ e = match LambdaS.evalC (fun (x x_1 : LambdaS.UExp LambdaS.Examples.Base 0) => 1.0) n ρ e with | some (LambdaS.Val.scalar x) => some x.mag | x => none
Instances For
Dimensionless phase #
Not evaluated: the point is the type. exp demands a dimensionless argument,
so a phase A·t/ħ typechecks only because the units of an energy, a time and an
action cancel. Get the Hamiltonian's units wrong and the program does not
compile.
Equations
- LambdaS.QM.splitting = LambdaS.QM.qty (1602176634 / 10 ^ 32) LambdaS.QM.joule
Instances For
Equations
- LambdaS.QM.elapsed = LambdaS.QM.qty (1 / 10 ^ 11) LambdaS.Examples.sec
Instances For
A·t/ħ. Dimensionless, and the checker says so.
Equations
Instances For
Higher order: the expectation as a reusable function #
expectH is written against two specific free variables. Abstracting it is where
eval needs closures, and closure bodies are not subterms of the applications
that invoke them, so evaluation carries a fuel bound. Fuel is consumed only at
app, so first-order arithmetic evaluates at every bound including zero.
λ(H : St ⊸ En). λ(ψ : vec St). ⟨ψ|H|ψ⟩
Equations
- One or more equations did not get rendered due to their size.
Instances For
Applied to the operator and state supplied in the environment.
Equations
Instances For
Every numeric claim about the two-state system, checked in one place so the compiled binary can report whether they all hold.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Validation in the compiled binary #
The box results are #guarded at build time in Lean's interpreter; the
binary re-runs them on the compiled evaluator, so the C code path is
validated against the same numbers. The literal check is new coverage: the
expectation applied to the Hamiltonian and state written as terms, so the
evaluator's vcons/mcons cases and the extern dgemv are all on the
path of one closed program.
The particle-in-a-box claims, re-checked by the compiled evaluator, plus
the undefined point (-8)^(1/3), which the compiled evaluator answers with
NaN rather than a conventional number.
Equations
- One or more equations did not get rendered due to their size.
Instances For
⟨ψ|H|ψ⟩ as one closed term: curried expectation, literal operator,
literal state.
Equations
Instances For
The literal program computes the symmetric eigenvalue −A through
dgemv.
Equations
- LambdaS.QM.literalChecks = Option.any (fun (x : Float) => decide ((x - -LambdaS.QM.splitJ).abs < 1e-28)) (LambdaS.QM.runIn [] LambdaS.QM.expectFromLiterals)
Instances For
The carrier-boundary conventions, asserted through the Num instance the
evaluator actually uses, so a platform or libm change cannot shift them
silently. Every undefined point of the classical operations behaves as IEEE:
division by zero and log 0 give infinities, 0^q at negative q gives an
infinity, and a non-integer power of a negative base gives NaN (the same
point, checked through the evaluator, is negBase in boxChecks).
Equations
- LambdaS.QM.boundaryChecks = ((LambdaS.Num.div 1.0 0.0).isInf && (LambdaS.Num.nlog 0.0).isInf && (LambdaS.Num.npow (-1) 0.0).isInf && (LambdaS.Num.npow (1 / 3) (-8.0)).isNaN)
Instances For
Every run-time numeric check the binary performs; main exits nonzero
unless this holds.
Equations
Instances For
The native kernel on the evaluation path #
Num.matVec is what eval reaches when it applies a linear map, and the Float
carrier overrides it with a single dgemv, which is @[extern]. So in the
compiled binary this runs in C, on a flat array of doubles, with no unit
information present, because the checker has already discharged all of it.