Lemmas: one claim resting on another¶
A lemma in mathema is not a separate kind of object. There is no
lemma keyword, no lemma field in the record, and nothing to declare.
A claim becomes a lemma by being referenced: another claim names it
in an assuming clause, and from then on the two are related by
evidence.
assuming grows holds, for x in [0,10], f(x) == 2*x
That claim rests on a sibling named grows. If grows is established,
this one is adjudicated normally. If it is not, this one does not
silently proceed: it reports that what it rests on was never settled.
Two spellings, and they ask for different strengths:
assuming grows holds, for x in [0,10], f(x) >= 0
assuming base_case is proven, for n in [2, 30] subset Z, f(n) == f(n-1) + f(n-2)
holds accepts either a proof or empirical agreement. is proven
accepts only a proof. Nothing else is interpreted as a verdict
reference: X is falsified and X is unknown are not premises, because
a claim that failed is not something to build on.
What a reference actually does¶
Three separate mechanisms fire, and it is worth keeping them apart because they fail differently.
It gates. If the referenced claim does not reach the strength
asked for, the dependent claim comes back unknown, with
meta["mathema.premise"] naming why. The claim itself was never
attempted; only what it rests on failed. That is an undecided state,
not a refutation, and the note says which prerequisite was missing.
It caps. A conclusion can never be stronger than what it rests on.
A lemma established only empirically caps its dependents at holds
even when their own proof would otherwise be exact.
It lends. A discharged lemma's relation is handed to the proof as an assumption, which is what makes this modus ponens rather than bookkeeping. This is the part that turns a chain of claims into a chain of reasoning.
Chaining¶
Claims are adjudicated in dependency order, not declaration order, so a lemma may be written after the claim that uses it. The results still come back in the order you wrote them.
from mathema.claims import check_conjectures, claim
def scale(x: float) -> float:
"""Twice x."""
return 2 * x
out = {p.name: p for p in check_conjectures(scale, [
claim("for x in [0,10], f(x) >= x", name="grows", route="derive"),
claim("assuming grows is proven, for x in [0,10], f(x) >= 0",
name="nonneg"),
claim("assuming nonneg holds, for x in [0,10], f(x) + 1 > 0",
name="offset"),
])}
for name, p in out.items():
print(f"{name:<8} {p.verdict}")
Each link is proven, and offset is proven on the strength of
nonneg, which is proven on the strength of grows:
grows proven
nonneg proven
offset proven
The weakest link bounds the conclusion¶
When a claim rests on several lemmas, the weakest one sets the ceiling. This is the rule that stops a chain from laundering sampled evidence into a proof.
out = {p.name: p for p in check_conjectures(scale, [
claim("for x in [0,10], f(x) >= x", name="grows", route="derive"),
claim("for x in [0,10], f(x) >= 0", name="sampled", route="probe"),
claim("assuming grows is proven and sampled holds, "
"for x in [0,10], f(x) + 1 > 0", name="rests_on_both"),
])}
for name, p in out.items():
cap = p.meta.get("mathema.capped_by")
print(f"{name:<14} {p.verdict}" + (f" capped_by={cap}" if cap else ""))
grows proven
sampled holds
rests_on_both holds capped_by=sampled
rests_on_both could have been proven on its own. It is reported as
holds because one of the things it rests on was only sampled, and
meta["mathema.capped_by"] names which one. Read that field when a
claim you expected to prove came back weaker: it points at the lemma to
strengthen, not at the claim you were looking at.
Lending has a domain guard¶
A lemma lends its content only where it was established. A lemma proven
over x in [0,10] says nothing about x = -3, so if the dependent
claim quantifies over a different region the lemma still gates and
still caps, but its relation is not handed to the proof. This is
deliberately strict: the bounds must agree, not merely overlap.
When a prerequisite cannot be resolved¶
(p,) = check_conjectures(scale, [
claim("assuming absent holds, for x in [0,10], f(x) >= 0",
name="orphan")])
print(p.name, p.verdict, "|", p.meta["mathema.premise"])
print("note:", p.note)
orphan unknown | missing-prerequisite
note: prerequisite 'absent' is not a claim in this batch, nothing to rest this claim on
The other resolution failures report themselves the same way. A name
matching more than one claim is ambiguous-reference rather than being
quietly bound to the first. A cycle skips every claim in it, each
note naming both claims. A premise that mixes a verdict reference with a relation in one
clause is refused rather than half-interpreted.
A premise can also reach outside the batch, to another function's claim, by qualifying the name:
assuming numpy.clip.clip_lower holds, for w in [-50, 50], f(F0,k,m,-w,c) == f(F0,k,m,w,c)
Resolution prefers an in-batch sibling, then the verified layer. An external row enters at declared status and satisfies nothing until it has been accepted or genuinely verified, so a library stub cannot quietly discharge your premise.
Two things that are not lemmas¶
Partiality lemmas are a different feature with a confusingly similar name. They are Python-registered facts about where a function raises, used by the derive route to reason about partial functions, and they are not claims. See Conditional claims.
The outcome clause looks like implication but is not adjudicated:
f(x) > 0 => self.stays_positive
Everything after => names a sibling claim that should follow when
this one holds. It is captured verbatim and travels in the record, but
nothing currently derives the named claim from it. If you want one
claim to actually rest on another, use an assuming premise, which is
what this page describes.
Limits¶
Resolution is by name, so names must be unique within what is being
adjudicated together. A bare sibling name without holds or is
proven is a different construct: it borrows that sibling's relation as
a region constraint rather than consulting its verdict, and it requires
the sibling to carry a plain relation.
Lending recurses as far as the chain goes, but each link is only as good as its own evidence, and the cap propagates the whole way. A long chain resting on one sampled lemma is a sampled result at the end of it, which is the honest outcome rather than a limitation to work around.