"""
Verification of Theorem 2.1 (Substrate Necessity)
Renewal Libertarianism, Part 1, Chapter II, Module 1.

Symbolic re-derivation of the cooperation threshold sigma_star = kappa^(1/(1-beta))
where kappa = alpha * (1 - delta + delta*p) / (1 - delta). Each algebraic step of
the manuscript's derivation is re-executed in SymPy. Assertions fail if SymPy's
symbolic result disagrees with the manuscript's claim at any step.

Run: uv run python substrate_necessity.py
"""

import sympy as sp

from _helpers import banner


def main():
    banner("Theorem 2.1 (Substrate Necessity) - symbolic verification")

    # Symbols with the positivity assumptions Part 1 makes
    sigma = sp.symbols("sigma", positive=True)
    alpha = sp.symbols("alpha", positive=True)
    beta = sp.symbols("beta", positive=True)
    delta = sp.symbols("delta", positive=True)
    p = sp.symbols("p", positive=True)
    kappa = sp.symbols("kappa", positive=True)

    # Manuscript: b(sigma) = alpha * sigma^beta, beta in (0,1)
    b = alpha * sigma**beta
    print(f"\n  b(sigma)             = {b}")

    # Manuscript: rho = b(sigma) * (1 - delta + delta*p)/(1 - delta)
    rho = b * (1 - delta + delta * p) / (1 - delta)
    print(f"  rho(sigma)           = {sp.nsimplify(rho)}")

    # Identity: rho = kappa * sigma^beta where kappa = alpha*(1-delta+delta*p)/(1-delta)
    kappa_expr = alpha * (1 - delta + delta * p) / (1 - delta)
    print(f"  kappa (definition)   = {kappa_expr}")

    identity_residual = sp.simplify(rho - kappa_expr * sigma**beta)
    print(f"  rho - kappa*sigma^beta = {identity_residual}")
    assert identity_residual == 0, "Manuscript identity rho = kappa*sigma^beta failed"
    print("  [pass] rho = kappa * sigma^beta")

    banner("Step 1: solve the cooperation condition sigma = kappa * sigma^beta")

    # SymPy solves the boundary equation for the threshold
    threshold_eq = sp.Eq(sigma, kappa * sigma**beta)
    print(f"\n  Equation: {threshold_eq}")
    solutions = sp.solve(threshold_eq, sigma)
    print(f"  SymPy solutions for sigma (positive branch only): {solutions}")

    # Manuscript claim: sigma_star = kappa^(1/(1-beta))
    sigma_star_claim = kappa ** (1 / (1 - beta))
    print(f"  Manuscript claim:  sigma_star = kappa^(1/(1-beta)) = {sigma_star_claim}")

    # Verify the claim by direct substitution into the equation.
    # SymPy's simplify is conservative with symbolic exponents, so combine
    # exponents explicitly via powdenest+powsimp with force=True (safe here
    # because kappa was declared positive).
    lhs = sigma_star_claim
    rhs = kappa * sigma_star_claim**beta
    residual = sp.powdenest(sp.powsimp(lhs - rhs, force=True, combine="exp"), force=True)
    residual = sp.simplify(residual)
    print(f"  Substitution check: sigma_star - kappa*sigma_star^beta = {residual}")
    assert residual == 0, "Manuscript threshold does not satisfy the cooperation boundary"
    print("  [pass] sigma_star = kappa^(1/(1-beta)) satisfies sigma = kappa * sigma^beta")

    banner("Step 2: confirm SymPy's solve returned the same expression")

    # SymPy returned kappa^(-1/(beta-1)); the manuscript writes kappa^(1/(1-beta)).
    # These are algebraically identical (-1/(beta-1) = 1/(1-beta)). Verify by
    # combining the powers and by independent numerical substitution at several
    # test calibrations.
    matched = False
    test_points = [
        {kappa: sp.Rational(2), beta: sp.Rational(1, 2)},
        {kappa: sp.Rational(3), beta: sp.Rational(1, 4)},
        {kappa: sp.Rational(5, 2), beta: sp.Rational(2, 3)},
        {kappa: sp.Rational(7, 4), beta: sp.Rational(9, 10)},
    ]
    for sol in solutions:
        sym_diff = sp.powdenest(sp.simplify(sol - sigma_star_claim), force=True)
        print(f"  SymPy solution {sol}  vs  kappa^(1/(1-beta))")
        print(f"      symbolic difference = {sym_diff}")

        max_num_diff = 0.0
        for tp in test_points:
            num_sym = float(sp.N(sol.subs(tp)))
            num_claim = float(sp.N(sigma_star_claim.subs(tp)))
            max_num_diff = max(max_num_diff, abs(num_sym - num_claim))
        print(f"      max numerical |difference| across {len(test_points)} test points "
              f"= {max_num_diff:.2e}")

        if sym_diff == 0 or max_num_diff < 1e-12:
            matched = True
            print(f"      [pass] SymPy solution equivalent to the manuscript form")
    assert matched, "No SymPy solution matched the manuscript threshold"

    banner("Step 3: properties of sigma_star")

    # (a) Finiteness and positivity for beta in (0,1), kappa > 0.
    # Verify at several interior calibrations that sigma_star is a positive
    # finite real number.
    print("\n  (a) Positivity and finiteness for beta in (0,1), kappa > 0")
    pos_fin_points = [
        {kappa: sp.Rational(2), beta: sp.Rational(1, 4)},
        {kappa: sp.Rational(1, 2), beta: sp.Rational(1, 2)},
        {kappa: sp.Rational(5), beta: sp.Rational(9, 10)},
        {kappa: sp.Rational(1, 10), beta: sp.Rational(1, 10)},
    ]
    for pt in pos_fin_points:
        val = float(sp.N(sigma_star_claim.subs(pt)))
        print(f"      sigma_star at kappa={pt[kappa]}, beta={pt[beta]} = {val:.6f}")
        assert val > 0 and val < float("inf"), \
            f"sigma_star not finite-positive at {pt}"
    print("      [pass] sigma_star finite and positive at every tested interior point")

    # (b) Limit as beta -> 0+ should yield sigma_star -> kappa.
    print("\n  (b) Limit as beta -> 0+ : sigma_star should approach kappa")
    lim_beta_0 = sp.limit(sigma_star_claim, beta, 0, "+")
    print(f"      lim_{{beta->0+}} sigma_star = {lim_beta_0}")
    assert sp.simplify(lim_beta_0 - kappa) == 0, "Limit at beta->0 does not equal kappa"
    print("      [pass] limit equals kappa")

    # (c) Limit as beta -> 1- with kappa > 1 should diverge.
    #     sp.limit on the raw expression hits a known SymPy issue and returns 0,
    #     but the limit via the logarithm is computed correctly, and a sequence
    #     of beta values approaching 1 from below clearly diverges. Verify both.
    print("\n  (c) Behavior as beta -> 1- with kappa > 1 : sigma_star diverges")

    expr_at_kappa2 = sigma_star_claim.subs(kappa, 2)
    log_limit = sp.limit(sp.log(expr_at_kappa2), beta, 1, "-")
    print(f"      lim_{{beta->1-}} log(sigma_star)(kappa=2) = {log_limit}")
    assert log_limit == sp.oo, "log-limit at beta->1- did not diverge for kappa>1"
    print("      [pass] log-limit diverges, so sigma_star diverges to infinity")

    print("\n      Numerical sequence demonstrating divergence (kappa=2):")
    prev = 0.0
    test_betas = [sp.Rational(1, 2), sp.Rational(9, 10),
                  sp.Rational(99, 100), sp.Rational(999, 1000)]
    for tb in test_betas:
        val = float(sp.N(expr_at_kappa2.subs(beta, tb)))
        print(f"        sigma_star at beta = {float(tb):.4f} : {val:.6e}")
        assert val > prev, "sigma_star not monotonically increasing in beta"
        prev = val
    assert prev > 1e100, "sigma_star did not reach an unboundedly large value"
    print("      [pass] sequence diverges monotonically beyond 1e100")

    banner("Step 4: sign of comparative statics on sigma_star")

    # Express sigma_star in (alpha, beta, delta, p)
    sigma_star_full = kappa_expr ** (1 / (1 - beta))
    print(f"\n  sigma_star(alpha, beta, delta, p) = {sigma_star_full}")

    # Build partial derivatives symbolically
    partials = {
        "d sigma_star / d alpha": sp.diff(sigma_star_full, alpha),
        "d sigma_star / d delta": sp.diff(sigma_star_full, delta),
        "d sigma_star / d p":     sp.diff(sigma_star_full, p),
    }

    # Evaluate each partial across a small grid of interior calibrations and
    # confirm positivity at every point. This is not a proof of global sign,
    # but each failure would falsify the manuscript's claimed sign.
    calibrations = [
        {alpha: sp.Rational(1, 2), beta: sp.Rational(1, 4), delta: sp.Rational(1, 2), p: sp.Rational(1, 2)},
        {alpha: sp.Rational(1, 1), beta: sp.Rational(1, 2), delta: sp.Rational(1, 2), p: sp.Rational(1, 2)},
        {alpha: sp.Rational(2, 1), beta: sp.Rational(9, 10), delta: sp.Rational(3, 4), p: sp.Rational(1, 4)},
        {alpha: sp.Rational(3, 2), beta: sp.Rational(1, 3), delta: sp.Rational(9, 10), p: sp.Rational(2, 3)},
    ]

    for name, expr in partials.items():
        print(f"\n  {name}")
        for c in calibrations:
            val = sp.N(expr.subs(c))
            tag = "+" if val > 0 else ("0" if val == 0 else "-")
            print(f"      alpha={c[alpha]}, beta={c[beta]}, delta={c[delta]}, p={c[p]}"
                  f"   value = {float(val):+.6f}   sign = {tag}")
            assert val > 0, f"{name} is not positive at calibration {c}"
        print(f"      [pass] {name} > 0 at every tested interior calibration")

    banner("Step 5: substrate-supported cooperation range (closing summary)")

    # This section is a closing summary of the manuscript's definitional claim
    # about how external enforcement extends the supported range; it does not
    # add a new symbolic identity to verify beyond Theorem 2.1's threshold result.
    print("\n  Without substrate: cooperation sustained for sigma in [0, rho]")
    print("  With substrate providing expected discounted punishment E:")
    print("      cooperation sustained for sigma in [0, rho + E], requiring E >= sigma - rho")
    print("  At any sigma > rho, the substrate's enforcement contribution is E(sigma) >= sigma - rho.")

    banner("All verifications passed")
    print("""
  Result: Theorem 2.1's algebraic content holds symbolically.
    - sigma_star = kappa^(1/(1-beta)) satisfies sigma = kappa * sigma^beta
    - sigma_star is finite and positive for beta in (0,1), kappa > 0
    - sigma_star -> kappa as beta -> 0+;  sigma_star -> infinity as beta -> 1- (kappa>1)
    - d sigma_star / d alpha,  d sigma_star / d delta,  d sigma_star / d p  > 0
      across tested interior calibrations
""")


if __name__ == "__main__":
    main()
