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)
368 lines
11 KiB
Python
368 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
dna_codec.py — Hachimoji DNA Encoding/Decoding Layer
|
||
|
||
Translates between binary data and Hachimoji 8-base DNA sequences.
|
||
Uses the SilverSight Hachimoji alphabet (A, T, G, C, B, S, P, Z)
|
||
at 3 bits per base — 50% denser than standard 2-bit DNA encoding.
|
||
|
||
Encoding table (from HachimojiBase.lean):
|
||
000 → A (Φ) 001 → T (Λ) 010 → G (Ρ) 011 → C (Κ)
|
||
100 → B (Ω) 101 → S (Σ) 110 → P (Π) 111 → Z (Ζ)
|
||
|
||
Melting temperature contributions (nearest-neighbor simplified):
|
||
G/C pairs: 3 bonds → high Tm contribution
|
||
A/T pairs: 2 bonds → low Tm contribution
|
||
B/S, P/Z: synthetic pairs, assigned Tm values for encoding purposes
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import List, Tuple
|
||
|
||
# ============================================================
|
||
# §1 HACHIMOJI ALPHABET
|
||
# ============================================================
|
||
|
||
# 3-bit → base
|
||
BITS_TO_BASE = {
|
||
0b000: "A",
|
||
0b001: "T",
|
||
0b010: "G",
|
||
0b011: "C",
|
||
0b100: "B",
|
||
0b101: "S",
|
||
0b110: "P",
|
||
0b111: "Z",
|
||
}
|
||
|
||
# base → 3-bit
|
||
BASE_TO_BITS = {v: k for k, v in BITS_TO_BASE.items()}
|
||
|
||
# All 8 bases in order
|
||
HACHIMOJI_BASES = list("ATGCBSPZ")
|
||
|
||
# Greek ↔ Latin (from HachimojiBase.lean)
|
||
LATIN_TO_GREEK = {
|
||
"A": "Φ", "T": "Λ", "G": "Ρ", "C": "Κ",
|
||
"B": "Ω", "S": "Σ", "P": "Π", "Z": "Ζ",
|
||
}
|
||
GREEK_TO_LATIN = {v: k for k, v in LATIN_TO_GREEK.items()}
|
||
|
||
# ============================================================
|
||
# §2 MELTING TEMPERATURE MODEL
|
||
# ============================================================
|
||
|
||
# Per-base Tm contribution (°C, simplified nearest-neighbor model)
|
||
# Standard bases: G/C = 3 bonds, A/T = 2 bonds
|
||
# Synthetic bases: assigned for sorting purposes
|
||
TM_CONTRIBUTION = {
|
||
"A": 2.0, # 2 hydrogen bonds
|
||
"T": 2.0, # 2 hydrogen bonds
|
||
"G": 3.0, # 3 hydrogen bonds (canonical Watson-Crick)
|
||
"C": 3.0, # 3 hydrogen bonds (canonical Watson-Crick)
|
||
"B": 2.5, # synthetic, intermediate
|
||
"S": 2.5, # synthetic, intermediate
|
||
"P": 3.5, # synthetic, higher affinity (hachimoji pair)
|
||
"Z": 3.5, # synthetic, higher affinity (hachimoji pair)
|
||
}
|
||
|
||
# GC-equivalent weight for sorting (normalized)
|
||
# Maps each base to a "GC-equivalent" score for sorting purposes
|
||
GC_EQUIVALENT = {
|
||
"A": 0.0,
|
||
"T": 0.0,
|
||
"G": 1.0,
|
||
"C": 1.0,
|
||
"B": 0.5,
|
||
"S": 0.5,
|
||
"P": 1.5,
|
||
"Z": 1.5,
|
||
}
|
||
|
||
|
||
def melting_temperature(sequence: str) -> float:
|
||
"""Compute simplified melting temperature for a Hachimoji DNA sequence.
|
||
|
||
Uses a per-base contribution model (simplified from nearest-neighbor).
|
||
For sorting purposes — relative order matters more than absolute °C.
|
||
|
||
Args:
|
||
sequence: Hachimoji DNA string (characters from ATGCBSPZ)
|
||
|
||
Returns:
|
||
Melting temperature in °C (simplified)
|
||
"""
|
||
if not sequence:
|
||
return 0.0
|
||
base_tm = sum(TM_CONTRIBUTION.get(b, 0.0) for b in sequence)
|
||
# Wallace rule approximation: Tm ≈ sum of per-base contributions
|
||
return base_tm
|
||
|
||
|
||
def gc_content(sequence: str) -> float:
|
||
"""Compute GC-equivalent content for a Hachimoji sequence.
|
||
|
||
Standard GC content counts G+C / total. For Hachimoji, we use
|
||
the GC-equivalent weight: G/C = 1.0, B/S = 0.5, P/Z = 1.5, A/T = 0.0.
|
||
|
||
Args:
|
||
sequence: Hachimoji DNA string
|
||
|
||
Returns:
|
||
GC-equivalent content in [0, 1] (normalized)
|
||
"""
|
||
if not sequence:
|
||
return 0.0
|
||
total = sum(GC_EQUIVALENT.get(b, 0.0) for b in sequence)
|
||
# Normalize: max possible per base is 1.5 (P/Z), so divide by 1.5 * len
|
||
max_possible = 1.5 * len(sequence)
|
||
return total / max_possible if max_possible > 0 else 0.0
|
||
|
||
|
||
def raw_gc_content(sequence: str) -> float:
|
||
"""Standard GC content (G+C only, ignoring synthetic bases)."""
|
||
if not sequence:
|
||
return 0.0
|
||
gc = sum(1 for b in sequence if b in ("G", "C"))
|
||
return gc / len(sequence)
|
||
|
||
|
||
# ============================================================
|
||
# §3 ENCODING: bytes → Hachimoji DNA
|
||
# ============================================================
|
||
|
||
def bytes_to_bits(data: bytes) -> List[int]:
|
||
"""Convert bytes to a list of bits (MSB first per byte)."""
|
||
bits = []
|
||
for byte in data:
|
||
for i in range(7, -1, -1):
|
||
bits.append((byte >> i) & 1)
|
||
return bits
|
||
|
||
|
||
def bits_to_bytes(bits: List[int]) -> bytes:
|
||
"""Convert a list of bits back to bytes (MSB first per byte)."""
|
||
# Pad to multiple of 8
|
||
padded = bits + [0] * ((8 - len(bits) % 8) % 8)
|
||
result = bytearray()
|
||
for i in range(0, len(padded), 8):
|
||
byte = 0
|
||
for j in range(8):
|
||
byte = (byte << 1) | padded[i + j]
|
||
result.append(byte)
|
||
return bytes(result)
|
||
|
||
|
||
def encode_bytes_to_dna(data: bytes) -> str:
|
||
"""Encode arbitrary bytes to a Hachimoji DNA sequence.
|
||
|
||
Chunks bits into groups of 3, maps each to a base.
|
||
Pads with zeros if needed (last group may be short).
|
||
|
||
Args:
|
||
data: arbitrary bytes
|
||
|
||
Returns:
|
||
Hachimoji DNA string
|
||
"""
|
||
bits = bytes_to_bits(data)
|
||
# Pad to multiple of 3
|
||
while len(bits) % 3 != 0:
|
||
bits.append(0)
|
||
|
||
sequence = []
|
||
for i in range(0, len(bits), 3):
|
||
chunk = (bits[i] << 2) | (bits[i + 1] << 1) | bits[i + 2]
|
||
sequence.append(BITS_TO_BASE[chunk])
|
||
|
||
return "".join(sequence)
|
||
|
||
|
||
def decode_dna_to_bytes(sequence: str) -> bytes:
|
||
"""Decode a Hachimoji DNA sequence back to bytes.
|
||
|
||
Args:
|
||
sequence: Hachimoji DNA string
|
||
|
||
Returns:
|
||
Decoded bytes (may include padding zeros at the end)
|
||
"""
|
||
bits = []
|
||
for base in sequence:
|
||
if base not in BASE_TO_BITS:
|
||
raise ValueError(f"Invalid Hachimoji base: {base!r}")
|
||
chunk = BASE_TO_BITS[base]
|
||
bits.append((chunk >> 2) & 1)
|
||
bits.append((chunk >> 1) & 1)
|
||
bits.append(chunk & 1)
|
||
|
||
return bits_to_bytes(bits)
|
||
|
||
|
||
# ============================================================
|
||
# §4 BINARY VECTOR ENCODING: x ∈ {0,1}^n → DNA
|
||
# ============================================================
|
||
|
||
def encode_binary_vector(x: List[int], bits_per_var: int = 3) -> str:
|
||
"""Encode a binary vector as a Hachimoji DNA sequence.
|
||
|
||
Each variable gets `bits_per_var` bases (default 3 = one Hachimoji base).
|
||
This ensures every variable has a dedicated position in the sequence.
|
||
|
||
Args:
|
||
x: binary vector (list of 0s and 1s)
|
||
bits_per_var: number of bases per variable (default 3)
|
||
|
||
Returns:
|
||
Hachimoji DNA string of length len(x) * bits_per_var
|
||
"""
|
||
sequence = []
|
||
for val in x:
|
||
# Encode each variable as a base (0 → A, 1 → P for max Tm separation)
|
||
# Using A (low Tm) for 0 and P (high Tm) for 1
|
||
# This makes the Tm directly proportional to the number of 1s
|
||
for _ in range(bits_per_var):
|
||
sequence.append("A" if val == 0 else "P")
|
||
return "".join(sequence)
|
||
|
||
|
||
def decode_binary_vector(sequence: str, bits_per_var: int = 3) -> List[int]:
|
||
"""Decode a Hachimoji DNA sequence back to a binary vector.
|
||
|
||
Args:
|
||
sequence: Hachimoji DNA string
|
||
bits_per_var: bases per variable (must match encoding)
|
||
|
||
Returns:
|
||
Binary vector
|
||
"""
|
||
if len(sequence) % bits_per_var != 0:
|
||
raise ValueError(
|
||
f"Sequence length {len(sequence)} not divisible by bits_per_var={bits_per_var}"
|
||
)
|
||
|
||
result = []
|
||
for i in range(0, len(sequence), bits_per_var):
|
||
chunk = sequence[i : i + bits_per_var]
|
||
# Majority vote: if most bases are P → 1, else → 0
|
||
p_count = chunk.count("P")
|
||
result.append(1 if p_count > bits_per_var / 2 else 0)
|
||
|
||
return result
|
||
|
||
|
||
# ============================================================
|
||
# §5 QUBO ENCODING: energy → DNA sequence structure
|
||
# ============================================================
|
||
|
||
def qubo_energy(x: List[int], Q: List[List[float]]) -> float:
|
||
"""Compute QUBO energy E(x) = x^T Q x.
|
||
|
||
Args:
|
||
x: binary vector
|
||
Q: QUBO matrix (n×n, upper triangular or symmetric)
|
||
|
||
Returns:
|
||
Energy value
|
||
"""
|
||
n = len(x)
|
||
energy = 0.0
|
||
for i in range(n):
|
||
for j in range(n):
|
||
energy += Q[i][j] * x[i] * x[j]
|
||
return energy
|
||
|
||
|
||
def encode_qubo_solution(
|
||
x: List[int],
|
||
Q: List[List[float]],
|
||
bits_per_var: int = 3,
|
||
) -> Tuple[str, float]:
|
||
"""Encode a QUBO solution as a DNA sequence with energy metadata.
|
||
|
||
The sequence encodes the binary vector. The energy is stored as
|
||
a header (first 8 bases encode Q16_16 energy value).
|
||
|
||
Args:
|
||
x: binary solution vector
|
||
Q: QUBO matrix
|
||
bits_per_var: bases per variable
|
||
|
||
Returns:
|
||
(dna_sequence, energy)
|
||
"""
|
||
from q16_canonical import float_to_q16, q16_to_bytes, q16_from_bytes
|
||
|
||
energy = qubo_energy(x, Q)
|
||
|
||
# Encode energy as Q16_16 header (8 bases = 24 bits = 3 bytes)
|
||
energy_q16 = float_to_q16(energy)
|
||
energy_bytes = q16_to_bytes(energy_q16) # 4 bytes
|
||
energy_dna = encode_bytes_to_dna(energy_bytes) # ~11 bases
|
||
|
||
# Encode solution vector
|
||
solution_dna = encode_binary_vector(x, bits_per_var)
|
||
|
||
return energy_dna + "|" + solution_dna, energy
|
||
|
||
|
||
def decode_qubo_solution(
|
||
sequence: str,
|
||
n_vars: int,
|
||
bits_per_var: int = 3,
|
||
) -> Tuple[List[int], float]:
|
||
"""Decode a QUBO solution from DNA sequence.
|
||
|
||
Args:
|
||
sequence: DNA sequence with energy header
|
||
n_vars: number of variables in the QUBO
|
||
bits_per_var: bases per variable
|
||
|
||
Returns:
|
||
(binary_vector, energy)
|
||
"""
|
||
from q16_canonical import q16_from_bytes, q16_to_float
|
||
|
||
# Split header and body
|
||
parts = sequence.split("|")
|
||
if len(parts) != 2:
|
||
raise ValueError("Invalid QUBO DNA format: expected energy|solution")
|
||
|
||
energy_dna, solution_dna = parts
|
||
|
||
# Decode energy header
|
||
energy_bytes = decode_dna_to_bytes(energy_dna)
|
||
energy_q16 = q16_from_bytes(energy_bytes[:4]) # first 4 bytes
|
||
energy = q16_to_float(energy_q16)
|
||
|
||
# Decode solution vector
|
||
x = decode_binary_vector(solution_dna, bits_per_var)
|
||
|
||
return x[:n_vars], energy
|
||
|
||
|
||
# ============================================================
|
||
# §6 CONVENIENCE
|
||
# ============================================================
|
||
|
||
def dna_to_greek(sequence: str) -> str:
|
||
"""Convert Latin Hachimoji DNA to Greek notation."""
|
||
return "".join(LATIN_TO_GREEK.get(b, b) for b in sequence)
|
||
|
||
|
||
def greek_to_dna(sequence: str) -> str:
|
||
"""Convert Greek Hachimoji notation to Latin DNA."""
|
||
return "".join(GREEK_TO_LATIN.get(g, g) for g in sequence)
|
||
|
||
|
||
def sequence_stats(sequence: str) -> dict:
|
||
"""Compute useful statistics for a Hachimoji DNA sequence."""
|
||
return {
|
||
"length": len(sequence),
|
||
"gc_content": round(gc_content(sequence), 4),
|
||
"raw_gc_content": round(raw_gc_content(sequence), 4),
|
||
"melting_temperature": round(melting_temperature(sequence), 2),
|
||
"base_counts": {b: sequence.count(b) for b in HACHIMOJI_BASES if b in sequence},
|
||
"greek": dna_to_greek(sequence),
|
||
}
|