File size: 3,927 Bytes
a7636cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from typing import Dict, Any, Tuple, List
import numpy as np

from .regimes import classify_regime, fragility_level


def compute_diagnostics(alpha: float, beta: float, D: float, k: float, tol: float, eps: float) -> Dict[str, Any]:
    alpha = float(alpha); beta = float(beta)
    D = float(D); k = float(k)
    tol = float(tol); eps = float(eps)

    lam = (alpha * D) - (beta * k)              # λ = αD − βk
    Delta = (beta * k) - (alpha * D)            # Δ = βk − αD (stability margin)
    denom = abs(alpha * D) + abs(beta * k) + eps
    F = abs(Delta) / denom                      # normalized fragility/proximity

    regime = classify_regime(lam, tol)
    frag = fragility_level(F)

    # Boundary expression: stable if k > (α/β)D (when β>0)
    boundary_k = (alpha / beta) * D if beta != 0 else float("nan")
    margin_to_boundary = k - boundary_k  # >0 means above boundary

    # Local sensitivities of λ
    ranked = sorted(
        {
            "∂λ/∂α = D": float(D),
            "∂λ/∂D = α": float(alpha),
            "∂λ/∂β = −k": float(-k),
            "∂λ/∂k = −β": float(-beta),
        }.items(),
        key=lambda kv: abs(kv[1]),
        reverse=True
    )

    return {
        "alpha": alpha, "beta": beta, "D": D, "k": k,
        "lambda": float(lam),
        "Delta": float(Delta),
        "F": float(F),
        "regime": regime,
        "fragility": frag,
        "boundary_k": float(boundary_k),
        "margin_to_boundary": float(margin_to_boundary),
        "sens_ranked": ranked,
    }


def interpret_governance(regime: str, fragility: str, Delta: float, margin_to_boundary: float) -> Dict[str, str]:
    """
    Decision-facing interpretation (structural, not behavioral).
    """
    if regime == "Unstable":
        posture = "Mandatory"
        decision = "Immediate stabilization required: raise k and/or reduce D to restore Δ > 0."
        risk = "Escalation expected if unchanged; structural governance failure probability rises."
        return {"posture": posture, "decision": decision, "risk": risk}

    if regime == "Near-boundary":
        posture = "Preventive"
        decision = "Treat as fragile: widen margin (increase k or reduce D) + define explicit intervention triggers."
        risk = "Small drift can flip stability; high sensitivity zone."
        return {"posture": posture, "decision": decision, "risk": risk}

    # Stable
    if fragility == "High":
        posture = "Adaptive"
        decision = "Stable but fragile: increase Δ minimally (proportional adjustments) to improve robustness."
        risk = "Stability exists, but it can be lost under minor parameter perturbations."
        return {"posture": posture, "decision": decision, "risk": risk}

    if fragility == "Moderate":
        posture = "Adaptive"
        decision = "Maintain stability and monitor drift; keep margin safely positive without unnecessary intensity."
        risk = "Moderate robustness; avoid overreach if smaller k preserves stability."
        return {"posture": posture, "decision": decision, "risk": risk}

    # Low fragility
    posture = "Advisory"
    decision = "Robust stability: guidance + periodic review. Consider proportionality if k greatly exceeds boundary."
    risk = "Low escalation probability under small perturbations."
    return {"posture": posture, "decision": decision, "risk": risk}


def euler_simulation(alpha: float, beta: float, D: float, k: float, R0: float, T: float, n: int) -> Tuple[np.ndarray, np.ndarray]:
    alpha = float(alpha); beta = float(beta)
    D = float(D); k = float(k)
    R0 = float(R0); T = float(T)
    n = int(n)

    lam = (alpha * D) - (beta * k)
    t = np.linspace(0.0, T, n)
    dt = float(t[1] - t[0])

    R = np.zeros_like(t)
    R[0] = max(R0, 1e-12)
    for i in range(1, len(t)):
        R[i] = R[i-1] + dt * (lam * R[i-1])
        R[i] = max(R[i], 0.0)
    return t, R