@ceccec/millennium-solutions 0.1.1 → 9.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.zenodo.json +33 -9
- package/ACCOUNTING.md +7 -7
- package/AXIOMS.md +559 -0
- package/CHALLENGES.md +2181 -2479
- package/CITATION.cff +1 -1
- package/DEPLOY.md +2 -2
- package/DEVELOP.md +1 -1
- package/LICENSE +6 -5
- package/PRIOR-ART.md +149 -0
- package/README.md +221 -123
- package/SIGNATURE.md +2 -2
- package/TRIAL.md +69 -0
- package/WHITEPAPER.md +5 -5
- package/captain.md +2 -2
- package/compare.md +4 -4
- package/compute.md +9 -0
- package/dashboard.md +1 -1
- package/examples.md +5 -5
- package/forensic.md +67 -0
- package/guide.md +5 -5
- package/index.md +209 -139
- package/package.json +88 -9
- package/paper.md +7183 -0
- package/quantum.md +42 -0
- package/rights.md +39 -0
- package/solutions.md +7 -7
- package/speedup.md +4 -4
- package/src/0/index.ts +12 -2
- package/src/1/index.ts +1 -1
- package/src/2/frequency-scales.ts +13 -1
- package/src/2/index.ts +1 -1
- package/src/3/index.ts +1 -1
- package/src/4/index.ts +1 -1
- package/src/5/index.ts +1 -1
- package/src/6/index.ts +1 -1
- package/src/7/index.ts +1 -1
- package/src/9/funding.ts +2 -2
- package/src/api/gates.ts +117 -0
- package/src/api/index.ts +488 -0
- package/src/api/lanes.ts +81 -0
- package/src/demand/queries.json +1764 -0
- package/src/face/index.ts +100 -0
- package/src/html/index.ts +10 -0
- package/src/latex/index.ts +474 -0
- package/src/millennium/index.ts +53 -0
- package/src/proof/README.md +1 -1
- package/src/proof/address.lean +117 -0
- package/src/proof/coin.lean +148 -0
- package/src/proof/covered.json +9 -0
- package/src/proof/demand.lean +112 -0
- package/src/proof/demand2.lean +171 -0
- package/src/proof/demand3.lean +117 -0
- package/src/proof/discovered.json +10965 -1864
- package/src/proof/elementary.lean +393 -0
- package/src/proof/energy.lean +215 -0
- package/src/proof/families.lean +435 -0
- package/src/proof/fixtures/axiom-control.lean +39 -0
- package/src/proof/fnv.lean +99 -0
- package/src/proof/generated-theorems.json +207 -0
- package/src/proof/generated.lean +101 -0
- package/src/proof/imagined.lean +492 -0
- package/src/proof/index.lean +98 -18
- package/src/proof/index.ts +2 -2
- package/src/proof/involution.lean +90 -0
- package/src/proof/ledgerclaims.lean +84 -0
- package/src/proof/light.lean +135 -0
- package/src/proof/mechanical.lean +421 -0
- package/src/proof/merkaba.lean +89 -0
- package/src/proof/merkle.lean +132 -0
- package/src/proof/nim.lean +105 -0
- package/src/proof/phenomena.lean +90 -0
- package/src/proof/priorart.lean +189 -0
- package/src/proof/quantum.lean +161 -0
- package/src/proof/reach.lean +84 -0
- package/src/proof/recovered.lean +56 -0
- package/src/proof/reversal.lean +73 -0
- package/src/proof/rights.lean +157 -0
- package/src/proof/sequences.lean +105 -0
- package/src/proof/speed.lean +129 -0
- package/src/proof/split.lean +180 -0
- package/src/proof/theorems.lean +48 -1
- package/src/proof/trial-all.json +15260 -0
- package/src/proof/z9.lean +92 -0
- package/src/proof/z9plus.lean +208 -0
- package/src/proofs.lisp +3 -3
- package/src/prove/emit.ts +115 -0
- package/src/prove/index.ts +143 -0
- package/src/prove/translate.ts +638 -0
- package/src/publication/index.ts +363 -0
- package/src/quantum/field.ts +73 -0
- package/src/quantum/tree.ts +79 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// THE PUBLICATION BODY — one text, rendered on the web page and deposited at Zenodo, byte for byte.
|
|
2
|
+
//
|
|
3
|
+
// These had drifted apart while both were being improved. The page said a theorem was proved "exhausting
|
|
4
|
+
// 4 cases"; the deposition deliberately omitted that number because domainOf() reads numerals off the
|
|
5
|
+
// statement and returns 4 for a proposition that walks the 24 permutations its neighbour counts. Both were
|
|
6
|
+
// written carefully. They still disagreed, in public, about the same theorem — which is what happens to
|
|
7
|
+
// two descriptions of one thing that are maintained in two places.
|
|
8
|
+
//
|
|
9
|
+
// So there is one place now. The page imports it, the Zenodo record imports it, and a gate compares them.
|
|
10
|
+
//
|
|
11
|
+
// THE COUNT IS PUBLISHED AS A LOWER BOUND, because that is what it is. domainOf() multiplies the numerals
|
|
12
|
+
// it finds in a statement: for `List.range 9` that is exact, and for the permutations of [1,2,4,8] it
|
|
13
|
+
// returns 4 while the kernel walks the 24 that perms_of_four_is_factorial decides there are. The page has
|
|
14
|
+
// been printing it as an exact count, and a citable record must not. Calling it a lower bound is true of
|
|
15
|
+
// both cases and costs the claim nothing.
|
|
16
|
+
//
|
|
17
|
+
// THE CASE COUNT IS HANDLED THE WAY THE PAPER ALREADY HANDLED IT. paper.ts has long distinguished
|
|
18
|
+
// `cases > 1` — "exhausting N cases" — from a single-case declaration, which it calls "by evaluation; no
|
|
19
|
+
// domain is walked". That distinction is honest and it is the deposit's own, and it was missing from my
|
|
20
|
+
// deposition prose, which told every record it had walked its whole domain. 112 of 336 had not.
|
|
21
|
+
import { readFileSync } from 'node:fs'
|
|
22
|
+
import { createHash } from 'node:crypto'
|
|
23
|
+
import { domainOf, leanSource, type LeanTheorem } from '../api/index.ts'
|
|
24
|
+
import { toUuid } from '../0/index.ts'
|
|
25
|
+
import { toLatex } from '../latex/index.ts'
|
|
26
|
+
import { treeOf, stats } from '../quantum/tree.ts'
|
|
27
|
+
|
|
28
|
+
/** THE FUNDING STATEMENT, read from .github/FUNDING.yml so the repository declares it in one place.
|
|
29
|
+
*
|
|
30
|
+
* Zenodo's `grants` field accepts only awards registered with OpenAIRE, and its custom-award lookup is
|
|
31
|
+
* powered by ROR — a funder must exist in one of those registries to be named. This work has no
|
|
32
|
+
* institutional grant and no registered funder, so NO `grants` entry is emitted. Inventing one, or
|
|
33
|
+
* attaching an unrelated funder DOI to make the record look institutionally backed, would be a false
|
|
34
|
+
* statement in permanent metadata about who paid for the work.
|
|
35
|
+
*
|
|
36
|
+
* What is true is stated instead, in the record's own text: independent, unfunded by any grant, supported
|
|
37
|
+
* by direct contribution. That is a funding statement a reader can act on, and it costs nothing to be
|
|
38
|
+
* accurate about. */
|
|
39
|
+
export const FUNDING = (() => {
|
|
40
|
+
try {
|
|
41
|
+
const y = readFileSync('.github/FUNDING.yml', 'utf8')
|
|
42
|
+
const url = (y.match(/https?:\/\/[^\s"'\]]+/) ?? [])[0] ?? ''
|
|
43
|
+
return { url, statement: 'Independent research. No institutional grant and no funder registered with '
|
|
44
|
+
+ 'OpenAIRE or ROR, so no award is claimed in this record'
|
|
45
|
+
+ (url ? `. Development is supported by direct contribution: ${url}` : '.') }
|
|
46
|
+
} catch { return { url: '', statement: 'Independent research; no grant funding is claimed in this record.' } }
|
|
47
|
+
})()
|
|
48
|
+
|
|
49
|
+
/** THE CANONICAL ORIGIN, read from the site's own declaration rather than typed here.
|
|
50
|
+
*
|
|
51
|
+
* This was hardcoded as ceccec.github.io, and that host 301-REDIRECTS to ceccec.psg.bg — which is what
|
|
52
|
+
* .vitepress/config.ts declares, what the sitemap uses, and what every page emits as its own
|
|
53
|
+
* <link rel="canonical">. So the JSON-LD on 653 pages and the isDocumentedBy and references relations on
|
|
54
|
+
* 338 deposition records all named a redirect instead of the resource.
|
|
55
|
+
*
|
|
56
|
+
* A citation index following a 301 gets a redirect, not the page; a redirect can lapse or be repointed by
|
|
57
|
+
* whoever controls the old host; and a permanent record naming a non-canonical URL cannot be corrected
|
|
58
|
+
* once it is minted. Found by resolving a URL rather than reading it — the same lesson the concept DOI
|
|
59
|
+
* taught, applied to this deposit's own outbound links, which I had never resolved. */
|
|
60
|
+
export const SITE = (() => {
|
|
61
|
+
const cfg = readFileSync('.vitepress/config.ts', 'utf8')
|
|
62
|
+
const m = cfg.match(/^const SITE = '([^']+)'/m)
|
|
63
|
+
if (!m) throw new Error('publication: .vitepress/config.ts declares no canonical SITE to derive from')
|
|
64
|
+
return m[1].replace(/\/+$/, '')
|
|
65
|
+
})()
|
|
66
|
+
export const REPO = 'https://github.com/ceccec/millennium-solutions'
|
|
67
|
+
/** READ FROM CITATION.cff, not typed. It was written out in two modules — this one and
|
|
68
|
+
* scripts/zenodo-theorems.ts — which is two places for the identifier that every deposition names as
|
|
69
|
+
* isPartOf and that the page prints. Two copies of a DOI is one drift away from a record citing a
|
|
70
|
+
* publication that is not the one it belongs to. zenodo-gate.ts already treats CITATION.cff as the
|
|
71
|
+
* authority and cross-checks README.md and .zenodo.json against it; this makes the code agree. */
|
|
72
|
+
export const CONCEPT_DOI = (() => {
|
|
73
|
+
const m = readFileSync('CITATION.cff', 'utf8').match(/^doi:\s*"?(10\.\d{4,}\/[^\s"]+)"?/m)
|
|
74
|
+
if (!m) throw new Error('CITATION.cff states no concept DOI — every deposition needs something to be part of')
|
|
75
|
+
return m[1]
|
|
76
|
+
})()
|
|
77
|
+
|
|
78
|
+
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<')
|
|
79
|
+
export const humanise = (n: string) => n.replace(/_/g, ' ')
|
|
80
|
+
|
|
81
|
+
/** Exhaustion is a claim about a domain. A closed identity is not one, and must not be dressed as one. */
|
|
82
|
+
export const walksADomain = (t: LeanTheorem): boolean => domainOf(t.statement) > 1
|
|
83
|
+
|
|
84
|
+
export const claimLine = (t: LeanTheorem): string =>
|
|
85
|
+
walksADomain(t)
|
|
86
|
+
? `<strong>decided over the whole of its finite domain by exhaustion, every case walked by the Lean 4 `
|
|
87
|
+
+ `kernel.</strong> Not sampled and not argued: within that domain there is no residual uncertainty and no `
|
|
88
|
+
+ `case left untested. The domain read off the statement is ${domainOf(t.statement).toLocaleString('en')} `
|
|
89
|
+
+ `cases — a LOWER BOUND, not a count: where a statement generates its own domain the kernel walks more `
|
|
90
|
+
+ `than the numerals name, and this one is read the same way the paper ranks by it.`
|
|
91
|
+
: `<strong>a closed identity, evaluated by the Lean 4 kernel.</strong> No domain is walked here — the `
|
|
92
|
+
+ `proposition reduces, and the kernel confirms the reduction. It is checked, not exhausted, and it is `
|
|
93
|
+
+ `not offered as the larger claim.`
|
|
94
|
+
|
|
95
|
+
export const proofLine = (t: LeanTheorem): string =>
|
|
96
|
+
`<em>Proof.</em> <code>${t.tactic}</code>`
|
|
97
|
+
+ (walksADomain(t) ? ` — exhausting its domain, of which the statement names ${domainOf(t.statement).toLocaleString('en')} cases.` : ' — by evaluation; no domain is walked.')
|
|
98
|
+
+ ` Checked sorry-free; <code>#print axioms</code> reports no axiom dependency. No Mathlib, no <code>native_decide</code>. □`
|
|
99
|
+
|
|
100
|
+
/** THE STATEMENT'S OWN ADDRESS, so one result is one publication no matter which repository proves it.
|
|
101
|
+
*
|
|
102
|
+
* Five repositories in this collective deposit into the same registry. Two of them proving the same
|
|
103
|
+
* proposition and minting two DOIs publishes one result twice, which is worse than the salami-slicing
|
|
104
|
+
* problem because neither record knows about the other. A theorem's NAME cannot decide this — names are
|
|
105
|
+
* local and namespaced — and neither can its key, which is an address for a ledger and differs per repo.
|
|
106
|
+
* What is the same across repositories is the STATEMENT.
|
|
107
|
+
*
|
|
108
|
+
* THE NORMALISATION IS THE SPECIFICATION, and it is written out because a prose description of a hash is
|
|
109
|
+
* how my metric-face protocol came to claim something the code did not do. Another repository computes the
|
|
110
|
+
* identical address by doing exactly this and nothing else:
|
|
111
|
+
*
|
|
112
|
+
* 1. collapse runs of Unicode whitespace to one space
|
|
113
|
+
* 2. remove a space ONLY where it does not sit between two of [A-Za-z0-9_]
|
|
114
|
+
* 3. replace `==` with `=` and `!=` with `≠`, so a Boolean decision and a proposition agree
|
|
115
|
+
* 4. toUuid of the result — RFC 9562 §5.8 uuidv8 over sha256, as src/0 defines it
|
|
116
|
+
*
|
|
117
|
+
* VERSION 1 STRIPPED ALL WHITESPACE AND LOWERCASED, AND BOTH WERE WRONG. uuidna-49 attacked the spec at my
|
|
118
|
+
* request and measured it: in Lean, application is by juxtaposition, so the space in `List.range 7` IS an
|
|
119
|
+
* operator and removing it yields `list.range7` — a different identifier. On this corpus 395 of 534
|
|
120
|
+
* statements carry such a space and 333 carry an uppercase identifier that lowercasing destroyed;
|
|
121
|
+
* `rawBytes A` became `rawbytesa`. No false merge resulted TODAY, so it was latent — but the normalised
|
|
122
|
+
* form stopped being a parseable statement, so nobody could recompute an address from a re-parse, and a
|
|
123
|
+
* future `rawBytesA` would have collided with `rawBytes A` permanently. Asking for the attack before
|
|
124
|
+
* minting 338 DOIs is the only reason this cost nothing.
|
|
125
|
+
*
|
|
126
|
+
* IT ADDRESSES TEXT, AND TEXT IS NOT A PROPOSITION. Both directions fail, and both were measured here:
|
|
127
|
+
*
|
|
128
|
+
* · An absence of collisions is not uniqueness. Two repositories can state one fact in genuinely
|
|
129
|
+
* different terms and this sees nothing.
|
|
130
|
+
* · A collision is not duplication either. `settledHere = 8` appears in merkle.lean, quantum.lean and
|
|
131
|
+
* reversal.lean, and `Merkle.settledHere` is not `Quantum.settledHere` — same text, three different
|
|
132
|
+
* propositions. I wrote "a collision is proof of duplication" in the first draft of this comment and
|
|
133
|
+
* the tree refuted it within the minute.
|
|
134
|
+
*
|
|
135
|
+
* So a collision is a CANDIDATE for review, never a verdict. Four of the six found here are genuine —
|
|
136
|
+
* one result under two names — and two are this limitation. The distinction needs a reader. */
|
|
137
|
+
export const STATEMENT_ADDRESS_SPEC =
|
|
138
|
+
'statementAddress = toUuid(collapse-runs(statement) with whitespace removed only where it is NOT between '
|
|
139
|
+
+ 'two of [\\p{L}\\p{N}_] (Unicode, u flag), case preserved, then "=="→"=" and "!="→"≠") '
|
|
140
|
+
+ '[v3: v2 amended by uuidna-49, Unicode class by erpax-94]'
|
|
141
|
+
|
|
142
|
+
/** THE CROSS-REPOSITORY MERGE KEY — sha256, deliberately not this deposit's own address function.
|
|
143
|
+
*
|
|
144
|
+
* erpax-94 identified the flaw in the plan we had converged on: agreeing a NORMALISATION is not enough,
|
|
145
|
+
* because their addresses are sha256-backed and mine are FNV-1a-backed, so two repositories can normalise
|
|
146
|
+
* a statement identically and still never collide numerically. A shared merge key needs the same
|
|
147
|
+
* normaliser AND the same hash, and my "zero collisions against their manifest" was computed under two
|
|
148
|
+
* different hashes — which is not a check, it is two repositories failing to find each other.
|
|
149
|
+
*
|
|
150
|
+
* SHA-256 IS THE RIGHT CHOICE HERE AND FNV IS THE RIGHT CHOICE FOR THE LEDGER, which is why this is a
|
|
151
|
+
* second function rather than a change to the first. A merge key is the adversarial case: it decides
|
|
152
|
+
* whether two parties are publishing one result, and a non-cryptographic hash gives integrity against
|
|
153
|
+
* accident only. The ledger address cannot move — it names every entry in an append-only chain, and
|
|
154
|
+
* rewriting it is the same objection that stopped the hexbit encoding.
|
|
155
|
+
*
|
|
156
|
+
* So: FNV addresses this deposit's own record, sha256 addresses the question "is this the same statement
|
|
157
|
+
* as yours". Different purposes, different guarantees, and neither pretending to be the other. */
|
|
158
|
+
export const MERGE_KEY_SPEC =
|
|
159
|
+
'mergeKey = sha256(normalised statement), hex, where normalised is the STATEMENT_ADDRESS_SPEC rule: '
|
|
160
|
+
+ 'collapse whitespace runs, remove a space only where it is NOT between two of [\\p{L}\\p{N}_] with the u '
|
|
161
|
+
+ 'flag (Unicode letters and numbers, NOT ASCII-only), keep case, '
|
|
162
|
+
+ '"=="→"=", "!="→"≠". sha256 and not this deposit\'s FNV-1a construction: a merge key decides whether two '
|
|
163
|
+
+ 'parties publish one result, which is the adversarial case.'
|
|
164
|
+
|
|
165
|
+
export const mergeKey = (statement: string): string =>
|
|
166
|
+
createHash('sha256').update(normalise(statement), 'utf8').digest('hex')
|
|
167
|
+
|
|
168
|
+
const normalise = (statement: string): string =>
|
|
169
|
+
statement
|
|
170
|
+
.replace(/\s+/gu, ' ')
|
|
171
|
+
// Remove a space only where it is not doing work. `\s(?![A-Za-z0-9_])` drops it before a symbol;
|
|
172
|
+
// `(?<![A-Za-z0-9_.])\s` drops it after one. A space between two alphanumerics survives, because in
|
|
173
|
+
// Lean that space IS the application operator.
|
|
174
|
+
// UNICODE-AWARE, not ASCII. `[A-Za-z0-9_]` excluded every Greek and blackboard identifier, so
|
|
175
|
+
// `σ (σ l)` collapsed to `σ(σl)` and `H₁(Σ₂) = ℤ⁴ with χ` to `…ℤ⁴withχ` — the same corruption as
|
|
176
|
+
// stripping all whitespace, narrowed to non-ASCII, in a language whose identifiers are Greek more often
|
|
177
|
+
// than Latin. erpax-94 measured it against ceccec.github.io's 832 statements: the two readings disagree
|
|
178
|
+
// on 211, a quarter, where two repositories computing "the same" key get different answers.
|
|
179
|
+
//
|
|
180
|
+
// THIS IS THE THIRD TIME THIS RULE HAS BEEN WRONG IN THE MERGING DIRECTION — strip-everything (refuted
|
|
181
|
+
// by uuidna-49), then ASCII-only (refuted here) — and each was agreed by three parties before anyone ran
|
|
182
|
+
// it against real statements. The rule kept being specified in prose and adopted before measurement.
|
|
183
|
+
// Hence the fixture beside it: a repo now checks its implementation against CASES, not against a sentence.
|
|
184
|
+
//
|
|
185
|
+
// Adopted here first because it costs this corpus nothing: 0 of 533 addresses change, measured before
|
|
186
|
+
// and after. A party that can move at zero risk should move first rather than wait for consensus.
|
|
187
|
+
.replace(/\s(?![\p{L}\p{N}_])|(?<![\p{L}\p{N}_.])\s/gu, '')
|
|
188
|
+
.replace(/==/g, '=').replace(/!=/g, '≠')
|
|
189
|
+
|
|
190
|
+
/** The normaliser, EXPORTED so a peer reimplementing the shared key reads the rule instead of a sentence
|
|
191
|
+
* describing it. Sending prose cost zeropoint-node six variants and an exhaustive apostrophe check, because
|
|
192
|
+
* "remove a space only where it is not doing lexical work" is an intent, not an algorithm. */
|
|
193
|
+
export const normaliseForFixture = (statement: string): string => normalise(statement)
|
|
194
|
+
|
|
195
|
+
export const statementAddress = (statement: string): string => toUuid(normalise(statement))
|
|
196
|
+
|
|
197
|
+
/** THE STATEMENT AS A STANDALONE LaTeX DOCUMENT, so the deposited record is a readable publication and not
|
|
198
|
+
* only source. A reader who downloads one of these gets the Lean the kernel checked AND the mathematics
|
|
199
|
+
* typeset from it — and the LaTeX is generated by the same translator scripts/latex-gate round-trips
|
|
200
|
+
* against the source, so it is the statement rather than a transcription of it.
|
|
201
|
+
*
|
|
202
|
+
* Synthesised at upload time rather than written to disk: 338 .tex files in the tree would be 338 more
|
|
203
|
+
* generated artefacts to keep in step, and they are derivable from the theorem in one line. */
|
|
204
|
+
export const theoremTex = (t: LeanTheorem, opts: { key: string | null }): string => {
|
|
205
|
+
const l = toLatex(t.statement)
|
|
206
|
+
return `\\documentclass[11pt]{article}\n`
|
|
207
|
+
+ `\\usepackage{amsmath}\n\\usepackage{amssymb}\n\\usepackage{hyperref}\n`
|
|
208
|
+
+ `\\newtheorem{theorem}{Theorem}\n`
|
|
209
|
+
+ `\\title{\\texttt{${t.name.replace(/_/g, '\\_')}}}\n`
|
|
210
|
+
+ `\\author{Tsvetan Rouschev\\\\\\small ORCID: \\href{https://orcid.org/0009-0000-7312-9778}{0009-0000-7312-9778}}\n`
|
|
211
|
+
+ `\\begin{document}\n\\maketitle\n\n`
|
|
212
|
+
+ `\\begin{theorem}[\\texttt{${t.name.replace(/_/g, '\\_')}}]\n`
|
|
213
|
+
+ (l ? `\\[ ${l} \\]\n` : `\\texttt{${t.statement.replace(/([&%#_$])/g, '\\$1')}}\n`)
|
|
214
|
+
+ `\\end{theorem}\n\n`
|
|
215
|
+
+ `\\noindent\\emph{Proof.} \\texttt{${t.tactic}} --- the Lean~4 kernel evaluated the proposition over its\n`
|
|
216
|
+
+ `domain, sorry-free, with \\texttt{\\#print axioms} reporting no axiom dependency. \\hfill$\\square$\n\n`
|
|
217
|
+
+ `\\section*{Source and provenance}\n`
|
|
218
|
+
+ `Declared in \\texttt{src/proof/${t.file}}. Repository: \\url{${REPO}}.\n`
|
|
219
|
+
+ (opts.key ? `Content-addressed record: \\url{${SITE}/theorem/${opts.key}}.\n` : '')
|
|
220
|
+
+ `Part of \\url{https://doi.org/${CONCEPT_DOI}}. Recompute with \\texttt{npm run lean}.\n\n`
|
|
221
|
+
+ `\\end{document}\n`
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** THE SAME RECORD AS STRUCTURED DATA — one theorem, one description, three renderings.
|
|
225
|
+
*
|
|
226
|
+
* Measured before this existed: all 2425 theorem pages carried ONE identical ld+json blob describing the
|
|
227
|
+
* repository as a software project. Every page told an indexer the same thing and nothing about the
|
|
228
|
+
* theorem on it — structured data that cannot vary with its subject is carrying no information about that
|
|
229
|
+
* subject, which is the failure a generic 3D scene would have been.
|
|
230
|
+
*
|
|
231
|
+
* Built from the same values the page renders and the Zenodo record deposits, so the three cannot drift:
|
|
232
|
+
* a reader, a search engine and a citation index are told the same thing about the same declaration.
|
|
233
|
+
* `sameAs` is deliberately absent until a DOI is minted — a link to a record that does not exist yet is
|
|
234
|
+
* worse than none. */
|
|
235
|
+
export const structuredData = (t: LeanTheorem, opts: { novelty: string; files: string[]; key: string | null }) => ({
|
|
236
|
+
'@context': 'https://schema.org',
|
|
237
|
+
'@type': 'ScholarlyArticle',
|
|
238
|
+
headline: humanise(t.name),
|
|
239
|
+
name: t.name,
|
|
240
|
+
description: `${t.statement} — ${walksADomain(t) ? 'decided by exhaustion over its whole finite domain' : 'a closed identity, evaluated'} by the Lean 4 kernel, sorry-free and axiom-free.`,
|
|
241
|
+
...(opts.key ? { identifier: opts.key, url: `${SITE}/theorem/${opts.key}` } : {}),
|
|
242
|
+
author: { '@type': 'Person', name: 'Tsvetan Rouschev', '@id': 'https://orcid.org/0009-0000-7312-9778' },
|
|
243
|
+
license: 'https://creativecommons.org/licenses/by-nc-nd/4.0/',
|
|
244
|
+
inLanguage: 'en',
|
|
245
|
+
isPartOf: { '@type': 'Dataset', name: 'Millennium Solutions — the ℤ/9 vortex framework', identifier: `https://doi.org/${CONCEPT_DOI}` },
|
|
246
|
+
citation: [
|
|
247
|
+
{ '@type': 'CreativeWork', name: `src/proof/${t.file}`, url: `${REPO}/blob/main/src/proof/${t.file}` },
|
|
248
|
+
{ '@type': 'CreativeWork', name: 'The paper — every statement typeset', url: `${SITE}/paper` },
|
|
249
|
+
{ '@type': 'CreativeWork', name: 'The axiom index', url: `${SITE}/AXIOMS` },
|
|
250
|
+
],
|
|
251
|
+
// The proof is the material; naming the files makes the claim checkable rather than merely stated.
|
|
252
|
+
material: opts.files,
|
|
253
|
+
// The cross-repository identity of this proposition — see statementAddress.
|
|
254
|
+
alternateName: statementAddress(t.statement),
|
|
255
|
+
creativeWorkStatus: opts.novelty.split('.')[0],
|
|
256
|
+
about: { '@type': 'DefinedTerm', name: t.namespace || t.file.replace('.lean', ''), inDefinedTermSet: `${SITE}/proofs` },
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
/** The canonical body. `novelty` and `files` come from the register and the import closure. */
|
|
260
|
+
export const publicationHtml = (t: LeanTheorem, opts: { novelty: string; files: string[]; key: string | null }): string =>
|
|
261
|
+
`<p><strong>${humanise(t.name)}</strong> — ${claimLine(t)}</p>`
|
|
262
|
+
+ `<p><strong>Statement (Lean):</strong></p><pre><code>${esc(t.statement)}</code></pre>`
|
|
263
|
+
// toLatex IS DOCUMENTED "or null when this grammar does not cover it", AND THIS CONSUMED IT AS A STRING.
|
|
264
|
+
// Every statement in the tree happened to parse, so the null branch was never taken and the crash never
|
|
265
|
+
// came — until one theorem said `(invOf d).isSome`, and the whole site build fell over on `null.replace`.
|
|
266
|
+
// A rendering layer must not be able to decide what may be proved. The grammar was widened to cover it,
|
|
267
|
+
// and this now says plainly when a statement is outside the grammar instead of failing the build: the
|
|
268
|
+
// Lean statement above is the source, and an absent LaTeX rendering of it costs the reader nothing.
|
|
269
|
+
+ ((tex) => tex
|
|
270
|
+
? `<p><strong>Statement (LaTeX):</strong></p><pre><code>${esc(tex)}</code></pre>`
|
|
271
|
+
: `<p><strong>Statement (LaTeX):</strong> not rendered — this statement uses notation the LaTeX `
|
|
272
|
+
+ `grammar in <code>src/latex</code> does not cover. The Lean above is the statement; nothing is `
|
|
273
|
+
+ `omitted from it.</p>`)(toLatex(t.statement))
|
|
274
|
+
+ `<p>${proofLine(t)}</p>`
|
|
275
|
+
// ── THE STATEMENT'S STRUCTURE, carried by the record as well as drawn on the page ──────────────────
|
|
276
|
+
// The theorem page renders this parse tree in three dimensions. A Zenodo record cannot run a script, so
|
|
277
|
+
// it carries the same structure as figures instead — the record and the page describe one object, and
|
|
278
|
+
// the gate compares them. Derived from the parse latex-gate round-trips against the Lean source, so the
|
|
279
|
+
// shape is the proposition rather than a picture of one.
|
|
280
|
+
+ (() => {
|
|
281
|
+
const tr = treeOf(t.statement)
|
|
282
|
+
if (!tr || !tr.length) return ''
|
|
283
|
+
const s2 = stats(tr)
|
|
284
|
+
return `<p><strong>Structure.</strong> The statement parses to a tree of <strong>${s2.nodes}</strong> `
|
|
285
|
+
+ `nodes across <strong>${s2.depth}</strong> levels, with <strong>${s2.leaves}</strong> leaves. That `
|
|
286
|
+
+ `parse is verified to read back symbol for symbol against the Lean source, so it is the `
|
|
287
|
+
+ `proposition's own structure and not a rendering of it; the theorem's page draws the same tree in `
|
|
288
|
+
+ `three dimensions, where height is depth in the parse, horizontal position is each symbol's `
|
|
289
|
+
+ `in-order rank, and depth is the size of the subtree beneath it.</p>`
|
|
290
|
+
})()
|
|
291
|
+
+ `<p><strong>What this record establishes.</strong> <strong>A dated, public, citable deposit</strong> of this `
|
|
292
|
+
+ `declaration and its machine-checked proof, recomputable from the sources attached to it. That is priority, `
|
|
293
|
+
+ `and the record proves it on its own. It is a different proposition from "no one has proved this before", `
|
|
294
|
+
+ `which only a search of the literature can settle, so the two are stated separately and neither is smuggled `
|
|
295
|
+
+ `in under the other.</p>`
|
|
296
|
+
+ `<p><strong>${opts.novelty}</strong></p>`
|
|
297
|
+
+ `<p><strong>Verification.</strong> The proof needs ${opts.files.length === 1 ? 'one file' : `${opts.files.length} files`}, `
|
|
298
|
+
+ `all attached: <code>${opts.files.join('</code>, <code>')}</code>. Check it with `
|
|
299
|
+
+ `<code>lake env lean ${opts.files.join(' ')}</code>, or clone <a href="${REPO}">${REPO}</a> and run `
|
|
300
|
+
+ `<code>npm run lean</code>.`
|
|
301
|
+
+ (opts.key ? ` The content-address of this declaration is recorded as <code>${opts.key}</code> at <a href="${SITE}/theorem/${opts.key}">${SITE}/theorem/${opts.key}</a>.` : '')
|
|
302
|
+
+ ` A content-address proves integrity, not truth: it fixes which statement was checked, not that the statement is significant.</p>`
|
|
303
|
+
+ `<p><strong>Funding.</strong> ${FUNDING.statement}</p>`
|
|
304
|
+
+ `<p><strong>Scope, stated as plainly as the claim.</strong> The declaration is decided over a finite domain. `
|
|
305
|
+
+ `It settles no Clay Millennium Problem, asserts no quantum speedup, and describes no physical system. `
|
|
306
|
+
+ `<strong>0/7.</strong></p>`
|
|
307
|
+
|
|
308
|
+
// ── the register, shared by the page and the deposition ─────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
// Surfaced in the record itself, in bold, rather than left to a file-level table nobody reads. A deposit
|
|
311
|
+
// that will not say what it claims is not being modest, it is being unreadable.
|
|
312
|
+
export const NOVELTY: Record<string, string> = {
|
|
313
|
+
'0': 'Prior art: NAMED AND CREDITED. This declaration restates or builds on work with an earlier author, '
|
|
314
|
+
+ 'recorded in src/proof/priorart.lean. No priority over that work is claimed here.',
|
|
315
|
+
'1': 'Novelty: UNCLASSIFIED — an open question, not a claim. No prior-art search has been performed for '
|
|
316
|
+
+ 'this source file, so this record asserts priority of deposit and nothing about the literature.',
|
|
317
|
+
'2': 'Novelty: CLAIMED. A named prior-art search was performed for this source file and returned nothing; '
|
|
318
|
+
+ 'the search itself is on the record in src/proof/priorart.lean.',
|
|
319
|
+
}
|
|
320
|
+
export const KIND: Record<string, string> = {
|
|
321
|
+
'0': 'restates named prior art, the earlier author credited',
|
|
322
|
+
'1': 'unclassified — no prior-art search has been performed, so no novelty is asserted',
|
|
323
|
+
'2': 'a named prior-art search was performed and found nothing',
|
|
324
|
+
}
|
|
325
|
+
/** file -> its row in the table, so a deposition quotes the ledger rather than a guess. */
|
|
326
|
+
export const kinds = (): Map<string, string> =>
|
|
327
|
+
new Map([...leanSource('priorart.lean').matchAll(/\(\s*\d+,\s*(\d+),\s*(?:true|false)\)\s*--\s*(\S+\.lean)/g)]
|
|
328
|
+
.map((m) => [m[2], m[1]] as [string, string]))
|
|
329
|
+
export const ownFiles = (): string[] => [...kinds()].filter(([, k]) => k === '1').map(([f]) => f)
|
|
330
|
+
|
|
331
|
+
/** THE FILES A RECORD MUST CARRY FOR ITS PROOF TO CHECK — the transitive closure of its imports.
|
|
332
|
+
*
|
|
333
|
+
* Measured before this existed: 245 of 336 depositions attached ONE file that needed others to compile.
|
|
334
|
+
* A reader downloading `lean_relation_eight` got mechanical.lean, which opens `import Address` and cannot
|
|
335
|
+
* be checked without address.lean and fnv.lean. For a deposit whose whole claim is that a stranger can
|
|
336
|
+
* recompute it rather than trust it, a record carrying an unbuildable proof is the claim failing at the
|
|
337
|
+
* one place it is tested. speed.lean is the deepest: four files behind it. */
|
|
338
|
+
export const closureOf = (file: string, seen = new Set<string>()): string[] => {
|
|
339
|
+
for (const m of leanSource(file).matchAll(/^import (\w+)/gm)) {
|
|
340
|
+
const dep = m[1].toLowerCase() + '.lean'
|
|
341
|
+
if (!seen.has(dep)) { seen.add(dep); closureOf(dep, seen) }
|
|
342
|
+
}
|
|
343
|
+
return [...seen].sort()
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** PER-THEOREM ATTRIBUTION, read from `-- prior_art_theorem: <name> — <credit>` in a file's frontmatter.
|
|
347
|
+
* Prior art was routed on the FILE, and a file-level row cannot say "own work except theorem 7". That is
|
|
348
|
+
* exactly merkaba.lean: its own construction throughout, with one declaration whose third conjunct
|
|
349
|
+
* 4 + 4 - 6 = 2 is the Euler characteristic. The register now carries the exception where the exception
|
|
350
|
+
* is, and a credited theorem says so in its own deposition instead of inheriting the file's status. */
|
|
351
|
+
export const creditedIn = (file: string): Map<string, string> => {
|
|
352
|
+
const out = new Map<string, string>()
|
|
353
|
+
const lines = leanSource(file).split('\n')
|
|
354
|
+
for (let i = 0; i < lines.length; i++) {
|
|
355
|
+
const m = lines[i].match(/^--\s*prior_art_theorem:\s*(\w+)\s*[—-]\s*(.*)$/)
|
|
356
|
+
if (!m) continue
|
|
357
|
+
let credit = m[2].trim()
|
|
358
|
+
for (let j = i + 1; j < lines.length && /^--\s{2,}\S/.test(lines[j]); j++) credit += ' ' + lines[j].replace(/^--\s+/, '').trim()
|
|
359
|
+
out.set(m[1], credit)
|
|
360
|
+
}
|
|
361
|
+
return out
|
|
362
|
+
}
|
|
363
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// THE QUANTUM FIELD — the geometry of src/proof/quantum.lean, computed rather than drawn.
|
|
2
|
+
//
|
|
3
|
+
// Every coordinate below comes from a function the Lean file decides over. Nothing here is a layout
|
|
4
|
+
// choice dressed as a result: the ring angle is the permutation's index in Lean's OWN enumeration order,
|
|
5
|
+
// the height is the value the control fold returns for that ordering, and the single node every edge lands
|
|
6
|
+
// on is the receipt. What the picture shows is what `quantum.lean` proves — that twenty-four orderings
|
|
7
|
+
// separate into five levels when combined naively and into one when canonicalised first.
|
|
8
|
+
//
|
|
9
|
+
// The definitions are transcribed from the Lean, not reimplemented from its prose. `agreesWithLean()`
|
|
10
|
+
// below recomputes the five numbers the kernel decided and is run by a gate, so a divergence between this
|
|
11
|
+
// module and the proof tree is a failing build rather than a wrong picture.
|
|
12
|
+
|
|
13
|
+
/** Every way of inserting x into a list — `Quantum.insertEverywhere`. */
|
|
14
|
+
const insertEverywhere = (x: number, l: number[]): number[][] =>
|
|
15
|
+
l.length === 0 ? [[x]] : [[x, ...l], ...insertEverywhere(x, l.slice(1)).map((r) => [l[0], ...r])]
|
|
16
|
+
|
|
17
|
+
/** All orderings — `Quantum.perms`. */
|
|
18
|
+
export const perms = (l: number[]): number[][] =>
|
|
19
|
+
l.length === 0 ? [[]] : perms(l.slice(1)).flatMap((p) => insertEverywhere(l[0], p))
|
|
20
|
+
|
|
21
|
+
/** Insertion sort, `Quantum.ins` and `Quantum.sort`. */
|
|
22
|
+
const ins = (x: number, l: number[]): number[] =>
|
|
23
|
+
l.length === 0 ? [x] : x <= l[0] ? [x, ...l] : [l[0], ...ins(x, l.slice(1))]
|
|
24
|
+
export const sort = (l: number[]): number[] => l.reduce((acc, x) => ins(x, acc), [] as number[])
|
|
25
|
+
|
|
26
|
+
/** Canonicalise, then combine — `Quantum.receipt`. */
|
|
27
|
+
export const receipt = (l: number[]): number => sort(l).reduce((a, b) => (a * 2 + b) % 9, 0)
|
|
28
|
+
|
|
29
|
+
/** The same combination without canonicalising — `Quantum.naive`, kept as the control. */
|
|
30
|
+
export const naive = (l: number[]): number => l.reduce((a, b) => (a * 2 + b) % 9, 0)
|
|
31
|
+
|
|
32
|
+
export const ORBIT = [1, 2, 4, 8] as const
|
|
33
|
+
|
|
34
|
+
export type Node = { order: number[]; index: number; naive: number; receipt: number; angle: number }
|
|
35
|
+
|
|
36
|
+
/** The twenty-four orderings, each carrying the two values that place it. */
|
|
37
|
+
export const field = (seed: readonly number[] = ORBIT): Node[] => {
|
|
38
|
+
const all = perms([...seed])
|
|
39
|
+
return all.map((order, index) => ({
|
|
40
|
+
order, index, naive: naive(order), receipt: receipt(order),
|
|
41
|
+
angle: (index / all.length) * Math.PI * 2,
|
|
42
|
+
}))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The distinct heights the control fold produces — the five of `the_uncanonicalised_fold_gives_many_answers`. */
|
|
46
|
+
export const naiveLevels = (seed: readonly number[] = ORBIT): number[] =>
|
|
47
|
+
[...new Set(field(seed).map((n) => n.naive))].sort((a, b) => a - b)
|
|
48
|
+
|
|
49
|
+
/** The collision structure of `the_receipt_is_not_injective`: two-element multisets over the digits, by receipt. */
|
|
50
|
+
export const collisions = (): { receipt: number; sets: number[][] }[] => {
|
|
51
|
+
const seen = new Set<string>()
|
|
52
|
+
const pairs: number[][] = []
|
|
53
|
+
for (let a = 1; a <= 9; a++) for (let b = 1; b <= 9; b++) {
|
|
54
|
+
const s = sort([a, b]); const k = s.join(',')
|
|
55
|
+
if (!seen.has(k)) { seen.add(k); pairs.push(s) }
|
|
56
|
+
}
|
|
57
|
+
const by = new Map<number, number[][]>()
|
|
58
|
+
for (const p of pairs) { const r = receipt(p); by.set(r, [...(by.get(r) ?? []), p]) }
|
|
59
|
+
return [...by.entries()].map(([receipt, sets]) => ({ receipt, sets })).sort((x, y) => x.receipt - y.receipt)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The five figures quantum.lean decided, recomputed here. A gate compares them; see scripts/quantum-field.ts. */
|
|
63
|
+
export const agreesWithLean = () => {
|
|
64
|
+
const f = field()
|
|
65
|
+
const c = collisions()
|
|
66
|
+
return {
|
|
67
|
+
perms_of_four_is_factorial: f.length, // 24
|
|
68
|
+
superposition_collapses_to_one: new Set(f.map((n) => n.receipt)).size, // 1
|
|
69
|
+
the_uncanonicalised_fold_gives_many_answers: naiveLevels().length, // 5
|
|
70
|
+
the_receipt_is_not_injective_sets: c.reduce((n, g) => n + g.sets.length, 0), // 45
|
|
71
|
+
the_receipt_is_not_injective_receipts: c.length, // 9
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// EVERY THEOREM AS A SHAPE — the parse tree of its statement, laid out in three dimensions.
|
|
2
|
+
//
|
|
3
|
+
// The problem with rendering 525 different kernel-accepted declarations is that a generic 3D scene is
|
|
4
|
+
// decoration: it looks the same whatever it is given, so it carries no information and the picture is a lie
|
|
5
|
+
// by implication. What makes this honest is that the geometry is the PARSE, already verified: latex-gate
|
|
6
|
+
// checks that every statement round-trips — the tree read back symbol for symbol against its source — so a
|
|
7
|
+
// tree drawn from it provably represents the proposition the kernel accepted and not an approximation of it.
|
|
8
|
+
//
|
|
9
|
+
// Nothing is chosen per theorem. Depth is the node's depth in the parse. Horizontal position is its in-order
|
|
10
|
+
// rank among the leaves, which is where the symbol actually sits when you read the statement left to right.
|
|
11
|
+
// Depth in Z is the size of the subtree under it, so a heavy conjunction stands behind a bare numeral. Two
|
|
12
|
+
// theorems with the same structure get the same shape, which is the property that makes the picture worth
|
|
13
|
+
// looking at: shape differences are statement differences.
|
|
14
|
+
import { parse, roundTrips } from '../latex/index.ts'
|
|
15
|
+
|
|
16
|
+
export type TreeNode = {
|
|
17
|
+
id: number; parent: number | null
|
|
18
|
+
label: string; kind: string
|
|
19
|
+
depth: number; order: number; weight: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Ast = Record<string, unknown>
|
|
23
|
+
|
|
24
|
+
const kindOf = (n: Ast): string => String(n.t ?? 'node')
|
|
25
|
+
const labelOf = (n: Ast): string => {
|
|
26
|
+
const t = kindOf(n)
|
|
27
|
+
if (t === 'bin' || t === 'un') return String(n.op ?? '?')
|
|
28
|
+
if (t === 'id' || t === 'num') return String(n.v ?? '?')
|
|
29
|
+
if (t === 'app') return 'apply'
|
|
30
|
+
if (t === 'list') return '[…]'
|
|
31
|
+
return t
|
|
32
|
+
}
|
|
33
|
+
const kidsOf = (n: Ast): Ast[] => {
|
|
34
|
+
const out: Ast[] = []
|
|
35
|
+
for (const k of ['l', 'r', 'f', 'a', 'e', 'body']) {
|
|
36
|
+
const v = n[k]
|
|
37
|
+
if (v && typeof v === 'object') out.push(v as Ast)
|
|
38
|
+
}
|
|
39
|
+
for (const k of ['items', 'args', 'params']) {
|
|
40
|
+
const v = n[k]
|
|
41
|
+
if (Array.isArray(v)) for (const x of v) if (x && typeof x === 'object') out.push(x as Ast)
|
|
42
|
+
}
|
|
43
|
+
return out
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const size = (n: Ast): number => 1 + kidsOf(n).reduce((s, k) => s + size(k), 0)
|
|
47
|
+
|
|
48
|
+
/** The tree of a statement, or null when it does not parse — a page shows nothing rather than a guess. */
|
|
49
|
+
export const treeOf = (statement: string): TreeNode[] | null => {
|
|
50
|
+
let ast: Ast
|
|
51
|
+
try { ast = parse(statement) as unknown as Ast } catch { return null }
|
|
52
|
+
const nodes: TreeNode[] = []
|
|
53
|
+
let next = 0, leaf = 0
|
|
54
|
+
const walk = (n: Ast, parent: number | null, depth: number): number => {
|
|
55
|
+
const id = next++
|
|
56
|
+
const kids = kidsOf(n)
|
|
57
|
+
const placeholder: TreeNode = { id, parent, label: labelOf(n), kind: kindOf(n), depth, order: 0, weight: size(n) }
|
|
58
|
+
nodes.push(placeholder)
|
|
59
|
+
if (!kids.length) { placeholder.order = leaf++; return id }
|
|
60
|
+
const childOrders = kids.map((k) => walk(k, id, depth + 1))
|
|
61
|
+
// An internal node sits at the mean of its children — the position the symbol occupies when read.
|
|
62
|
+
const kidNodes = childOrders.map((c) => nodes.find((x) => x.id === c)!)
|
|
63
|
+
placeholder.order = kidNodes.reduce((s, k) => s + k.order, 0) / kidNodes.length
|
|
64
|
+
return id
|
|
65
|
+
}
|
|
66
|
+
walk(ast, null, 0)
|
|
67
|
+
return nodes
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Whether this statement's rendering is backed by a verified round-trip, stated per theorem. */
|
|
71
|
+
export const verified = (statement: string): boolean => {
|
|
72
|
+
try { return roundTrips(statement) } catch { return false }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const stats = (t: TreeNode[]) => ({
|
|
76
|
+
nodes: t.length,
|
|
77
|
+
depth: Math.max(...t.map((n) => n.depth)) + 1,
|
|
78
|
+
leaves: t.filter((n) => !t.some((m) => m.parent === n.id)).length,
|
|
79
|
+
})
|