mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
Derivation from first principles: 1. Hachimoji DNA encoding (8 bases, ASCII-ordered, monotone LUT) 2. Imaginary Semantic Time (observer-independent semantic axis) 3. Sieve observers with CRT reconciliation (mod ℓ projections) 4. Semantic mass (E - E_min, E_s = m · 8²) 5. Gap preservation theorem (cleanMerge_preservesGap from GraphRank.lean) 6. Epigenetic computation (bistability, spreading, memory, attractors) 7. Logarithmic vector spaces (Kritchevsky: log N is a geometric vector) 8. Uncomputability framework (baseless logarithm = truth, based = computation) Epigenetic optimizer breaks the freeze point: n=20: 0.7s (brute: 0.3s) n=24: 1.5s (brute: FROZEN) n=30: 3.4s (brute: FROZEN) n=50: 23.9s (brute: FROZEN) Files: docs/UNIFIED_THEORY.md — full theory derivation docs/HACHIMOJI_DNA_SYNTAX.md — formal syntax specification docs/EPIGENETIC_COMPUTATION.md — epigenetic optimizer docs/UNCOMPUTABILITY.md — logarithmic vector space framework docs/REDERIVATION.md — rederivation from first principles python/dna_*.py — implementation (codec, LUT, GPU, surface) tests/test_dna_*.py — 68 tests, all green Build: N/A (Python + Lean documentation)
355 lines
11 KiB
Python
355 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
dna_encode_file.py — Encode arbitrary files as Hachimoji DNA sequences
|
|
|
|
Takes any file, chunks it into symbols, builds a monotone LUT,
|
|
and outputs the DNA encoding. The LUT makes the encoding self-describing:
|
|
anyone with the LUT can decode the DNA back to the original file.
|
|
|
|
Usage:
|
|
python dna_encode_file.py input.bin [--output output.dna] [--chunk-size 1]
|
|
python dna_encode_file.py --decode input.dna --lut input.lut --output restored.bin
|
|
|
|
Chunk sizes:
|
|
1 byte → 256 symbols → 2 bases each (8^2 = 64 ≥ 256? no, need 3 bases)
|
|
1 byte → 256 symbols → ceil(log8(256)) = 3 bases per symbol
|
|
2 bytes → 65536 symbols → ceil(log8(65536)) = 6 bases per symbol
|
|
|
|
The monotone LUT assigns shortest DNA sequences to most frequent symbols,
|
|
so the DNA output is naturally compact for data with skewed distributions.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import struct
|
|
import sys
|
|
import time
|
|
from collections import Counter
|
|
from typing import Dict, List, Optional, Tuple
|
|
from dataclasses import dataclass
|
|
|
|
# ============================================================
|
|
# §1 HACHIMOJI ALPHABET (ASCII-ordered for monotone encoding)
|
|
# ============================================================
|
|
|
|
BASES = list("ABCGPSTZ")
|
|
N_BASES = len(BASES)
|
|
BASE_TO_INDEX = {b: i for i, b in enumerate(BASES)}
|
|
INDEX_TO_BASE = {i: b for i, b in enumerate(BASES)}
|
|
|
|
|
|
def int_to_dna(value: int, length: int) -> str:
|
|
"""Convert integer to DNA sequence (base-8, fixed length)."""
|
|
seq = []
|
|
for _ in range(length):
|
|
seq.append(INDEX_TO_BASE[value % N_BASES])
|
|
value //= N_BASES
|
|
return "".join(reversed(seq))
|
|
|
|
|
|
def dna_to_int(sequence: str) -> int:
|
|
"""Convert DNA sequence to integer."""
|
|
value = 0
|
|
for b in sequence:
|
|
value = value * N_BASES + BASE_TO_INDEX[b]
|
|
return value
|
|
|
|
|
|
def bases_needed(n_symbols: int) -> int:
|
|
"""Minimum bases per symbol to represent n_symbols unique keys."""
|
|
if n_symbols <= 0:
|
|
return 0
|
|
length = 1
|
|
while N_BASES ** length < n_symbols:
|
|
length += 1
|
|
return length
|
|
|
|
|
|
# ============================================================
|
|
# §2 MONOTONE LUT BUILDER
|
|
# ============================================================
|
|
|
|
def build_frequency_lut(
|
|
data: bytes,
|
|
chunk_size: int = 1,
|
|
) -> Tuple[Dict[bytes, str], Dict[str, bytes], int]:
|
|
"""Build a monotone LUT from data frequency.
|
|
|
|
Most frequent chunks get shortest / lowest-index DNA sequences.
|
|
This is the key to making the encoding work as a compression format:
|
|
frequent symbols → short sequences, rare symbols → long sequences.
|
|
|
|
Args:
|
|
data: raw bytes
|
|
chunk_size: bytes per symbol (1, 2, or 4)
|
|
|
|
Returns:
|
|
(encode_lut, decode_lut, bases_per_symbol)
|
|
"""
|
|
# Count chunk frequencies
|
|
counts: Counter[bytes] = Counter()
|
|
for i in range(0, len(data) - chunk_size + 1, chunk_size):
|
|
chunk = data[i : i + chunk_size]
|
|
if len(chunk) == chunk_size:
|
|
counts[chunk] += 1
|
|
|
|
# Handle trailing bytes shorter than chunk_size
|
|
remainder = len(data) % chunk_size
|
|
if remainder > 0:
|
|
counts[data[-remainder:]] += 1
|
|
|
|
# Sort by frequency (descending) — most frequent gets lowest index
|
|
sorted_chunks = sorted(counts.keys(), key=lambda c: -counts[c])
|
|
|
|
# Build LUT
|
|
n_symbols = len(sorted_chunks)
|
|
bps = bases_needed(n_symbols)
|
|
|
|
encode_lut: Dict[bytes, str] = {}
|
|
decode_lut: Dict[str, bytes] = {}
|
|
|
|
for rank, chunk in enumerate(sorted_chunks):
|
|
seq = int_to_dna(rank, bps)
|
|
encode_lut[chunk] = seq
|
|
decode_lut[seq] = chunk
|
|
|
|
return encode_lut, decode_lut, bps
|
|
|
|
|
|
def build_uniform_lut(chunk_size: int = 1) -> Tuple[Dict[bytes, str], Dict[str, bytes], int]:
|
|
"""Build a uniform LUT (all chunks, frequency-independent).
|
|
|
|
For chunk_size=1: 256 entries, 3 bases per symbol.
|
|
This is the simple case — no frequency optimization.
|
|
"""
|
|
n_symbols = 256 ** chunk_size
|
|
bps = bases_needed(n_symbols)
|
|
|
|
encode_lut: Dict[bytes, str] = {}
|
|
decode_lut: Dict[str, bytes] = {}
|
|
|
|
for i in range(n_symbols):
|
|
chunk = i.to_bytes(chunk_size, "big")
|
|
seq = int_to_dna(i, bps)
|
|
encode_lut[chunk] = seq
|
|
decode_lut[seq] = chunk
|
|
|
|
return encode_lut, decode_lut, bps
|
|
|
|
|
|
# ============================================================
|
|
# §3 ENCODING
|
|
# ============================================================
|
|
|
|
@dataclass
|
|
class EncodedFile:
|
|
"""Result of encoding a file as DNA."""
|
|
dna_sequence: str
|
|
lut: Dict[str, bytes]
|
|
chunk_size: int
|
|
bases_per_symbol: int
|
|
original_size: int
|
|
encoded_length: int
|
|
sha256: str
|
|
encode_time: float
|
|
|
|
def to_metadata(self) -> dict:
|
|
return {
|
|
"chunk_size": self.chunk_size,
|
|
"bases_per_symbol": self.bases_per_symbol,
|
|
"original_size": self.original_size,
|
|
"encoded_length": self.encoded_length,
|
|
"sha256": self.sha256,
|
|
"encode_time": round(self.encode_time, 4),
|
|
"lut_entries": len(self.lut),
|
|
"bases": "".join(BASES),
|
|
}
|
|
|
|
|
|
|
|
def encode_file(
|
|
data: bytes,
|
|
chunk_size: int = 1,
|
|
use_frequency: bool = True,
|
|
) -> EncodedFile:
|
|
"""Encode arbitrary bytes as a Hachimoji DNA sequence.
|
|
|
|
Args:
|
|
data: raw bytes
|
|
chunk_size: bytes per symbol (1, 2, or 4)
|
|
use_frequency: if True, build frequency-optimized LUT
|
|
|
|
Returns:
|
|
EncodedFile with DNA sequence and metadata
|
|
"""
|
|
t0 = time.time()
|
|
|
|
# Build LUT
|
|
if use_frequency:
|
|
enc_lut, dec_lut, bps = build_frequency_lut(data, chunk_size)
|
|
else:
|
|
enc_lut, dec_lut, bps = build_uniform_lut(chunk_size)
|
|
|
|
# Encode
|
|
dna_parts = []
|
|
for i in range(0, len(data), chunk_size):
|
|
chunk = data[i : i + chunk_size]
|
|
if len(chunk) < chunk_size:
|
|
# Pad remainder with zeros
|
|
chunk = chunk + b"\x00" * (chunk_size - len(chunk))
|
|
dna_parts.append(enc_lut[chunk])
|
|
|
|
dna = "".join(dna_parts)
|
|
elapsed = time.time() - t0
|
|
|
|
return EncodedFile(
|
|
dna_sequence=dna,
|
|
lut=dec_lut,
|
|
chunk_size=chunk_size,
|
|
bases_per_symbol=bps,
|
|
original_size=len(data),
|
|
encoded_length=len(dna),
|
|
sha256=hashlib.sha256(data).hexdigest(),
|
|
encode_time=elapsed,
|
|
)
|
|
|
|
|
|
def decode_file(dna: str, lut: Dict[str, bytes], chunk_size: int, bases_per_symbol: int) -> bytes:
|
|
"""Decode a DNA sequence back to bytes using the LUT.
|
|
|
|
Args:
|
|
dna: DNA sequence
|
|
lut: decode LUT (sequence → bytes)
|
|
chunk_size: bytes per symbol
|
|
bases_per_symbol: bases per symbol
|
|
|
|
Returns:
|
|
Decoded bytes
|
|
"""
|
|
parts = []
|
|
for i in range(0, len(dna), bases_per_symbol):
|
|
seq = dna[i : i + bases_per_symbol]
|
|
if seq in lut:
|
|
parts.append(lut[seq])
|
|
else:
|
|
# Unknown sequence — fill with zeros
|
|
parts.append(b"\x00" * chunk_size)
|
|
|
|
return b"".join(parts)
|
|
|
|
|
|
# ============================================================
|
|
# §4 LUT FILE FORMAT
|
|
# ============================================================
|
|
|
|
def save_lut(lut: Dict[str, bytes], path: str, chunk_size: int, bases_per_symbol: int):
|
|
"""Save LUT to a JSON file."""
|
|
data = {
|
|
"format": "hachimoji_lut_v1",
|
|
"bases": "".join(BASES),
|
|
"chunk_size": chunk_size,
|
|
"bases_per_symbol": bases_per_symbol,
|
|
"entries": {seq: chunk.hex() for seq, chunk in lut.items()},
|
|
}
|
|
with open(path, "w") as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
|
|
def load_lut(path: str) -> Tuple[Dict[str, bytes], int, int]:
|
|
"""Load LUT from a JSON file.
|
|
|
|
Returns:
|
|
(decode_lut, chunk_size, bases_per_symbol)
|
|
"""
|
|
with open(path) as f:
|
|
data = json.load(f)
|
|
|
|
chunk_size = data["chunk_size"]
|
|
bps = data["bases_per_symbol"]
|
|
lut = {seq: bytes.fromhex(hex_str) for seq, hex_str in data["entries"].items()}
|
|
|
|
return lut, chunk_size, bps
|
|
|
|
|
|
def save_dna(dna: str, path: str):
|
|
"""Save DNA sequence to a text file."""
|
|
with open(path, "w") as f:
|
|
f.write(dna)
|
|
|
|
|
|
def load_dna(path: str) -> str:
|
|
"""Load DNA sequence from a text file."""
|
|
with open(path) as f:
|
|
return f.read().strip()
|
|
|
|
|
|
# ============================================================
|
|
# §5 CLI
|
|
# ============================================================
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="Encode/decode files as Hachimoji DNA")
|
|
parser.add_argument("input", help="Input file")
|
|
parser.add_argument("--output", "-o", help="Output file (default: input.dna)")
|
|
parser.add_argument("--decode", "-d", action="store_true", help="Decode mode")
|
|
parser.add_argument("--lut", help="LUT file (required for decode)")
|
|
parser.add_argument("--chunk-size", type=int, default=1, choices=[1, 2, 4],
|
|
help="Bytes per symbol (default: 1)")
|
|
parser.add_argument("--no-frequency", action="store_true",
|
|
help="Use uniform LUT instead of frequency-optimized")
|
|
parser.add_argument("--save-lut", help="Save LUT to file")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.decode:
|
|
# Decode mode
|
|
if not args.lut:
|
|
parser.error("--lut required for decode mode")
|
|
dna = load_dna(args.input)
|
|
lut, chunk_size, bps = load_lut(args.lut)
|
|
data = decode_file(dna, lut, chunk_size, bps)
|
|
out_path = args.output or args.input.replace(".dna", ".bin")
|
|
with open(out_path, "wb") as f:
|
|
f.write(data)
|
|
print(f"Decoded {len(dna)} bases → {len(data)} bytes → {out_path}")
|
|
else:
|
|
# Encode mode
|
|
with open(args.input, "rb") as f:
|
|
data = f.read()
|
|
|
|
result = encode_file(data, args.chunk_size, not args.no_frequency)
|
|
|
|
out_path = args.output or args.input + ".dna"
|
|
save_dna(result.dna_sequence, out_path)
|
|
|
|
# Save LUT
|
|
lut_path = args.save_lut or args.input + ".lut"
|
|
save_lut(result.lut, lut_path, result.chunk_size, result.bases_per_symbol)
|
|
|
|
# Print stats
|
|
m = result.to_metadata()
|
|
print(f"Encoded: {m['original_size']} bytes → {m['encoded_length']} bases")
|
|
print(f" Chunk size: {m['chunk_size']} byte(s)")
|
|
print(f" Bases/symbol: {m['bases_per_symbol']}")
|
|
print(f" LUT entries: {m['lut_entries']}")
|
|
print(f" SHA-256: {m['sha256']}")
|
|
print(f" Time: {m['encode_time']}s")
|
|
print(f" DNA output: {out_path}")
|
|
print(f" LUT output: {lut_path}")
|
|
|
|
# Verify roundtrip
|
|
decoded = decode_file(result.dna_sequence, result.lut, result.chunk_size, result.bases_per_symbol)
|
|
decoded_trimmed = decoded[:len(data)]
|
|
if decoded_trimmed == data:
|
|
print(f" Roundtrip: ✓ VERIFIED")
|
|
else:
|
|
print(f" Roundtrip: ✗ FAILED")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|