Mathematics, L3 — Linear, proof, and what this project does not claim#
Level 3 · Advanced
Source: Linear.hb ·
Maths.hs ·
Maths.hp
One contraction, wearing several names#
Prelude.LinearAlgebra does not implement matrix multiplication, transposition, the inner product
and the outer product. It implements one operation and names it four times:
def mul(a, b) ≔ einsum("ij,jk->ik", a, b)
def transpose(a) ≔ einsum("ij->ji", a)
def dot(x, y) ≔ einsum("i,i->", x, y)
def outer(x, y) ≔ einsum("i,j->ij", x, y)
A def is inlined, so naming costs nothing at run time. Linear.hb
writes the product twice, once by each route, and a test asserts the two agree elementwise:
They are not two implementations that happen to match. After inlining they are the same arithmetic, and "matrix multiplication" turns out to be a name for a contraction rather than an operation in its own right.
Two things called solve#
solve(a, b) here, numeric x = [2 3] conjugate gradient
Compute.Solve in Solve.hb x = 2 rewriting, by ⇔ laws
| What it is | Where it lives | |
|---|---|---|
solve(a, b) |
conjugate gradient over a real matrix — numbers in, numbers out | Prelude.LinearAlgebra, callable from .hb |
Compute.Solve(eq, x) |
symbolic isolation of a variable — an equation in, an expression out | the CAS, callable only from C# |
They share an English word and nothing else. There is no solve you can write inside an .hb
expression that isolates a variable, and no numeric conjugate gradient reachable from the CAS.
Knowing which one you are holding is most of the skill.
Three grades of certainty#
L2 watched ∂ x² / ∂ x
become 2x, and trusted it because the generated code said Reduced by power, self, unit,
power_one and those are real law names. Trusting a list of names is still trusting.
There are three different things this project can mean by "proved", and they are not interchangeable:
Demonstrate searched, and found theorem t : x · 1 + 0 = x by rw [unit, zero]
Proof checked those steps [unit, zero]
the .hp checked by the build Proved
Demonstrate searches. Give it a claim and no steps, and it looks for a derivation — bounded, so
it always terminates: depth ≤ 8, at most 10,000 goals, and a term may not grow past
2 · max(|l|, |r|) + 8. When it hits a cap it returns NotFound carrying the closest goal it
reached. A NotFound means this search did not find one, never there is none.
Proof checks. Hand it steps and it replays them. It does not look for anything, so it cannot
run out of room — and it cannot help you find a proof you do not already have.
These two round-trip: what Demonstrate writes back is a script Proof accepts, and the test
asserts exactly that.
The .hp kernel decides. Maths.hp proves the product
rule derivation itself, and HilbertProofStrict is on, so a theorem that stops checking fails the
build. sorry is not available.
theorem deriv_of_x_squared : d(times(var, var)) = times(konst(2), var)
proof
rewrite [d_times, d_var, d_var, unit_left, unit_right, double]
ring
qed
Six rewrites and a ring: the product rule, d_var twice, the two unit laws, and u + u = 2u.
Nothing is folded silently and no step is a built-in. This is the L2 derivative, computed rather
than asserted.
Note what changed on the way in. The .hb rule is ∂ u / ∂ x = 0 where x ∉ u — a side condition on
a rewrite. A .hs law has no where, and needs none, because the term algebra carries the
condition: var is the variable and konst(c) is anything that is not, so "x does not occur in
u" becomes a matching problem. The guard became structure.
Where the mathematics meets the AI#
Perceptron.hb in the machine learning subject descends a mean squared
error. For a single linear unit that loss is (w · x − y)², and the update rule is written there by
hand, the way every textbook writes it.
Here it is derived, by the same thirteen laws that took the derivative at L2:
Perceptron.hb's loss, for one linear unit (w · x − y)²
∂ of it in w, by the same thirteen laws 2(w · x − y) · x
by [power, difference, product, constant,
minus_zero, self, unit_left, nil, zero, power_one]
Ten laws, and the answer is the update the ML subject states. A test asserts the derived text exactly, so if the two ever stop agreeing, something has to give.
That is the argument for a mathematics subject sitting beside the AI ones. The gradient in a learner is not a piece of machine learning trivia to be memorised — it is a derivative, it follows from rules you can open and read, and here it is checked rather than quoted.
What this project does not claim#
Matching is structural. There is no associative-commutative matching, so x · 1 = x and
1 · x = x are two separate laws and Prelude.Arithmetic declares both. Two expressions that are
equal as mathematics can fail to meet, and Equivalent returning false means these laws did not
connect them.
Only shrinking rewrites are kept. A rewrite that makes the term larger is discarded — the sole
exception being one that removes a derivative. So "expand this" is not expressible as a law at all;
Prelude.Trigonometry says so about its own double-angle rules.
Guards are x ∉ u and nothing else. There is no n ≠ −1, no x > 0. Prelude.Integration
works around it by declaring int_reciprocal before int_power and relying on first-match-wins
against the literal −1 — and a symbolic n that turns out to be −1 only at run time divides by
zero. That is a stated limitation, not a bug. Laws true only on part of a domain, like
exp(ln(x)) = x, are deliberately absent from the preludes: a rewrite cannot check the sign of a
run-time value, and would otherwise silently repair a formula that ought to fail.
There is no log. ln is an intrinsic; base-10 is not in the language.
Compute reads expressions only — never a def or fn body. You cannot hand it the name of
something in your own .hb and have it look up the definition.
The samples are small on purpose. Nothing here says anything about performance, about conditioning, or about what conjugate gradient does to a matrix that is not well behaved. The 2×2 systems on this page are checkable by hand, which is the only property they were chosen for.
Run it#
dotnet run --project src/MLambda.AI.Maths -- Linear
dotnet run --project src/MLambda.AI.Maths -- Proof
[1 2; 3 4] squared, by two names for one contraction.
mul(a, a) [7 10; 15 22]
einsum("ij,jk->ik", a, a) [7 10; 15 22]
And two different things are called solve:
solve(a, b) here, numeric x = [2 3] conjugate gradient
Compute.Solve in Solve.hb x = 2 rewriting, by ⇔ laws
What the tests assert#
LinearTests.cs ·
ProofTests.cs ·
CalculusTests.cs
[1 2; 3 4] · Iis[1 2; 3 4], elementwise.ProductandContractedagree in all four cells — the name and theeinsumare one function.- The numeric
solverecoversx = [2, 3]from a diagonal system, to six places. MathsProofs.DerivOfXSquared()is judgedProvedby the kernel while the test runs, not read back from a build log.DemonstratethenProofround-trips onx · 1 + 0 = x.Demonstrateontan(x) = xisNotFound— the bounded search comes back rather than hanging.Derivate("(w · x − y)²", "w")is exactly2(w · x − y) · x.
Try it yourself#
- Delete a law from the
axioms [...]list inMaths.hpand rebuild. The build fails, and names the step the kernel could not take — which is what "the build checks the proof" means in practice. - Ask
Compute.Equivalent("x · 2", "2 · x"). Structural matching, no commutativity — and now the first limit on this page is something you have seen rather than read. - Widen the search with
Compute.WithLimits(new Limits(Depth: 12))and try aDemonstratethat returnedNotFoundat the default depth. Some of them come back proved, which is the useful way to learn thatNotFoundwas never a claim about mathematics.