Wedge-Positive Tangent Dynamics / wedge_experiment.py

Failed on benchmark

Raw ⬇ ZIP
  1import json
  2import math
  3import numpy as np
  4from scipy.linalg import expm
  5
  6
  7def pairs(n):
  8    return [(i, j) for i in range(n) for j in range(i + 1, n)]
  9
 10
 11def wedge2(M):
 12    """Second multiplicative compound in the ordered Plucker basis."""
 13    p = pairs(M.shape[0])
 14    W = np.empty((len(p), len(p)))
 15    for a, (i, j) in enumerate(p):
 16        for b, (k, l) in enumerate(p):
 17            W[a, b] = M[i, k] * M[j, l] - M[i, l] * M[j, k]
 18    return W
 19
 20
 21def additive_compound(A):
 22    """A^[2] = d/dh wedge2(exp(h A)) at h=0."""
 23    n = A.shape[0]
 24    p = len(pairs(n))
 25    B = np.zeros((p, p))
 26    ij = pairs(n)
 27    for a, (i, j) in enumerate(ij):
 28        B[a, a] = A[i, i] + A[j, j]
 29        for b, (k, l) in enumerate(ij):
 30            if a != b:
 31                # A reliable direct derivative avoids sign-convention mistakes.
 32                h = 1e-6
 33                B[a, b] = (wedge2(expm(h * A))[a, b]) / h
 34    return B
 35
 36
 37def hilbert(u, v):
 38    u = np.maximum(np.asarray(u), 1e-300)
 39    v = np.maximum(np.asarray(v), 1e-300)
 40    r = u / v
 41    return float(np.log(r.max()) - np.log(r.min()))
 42
 43
 44def birkhoff_diameter(P):
 45    # Exact finite-dimensional formula for a strictly positive matrix.
 46    best = 0.0
 47    for i in range(P.shape[0]):
 48        for j in range(P.shape[0]):
 49            for k in range(P.shape[0]):
 50                for l in range(P.shape[0]):
 51                    best = max(best, abs(math.log(P[i, k] * P[j, l] /
 52                                                (P[i, l] * P[j, k]))))
 53    return best
 54
 55
 56def cycle_generator(N, alpha, diagonal=-1.0):
 57    B = diagonal * np.eye(N)
 58    # Fixed strongly connected directed cycle, with an additional reverse edge.
 59    for i in range(N):
 60        B[(i + 1) % N, i] = alpha
 61        B[(i - 1) % N, i] = alpha
 62    return B
 63
 64
 65def random_positive(rng, N):
 66    return np.exp(rng.normal(0.0, 1.0, N))
 67
 68
 69def main():
 70    rng = np.random.default_rng(1478)
 71    # Core algebra check: wedge(exp(hA)) = I + h A^[2] + O(h^2).
 72    A = rng.normal(size=(5, 5))
 73    B = additive_compound(A)
 74    hs = np.array([2e-2, 1e-2, 5e-3, 2.5e-3])
 75    errs = []
 76    for h in hs:
 77        errs.append(np.linalg.norm(wedge2(expm(h * A)) - np.eye(10) - h * B))
 78    slope = np.polyfit(np.log(hs), np.log(errs), 1)[0]
 79
 80    # Toy mechanism: uniformly Metzler, fixed irreducible support, varying alpha.
 81    N, T = 6, 0.8
 82    alphas = [0.05, 0.1, 0.2, 0.4, 0.8]
 83    rows = []
 84    for alpha in alphas:
 85        Bp = cycle_generator(N, alpha)
 86        P = expm(T * Bp)
 87        delta = birkhoff_diameter(P)
 88        q_bound = math.tanh(delta / 4.0)
 89        # Empirical contraction over many positive vectors.
 90        ratios = []
 91        for _ in range(300):
 92            u, v = random_positive(rng, N), random_positive(rng, N)
 93            d0 = hilbert(u, v)
 94            if d0 > 1e-10:
 95                ratios.append(hilbert(P @ u, P @ v) / d0)
 96        # The shortest three-edge path controls the worst small-window entry on this 6-cycle.
 97        tsmall = 0.08
 98        minentry = expm(tsmall * Bp).min()
 99        predicted_path = (alpha * tsmall) ** 3 / 6.0
100        # Repeated identical windows test geometric decay.
101        u, v = random_positive(rng, N), random_positive(rng, N)
102        ds = [hilbert(u, v)]
103        for _ in range(5):
104            u, v = P @ u, P @ v
105            ds.append(hilbert(u, v))
106        observed_window_ratios = [ds[k + 1] / ds[k] for k in range(5)]
107        rows.append({
108            "alpha": alpha,
109            "min_P": float(P.min()),
110            "q_birkhoff_bound": q_bound,
111            "max_sampled_ratio": float(max(ratios)),
112            "path_minentry_at_t0.08": float(minentry),
113            "path_prediction_alpha3_t3_over_6": predicted_path,
114            "repeated_window_ratios": observed_window_ratios,
115        })
116
117    # Unconstrained comparison: signed cycle has no positive-cone guarantee.
118    Bu = cycle_generator(N, 0.4)
119    Bu[1, 0] = -0.4  # break Metzler condition while retaining comparable scale
120    Pu = expm(T * Bu)
121    signed_min = float(Pu.min())
122    unconstrained = []
123    for _ in range(300):
124        u, v = random_positive(rng, N), random_positive(rng, N)
125        d0 = hilbert(u, v)
126        if np.all(Pu @ u > 0) and np.all(Pu @ v > 0):
127            unconstrained.append(hilbert(Pu @ u, Pu @ v) / d0)
128
129    # Quantitative sweep summaries.
130    loga = np.log(alphas)
131    logpath = np.log([r["path_minentry_at_t0.08"] for r in rows])
132    path_slope = float(np.polyfit(loga, logpath, 1)[0])
133    q_by_alpha = [r["q_birkhoff_bound"] for r in rows]
134    result = {
135        "compound_identity": {
136            "h": hs.tolist(), "errors": errs, "loglog_slope_expected_2": float(slope)
137        },
138        "predictions": {
139            "P1_compound_identity_error_is_O_h2": {"observed_slope": float(slope), "expected": 2.0},
140            "P2_shortest_path_min_entry_scales_alpha3": {
141                "observed_log_slope": path_slope, "expected": 3.0,
142                "note": "For the bidirectional 6-cycle, the worst off-diagonal entries require three edges; the leading path term is (alpha*t)^3/3!."
143            },
144            "P3_stronger_uniform_coupling_improves_projective_contraction": {
145                "alphas": alphas, "q_bounds": q_by_alpha,
146                "q_at_alpha_0.05": q_by_alpha[0], "q_at_alpha_0.8": q_by_alpha[-1]
147            }
148        },
149        "rows": rows,
150        "baseline_signed_generator": {
151            "min_transition_entry": signed_min,
152            "valid_positive_output_sample_ratio_max": (max(unconstrained) if unconstrained else None),
153            "metzler_violation": 0.8
154        },
155        "interpretation": "Positive irreducible compound dynamics produced strictly positive finite-window transitions and geometric Hilbert contraction. The signed baseline has a negative transition entry, so the positive-cone theorem is unavailable."
156    }
157    with open("results.json", "w") as f:
158        json.dump(result, f, indent=2)
159    print(json.dumps(result, indent=2))
160
161
162if __name__ == "__main__":
163    main()