CharlesCNorton
Give neural_attractor and neural_reversible a machine metadata field and eval_all skip entries, so python src/eval_all.py variants/ skips them cleanly instead of erroring (it scores fitness variants and skips standalone machines by that field). README sync: list both new machines in the variant table, intro, and repository layout; correct the standalone-machine count (5->7), the eval_all skip count (four->seven), and the universal-constructor family round-trip (23->26 files, 551->971 MB, both new files codec-verified byte-identical).
782741e | """Attractor computer: an energy-based threshold network. | |
| A Boolean circuit is compiled to a quadratic pseudo-Boolean energy | |
| E(s) = sum_i L[i] s_i + sum_{i<j} Q[i,j] s_i s_j over binary wire variables, with | |
| per-gate gadgets that are non-negative and zero exactly on the gate truth table: | |
| AND z=x&y : 3z + xy - 2xz - 2yz | |
| OR z=x|y : x + y + z + xy - 2xz - 2yz | |
| NOT z=~x : 1 - x - z + 2xz | |
| AND/OR/NOT are functionally complete, so any circuit compiles and its consistent | |
| assignment is the global minimum. There is no program counter or clock: the | |
| coupling matrix Q (with linear terms L) is the program, and execution is | |
| relaxation toward the minimum. The relaxation step is a threshold neuron over the | |
| same integer weights, s_i <- H(-(L[i] + sum_j Q[i,j] s_j)). | |
| The clamped wire subset selects the mode. Clamp inputs to evaluate forward (exact | |
| via topological propagation to the energy-0 fixed point); clamp outputs to invert | |
| (a multiplier run backward returns factors); clamp a CNF output to 1 to solve | |
| SAT. Forward evaluation is exact and linear in gate count; inversion and | |
| open-constraint solving are annealed ground-state search, NP-hard in general, | |
| with a zero-energy state certifying a correct assignment. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| import random | |
| from collections import defaultdict | |
| from typing import Dict, List, Optional, Tuple | |
| class Circuit: | |
| """Wire allocator and energy accumulator. Gates append exact QUBO gadgets | |
| and record the relation for topological forward evaluation.""" | |
| def __init__(self) -> None: | |
| self.n = 0 | |
| self.L: Dict[int, int] = defaultdict(int) | |
| self.Q: Dict[Tuple[int, int], int] = defaultdict(int) | |
| self.const = 0 | |
| self.gates: List[Tuple[str, int, Tuple[int, ...]]] = [] | |
| def wire(self) -> int: | |
| i = self.n | |
| self.n += 1 | |
| return i | |
| def wires(self, k: int) -> List[int]: | |
| return [self.wire() for _ in range(k)] | |
| def _q(self, i: int, j: int, c: int) -> None: | |
| if i == j: | |
| self.L[i] += c | |
| else: | |
| self.Q[(min(i, j), max(i, j))] += c | |
| def AND(self, x: int, y: int) -> int: | |
| z = self.wire() | |
| self.L[z] += 3 | |
| self._q(x, y, 1); self._q(x, z, -2); self._q(y, z, -2) | |
| self.gates.append(("AND", z, (x, y))) | |
| return z | |
| def OR(self, x: int, y: int) -> int: | |
| z = self.wire() | |
| self.L[x] += 1; self.L[y] += 1; self.L[z] += 1 | |
| self._q(x, y, 1); self._q(x, z, -2); self._q(y, z, -2) | |
| self.gates.append(("OR", z, (x, y))) | |
| return z | |
| def NOT(self, x: int) -> int: | |
| z = self.wire() | |
| self.const += 1 | |
| self.L[x] += -1; self.L[z] += -1 | |
| self._q(x, z, 2) | |
| self.gates.append(("NOT", z, (x,))) | |
| return z | |
| def XOR(self, x: int, y: int) -> int: | |
| return self.OR(self.AND(x, self.NOT(y)), self.AND(self.NOT(x), y)) | |
| def full_adder(self, x: int, y: int, cin: int) -> Tuple[int, int]: | |
| axy = self.XOR(x, y) | |
| s = self.XOR(axy, cin) | |
| cout = self.OR(self.AND(x, y), self.AND(cin, axy)) | |
| return s, cout | |
| # ---- energy + couplings ------------------------------------------------ | |
| def energy(self, s: List[int]) -> int: | |
| e = self.const | |
| for i, c in self.L.items(): | |
| e += c * s[i] | |
| for (i, j), c in self.Q.items(): | |
| e += c * s[i] * s[j] | |
| return e | |
| def neighbors(self) -> Dict[int, List[Tuple[int, int]]]: | |
| nbr: Dict[int, List[Tuple[int, int]]] = defaultdict(list) | |
| for (i, j), c in self.Q.items(): | |
| nbr[i].append((j, c)) | |
| nbr[j].append((i, c)) | |
| return nbr | |
| # ---- relaxation modes -------------------------------------------------- | |
| def forward_eval(self, clamp: Dict[int, int]) -> List[int]: | |
| """Exact forward relaxation: propagate clamped inputs through the gate | |
| relations in topological order onto the energy-0 fixed point.""" | |
| s = [0] * self.n | |
| for w, v in clamp.items(): | |
| s[w] = v | |
| for op, z, ins in self.gates: | |
| if op == "AND": | |
| s[z] = s[ins[0]] & s[ins[1]] | |
| elif op == "OR": | |
| s[z] = s[ins[0]] | s[ins[1]] | |
| else: | |
| s[z] = 1 - s[ins[0]] | |
| return s | |
| def relax_energy(self, clamp: Dict[int, int], sweeps: int = 4000, | |
| t0: float = 4.0, t1: float = 0.02, seed: int = 0 | |
| ) -> Tuple[List[int], bool]: | |
| """Canonical relaxation: anneal the full threshold network (every free | |
| wire), tracking the lowest-energy state. Universal but hard; the exact | |
| gadgets keep the target at energy 0.""" | |
| nbr = self.neighbors() | |
| rng = random.Random(seed) | |
| s = [rng.randint(0, 1) for _ in range(self.n)] | |
| for w, v in clamp.items(): | |
| s[w] = v | |
| free = [i for i in range(self.n) if i not in clamp] | |
| best, best_e = list(s), self.energy(s) | |
| for step in range(sweeps): | |
| T = t0 * (t1 / t0) ** (step / max(1, sweeps - 1)) | |
| for _ in range(len(free)): | |
| i = free[rng.randrange(len(free))] | |
| field = self.L[i] + sum(c * s[j] for j, c in nbr[i]) | |
| dE = (1 - 2 * s[i]) * field | |
| if dE <= 0 or rng.random() < math.exp(-dE / T): | |
| s[i] ^= 1 | |
| e = self.energy(s) | |
| if e < best_e: | |
| best, best_e = list(s), e | |
| if best_e == 0: | |
| return best, True | |
| return best, best_e == 0 | |
| def solve(self, free_inputs: List[int], fixed: Dict[int, int], | |
| target: Dict[int, int], sweeps: int = 3000, restarts: int = 80, | |
| seed: int = 0) -> Optional[List[int]]: | |
| """Open-constraint relaxation over a chosen set of driver wires, with | |
| the rest slaved through the circuit; anneal the output Hamming mismatch | |
| to zero. Clamp outputs and pass the inputs here to run backward.""" | |
| rng = random.Random(seed) | |
| def mism(vals: Dict[int, int]) -> int: | |
| s = self.forward_eval({**fixed, **vals}) | |
| return sum(1 for w, v in target.items() if s[w] != v) | |
| for _ in range(restarts): | |
| vals = {w: rng.randint(0, 1) for w in free_inputs} | |
| m = mism(vals) | |
| if m == 0: | |
| return self.forward_eval({**fixed, **vals}) | |
| for step in range(sweeps): | |
| T = 2.0 * (0.02 / 2.0) ** (step / sweeps) | |
| w = free_inputs[rng.randrange(len(free_inputs))] | |
| vals[w] ^= 1 | |
| m2 = mism(vals) | |
| if m2 <= m or rng.random() < math.exp(-(m2 - m) / T): | |
| m = m2 | |
| if m == 0: | |
| return self.forward_eval({**fixed, **vals}) | |
| else: | |
| vals[w] ^= 1 | |
| return None | |
| # --------------------------------------------------------------------------- | |
| # Circuit builders | |
| # --------------------------------------------------------------------------- | |
| def adder(bits: int) -> Tuple[Circuit, dict]: | |
| c = Circuit() | |
| xs, ys = c.wires(bits), c.wires(bits) | |
| cin = c.wire() | |
| outs, carry = [], cin | |
| for k in range(bits): | |
| s, carry = c.full_adder(xs[k], ys[k], carry) | |
| outs.append(s) | |
| return c, {"xs": xs, "ys": ys, "cin": cin, "sum": outs + [carry]} | |
| def multiplier(bits: int) -> Tuple[Circuit, dict]: | |
| c = Circuit() | |
| xs, ys = c.wires(bits), c.wires(bits) | |
| zero = c.wire() | |
| acc = [zero] * (2 * bits) | |
| for i in range(bits): | |
| carry = zero | |
| for j in range(bits): | |
| acc[i + j], carry = c.full_adder(acc[i + j], c.AND(xs[i], ys[j]), carry) | |
| acc[i + bits] = carry | |
| return c, {"xs": xs, "ys": ys, "zero": zero, "prod": acc} | |
| _OPCODE = {"AND": 0, "OR": 1, "NOT": 2} | |
| _OPNAME = {v: k for k, v in _OPCODE.items()} | |
| def to_tensors(circ: Circuit, io: dict): | |
| """Serialize the coupling matrix (the program) and the gate list to tensors. | |
| Q is stored sparsely as index pairs and integer values.""" | |
| import torch | |
| qi = sorted(circ.Q) | |
| q_idx = torch.tensor(qi if qi else [], dtype=torch.long).reshape(-1, 2) | |
| q_val = torch.tensor([circ.Q[k] for k in qi], dtype=torch.long) | |
| li = sorted(circ.L) | |
| l_idx = torch.tensor(li, dtype=torch.long) | |
| l_val = torch.tensor([circ.L[i] for i in li], dtype=torch.long) | |
| g_op = torch.tensor([_OPCODE[op] for op, _, _ in circ.gates], dtype=torch.long) | |
| g_out = torch.tensor([o for _, o, _ in circ.gates], dtype=torch.long) | |
| g_in = torch.tensor([[ins[0], ins[1] if len(ins) > 1 else -1] | |
| for _, _, ins in circ.gates], dtype=torch.long).reshape(-1, 2) | |
| t = {"Q_idx": q_idx, "Q_val": q_val, "L_idx": l_idx, "L_val": l_val, | |
| "gate_op": g_op, "gate_out": g_out, "gate_in": g_in} | |
| import json | |
| meta = {"machine": "attractor", "n": str(circ.n), "const": str(circ.const), | |
| "io": json.dumps({k: v for k, v in io.items()})} | |
| return t, meta | |
| def from_tensors(t: dict, meta: dict) -> Tuple[Circuit, dict]: | |
| import json | |
| c = Circuit() | |
| c.n = int(meta["n"]) | |
| c.const = int(meta["const"]) | |
| for (i, j), v in zip(t["Q_idx"].tolist(), t["Q_val"].tolist()): | |
| c.Q[(i, j)] = v | |
| for i, v in zip(t["L_idx"].tolist(), t["L_val"].tolist()): | |
| c.L[i] = v | |
| for op, out, ins in zip(t["gate_op"].tolist(), t["gate_out"].tolist(), t["gate_in"].tolist()): | |
| c.gates.append((_OPNAME[op], out, tuple(x for x in ins if x >= 0))) | |
| return c, json.loads(meta["io"]) | |
| def cnf(clauses: List[List[int]], n_vars: int) -> Tuple[Circuit, dict]: | |
| """Compile a CNF formula. Literals are +v (var v) or -v (negation), v>=1. | |
| Returns the circuit, the variable wires, and the wire that is 1 iff the | |
| formula is satisfied. Clamp that wire to 1 and relax to find a model.""" | |
| c = Circuit() | |
| var = {v: c.wire() for v in range(1, n_vars + 1)} | |
| clause_ws = [] | |
| for cl in clauses: | |
| lits = [var[abs(l)] if l > 0 else c.NOT(var[abs(l)]) for l in cl] | |
| acc = lits[0] | |
| for w in lits[1:]: | |
| acc = c.OR(acc, w) | |
| clause_ws.append(acc) | |
| sat = clause_ws[0] | |
| for w in clause_ws[1:]: | |
| sat = c.AND(sat, w) | |
| return c, {"vars": var, "sat": sat} | |