Skip to content

Commit 8e8e23a

Browse files
feat(english-gonol): measure corpus-native letter density outside the construct
- Count exact admitted character occurrences through the constructed word_characters occurrence relations of the verified v2 construct; no retokenizing, case-folding, or normalization - Record corpus hashes (OEWN commit, source-tree sha256) and builder hashes (construct schema/version, construct receipt, construct.db sha256, UCNS pin) - Record exact integer count per admitted scalar, total, exact reduced frequency fractions, and 6903 reduced pairwise ratios between scalars - Generic density table stays outside the construct; measured frequency does not imply any geometric density law (hmmm) - Evidence: experiments/density-v0/density.json + density.md total 1,739,949 occurrences over 118 admitted scalars density receipt 9e9f55ea6b9cffe86142383209d511ea13462c56193c789b09284c17e4046569
1 parent f394ba9 commit 8e8e23a

4 files changed

Lines changed: 710 additions & 0 deletions

File tree

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
# === MODULE_BUILD ===
2+
# id: english_gonol_corpus_native_density
3+
# module_name: density_run
4+
# module_kind: measurement
5+
# summary: counts exact admitted character occurrences through the constructed word-character occurrence relations and records corpus-native frequency density with full provenance
6+
# owner: Erin Spencer
7+
# public_surface: SCHEMA, VERSION, DensityError, DensityResult, build_density, run
8+
# internal_surface: read-only construct inspection, exact Fraction arithmetic, canonical receipt serialization
9+
# auth_boundary: measures the already-constructed English Gonol v2 database; does not retokenize, normalize, or re-admit corpus text; supplies no geometry
10+
# storage_boundary: one caller-selected out directory with density.json and density.md
11+
# network_boundary: none
12+
# user_data_boundary: none
13+
# admin_only: false
14+
# tests: tests.test_density_run
15+
# rollout: stack-local research measurement outside the construct; no canon or semantic measurement promotion
16+
# rollback: delete this module and its generated output directory
17+
# requires: english_gonol_full_construct
18+
# since: 2026-09-15
19+
# unresolved: what measured density does geometrically remains unresolved
20+
# === END MODULE_BUILD ===
21+
22+
# === CONTRACTS ===
23+
# id: density_counts_constructed_occurrence_relations
24+
# given: a verified English Gonol v2 construct.db
25+
# then: every letter count is taken from the constructed word_characters occurrence relations joined to shared character identities, never by retokenizing or normalizing source text
26+
# class: correctness
27+
# since: 2026-09-15
28+
#
29+
# id: density_fractions_are_exact
30+
# given: exact integer per-letter counts and an exact total
31+
# then: every frequency is recorded as an exact reduced Fraction of the total
32+
# class: correctness
33+
# since: 2026-09-15
34+
#
35+
# id: density_ratios_are_reduced
36+
# given: any pair of letters with exact integer counts
37+
# then: the ratio between their counts is recorded reduced to lowest terms
38+
# class: correctness
39+
# since: 2026-09-15
40+
#
41+
# id: density_records_provenance_and_receipt
42+
# given: a construct manifest and measured database hash
43+
# then: corpus and builder hashes, the construct receipt, and a canonical density receipt are all recorded
44+
# class: correctness
45+
# since: 2026-09-15
46+
#
47+
# id: density_stays_outside_the_construct
48+
# given: a density result
49+
# then: it is a separate measurement table that modifies no construct table and invents no geometry
50+
# class: doctrine
51+
# since: 2026-09-15
52+
#
53+
# id: density_fails_closed_on_wrong_construct_schema
54+
# given: a construct manifest whose schema is not english-gonol.full-construct
55+
# then: build_density raises DensityError
56+
# class: safety
57+
# since: 2026-09-15
58+
# === END CONTRACTS ===
59+
60+
"""Corpus-native letter density from the constructed occurrence relations.
61+
62+
The English Gonol v2 construct materializes one shared identity per exact
63+
character scalar and one ``word_characters`` occurrence relation per ordered
64+
character reference inside a word. This measurement reads only those
65+
constructed relations::
66+
67+
characters -> id, scalar, public_position
68+
word_characters -> word_id, ordinal, character_id
69+
70+
Each admitted character scalar gets an exact integer occurrence count, an
71+
exact frequency fraction of the total, and exact reduced pairwise ratios
72+
between scalars. Nothing is retokenized, case-folded, normalized, or
73+
re-admitted; the generic density table stays outside the construct.
74+
75+
hmmm: frequency becomes measured; what density does geometrically remains
76+
unresolved.
77+
"""
78+
79+
from __future__ import annotations
80+
81+
from dataclasses import dataclass
82+
from fractions import Fraction
83+
from hashlib import sha256
84+
import json
85+
from pathlib import Path
86+
import sqlite3
87+
from typing import Any
88+
89+
SCHEMA = "english-gonol.corpus-native-density"
90+
VERSION = "1.0.0"
91+
CONSTRUCT_SCHEMA = "english-gonol.full-construct"
92+
93+
_HMMM = (
94+
"frequency becomes measured; "
95+
"what density does geometrically remains unresolved"
96+
)
97+
98+
99+
class DensityError(ValueError):
100+
"""Raised when the density measurement fails closed."""
101+
102+
103+
@dataclass(frozen=True)
104+
class DensityResult:
105+
schema: str
106+
version: str
107+
corpus: dict[str, Any]
108+
builder: dict[str, Any]
109+
total: int
110+
letters: tuple[tuple[str, int | None, int, str], ...]
111+
ratios: tuple[tuple[str, str, str], ...]
112+
hmmm: str
113+
receipt_sha256: str
114+
115+
def as_dict(self) -> dict[str, Any]:
116+
return {
117+
"schema": self.schema,
118+
"version": self.version,
119+
"corpus": self.corpus,
120+
"builder": self.builder,
121+
"total": self.total,
122+
"letters": [
123+
{
124+
"scalar": scalar,
125+
"public_position": public_position,
126+
"count": count,
127+
"frequency_fraction": fraction,
128+
}
129+
for scalar, public_position, count, fraction in self.letters
130+
],
131+
"ratios": [
132+
{"a": a, "b": b, "reduced_ratio": ratio}
133+
for a, b, ratio in self.ratios
134+
],
135+
"hmmm": self.hmmm,
136+
"receipt_sha256": self.receipt_sha256,
137+
}
138+
139+
def canonical_bytes(self) -> bytes:
140+
payload = self.as_dict()
141+
payload.pop("receipt_sha256", None)
142+
return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
143+
144+
def receipt_bytes(self) -> bytes:
145+
return json.dumps(self.as_dict(), sort_keys=True, separators=(",", ":")).encode("utf-8")
146+
147+
148+
def _load_manifest(path: Path) -> dict[str, Any]:
149+
try:
150+
manifest = json.loads(path.read_text(encoding="utf-8"))
151+
except (OSError, json.JSONDecodeError) as exc:
152+
raise DensityError(f"construct manifest is not readable JSON: {path}") from exc
153+
if manifest.get("schema") != CONSTRUCT_SCHEMA:
154+
raise DensityError(
155+
f"construct manifest schema must be {CONSTRUCT_SCHEMA}"
156+
)
157+
return manifest
158+
159+
160+
def _hash_file(path: Path) -> str:
161+
digest = sha256()
162+
with path.open("rb") as handle:
163+
while True:
164+
chunk = handle.read(8 * 1024 * 1024)
165+
if not chunk:
166+
break
167+
digest.update(chunk)
168+
return digest.hexdigest()
169+
170+
171+
def build_density(construct_db: Path, construct_manifest: Path) -> DensityResult:
172+
"""Measure corpus-native letter density through the constructed relations only."""
173+
174+
manifest = _load_manifest(construct_manifest)
175+
if not construct_db.is_file():
176+
raise DensityError(f"construct database does not exist: {construct_db}")
177+
178+
construct_db_sha256 = _hash_file(construct_db)
179+
connection = sqlite3.connect(f"file:{construct_db}?mode=ro", uri=True)
180+
try:
181+
rows = connection.execute(
182+
"""
183+
SELECT c.scalar, c.public_position, COUNT(wc.character_id)
184+
FROM characters AS c
185+
LEFT JOIN word_characters AS wc ON wc.character_id = c.id
186+
GROUP BY c.id, c.scalar, c.public_position
187+
ORDER BY c.id
188+
"""
189+
).fetchall()
190+
except sqlite3.Error as exc:
191+
raise DensityError(f"construct database is not readable: {exc}") from exc
192+
finally:
193+
connection.close()
194+
195+
if not rows:
196+
raise DensityError("word_characters occurrence relations are empty")
197+
198+
total = sum(count for _, _, count in rows)
199+
if total <= 0:
200+
raise DensityError("total admitted character occurrences must be positive")
201+
202+
letters: list[tuple[str, int | None, int, str]] = []
203+
for scalar, public_position, count in rows:
204+
fraction = Fraction(count, total)
205+
letters.append(
206+
(
207+
scalar,
208+
public_position,
209+
count,
210+
f"{fraction.numerator}/{fraction.denominator}",
211+
)
212+
)
213+
214+
ratios: list[tuple[str, str, str]] = []
215+
for left in range(len(letters)):
216+
for right in range(left + 1, len(letters)):
217+
a_scalar = letters[left][0]
218+
b_scalar = letters[right][0]
219+
a_count = letters[left][2]
220+
b_count = letters[right][2]
221+
if b_count == 0:
222+
ratio_text = f"{a_count}/0"
223+
elif a_count == 0:
224+
ratio_text = f"0/{b_count}"
225+
else:
226+
ratio = Fraction(a_count, b_count)
227+
ratio_text = f"{ratio.numerator}/{ratio.denominator}"
228+
ratios.append((a_scalar, b_scalar, ratio_text))
229+
230+
payload: dict[str, Any] = {
231+
"schema": SCHEMA,
232+
"version": VERSION,
233+
"corpus": manifest["corpus"],
234+
"builder": {
235+
"construct_schema": manifest["schema"],
236+
"construct_version": manifest["version"],
237+
"construct_receipt_sha256": manifest.get("receipt_sha256"),
238+
"construct_db_sha256": construct_db_sha256,
239+
"ucns_commit": manifest.get("ucns", {}).get("commit"),
240+
"public_gonol_sha256": manifest.get("ucns", {}).get("public_gonol_sha256"),
241+
},
242+
"total": total,
243+
"letters": [
244+
{
245+
"scalar": scalar,
246+
"public_position": public_position,
247+
"count": count,
248+
"frequency_fraction": fraction,
249+
}
250+
for scalar, public_position, count, fraction in letters
251+
],
252+
"ratios": [
253+
{"a": a, "b": b, "reduced_ratio": ratio}
254+
for a, b, ratio in ratios
255+
],
256+
"hmmm": _HMMM,
257+
}
258+
receipt = sha256(
259+
json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
260+
).hexdigest()
261+
262+
return DensityResult(
263+
schema=SCHEMA,
264+
version=VERSION,
265+
corpus=manifest["corpus"],
266+
builder=payload["builder"],
267+
total=total,
268+
letters=tuple(letters),
269+
ratios=tuple(ratios),
270+
hmmm=_HMMM,
271+
receipt_sha256=receipt,
272+
)
273+
274+
275+
def _render_markdown(result: DensityResult) -> str:
276+
lines = [
277+
"# English Gonol corpus-native letter density",
278+
"",
279+
"Generic measurement table outside the construct.",
280+
"",
281+
"## Provenance",
282+
"",
283+
f"- corpus: {result.corpus.get('repository')} @ {result.corpus.get('commit')}",
284+
f"- source tree sha256: {result.corpus.get('source_tree_sha256')}",
285+
f"- construct schema: {result.builder.get('construct_schema')}",
286+
f"- construct version: {result.builder.get('construct_version')}",
287+
f"- construct receipt: {result.builder.get('construct_receipt_sha256')}",
288+
f"- construct database sha256: {result.builder.get('construct_db_sha256')}",
289+
f"- density receipt: {result.receipt_sha256}",
290+
"",
291+
f"Total admitted character occurrences: {result.total}",
292+
"",
293+
"## Per-letter counts and exact frequency fractions",
294+
"",
295+
"| scalar | public_position | count | frequency_fraction |",
296+
"|---|---:|---:|---|",
297+
]
298+
for scalar, public_position, count, fraction in result.letters:
299+
display = scalar if scalar != " " else "` `"
300+
lines.append(
301+
f"| {display} | {public_position} | {count} | {fraction} |"
302+
)
303+
lines.extend(
304+
[
305+
"",
306+
"## Reduced ratios between letters",
307+
"",
308+
f"Full pairwise reduced ratios ({len(result.ratios)} pairs) are in `density.json`.",
309+
"",
310+
"## hmmm",
311+
"",
312+
result.hmmm,
313+
"",
314+
]
315+
)
316+
return "\n".join(lines)
317+
318+
319+
def run(
320+
construct_db: Path,
321+
construct_manifest: Path,
322+
out_dir: Path,
323+
*,
324+
overwrite: bool = False,
325+
) -> DensityResult:
326+
"""Build the density measurement and write ``density.json`` and ``density.md``."""
327+
328+
result = build_density(construct_db, construct_manifest)
329+
out_dir.mkdir(parents=True, exist_ok=True)
330+
json_path = out_dir / "density.json"
331+
markdown_path = out_dir / "density.md"
332+
if not overwrite and (json_path.exists() or markdown_path.exists()):
333+
raise DensityError(f"output files already exist in {out_dir}; pass --overwrite")
334+
json_path.write_text(
335+
result.receipt_bytes().decode("utf-8") + "\n",
336+
encoding="utf-8",
337+
)
338+
markdown_path.write_text(_render_markdown(result), encoding="utf-8")
339+
return result
340+
341+
342+
def main(argv: list[str] | None = None) -> int:
343+
import argparse
344+
345+
parser = argparse.ArgumentParser(description=__doc__)
346+
parser.add_argument("--construct-db", required=True, help="English Gonol v2 construct.db path")
347+
parser.add_argument("--construct-manifest", required=True, help="construct manifest.json path")
348+
parser.add_argument("--out-dir", required=True, help="density output directory")
349+
parser.add_argument("--overwrite", action="store_true")
350+
args = parser.parse_args(argv)
351+
352+
try:
353+
result = run(
354+
Path(args.construct_db),
355+
Path(args.construct_manifest),
356+
Path(args.out_dir),
357+
overwrite=args.overwrite,
358+
)
359+
except DensityError as exc:
360+
raise SystemExit(f"density error: {exc}") from exc
361+
362+
print(json.dumps(
363+
{
364+
"schema": result.schema,
365+
"version": result.version,
366+
"total": result.total,
367+
"letters": len(result.letters),
368+
"ratios": len(result.ratios),
369+
"receipt_sha256": result.receipt_sha256,
370+
"hmmm": result.hmmm,
371+
},
372+
sort_keys=True,
373+
indent=2,
374+
))
375+
return 0
376+
377+
378+
if __name__ == "__main__":
379+
raise SystemExit(main())

research/english-gonol/experiments/density-v0/density.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)