#!/usr/bin/env python3 """CAffNet: Hard Constraint-Affine Neural Networks (arXiv:2605.24437). Reproduces Theorem 3.5 / 3.4: [0a] Hard constraint satisfaction: the CAffNet parameterization P(x) = f(x) - A^+(A f(x) - b(x)) + (I - A^+ A) w(x) makes the output ALWAYS satisfy the affine constraint A(x) y = b(x) EXACTLY (0 violation), for ANY base network f and null-space network w. [0b] Universal approximation (Thm 3.5): if the unconstrained base class approximates a target, then CAffNet approximates the constraint-satisfying target -- approximation error -> 0 as base width grows, while the constraint stays satisfied exactly. Pure numpy MLP; equality + inequality affine constraints. Deterministic seeds. """ import numpy as np, json, hashlib def mlp_init(sizes, rng): return [(rng.standard_normal((a, b)) * np.sqrt(2.0 / a), np.zeros(b)) for a, b in zip(sizes[:-1], sizes[1:])] def mlp_fwd(W, X): a = X for i, (w, b) in enumerate(W): a = a @ w + b if i < len(W) - 1: a = np.maximum(a, 0.0) return a def main(): R = {"claim": "CAffNet_hard_constraint_and_universal_approx", "paper": "arXiv:2605.24437"} rng = np.random.default_rng(0) N = 400; X = rng.uniform(0, 1, (N, 1)) m = 3 # 3-dim output # affine EQUALITY constraint A y = b(x): sum of outputs must equal g(x) = 1 + x A = np.ones((1, m)); Apinv = A.T @ np.linalg.inv(A @ A.T) # Moore-Penrose pseudoinverse def bvec(x): return (1.0 + x) # b(x) in R^1 (per sample) def caffnet(fbase, wnull, Xb): b = bvec(Xb) # (N,1) # P = f - A^+(A f - b) + (I - A^+ A) w Af = fbase @ A.T # (N,1) proj = fbase - (Af - b) @ Apinv.T # enforce equality nullw = wnull - (wnull @ A.T) @ Apinv.T # (I-A^+A) w return proj + nullw # ---------- [0a] hard constraint satisfaction for ARBITRARY (random, untrained) base+null nets ---------- max_viol = 0.0 for s in range(50): r = np.random.default_rng(100 + s) fb = mlp_fwd(mlp_init([1, 16, m], r), X); wn = mlp_fwd(mlp_init([1, 16, m], r), X) P = caffnet(fb, wn, X) viol = np.max(np.abs(P @ A.T - bvec(X))) # |A P - b| max_viol = max(max_viol, float(viol)) R["max_constraint_violation_equality"] = float(f"{max_viol:.2e}") R["thm3.4_hard_equality_satisfied"] = max_viol < 1e-9 # inequality constraint y_1 <= 3x (project only when violated) -> 0 violation def caffnet_ineq(fbase, Xb): cap = 3 * Xb[:, 0] # y_1 <= 3x out = fbase.copy(); mask = out[:, 0] > cap out[mask, 0] = cap[mask] # project onto feasible (closest) return out max_viol_ineq = 0.0 for s in range(50): r = np.random.default_rng(200 + s) fb = mlp_fwd(mlp_init([1, 16, m], r), X) out = caffnet_ineq(fb, X) max_viol_ineq = max(max_viol_ineq, float(np.max(np.maximum(out[:, 0] - 3 * X[:, 0], 0)))) R["max_constraint_violation_inequality"] = float(f"{max_viol_ineq:.2e}") R["thm3.4_hard_inequality_satisfied"] = max_viol_ineq < 1e-12 # ---------- [0b] universal approximation: error -> 0 as base width grows (constraint held exactly) ---------- # constrained target: y*(x) = softmax-like split of g(x)=1+x among 3 coords with a nonlinear pattern def target(x): raw = np.stack([np.sin(3 * x[:, 0]), np.cos(2 * x[:, 0]), 0.5 * x[:, 0]], 1) raw = np.exp(raw); raw = raw / raw.sum(1, keepdims=True); return raw * (1.0 + x) # sums to g(x)=1+x Yt = target(X) # P = (I - A^+A) f + A^+ b (single base net f; the projection is affine in f). Fit f so P ~ Yt. Proj = np.eye(m) - Apinv @ A # symmetric idempotent projector onto null(A) def fit_and_error(width, epochs=3000): r = np.random.default_rng(7) W = mlp_init([1, width, width, m], r); lr = 0.05 Ab = bvec(X) @ Apinv.T # A^+ b term (fixed) for _ in range(epochs): a = X; acts = [X] for i, (w, b) in enumerate(W): a = a @ w + b if i < len(W) - 1: a = np.maximum(a, 0.0) acts.append(a) P = acts[-1] @ Proj + Ab # CAffNet output g = 2 * (P - Yt) / N delta = g @ Proj.T # grad through the projector for i in range(len(W) - 1, -1, -1): gw = acts[i].T @ delta + 1e-5 * W[i][0]; gb = delta.sum(0) if i > 0: delta = (delta @ W[i][0].T) * (acts[i] > 0) W[i] = (W[i][0] - lr * gw, W[i][1] - lr * gb) a = X for i, (w, b) in enumerate(W): a = a @ w + b if i < len(W) - 1: a = np.maximum(a, 0.0) P = a @ Proj + Ab err = float(np.sqrt(np.mean((P - Yt) ** 2))) viol = float(np.max(np.abs(P @ A.T - bvec(X)))) return err, viol rows = [] for width in (4, 8, 16, 32, 64): err, viol = fit_and_error(width) rows.append({"width": width, "approx_rmse": round(err, 4), "constraint_violation": float(f"{viol:.1e}")}) R["approx_vs_width"] = rows R["thm3.5_error_decreases_with_width"] = rows[-1]["approx_rmse"] < rows[0]["approx_rmse"] - 1e-4 R["constraint_held_during_training"] = all(r["constraint_violation"] < 1e-9 for r in rows) R["verdict"] = "supports" if (R["thm3.4_hard_equality_satisfied"] and R["thm3.4_hard_inequality_satisfied"] and R["thm3.5_error_decreases_with_width"] and R["constraint_held_during_training"]) else "inconclusive" print("claim: " + R["claim"]) print(f"[0a] hard constraint satisfaction (arbitrary nets): equality |Ay-b|={R['max_constraint_violation_equality']} -> {R['thm3.4_hard_equality_satisfied']}; " f"inequality max viol={R['max_constraint_violation_inequality']} -> {R['thm3.4_hard_inequality_satisfied']}") print(f"[0b] universal approximation (Thm 3.5): RMSE to constrained target vs base width:") for r in rows: print(f" width={r['width']:>2}: approx RMSE={r['approx_rmse']}, constraint violation={r['constraint_violation']}") print(f" error decreases with width: {R['thm3.5_error_decreases_with_width']}; constraint held exactly throughout: {R['constraint_held_during_training']}") print(f"verdict: {R['verdict']}") def _np(o): if isinstance(o, np.bool_): return bool(o) if isinstance(o, np.integer): return int(o) if isinstance(o, np.floating): return float(o) raise TypeError import os; os.makedirs("outputs", exist_ok=True) open("outputs/caffnet_results.json", "w").write(json.dumps(R, indent=2, default=_np)) print("RESULTS_SHA256=" + hashlib.sha256(json.dumps(R, sort_keys=True, default=_np).encode()).hexdigest()) return 0 if R["verdict"] == "supports" else 1 if __name__ == "__main__": raise SystemExit(main())