File size: 7,656 Bytes
e5522a6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python3
"""Forge a REAL trained torch surrogate for szl-lambda-gate.
Kernel = ground truth. Surrogate = a tiny torch MLP that predicts the ADVISORY
gate decision `lambda_gate(axes, threshold).passed` — i.e. Λ(axes) >= threshold —
over the canonical 13-axis Yuyay space. The kernel's weighted-geometric-mean Λ
(with non-compensatory zero-routing: any zero/non-finite axis fails the gate) is
the sole labeler; a sample of labels is re-audited by full kernel replay and MUST
agree or the run fails loudly.

Λ IS NOT PROVEN TRUST — it is the ADVISORY weighted geometric mean, uniqueness =
Conjecture 1 (OPEN). The surrogate approximates the gate DECISION, nothing more.
Seeded, receipted, reproducible. Ships .safetensors + config.json."""
import json, os, random, sys, time, hashlib, platform
_here = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if os.path.isdir(os.path.join(_here, "build", "torch-universal")):
    sys.path.insert(0, os.path.join(_here, "build", "torch-universal"))  # in-repo run
else:
    sys.path.insert(0, "/tmp/kernel-probe/szl-lambda-gate/build/torch-universal")  # forge-dev run
import szl_lambda_gate as lg
import numpy as np
import torch
from torch import nn
from safetensors.torch import save_file

SEED = 20260721
random.seed(SEED); np.random.seed(SEED)
torch.manual_seed(SEED)
T0 = time.time()

K = len(lg.YUYAY_AXES)          # 13 canonical axes
THRESHOLD = 0.5
WEIGHTS = lg.yuyay_weights(dtype=torch.float64)   # uniform 1/13, advisory


def kernel_gate(axes_np):
    """Ground truth: lambda_gate(axes).passed for a batch (N,K)."""
    t = torch.tensor(axes_np, dtype=torch.float64)
    res = lg.lambda_gate(t, weights=WEIGHTS, threshold=THRESHOLD)
    return res.passed.numpy().astype(np.int64)


def synth_axes(n):
    """Synthesize axis-score vectors that straddle the gate boundary, including
    non-compensatory zero-route cases (a single zeroed axis must fail)."""
    X = np.random.uniform(0.0, 1.0, size=(n, K)).astype(np.float64)
    # push a chunk toward the boundary region so the label is non-trivial
    hi = np.random.rand(n) < 0.45
    X[hi] = np.random.uniform(0.55, 1.0, size=(hi.sum(), K))
    # inject explicit zero-route rows (one axis exactly 0 -> Λ=0 -> fail)
    zr = np.random.rand(n) < 0.12
    idx = np.where(zr)[0]
    for i in idx:
        X[i, np.random.randint(K)] = 0.0
    return X


class GateMLP(nn.Module):
    def __init__(self, k, hidden=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(k, hidden), nn.ReLU(),
            nn.Linear(hidden, hidden), nn.ReLU(),
            nn.Linear(hidden, hidden), nn.ReLU(),
            nn.Linear(hidden, 1),
        )

    def forward(self, x):
        return self.net(x).squeeze(-1)


# ---- generate (kernel-labeled) ----
N = 40000
Xall = synth_axes(N)
yall = kernel_gate(Xall)

# ground-truth audit: independent fresh kernel replay must agree on a sample
audit_idx = np.array(sorted(random.sample(range(N), 800)))
replay = kernel_gate(Xall[audit_idx])
assert np.array_equal(replay, yall[audit_idx]), "kernel disagrees on audited sample"
audit_checked = int(len(audit_idx))

# split 80/20 deterministic
perm = np.random.permutation(N)
cut = int(N * 0.8)
tr, te = perm[:cut], perm[cut:]
Xtr = torch.tensor(Xall[tr], dtype=torch.float32)
ytr = torch.tensor(yall[tr], dtype=torch.float32)
Xte = torch.tensor(Xall[te], dtype=torch.float32)
yte = torch.tensor(yall[te], dtype=torch.float32)

HIDDEN = 64
model = GateMLP(K, hidden=HIDDEN)
opt = torch.optim.Adam(model.parameters(), lr=2e-3)
lossf = nn.BCEWithLogitsLoss()
EPOCHS = 150
BATCH = 512
model.train()
for ep in range(EPOCHS):
    order = torch.randperm(Xtr.shape[0])
    for b in range(0, Xtr.shape[0], BATCH):
        bi = order[b:b + BATCH]
        opt.zero_grad()
        logits = model(Xtr[bi])
        loss = lossf(logits, ytr[bi])
        loss.backward(); opt.step()

