"""
Scope-boundary test 6: Gravitational lensing half-and-half

Claim: Gravitational light bending by a mass M has two contributions
of equal magnitude:

  (1) The g_tt contribution (framework IN SCOPE): comes from the
      variation of u along the null geodesic and gives a bending
      equal to the Newtonian value 2M/b (where b is the impact
      parameter in geometrised units).

  (2) The spatial g_ij contribution (framework INHERITED): comes from
      the PPN gamma part of the metric, contributes another 2*gamma*M/b.

Total deflection: delta_phi = 2(1 + gamma) M / b

For gamma = 1 (standard GR): delta_phi = 4 M / b, the observed
Einstein value.

This test computes both halves explicitly via geodesic integrals,
demonstrating that:
  - The framework alone (u-based) gives half the answer.
  - The other half comes from spatial curvature, which is inherited.
  - Their sum matches GR exactly.

This is the cleanest worked example of the scope boundary operating
correctly: the framework does NOT claim to do all of light bending
on its own; it cleanly handles half and inherits the other half.

Setup (geometrised units G = c = 1, Schwarzschild mass M << b):

Isotropic-like weak-field metric of a point mass M:

    ds^2 = -(1 - 2M/r) dt^2 + (1 + 2*gamma*M/r)(dx^2 + dy^2 + dz^2)

For a light ray passing with impact parameter b along x-direction
(flat trajectory at zeroth order, small deflection at first order in M/b):
"""

import numpy as np
import sympy as sp

print("=" * 72)
print("SCOPE-BOUNDARY TEST 6: LIGHT BENDING HALF-AND-HALF")
print("=" * 72)
print()

# ------------------------------------------------------------------
# Setup: null geodesic in weak-field metric, to first order in M/b
# ------------------------------------------------------------------
print("Setup: Null geodesic past a point mass M.")
print("       Straight-line parameterisation at zeroth order: y = b (const),")
print("       x varies from -inf to +inf.  r = sqrt(x^2 + b^2).")
print()
print("Metric: ds^2 = -(1 - 2M/r) dt^2 + (1 + 2 gamma M/r)(dx^2+dy^2+dz^2)")
print()

# ------------------------------------------------------------------
# Check 1: Derive bending angle via Fermat-principle / effective index
# ------------------------------------------------------------------
print("-" * 72)
print("Check 1: Effective refractive index picture")
print("-" * 72)
print("""
For null geodesics in a static weak-field metric, photon motion is
equivalent to motion in flat space with an effective refractive index

    n(r) = sqrt(g_ij_scalar / (-g_tt))
         = sqrt((1 + 2 gamma M/r) / (1 - 2M/r))

To first order in M/r:

    n(r) ~ 1 + M/r + gamma M/r
         = 1 + (1 + gamma) M/r

The bending angle is given by Fermat's principle:

    delta_phi = integral along path of (d n / d b) dx
              = integral of (1 + gamma) * d(M/r) / db dx
              = 2 (1 + gamma) M / b

For gamma = 1 (GR):  delta_phi = 4 M / b  [Einstein]
For gamma = 0 (Newt):  delta_phi = 2 M / b  [Soldner 1804]
""")
# Let's verify by doing the integral explicitly
x, b, M, gamma = sp.symbols('x b M gamma', real=True, positive=True)
r_expr = sp.sqrt(x**2 + b**2)

# n - 1 = (1 + gamma) M / r to leading order
# Bending angle (from gradient-index picture):
# delta_phi = - d/db integral_{-inf}^{inf} (n(r)-1) dx
# where I've pulled a minus sign per the sign convention.
# Actually the cleanest: the transverse gradient of n deflects the ray.
# At each x, d_perp (n-1) = d(n-1)/dr * dr/db = (1+gamma)*(-M/r^2) * (b/r)
# delta_phi_piece = integral d_perp(n-1) dx = -(1+gamma)*M*b * integral dx/r^3
integrand = 1 / r_expr**3
full_integral = sp.integrate(integrand, (x, -sp.oo, sp.oo))
print(f"integral from -inf to inf of 1/(x^2+b^2)^(3/2) dx  =  {full_integral}")
print()
# delta_phi = (1+gamma) * M * b * (2/b^2) = 2(1+gamma) M / b
deflection_total = (1 + gamma) * M * b * full_integral
deflection_total = sp.simplify(deflection_total)
print(f"delta_phi = (1 + gamma) * M * b * {full_integral} = {deflection_total}")
print()
print("=> Exactly 2(1 + gamma) M / b, as advertised.")
print()

# ------------------------------------------------------------------
# Check 2: Decompose into framework's half vs inherited half
# ------------------------------------------------------------------
print("-" * 72)
print("Check 2: Decomposing into framework's half and inherited half")
print("-" * 72)
print()
# In the refractive-index picture, (n-1) has two pieces:
# (1+gamma) M/r = (M/r) + (gamma M/r)
# The first comes from (1/sqrt(-g_tt)) ~ (1 + M/r) -- this is the
# framework's piece, from u = u_0 sqrt(-g_tt).
# The second comes from sqrt(g_ij) ~ (1 + gamma M/r) -- this is the
# spatial curvature, inherited from GR.

