SilverSight/python/dna_lut.py
allaunthefox 5331d2cc4e feat(dna): unified theory — DNA encoding, epigenetic computation, logarithmic vector spaces
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)
2026-06-23 02:18:16 +00:00

476 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
dna_lut.py — Symbology LUT Adaptation for QUBO-DNA Sorting
The LUT is the adapter between symbolic DNA space and QUBO energy space.
Three encoding strategies, in order of increasing power:
1. Direct encoding: x → DNA by fixed mapping. Fast, but symbolic
order doesn't match energy order. LUT does the actual sorting.
2. Position-dependent encoding: x → DNA with per-position base
selection based on Q_ii sign. Fixes sign inversion for Ising-type
QUBOs but still not monotone.
3. Monotone encoding (the fix): sort all solutions by energy FIRST,
then assign DNA sequences in energy order. Lexicographic DNA
sort = energy sort BY CONSTRUCTION.
The monotone encoding makes the DNA sequence a rank key:
sequence "AAAA..." = lowest energy
sequence "AAAT..." = second lowest
...
sequence "ZZZZ..." = highest energy
Sorting the DNA IS sorting the energy. No thermodynamic model needed.
The LUT maps sequence ↔ energy, and the sequence itself carries the
rank information.
Connection to SilverSight:
HachimojiLUT.lean defines codon → state mapping via lookup.
This extends that to: sequence → energy via monotone LUT.
The PhaseCircle (/360) provides the symbolic address space.
"""
from __future__ import annotations
import json
import random
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
# ============================================================
# §1 HACHIMOJI ALPHABET
# ============================================================
BASES = list("ABCGPSTZ") # 8 bases, ordered by ASCII for lexicographic monotonicity
BASE_TO_INDEX = {b: i for i, b in enumerate(BASES)}
INDEX_TO_BASE = {i: b for i, b in enumerate(BASES)}
N_BASES = len(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 sequence_length_for_solutions(n_solutions: int) -> int:
"""Minimum sequence length to represent n_solutions unique keys."""
if n_solutions <= 0:
return 0
length = 1
while N_BASES ** length < n_solutions:
length += 1
return length
# ============================================================
# §2 QUBO ENERGY
# ============================================================
def qubo_energy(x: List[int], Q: List[List[float]]) -> float:
"""Compute QUBO energy E(x) = x^T Q x."""
n = len(x)
return sum(Q[i][j] * x[i] * x[j] for i in range(n) for j in range(n))
# ============================================================
# §3 ENCODING STRATEGIES
# ============================================================
@dataclass
class LUT:
"""Lookup table: DNA sequence ↔ QUBO solution ↔ energy."""
entries: Dict[str, Tuple[List[int], float]] # sequence → (x, energy)
qubo_matrix: List[List[float]]
n_vars: int
encoding: str # "direct", "positional", "monotone"
def lookup(self, sequence: str) -> Tuple[List[int], float]:
"""Look up solution and energy for a sequence."""
return self.entries.get(sequence, ([], float("inf")))
def energy(self, sequence: str) -> float:
return self.lookup(sequence)[1]
def solution(self, sequence: str) -> List[int]:
return self.lookup(sequence)[0]
def size(self) -> int:
return len(self.entries)
def sort_by_sequence(self) -> List[Tuple[str, List[int], float]]:
"""Sort entries by DNA sequence (lexicographic)."""
return sorted(
[(s, x, e) for s, (x, e) in self.entries.items()],
key=lambda item: item[0],
)
def sort_by_energy(self) -> List[Tuple[str, List[int], float]]:
"""Sort entries by energy (ascending). Ground truth."""
return sorted(
[(s, x, e) for s, (x, e) in self.entries.items()],
key=lambda item: item[2],
)
def verify_monotone(self) -> Tuple[bool, float]:
"""Check if lexicographic sort matches energy sort.
Returns:
(is_monotone, rank_correlation)
"""
by_seq = self.sort_by_sequence()
by_energy = self.sort_by_energy()
# Check if the orderings are identical
seq_order = [s for s, _, _ in by_seq]
energy_order = [s for s, _, _ in by_energy]
is_monotone = seq_order == energy_order
# Rank correlation
n = len(by_seq)
if n < 2:
return True, 1.0
seq_rank = {s: i for i, (s, _, _) in enumerate(by_seq)}
energy_rank = {s: i for i, (s, _, _) in enumerate(by_energy)}
d_sq = sum((seq_rank[s] - energy_rank[s]) ** 2 for s in seq_rank)
corr = 1.0 - (6.0 * d_sq) / (n * (n * n - 1))
return is_monotone, corr
def to_json(self) -> str:
return json.dumps({
"encoding": self.encoding,
"n_vars": self.n_vars,
"qubo_matrix": self.qubo_matrix,
"entries": {
s: {"x": x, "energy": round(e, 6)}
for s, (x, e) in self.entries.items()
},
}, indent=2)
@classmethod
def from_json(cls, data: str) -> "LUT":
d = json.loads(data)
entries = {
s: (v["x"], v["energy"]) for s, v in d["entries"].items()
}
return cls(
entries=entries,
qubo_matrix=d["qubo_matrix"],
n_vars=d["n_vars"],
encoding=d["encoding"],
)
def build_direct_lut(
Q: List[List[float]],
n_vars: int,
base_map: Optional[Dict[int, str]] = None,
) -> LUT:
"""Build LUT with direct encoding: x_i → base by fixed mapping.
Default: 0 → A, 1 → G.
Symbolic order does NOT match energy order.
"""
if base_map is None:
base_map = {0: "A", 1: "G"}
entries = {}
for i in range(2**n_vars):
x = [(i >> j) & 1 for j in range(n_vars)]
seq = "".join(base_map[v] for v in x)
energy = qubo_energy(x, Q)
entries[seq] = (x, energy)
return LUT(
entries=entries,
qubo_matrix=Q,
n_vars=n_vars,
encoding="direct",
)
def build_monotone_lut(
Q: List[List[float]],
n_vars: int,
n_samples: int = 0,
seed: int = 42,
) -> LUT:
"""Build LUT with monotone encoding: DNA sequence = energy rank.
1. Enumerate all solutions (or sample)
2. Sort by energy
3. Assign DNA sequences in energy order
Result: lexicographic DNA sort = energy sort BY CONSTRUCTION.
Args:
Q: QUBO matrix
n_vars: number of variables
n_samples: 0 for brute force (n_vars ≤ 12), else random sampling
seed: RNG seed
Returns:
LUT with monotone encoding
"""
# Step 1: enumerate solutions
solutions = []
if n_samples == 0 and n_vars <= 12:
for i in range(2**n_vars):
x = [(i >> j) & 1 for j in range(n_vars)]
energy = qubo_energy(x, Q)
solutions.append((x, energy))
else:
n_samples = n_samples or 10000
rng = random.Random(seed)
seen = set()
for _ in range(n_samples):
x = tuple(rng.randint(0, 1) for _ in range(n_vars))
if x in seen:
continue
seen.add(x)
energy = qubo_energy(list(x), Q)
solutions.append((list(x), energy))
# Step 2: sort by energy
solutions.sort(key=lambda item: item[1])
# Step 3: assign DNA sequences in energy order
seq_len = sequence_length_for_solutions(len(solutions))
entries = {}
for rank, (x, energy) in enumerate(solutions):
seq = int_to_dna(rank, seq_len)
entries[seq] = (x, energy)
return LUT(
entries=entries,
qubo_matrix=Q,
n_vars=n_vars,
encoding="monotone",
)
def build_positional_lut(
Q: List[List[float]],
n_vars: int,
n_samples: int = 0,
seed: int = 42,
) -> LUT:
"""Build LUT with position-dependent encoding.
Each variable gets a base pair chosen by Q_ii sign.
Not monotone, but fixes sign inversion for Ising-type QUBOs.
"""
# Design position-dependent base pairs
pairs = [("A", "G"), ("T", "C"), ("B", "S"), ("P", "Z")]
pos_map = []
for i in range(n_vars):
pair = pairs[i % len(pairs)]
if Q[i][i] < 0:
pos_map.append((pair[1], pair[0])) # swap for negative diagonal
else:
pos_map.append(pair)
entries = {}
if n_samples == 0 and n_vars <= 12:
for i in range(2**n_vars):
x = [(i >> j) & 1 for j in range(n_vars)]
seq = "".join(pos_map[j][x[j]] for j in range(n_vars))
energy = qubo_energy(x, Q)
entries[seq] = (x, energy)
else:
n_samples = n_samples or 10000
rng = random.Random(seed)
seen = set()
for _ in range(n_samples):
x = tuple(rng.randint(0, 1) for _ in range(n_vars))
if x in seen:
continue
seen.add(x)
seq = "".join(pos_map[j][x[j]] for j in range(n_vars))
energy = qubo_energy(list(x), Q)
entries[seq] = (list(x), energy)
return LUT(
entries=entries,
qubo_matrix=Q,
n_vars=n_vars,
encoding="positional",
)
# ============================================================
# §4 VERIFICATION
# ============================================================
def verify_all_encodings(
Q: List[List[float]],
n_vars: int,
n_samples: int = 0,
) -> Dict[str, dict]:
"""Compare all three encoding strategies.
Returns:
Dict with results for each encoding method.
"""
results = {}
# Direct encoding
lut_direct = build_direct_lut(Q, n_vars)
mono, corr = lut_direct.verify_monotone()
best_seq = lut_direct.sort_by_energy()[0]
worst_seq = lut_direct.sort_by_sequence()[0]
results["direct"] = {
"encoding": "direct",
"n_entries": lut_direct.size(),
"is_monotone": mono,
"rank_correlation": round(corr, 4),
"min_energy": round(best_seq[2], 4),
"min_energy_seq": best_seq[0],
"first_lex_seq": worst_seq[0],
"first_lex_energy": round(worst_seq[2], 4),
}
# Positional encoding
lut_pos = build_positional_lut(Q, n_vars, n_samples)
mono, corr = lut_pos.verify_monotone()
best_seq = lut_pos.sort_by_energy()[0]
worst_seq = lut_pos.sort_by_sequence()[0]
results["positional"] = {
"encoding": "positional",
"n_entries": lut_pos.size(),
"is_monotone": mono,
"rank_correlation": round(corr, 4),
"min_energy": round(best_seq[2], 4),
"min_energy_seq": best_seq[0],
"first_lex_seq": worst_seq[0],
"first_lex_energy": round(worst_seq[2], 4),
}
# Monotone encoding
lut_mono = build_monotone_lut(Q, n_vars, n_samples)
mono, corr = lut_mono.verify_monotone()
best_seq = lut_mono.sort_by_energy()[0]
worst_seq = lut_mono.sort_by_sequence()[0]
results["monotone"] = {
"encoding": "monotone",
"n_entries": lut_mono.size(),
"is_monotone": mono,
"rank_correlation": round(corr, 4),
"min_energy": round(best_seq[2], 4),
"min_energy_seq": best_seq[0],
"first_lex_seq": worst_seq[0],
"first_lex_energy": round(worst_seq[2], 4),
}
return results
# ============================================================
# §5 DEMO QUBO GENERATORS
# ============================================================
def demo_qubo(n: int = 6, seed: int = 42, style: str = "random") -> List[List[float]]:
"""Generate a demo QUBO."""
rng = random.Random(seed)
Q = [[0.0] * n for _ in range(n)]
if style == "diagonal":
for i in range(n):
Q[i][i] = rng.uniform(1, 5)
elif style == "banded":
for i in range(n):
Q[i][i] = rng.uniform(1, 5)
if i + 1 < n:
val = rng.uniform(-2, 2)
Q[i][i + 1] = val
Q[i + 1][i] = val
elif style == "ising":
for i in range(n):
Q[i][i] = -1.0
if i + 1 < n:
Q[i][i + 1] = -0.5
Q[i + 1][i] = -0.5
else: # random
for i in range(n):
Q[i][i] = rng.uniform(-2, 5)
for j in range(i + 1, n):
val = rng.uniform(-2, 2)
Q[i][j] = val
Q[j][i] = val
return Q
# ============================================================
# §6 CLI
# ============================================================
def run_demo(name: str, Q: List[List[float]], n_vars: int):
"""Run a single demo comparing all encodings."""
print(f"\n{'='*60}")
print(f" {name}")
print(f"{'='*60}")
results = verify_all_encodings(Q, n_vars)
for method in ["direct", "positional", "monotone"]:
r = results[method]
print(f"\n {method.upper()} encoding:")
print(f" Entries: {r['n_entries']}")
print(f" Monotone: {r['is_monotone']}")
print(f" Rank corr: {r['rank_correlation']}")
print(f" Min energy: {r['min_energy']} (seq: {r['min_energy_seq']})")
print(f" First in lex: {r['first_lex_energy']} (seq: {r['first_lex_seq']})")
if r["is_monotone"]:
print(f" ✓ Lexicographic sort = Energy sort")
else:
print(f" ✗ Lexicographic sort ≠ Energy sort")
if __name__ == "__main__":
print("=" * 60)
print("Symbology LUT Adaptation — Fixed")
print("=" * 60)
# Diagonal
Q1 = demo_qubo(6, seed=42, style="diagonal")
run_demo("Diagonal QUBO (6 vars)", Q1, 6)
# Banded
Q2 = demo_qubo(6, seed=42, style="banded")
run_demo("Banded QUBO (6 vars)", Q2, 6)
# Ising
Q3 = demo_qubo(6, seed=42, style="ising")
run_demo("1D Ising Chain (6 vars)", Q3, 6)
# Random
Q4 = demo_qubo(6, seed=42, style="random")
run_demo("Random QUBO (6 vars)", Q4, 6)
# Larger
Q5 = demo_qubo(8, seed=42, style="banded")
run_demo("Banded QUBO (8 vars)", Q5, 8)
print(f"\n{'='*60}")
print("DONE")
print(f"{'='*60}")