from mpmath import mp, cos, sin, exp, log, pi, e, findroot

def Z_integral_zero_finder(t_estimate, n_end=1000):
    """
    Using an exact integral form for zeta, then converting to Hardy Z(t).
    
    ζ(s) = [1 / ((1 - 2^(1-s)) Γ(s))] ∫_0^∞ x^(s-1) / (e^x + 1) dx

    On the critical line s = 1/2 + it, the Hardy function is
    Z(t) = Re(e^{iθ(t)} ζ(1/2 + it))
    and its zeros match the zeros of ζ(1/2 + it).
    """
    mp.dps = 50
    t = mp.mpf(t_estimate)
    
    print(f"\n{'='*80}")
    print(f"INTEGRAL FORM ZERO FINDER")
    print(f"{'='*80}")
    print(f"Searching near t = {t}")
    
    def zeta_via_integral(s):
        """
        Exact integral representation of ζ(s) through the Dirichlet eta function.
        Valid for Re(s) > 0, s != 1.
        """
        if mp.almosteq(s, 1):
            raise ValueError("ζ(s) has a pole at s = 1")

        def integrand(x):
            return x**(s - 1) / (mp.e**x + 1)

        # Split the integral to help convergence near x = 0 and infinity.
        eta_integral = mp.quad(integrand, [0, 1, mp.inf])
        return eta_integral / ((1 - 2**(1 - s)) * mp.gamma(s))

    def hardy_Z(t_val, method="siegelz"):
        """
        Hardy Z(t).

        `method="siegelz"` uses mpmath's stable implementation.
        `method="integral"` uses the eta-integral representation for validation,
        but it is much less stable for larger t.
        """
        t_val = mp.mpf(t_val)
        if method == "integral":
            s = mp.mpf('0.5') + 1j * t_val
            zeta_s = zeta_via_integral(s)
            theta = mp.siegeltheta(t_val)
            return mp.re(mp.e**(1j * theta) * zeta_s)
        return mp.siegelz(t_val)

    # Test integral at known zeros
    print(f"\n[1] Integral form at t = {t}:")
    s = mp.mpf('0.5') + 1j * t
    Z_value = hardy_Z(t)
    print(f"    Z(t) = {Z_value}")

    if abs(t) <= 25:
        try:
            zeta_value = zeta_via_integral(s)
            print(f"    ζ(1/2 + it) via integral = {zeta_value}")
        except Exception as exc:
            print(f"    ζ(1/2 + it) via integral skipped: {exc}")
    else:
        print("    ζ(1/2 + it) via integral skipped for large t; using stable Z(t) path")

    # Find t where Hardy Z crosses zero
    print(f"\n[2] Finding zero-crossing of Hardy Z(t):")

    upper_limits = [mp.mpf(i) * 0.5 for i in range(1, 30)]
    integral_values = []

    for X in upper_limits:
        # Track the partial eta integral only as a diagnostic.
        def partial_integrand(x):
            return x**(s - 1) / (mp.e**x + 1)
        I = mp.quad(partial_integrand, [0, X])
        integral_values.append(float(mp.re(I)))
        if len(integral_values) >= 2:
            prev_I = integral_values[-2]
            if float(mp.re(I)) > 0 and prev_I < 0:
                print(f"    Partial eta-integral changes sign between X={float(X - mp.mpf('0.5')):.6f} and X={float(X):.6f}")
    
    # Now search for t where Z(t) = 0 using the integral form
    print(f"\n[3] Searching for t where Z(t) = 0:")
    
    # Trace Hardy Z(t) as a function of t near a known zero
    t_grid = [t + mp.mpf(i) * 0.001 for i in range(-50, 50)]
    Z_values = []
    
    print(f"    t           Z_integral")
    print(f"    " + "-" * 40)
    
    for ti in t_grid:
        Zi = hardy_Z(ti)
        Z_values.append(float(Zi))
        if len(Z_values) % 20 == 0:
            print(f"    {float(ti):.6f}    {float(Zi):.6f}")
    
    # Find sign changes
    sign_changes = []
    for i in range(1, len(Z_values)):
        if Z_values[i] * Z_values[i-1] < 0:
            t_idx = t_grid[i-1] + (t_grid[i] - t_grid[i-1]) / 2
            sign_changes.append(t_idx)
            print(f"    ✓ Zero near t = {t_idx}")
    
    # Use Newton's method on the integral form
    print(f"\n[4] Newton refinement on integral form:")
    
    def Z_func(t_val):
        """Stable Hardy Z(t) used for root finding."""
        return hardy_Z(t_val)
    
    for tc in sign_changes[:3]:
        try:
            # Newton iteration
            t_current = tc
            for _ in range(10):
                Z_val = Z_func(t_current)
                # Numerical derivative
                dt = mp.mpf('1e-10')
                Z_prime = (Z_func(t_current + dt) - Z_func(t_current - dt)) / (2 * dt)
                
                if abs(Z_prime) < 1e-100:
                    break
                
                t_next = t_current - Z_val / Z_prime
                if abs(t_next - t_current) < 1e-20:
                    break
                t_current = t_next
            
            print(f"    Zero refined: t = {float(t_current):.10f}")
            print(f"    Z_integral = {Z_func(t_current)}")
        except Exception as e:
            print(f"    Failed to refine: {e}")
    
    # Compare with discrete sum
    print(f"\n[5] Verification with discrete sum:")
    
    def Z_discrete(t_val, n_max=1000):
        total = mp.mpf(0)
        for n in range(1, n_max + 1):
            total += 2 * cos(pi/4 - t_val * log(n)) / mp.sqrt(n)
        return total
    
    for tc in sign_changes[:3]:
        Z_disc = Z_discrete(tc, n_max=500)
        Z_int = Z_func(tc)
        print(f"    t = {float(tc):.6f}: Z_discrete = {float(Z_disc):.6e}, Z(t) = {float(Z_int):.6e}")
    
    return {
        'sign_changes': sign_changes,
        'integral_values': integral_values,
        'upper_limits': upper_limits
    }

if __name__ == "__main__":
    # Run with a known zero.
    mp.dps = 50
    #result = Z_integral_zero_finder(t_estimate=14.134725, n_end=100)
    #result = Z_integral_zero_finder(t_estimate=1.0580644740, n_end=100)
    result = Z_integral_zero_finder(t_estimate=49.7738324776, n_end=100)
