mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
242 lines
8.9 KiB
Python
242 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Probe numeric predictability in Bitcoin header JSONL streams.
|
|
|
|
The probe emits compression-route diagnostics only. It does not forecast price,
|
|
claim chain semantics, or prove Hutter improvement. The useful object is the
|
|
residual: if a declared predictor leaves small residuals, the stream may be a
|
|
good candidate for a receipt-bearing residual codec or logogram route.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import math
|
|
import statistics
|
|
import sys
|
|
from collections import Counter
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any, Iterable, TextIO
|
|
|
|
|
|
REPO = Path(__file__).resolve().parents[2]
|
|
OUT_DIR = REPO / "shared-data/data/blockchain_corpus"
|
|
|
|
|
|
def now_iso() -> str:
|
|
return datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")
|
|
|
|
|
|
def stable_json(value: Any) -> str:
|
|
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True, allow_nan=False)
|
|
|
|
|
|
def sha256_text(value: str) -> str:
|
|
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def iter_records(handle: TextIO, max_records: int | None) -> Iterable[dict[str, Any]]:
|
|
count = 0
|
|
for line in handle:
|
|
if not line.strip():
|
|
continue
|
|
yield json.loads(line)
|
|
count += 1
|
|
if max_records is not None and count >= max_records:
|
|
break
|
|
|
|
|
|
def signed_int(value: Any) -> int | None:
|
|
if value is None:
|
|
return None
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def hex_int(value: Any) -> int | None:
|
|
if value is None:
|
|
return None
|
|
try:
|
|
return int(str(value), 16)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def deltas(values: list[int]) -> list[int]:
|
|
return [b - a for a, b in zip(values, values[1:])]
|
|
|
|
|
|
def residuals(values: list[int], mode: str) -> list[int]:
|
|
if len(values) < 2:
|
|
return []
|
|
if mode == "previous":
|
|
return deltas(values)
|
|
if mode == "linear_delta":
|
|
first = deltas(values)
|
|
return deltas(first)
|
|
raise ValueError(mode)
|
|
|
|
|
|
def entropy_bits(values: list[int]) -> float:
|
|
if not values:
|
|
return 0.0
|
|
total = len(values)
|
|
counts = Counter(values)
|
|
return -sum((count / total) * math.log2(count / total) for count in counts.values())
|
|
|
|
|
|
def median_abs(values: list[int]) -> float:
|
|
return float(statistics.median(abs(value) for value in values)) if values else 0.0
|
|
|
|
|
|
def summarize_residual(field: str, mode: str, raw_values: list[int], resids: list[int]) -> dict[str, Any]:
|
|
raw_entropy = entropy_bits(raw_values)
|
|
resid_entropy = entropy_bits(resids)
|
|
unique_raw = len(set(raw_values))
|
|
unique_resid = len(set(resids))
|
|
improvement = raw_entropy - resid_entropy
|
|
return {
|
|
"field": field,
|
|
"predictor": mode,
|
|
"sample_count": len(raw_values),
|
|
"residual_count": len(resids),
|
|
"raw_unique": unique_raw,
|
|
"residual_unique": unique_resid,
|
|
"raw_entropy_bits_per_symbol": round(raw_entropy, 6),
|
|
"residual_entropy_bits_per_symbol": round(resid_entropy, 6),
|
|
"entropy_delta_bits_per_symbol": round(improvement, 6),
|
|
"median_abs_residual": round(median_abs(resids), 6),
|
|
"zero_residual_share": round(sum(1 for item in resids if item == 0) / len(resids), 6) if resids else 0.0,
|
|
"route_candidate": (
|
|
"ADMIT_RESIDUAL_ROUTE_CANDIDATE"
|
|
if improvement > 0 and unique_resid <= unique_raw
|
|
else "HOLD_RESIDUAL_ROUTE"
|
|
),
|
|
}
|
|
|
|
|
|
def longest_run(values: list[Any]) -> dict[str, Any]:
|
|
if not values:
|
|
return {"value": None, "length": 0}
|
|
best_value = values[0]
|
|
best_length = 1
|
|
current_value = values[0]
|
|
current_length = 1
|
|
for value in values[1:]:
|
|
if value == current_value:
|
|
current_length += 1
|
|
else:
|
|
if current_length > best_length:
|
|
best_value = current_value
|
|
best_length = current_length
|
|
current_value = value
|
|
current_length = 1
|
|
if current_length > best_length:
|
|
best_value = current_value
|
|
best_length = current_length
|
|
return {"value": best_value, "length": best_length}
|
|
|
|
|
|
def build_probe(records: list[dict[str, Any]], source_label: str) -> dict[str, Any]:
|
|
heights = [signed_int(row.get("height")) for row in records]
|
|
times = [signed_int(row.get("time")) for row in records]
|
|
mediantimes = [signed_int(row.get("mediantime")) for row in records]
|
|
ntx = [signed_int(row.get("nTx")) for row in records]
|
|
nonces = [signed_int(row.get("nonce")) for row in records]
|
|
chainwork = [hex_int(row.get("chainwork")) for row in records]
|
|
bits = [row.get("bits") for row in records]
|
|
versions = [signed_int(row.get("version")) for row in records]
|
|
|
|
numeric = {
|
|
"height": [value for value in heights if value is not None],
|
|
"time": [value for value in times if value is not None],
|
|
"mediantime": [value for value in mediantimes if value is not None],
|
|
"nTx": [value for value in ntx if value is not None],
|
|
"nonce": [value for value in nonces if value is not None],
|
|
"chainwork": [value for value in chainwork if value is not None],
|
|
"version": [value for value in versions if value is not None],
|
|
}
|
|
summaries = []
|
|
for field, values in numeric.items():
|
|
if len(values) < 3:
|
|
continue
|
|
summaries.append(summarize_residual(field, "previous", values, residuals(values, "previous")))
|
|
summaries.append(summarize_residual(field, "linear_delta", values, residuals(values, "linear_delta")))
|
|
|
|
summaries.sort(key=lambda item: item["entropy_delta_bits_per_symbol"], reverse=True)
|
|
bits_values = [value for value in bits if value is not None]
|
|
version_values = [value for value in versions if value is not None]
|
|
height_values = numeric["height"]
|
|
continuity_breaks = []
|
|
for previous, current in zip(height_values, height_values[1:]):
|
|
if current != previous + 1:
|
|
continuity_breaks.append({"previous": previous, "current": current})
|
|
|
|
payload = {
|
|
"schema": "blockchain_header_pattern_probe_v0",
|
|
"created_utc": now_iso(),
|
|
"source_label": source_label,
|
|
"claim_boundary": "Numeric residual and route-prior diagnostic only. This does not forecast price, prove consensus validity, claim compression gain, or claim Hutter Prize progress.",
|
|
"record_count": len(records),
|
|
"height_range": {
|
|
"start": height_values[0] if height_values else None,
|
|
"end": height_values[-1] if height_values else None,
|
|
"continuity_break_count": len(continuity_breaks),
|
|
"continuity_break_examples": continuity_breaks[:10],
|
|
},
|
|
"categorical_runs": {
|
|
"bits_unique": len(set(bits_values)),
|
|
"bits_longest_run": longest_run(bits_values),
|
|
"version_unique": len(set(version_values)),
|
|
"version_longest_run": longest_run(version_values),
|
|
},
|
|
"residual_summaries": summaries,
|
|
"top_route_candidates": [
|
|
item for item in summaries if item["route_candidate"] == "ADMIT_RESIDUAL_ROUTE_CANDIDATE"
|
|
][:10],
|
|
"hutter_feedback_rule": (
|
|
"Only fields with declared predictors, lower residual entropy, and replayable "
|
|
"height-contiguous provenance may be promoted into a Hutter/logogram fixture. "
|
|
"Compression gain remains HOLD until byte-exact codec baselines exist."
|
|
),
|
|
}
|
|
payload["payload_hash"] = sha256_text(stable_json({k: v for k, v in payload.items() if k != "payload_hash"}))
|
|
payload["decision"] = (
|
|
"ADMIT_HEADER_PATTERN_ROUTE_PRIORS"
|
|
if payload["height_range"]["continuity_break_count"] == 0 and payload["top_route_candidates"]
|
|
else "HOLD_HEADER_PATTERN_ROUTE_PRIORS"
|
|
)
|
|
return payload
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--source", default="-", help="Bitcoin header JSONL path or '-' for stdin")
|
|
parser.add_argument("--source-label", default=None)
|
|
parser.add_argument("--max-records", type=int, default=None)
|
|
parser.add_argument("--out", type=Path, default=None)
|
|
args = parser.parse_args()
|
|
|
|
if args.source == "-":
|
|
records = list(iter_records(sys.stdin, args.max_records))
|
|
source_label = args.source_label or "stdin"
|
|
else:
|
|
path = Path(args.source)
|
|
with path.open("r", encoding="utf-8") as handle:
|
|
records = list(iter_records(handle, args.max_records))
|
|
source_label = args.source_label or str(path)
|
|
|
|
payload = build_probe(records, source_label)
|
|
if args.out:
|
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
|
args.out.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
|
|
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|