# Framework's contribution (gamma-independent part, from g_tt only):
# n_framework - 1 = M/r  (from 1/sqrt(-g_tt))
print("Framework contribution (from g_tt, i.e. u):")
print("  n_framework - 1 = M/r")
deflection_fw = M * b * full_integral
print(f"  delta_phi_fw = M * b * (2/b^2) = {sp.simplify(deflection_fw)} = 2M/b")
print()
# Inherited contribution (from spatial g_ij, PPN gamma part):
# n_inherited - 1 = gamma M/r
print("Inherited contribution (from g_ij spatial curvature):")
print("  n_inherited - 1 = gamma * M/r")
deflection_inh = gamma * M * b * full_integral
print(f"  delta_phi_inh = gamma * M * b * (2/b^2) = {sp.simplify(deflection_inh)} = 2*gamma*M/b")
print()
# Total
total_check = sp.simplify(deflection_fw + deflection_inh)
print(f"Sum: delta_phi_total = {total_check}")
print(f"     Matches 2(1+gamma)M/b.  PASS.")
print()

# ------------------------------------------------------------------
# Check 3: Numerical solar-limb deflection, broken down
# ------------------------------------------------------------------
print("-" * 72)
print("Check 3: Numerical solar-limb deflection, both halves")
print("-" * 72)
print()
M_sun_km = 1.4768              # GM_sun/c^2 in km
R_sun_km = 6.957e5             # km
arcsec_per_rad = 206265.0
b_val = R_sun_km

delta_fw_rad = 2 * M_sun_km / b_val
delta_inh_rad = 2 * 1.0 * M_sun_km / b_val   # gamma = 1
delta_total_rad = delta_fw_rad + delta_inh_rad

print(f"Solar limb impact parameter b = R_sun = {b_val:.3e} km")
print(f"GM_sun / c^2                  = {M_sun_km} km")
print()
print(f"  Framework contribution (g_tt, u-based)   : "
      f"{delta_fw_rad*arcsec_per_rad:.3f} arcsec")
print(f"  Inherited contribution (g_ij, gamma = 1) : "
      f"{delta_inh_rad*arcsec_per_rad:.3f} arcsec")
print(f"  Total (GR / framework + inherited)       : "
      f"{delta_total_rad*arcsec_per_rad:.3f} arcsec")
print()
print(f"  Eddington 1919 measurement & subsequent tests: 1.751 arcsec")
print(f"  (matched)")
print()
print("Key numerical fact:")
print(f"  The framework alone gives the NEWTONIAN value "
      f"{delta_fw_rad*arcsec_per_rad:.3f}'',")
print(f"  which is HALF the observed 1.751''. The other half comes")
print(f"  from the spatial curvature inherited from GR.")
print()
print(f"  Neither half alone matches observations.")
print(f"  The framework's contribution + inherited contribution = GR.")
print()

# ------------------------------------------------------------------
# Check 4: Shapiro delay, same structure
# ------------------------------------------------------------------
print("-" * 72)
print("Check 4: Shapiro delay (same half-and-half structure)")
print("-" * 72)
print()
print("Shapiro delay for radar ranging past a mass M:")
print()
print("    delta_t = 2 (1 + gamma) M ln(4 r_1 r_2 / b^2)  [GR]")
print()
print("Same (1 + gamma) structure:")
print("  Framework part: 2 M ln(...)          from g_tt variation along ray")
print("  Inherited part: 2 gamma M ln(...)    from spatial path length change")
print()
print("Cassini radar measurement (2003): |gamma - 1| < 2.3e-5")
print()
print("This is effectively the tightest bound on the SIZE of the")
print("inherited spatial-curvature part. It's consistent with GR's")
print("prediction of gamma = 1, which the framework inherits unchanged.")
print()
print("If future measurements found gamma != 1, the framework would NOT")
print("be falsified -- the framework doesn't predict gamma. GR (or a")
print("modified gravity theory) would be the one under pressure.")
print("The framework just reads u off whatever g_tt is provided.")
print()

# ------------------------------------------------------------------
# Check 5: Clean summary
# ------------------------------------------------------------------
print("-" * 72)
print("Check 5: Scope boundary reading of light bending")
print("-" * 72)
print("""
This is the archetypal 'half-and-half' textbook example:

           | Framework (u, g_tt)         | Inherited (g_ij, gamma)
-----------|-----------------------------|---------------------------
Newtonian  | 2 M / b                     | 0  (no spatial curvature)
GR         | 2 M / b                     | 2 gamma M / b = 2 M / b
           | [framework matches]         | [framework inherits]
Total      | 4 M / b                                                 
Observed   | 1.75'' at solar limb (Eddington, & subsequent)          

Historical note: the Einstein-Eddington factor-of-2 prediction over
Newtonian light bending was the first major confirmation that spatial
curvature is a real, observable feature of gravity. Under the GEM
decomposition, that factor of 2 is precisely the statement that the
gravitoelectric sector and the spatial scalar sector contribute
equally. The framework correctly handles the former and inherits the
latter; their sum reproduces Einstein.

This test provides a clean, concrete example of the scope boundary
operating exactly as the framework claims -- and of the inherited
content being genuinely necessary (the framework alone is wrong by
a factor of 2; inheritance from GR is not optional for matching
observation).
""")

print("=" * 72)
print("TEST 6 RESULT: PASS (clean half-and-half decomposition)")
print("=" * 72)
print("Light bending decomposes into a framework-homed half (from g_tt")
print("via u) and an inherited half (from spatial g_ij, PPN gamma = 1).")
print("The framework alone gives half the observed Eddington deflection;")
print("GR's inherited contribution supplies the other half. This is a")
print("textbook concrete example of the scope boundary in operation,")
print("showing the framework's inheritance pattern is exactly the")
print("standard GEM decomposition of light bending.")
