Multi-function claims¶
Most claims speak about one function, spelled f. A claim may also
bind further function symbols and state laws that relate them: a
composition against a library function, a comparison against a sibling
in the same module, or full behavioural equivalence between two
implementations.
Binding a second function¶
Three spellings, from most to least explicit:
let g = math.sqrt, for x in (0, 100], g(x) >= 0
The let spelling binds g to an importable function by its dotted
path and is the form the record keeps: it survives every store round
trip, because the path is text.
mathema.check(mine, claims=[mathema.claims.claim("f(x) == g(x)", funcs={"g": other})])
funcs= on claim() binds a live callable and adjudicates
identically. When the callable is a module-level function importable
by its own dotted path, the record stores the binding in the let
spelling (let g = mymodule.other, f(x) = g(x)), so the claim
rebuilds from the record alone. A callable with no such path (a
lambda, a nested function, a function defined in __main__) has no
text spelling: the record keeps the law but not the binding, and a
later run reconstructing the claim from the record cannot re-bind g.
A bare call name (g(x) with no let and no funcs=) binds
automatically when a function of that name is defined in f's module
or the calling scope. The record keeps the bare name, which binds the
same way again from f's module; a function found only in the calling
scope (a notebook cell, a script) is not there for a later run to find.
Laws over two functions¶
Once bound, the second symbol participates like any expression:
for x in [0, 4], f(x) == g(x) + 1
for x in [1, 8], f(g(x)) >= 0
Both routes handle these: derive substitutes each function's lifted body and decides the combined expression; probe executes both real functions on shared draws.
Behavioural equivalence¶
f =:= g
f equiv g
The equivalence relation asks whether two implementations are the same mathematics, which is the question behind every rewrite, refactor and port: an agent's new version against the one it replaces, a C++ or TypeScript implementation against the Python reference. It climbs a ladder, strongest rung first, and each rung may decline:
- Identical canonical form. Both functions lift to the same alpha-renamed shape, and the claim is proven by the form hash alone: two spellings of one computation.
- Symbolic difference. The two lifts, positionally aligned, subtract to zero under the declared domain. A symbolic falsification here must reproduce code-versus-code before it stands, like every other disproof. Where both sides raise, this rung proves the equivalence when they raise the same exception on exactly the same region and their lifts agree everywhere else (for a function of one parameter; with more, sampling decides).
- Closed forms. Where both bodies provably never raise, their closed forms are compared directly.
- Code-versus-code sampling. Both real functions run on the same
96 seeded draws from the declared domain, and at least 24 of them
must agree before the verdict is
holds. Two results agree within the claim's own tolerance, so an equivalence between a float and a fixed-point implementation is stated, not guessed. A draw where both sides raise the same exception agrees. A draw where either side returns a non-finite value or something non-numeric is not compared. Both kinds of draw are counted in the record rather than dropped. Evidence ceilingholds: sampling never proves.
f =:= g claims f(x) == g(x) at every point of the domain, so a
point where one side raises and the other returns a value is a
counterexample, and the executed raise is its witness. x / x and 1.0 agree everywhere
except x = 0:
import mathema
def ratio(x: float) -> float:
return x / x
def one(x: float) -> float:
return 1.0
(p,) = mathema.claims.check(ratio, [mathema.claim("f =:= g", funcs={"g": one})])
print(p.verdict, p.counterexample)
falsified x=0: f raised ZeroDivisionError, g returned 1
=:= compares behaviour, so a point where both sides raise the same
exception type is a point where they agree: 1 / x and 2 / (2 * x)
are proven equivalent over [-1, 1], both raising ZeroDivisionError
at x = 0. Different exception types at the same point are a
disagreement, and the executed pair is the witness: against a version
that raises ValueError at zero, the claim is falsified with
x=0: f raised ZeroDivisionError, g raised ValueError. A complex
result from one side counts as a raise, unless that side is annotated
complex or the claim is over C. A declared tolerance is the whole allowance the two values get;
with none declared, they may differ by 1e-9 plus 1e-9 times the
larger magnitude.
The record annotates both sides' structural complexity, so an equivalence between a one-liner and a loop reads as what it is.
Another language on the other side¶
What an equivalence licenses, and what it never does, is the subject
of Claims transfer. How an implementation in
another language is reached is a target resolver,
an extension point that maps a key such as cpp: or ts: to a
callable.
Because g may be any callable, the other implementation need not be
Python: examples/cpp-equivalence/ checks a C++ ema (reached
through a ctypes shim over the C ABI) equivalent to the Python
reference, landing holds on shared-draw sampling with no special
integration. The example's README spells out the honest reading:
sampling never proves, the shim's library handle is real out-of-walk
state, a live-callable binding does not survive the record, and
safety claims never transfer between implementations, because they
are facts about an implementation and its language rather than about
the mathematics.
Premises over bound functions¶
The assuming machinery composes with bindings: a premise may
reference the bound function's own claims, and the definedness
machinery reads registered partiality lemmas for functions the body
calls (see Conditional claims and lemmas).