model.eval()
with torch.no_grad():
    pred = (torch.sigmoid(model(Xte)) >= 0.5).long()
    yte_l = yte.long()
    acc = float((pred == yte_l).float().mean())   # fidelity vs kernel
    # per-class recall
    pos = yte_l == 1
    neg = yte_l == 0
    rec_pass = float((pred[pos] == 1).float().mean()) if pos.any() else float("nan")
    rec_fail = float((pred[neg] == 0).float().mean()) if neg.any() else float("nan")

out = os.path.dirname(os.path.abspath(__file__))
state = {k: v.contiguous() for k, v in model.state_dict().items()}
save_file(state, f"{out}/model.safetensors")
model_sha = hashlib.sha256(open(f"{out}/model.safetensors", "rb").read()).hexdigest()

config = {
    "architecture": "GateMLP",
    "task": "advisory-lambda-gate-decision-surrogate",
    "framework": "pytorch",
    "input_dim": K,
    "input_axes": list(lg.YUYAY_AXES),
    "hidden": HIDDEN,
    "layers": ["Linear(13,64)", "ReLU", "Linear(64,64)", "ReLU", "Linear(64,64)", "ReLU", "Linear(64,1)"],
    "output": "logit; sigmoid>=0.5 => predicted gate PASS (Λ>=threshold)",
    "threshold": THRESHOLD,
    "weights": "yuyay uniform 1/13",
    "label_source": "szl_lambda_gate.lambda_gate(axes, weights=yuyay, threshold=0.5).passed",
    "honesty": "predicts the ADVISORY gate DECISION only; Λ is NOT proven trust; uniqueness = Conjecture 1 (open)",
}
with open(f"{out}/config.json", "w") as f:
    json.dump(config, f, indent=2)

n_pos = int(yall.sum()); n_neg = int(N - n_pos)
receipt = {
  "artifact": "SZLHOLDINGS/szl-lambda-gate surrogate v1",
  "role": "advisory Λ gate-decision surrogate (torch MLP) — kernel remains ground truth",
  "generator": {"script": "scripts/forge.py", "seed": SEED, "kernel_version": lg.__version__,
                 "kernel_labelled": True, "kernel_audited_samples": audit_checked,
                 "labeler": "lambda_gate(axes, weights=yuyay_uniform_1/13, threshold=0.5).passed",
                 "axes": list(lg.YUYAY_AXES), "threshold": THRESHOLD},
  "data": {"rows": int(N), "classes": ["GATE_FAIL", "GATE_PASS"],
            "class_counts": {"GATE_FAIL": n_neg, "GATE_PASS": n_pos},
            "split": "80/20 permutation", "features": list(lg.YUYAY_AXES),
            "feature_policy": "13 Yuyay axis scores in [0,1]; includes non-compensatory zero-route rows"},
  "model": {"type": "pytorch GateMLP (3 hidden ReLU layers, 64 units)",
             "params": {"input_dim": K, "hidden": HIDDEN, "epochs": EPOCHS, "batch": BATCH,
                        "lr": 2e-3, "optimizer": "Adam", "loss": "BCEWithLogits", "seed": SEED},
             "file": "model.safetensors", "sha256": model_sha, "config": "config.json"},
  "metrics_MEASURED": {"fidelity_vs_kernel_heldout": round(acc, 4),
                        "test_accuracy": round(acc, 4),
                        "recall_GATE_PASS": round(rec_pass, 4),
                        "recall_GATE_FAIL": round(rec_fail, 4)},
  "environment": {"python": platform.python_version(), "torch": torch.__version__,
                   "numpy": np.__version__, "host": "replit 2-vCPU container",
                   "wall_seconds": round(time.time() - T0, 1)},
  "honesty": "Every number above is MEASURED by this run. Fidelity = agreement%% with the kernel's ADVISORY lambda_gate decision on a held-out split. Λ is the weighted geometric mean, NOT proven trust; uniqueness = Conjecture 1 (open). The surrogate never replaces the kernel gate.",
  "trained_at_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
with open(f"{out}/TRAINING_RECEIPT.json", "w") as f:
    json.dump(receipt, f, indent=2)
print(json.dumps(receipt["metrics_MEASURED"], indent=2))
print(f"rows={N} pos={n_pos} neg={n_neg} kernel_audited={audit_checked} wall={receipt['environment']['wall_seconds']}s")