"""Shared helpers (same as the neural-DDR project): bit<->int, MLP, verify, train.""" from __future__ import annotations import torch import torch.nn as nn DEV = "cuda" if torch.cuda.is_available() else "cpu" def bits_of(v: int, n: int) -> list[int]: return [(v >> k) & 1 for k in range(n)] def int_of(bits) -> int: return sum((1 << k) for k, b in enumerate(bits) if b > 0) def pm(bits) -> torch.Tensor: return torch.tensor([1.0 if b else -1.0 for b in bits], dtype=torch.float32) def mlp(inp: int, out: int, h: int = 256, layers: int = 2) -> nn.Sequential: mods = [nn.Linear(inp, h), nn.GELU()] for _ in range(layers - 1): mods += [nn.Linear(h, h), nn.GELU()] mods += [nn.Linear(h, out)] return nn.Sequential(*mods) @torch.no_grad() def verify(net, X, Ybits) -> tuple[int, int]: net = net.to("cpu") pred = (net(X) > 0).int() ok = (pred == Ybits.int()).all(dim=1).sum().item() return ok, X.shape[0] def train(net, X, Ybits, steps=8000, lr=2e-3, tag="", report=2000): net = net.to(DEV) Xd, Yd = X.to(DEV), Ybits.to(DEV) opt = torch.optim.Adam(net.parameters(), lr=lr) sch = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=steps) lossfn = nn.BCEWithLogitsLoss() for e in range(steps): opt.zero_grad() loss = lossfn(net(Xd), Yd) loss.backward(); opt.step(); sch.step() if e % report == 0 or e == steps - 1: ok, tot = verify(net, X, Ybits); net.to(DEV) print(f" [{tag}] epoch {e:5d} loss {loss.item():.2e} verified {ok}/{tot}") if ok == tot: print(f" [{tag}] -> N/N"); break return net.to("cpu") def run8(net, v: int) -> int: return int_of((net(pm(bits_of(v, 8)).unsqueeze(0))[0] > 0).int().tolist())