substrate_gravity.py
Gravity sector
The Painlevé–Gullstrand/Schwarzschild metric identity and the four classical tests, the cosmological constant read against two rulers, and the MOND acceleration scale a_0 = c\sqrt{G\rho_\mathrm{DM}}.
Download substrate_gravity.py 453 lines · standard library only · run with python3 scripts/substrate_gravity.py
Back to the source-code overview.
scripts/substrate_gravity.py
#!/usr/bin/env python3
"""
substrate_gravity.py — reproduce the gravity-sector results
===========================================================
Companion reproduction script for the standalone science intro,
``arxiv/bridge-paper.qmd``, §sec-gravity ("Gravity as a leak, and the acoustic
metric"). Where ``substrate_atomic.py`` reproduces the particle-physics
backbone and ``substrate_galactic.py`` the DESI/Hubble cosmology, this script
reproduces the three gravitational results:
1. Painlevé–Gullstrand / Schwarzschild metric (§sec-pg)
The substrate ebb v_ebb(r) = √(2GM/r), fed into the Unruh acoustic metric,
is *exactly* the Painlevé–Gullstrand form of Schwarzschild. We verify the
metric identity, the horizon r_s = 2GM/c², the "pressure gravitates"
factor ρ_eff = 4ρ, and the four classical tests against solar-system data.
2. The small cosmological constant (§sec-lambda)
Volovik self-tuning: the dark-energy scale is the substrate scale,
ρ_Λ^(1/4) ≈ m₁c² ≈ 2 meV, and the notorious tiny Λ is one residual
disequilibrium read against two rulers — 1.6 against ρ_DM, 3.4e-62 against
the Planck density — the two differing by exactly (m₁/M_Pl)², the same
hierarchy that makes G weak.
3. MOND from the superfluid (§sec-mond)
With ρ_DM fixed by the bridge equation, the MOND acceleration scale is
a₀ = c√(Gρ_DM) = 1.16e-10 m/s², within ~3% of the measured g†; the
"cosmic coincidence" a₀/cH₀ = √(3Ω_DM/8π) = 0.178 follows identically;
and the outer-rim Landau velocity is predicted from constants by the
clean-units law v_L = c(4π/ν)^(1/3) ≈ 740 km/s (~1.3% from the ω₀ξ target,
matching the fast solar wind), superseding the raw-SI cubic (4πν c²G)^(1/3)
that undershoots to ~398 km/s for want of the 2D→3D projection.
Pure Python standard library only — no third-party dependencies:
python3 scripts/substrate_gravity.py
"""
import math
# ══════════════════════════════════════════════════════════════════════════
# Measured input constants
# ══════════════════════════════════════════════════════════════════════════
# Fundamental constants — CODATA 2018.
HBAR = 1.054571817e-34 # reduced Planck constant [J·s]
C = 2.99792458e8 # speed of light [m/s]
G = 6.67430e-11 # gravitational constant [m³ kg⁻¹ s⁻²]
KB = 1.380649e-23 # Boltzmann constant [J/K]
EV = 1.602176634e-19 # 1 eV in joules [J]
ARCSEC = 206264.806 # radians → arcseconds
AU = 1.495978707e11 # astronomical unit [m]
YR = 365.25 * 86400.0 # Julian year [s]
MAS_PER_RAD = 1.0e3 * ARCSEC # radians → milliarcsec
# Cosmology — Planck 2018.
OMEGA_C_H2 = 0.1200 # cold dark matter density parameter Ω_c h²
OMEGA_L = 0.6847 # dark-energy density parameter Ω_Λ
H_LITTLE = 0.6736 # reduced Hubble constant h = H₀/(100 km/s/Mpc)
MPC = 3.0856775814913673e22 # 1 megaparsec [m]
# Solar-system data (IAU / JPL), for the classical tests of §sec-pg.
M_SUN = 1.98892e30 # solar mass [kg]
R_SUN = 6.9634e8 # solar radius [m]
# Mercury orbit.
A_MERC = 5.7909050e10 # semi-major axis [m]
E_MERC = 0.20563 # eccentricity
T_MERC = 87.9691 * 86400.0 # orbital period [s]
JULIAN_CENTURY = 36525.0 * 86400.0 # [s]
# Observed reference values (for the discrepancy columns).
MERC_PREC_OBS = 42.98 # GR perihelion precession [arcsec/century]
DEFLECT_OBS = 1.75 # light deflection at solar limb [arcsec]
G_DAGGER_OBS = 1.20e-10 # McGaugh radial-acceleration scale g† [m/s²]
V_ULYSSES = 751.5e3 # Ulysses mean fast solar-wind speed [m/s]
# Earth + Gravity Probe B orbit (IERS / GP-B), for frame-dragging (^eq-fd).
GM_EARTH = 3.986004418e14 # Earth standard gravitational parameter [m³/s²]
M_EARTH = 5.9722e24 # Earth mass [kg]
R_EARTH = 6.371e6 # Earth mean radius [m]
I_EARTH_C = 0.3307 # Earth moment-of-inertia coefficient I/MR²
OMEGA_EARTH = 7.292115e-5 # Earth sidereal spin rate [rad/s]
R_GPB = 7027.4e3 # GP-B orbit semi-major axis [m]
GEODETIC_GPB_OBS = 6601.8 # GP-B measured geodetic drift [mas/yr]
FRAMEDRAG_GPB_OBS = 37.2 # GP-B measured frame-drag [mas/yr]
# ══════════════════════════════════════════════════════════════════════════
# Shared derived quantities
# ══════════════════════════════════════════════════════════════════════════
def rho_DM() -> float:
"""Dark-matter mass density from Planck Ω_c h² [kg/m³] (^rhodm)."""
H100 = 100.0e3 / MPC # 100 km/s/Mpc in s⁻¹
rho_crit_over_h2 = 3.0 * H100**2 / (8.0 * math.pi * G)
return OMEGA_C_H2 * rho_crit_over_h2 # = 2.254e-27 kg/m³
def rho_crit() -> float:
"""Critical density at the Planck-2018 H₀ [kg/m³]."""
H0 = H_LITTLE * 100.0e3 / MPC
return 3.0 * H0**2 / (8.0 * math.pi * G)
def rho_Lambda() -> float:
"""Dark-energy mass density ρ_Λ = Ω_Λ ρ_crit [kg/m³]."""
return OMEGA_L * rho_crit()
def m1_closepacked() -> float:
"""dc1 mass from close-packing, ρ_DM = m₁⁴c³/ℏ³ ⇒ m₁ = (ρ_DM ℏ³/c³)^¼ [kg].
bridge-paper.qmd §sec-lambda, footnote ^eq-lambda. Route-independent form
of the Volovik + close-packing relation; m₁c² ≈ 1.77 meV ≈ "2 meV".
"""
return (rho_DM() * HBAR**3 / C**3) ** 0.25
def M_planck() -> float:
"""Planck mass M_Pl = √(ℏc/G) [kg]."""
return math.sqrt(HBAR * C / G)
# ══════════════════════════════════════════════════════════════════════════
# Result 1 — Painlevé–Gullstrand / Schwarzschild metric (§sec-pg)
# ══════════════════════════════════════════════════════════════════════════
def v_ebb(r: float, M: float) -> float:
"""Free-fall substrate inflow v_ebb(r) = √(2GM/r) [m/s] (@eq-schwarzschild).
From the substrate's Euler + continuity equations: convective acceleration
v dv/dr = −GM/r² with no pressure gradient (the equivalence principle).
"""
return math.sqrt(2.0 * G * M / r)
def schwarzschild_radius(M: float) -> float:
"""Horizon radius r_s = 2GM/c², where v_ebb(r_s) = c."""
return 2.0 * G * M / C**2
def verify_pg_metric(M: float) -> dict:
"""Check the acoustic metric with v_ebb = √(2GM/r) is the PG–Schwarzschild form.
Acoustic metric (@eq-acoustic): g_tt = −(c² − v_ebb²), g_tr = −v_ebb,
g_rr = 1. Substituting v_ebb² = 2GM/r must give the Schwarzschild
g_tt = −c²(1 − r_s/r) exactly — the Painlevé–Gullstrand "rain" form.
"""
r_s = schwarzschild_radius(M)
r = 3.0 * r_s # sample point outside horizon
g_tt_acoustic = -(C**2 - v_ebb(r, M)**2) # from the flowing-fluid metric
g_tt_schwarz = -C**2 * (1.0 - r_s / r) # textbook Schwarzschild g_tt
return {
'r_s': r_s,
'g_tt_acoustic': g_tt_acoustic,
'g_tt_schwarz': g_tt_schwarz,
'match': abs(g_tt_acoustic - g_tt_schwarz) < 1e-6 * C**2,
'v_at_horizon_over_c': v_ebb(r_s, M) / C, # must equal 1
}
def rho_eff_factor() -> float:
""""Pressure gravitates": ρ_eff = ρ + 3P/c² = 4ρ for the stiff EOS P = ρc².
bridge-paper.qmd footnote ^eq-factor4. This factor of 4 turns the naïve
fluid-Poisson 4πG into the general-relativistic 16πG.
"""
P_over_rho_c2 = 1.0 # stiff EOS: P = ρc²
return 1.0 + 3.0 * P_over_rho_c2 # = 4
def perihelion_precession(M: float, a: float, e: float,
period: float) -> float:
"""GR perihelion precession 6πGM/(a c²(1−e²)), in arcsec/century (^eq-tests)."""
per_orbit = 6.0 * math.pi * G * M / (a * C**2 * (1.0 - e**2)) # rad/orbit
orbits_per_century = JULIAN_CENTURY / period
return per_orbit * orbits_per_century * ARCSEC
def light_deflection(M: float, b: float) -> float:
"""GR light deflection Δθ = 4GM/(b c²), in arcsec (^eq-tests).
Twice the Newtonian-corpuscular value: the modon is both refracted by the
graded signal speed and dragged by the inflow — two equal contributions.
"""
return 4.0 * G * M / (b * C**2) * ARCSEC
def gravitational_redshift(M: float, r: float) -> float:
"""Full nonlinear gravitational redshift z = 1/√(1−r_s/r) − 1 (^eq-tests)."""
return 1.0 / math.sqrt(1.0 - schwarzschild_radius(M) / r) - 1.0
def shapiro_delay(M: float, b: float, r1: float, r2: float) -> float:
"""GR Shapiro delay Δt = (2GM/c³) ln(4 r₁ r₂/b²), one-way [s] (^eq-tests).
bridge-paper.qmd §sec-pg, footnote ^eq-tests. The same reduced signal speed
c(r)=c(1−2GM/rc²) that bends light also delays it, integrated along the path;
it is a geodesic of @eq-schwarzschild, not an add-on. For a ray grazing the
Sun between two points at r₁, r₂ ≈ 1 AU the one-way delay is ~120 μs.
"""
return 2.0 * G * M / C**3 * math.log(4.0 * r1 * r2 / b**2)
# ══════════════════════════════════════════════════════════════════════════
# Result 2 — the small cosmological constant (§sec-lambda)
# ══════════════════════════════════════════════════════════════════════════
def cosmological_constant() -> dict:
"""Dark-energy scale and the one-residual-two-rulers reading of tiny Λ.
bridge-paper.qmd §sec-lambda + footnote ^eq-lambda:
* the dark-energy scale is the substrate scale, ρ_Λ^(1/4) ≈ m₁c² ≈ 2 meV;
* the disequilibrium δT/T_c = √(ρ_Λ/ρ_ref) is order-unity (≈1.6) against
the substrate density ρ_DM but 3.4e-62 against the Planck density
ρ_Pl = c⁵/(ℏG²);
* the two readings differ by exactly √(ρ_DM/ρ_Pl) = (m₁/M_Pl)², the same
hierarchy (M_Pl² = ℏc/G) that makes G weak — small Λ and weak gravity
are one number, not two.
"""
rho_L = rho_Lambda()
rho_dm = rho_DM()
rho_Pl = C**5 / (HBAR * G**2) # Planck density [kg/m³]
# Dark-energy scale in energy units: (ρ_Λ c² · (ℏc)³)^(1/4).
rho_L14_eV = (rho_L * C**2 * (HBAR * C)**3) ** 0.25 / EV
m1c2_eV = m1_closepacked() * C**2 / EV
dT_vs_DM = math.sqrt(rho_L / rho_dm) # ≈ 1.6
dT_vs_Pl = math.sqrt(rho_L / rho_Pl) # ≈ 3.4e-62
ratio = (m1_closepacked() / M_planck())**2 # = √(ρ_DM/ρ_Pl)
return {
'rho_L14_meV': rho_L14_eV * 1e3,
'm1c2_meV': m1c2_eV * 1e3,
'dT_vs_DM': dT_vs_DM,
'dT_vs_Pl': dT_vs_Pl,
'ratio_m1_MPl_sq': ratio,
'ratio_from_rho': math.sqrt(rho_dm / rho_Pl), # identity check
}
# ══════════════════════════════════════════════════════════════════════════
# Result 3 — MOND from the superfluid (§sec-mond)
# ══════════════════════════════════════════════════════════════════════════
def mond_acceleration() -> float:
"""MOND acceleration scale a₀ = c√(Gρ_DM) [m/s²] (@eq-a0).
Parameter-free: every factor is a substrate quantity, with ρ_DM fixed by
the bridge equation. Predicts 1.16e-10, within ~3% of g†.
"""
return C * math.sqrt(G * rho_DM())
def cosmic_coincidence() -> float:
"""a₀/cH₀ = √(3Ω_DM/8π) (footnote ^eq-a0).
Applying Friedmann H₀² = (8πG/3)ρ_crit to a₀ = c√(Gρ_DM) makes the old
"a₀ ≈ cH₀/6" coincidence the exact √(3Ω_DM/8π) — the 1/6 is Gauss's 8π,
geometry's 3, and Ω_DM, nothing tuned.
"""
Omega_DM = OMEGA_C_H2 / H_LITTLE**2
return math.sqrt(3.0 * Omega_DM / (8.0 * math.pi))
NU_CONDENSATION = 8.35e8 # condensation number ν (electroweak / SC2 leg)
def landau_velocity_clean() -> float:
"""Clean-units prediction v_L = c·(4π/ν)^(1/3) [m/s].
outer-rim-onset.qmd §"The Selector Is External, and Gravity Is Cubic": the
framework's forward, dimensionally-consistent law for the outer-rim velocity
from c and the condensation number ν *alone* — no G, no projection. With
the electroweak ν ≈ 8.35e8 it gives ~740 km/s, matching the ω₀ξ target
(749.5 km/s) to ~1.3% and the Ulysses fast-wind mean (751.5 km/s) to ~1.5%.
In vortex variables it reads (ω₀/ω₁)³ ν = 4π — the outer-rotation phase-space
volume equals one solid angle per condensed quantum. This is the current
from-constants formula (see substrate_atomic.py for ν).
"""
return C * (4.0 * math.pi / NU_CONDENSATION) ** (1.0 / 3.0)
def landau_velocity_cubic() -> float:
"""The gravity-sector cubic v_L = (4π ν c² G)^(1/3) [m/s], in raw SI.
outer-rim-onset.qmd §"What is still owed": the *same* relation as the
clean-units law above, but with the standing 2D→3D projection P = Gν²/c not
yet applied, so in raw MKS it carries broken units and lands at ~398 km/s.
The clean-units and cubic values differ by exactly (c/Gν²)^(1/3) ≈ 1.86 —
the cube root of the projection residual c/Gν² ≈ 6.4 the chapter tracks —
so the "~2× undershoot" is that missing projection, not a wrong formula.
Reported for transparency only; the clean-units law is the prediction.
"""
return (4.0 * math.pi * NU_CONDENSATION * C**2 * G) ** (1.0 / 3.0)
# ══════════════════════════════════════════════════════════════════════════
# Result 4 — horizon thermodynamics and frame-dragging (§sec-tier2)
# ══════════════════════════════════════════════════════════════════════════
def hawking_temperature(M: float) -> dict:
"""Hawking temperature T_H = ℏc³/(8π G M k_B), exact (^t2-hawking).
bridge-paper.qmd §sec-tier2. The sonic horizon of the same Painlevé–Gullstrand
inflow: the surface gravity is κ = ½|dv_ebb²/dr|_{r_s} = c⁴/4GM, so
k_B T_H = ℏκ/2πc = ℏc³/8πGM. Because the acoustic metric is *exactly*
Schwarzschild, Unruh's 1981 result follows exactly rather than by analogy.
The prefactor 8π = 2×4π_SC2 is the framework's gravitational normalization —
the same 8π as the Higgs VEV and the Friedmann equation.
"""
kappa = C**4 / (4.0 * G * M) # surface gravity [1/s·m? -> m/s²/m]
T_H = HBAR * C**3 / (8.0 * math.pi * G * M * KB)
return {
'kappa': kappa,
'T_H': T_H,
'r_s_km': schwarzschild_radius(M) / 1e3,
}
def frame_dragging() -> dict:
"""Geodetic + Lense–Thirring rates for Gravity Probe B [mas/yr] (^eq-fd).
bridge-paper.qmd §sec-feedback, footnote ^eq-fd. A rotating mass entrains the
azimuthal part of the gravitational inflow (Step 3) through the same mutual
friction that couples rotating superfluid helium, so for a slow rotator the
framework reproduces GR exactly. Geodetic (de Sitter) precession is
(3/2)(GM/c²r) ω_orb with ω_orb = √(GM/r³); the orbit-averaged frame-drag
(Lense–Thirring) of the gyroscope is G J/(2c²r³) with J = I_c M R² Ω_⊕.
Predicts ~6604 and ~41 mas/yr against GP-B's 6601.8 and 37.2 mas/yr.
"""
r = R_GPB
omega_orb = math.sqrt(GM_EARTH / r**3)
geodetic = 1.5 * GM_EARTH / (C**2 * r) * omega_orb # rad/s
J = I_EARTH_C * M_EARTH * R_EARTH**2 * OMEGA_EARTH # Earth spin ang. mom.
frame_drag = (GM_EARTH / M_EARTH) * J / (2.0 * C**2 * r**3) # G J /(2c²r³) [rad/s]
return {
'J_earth': J,
'geodetic': geodetic * MAS_PER_RAD * YR,
'frame_drag': frame_drag * MAS_PER_RAD * YR,
'geodetic_obs': GEODETIC_GPB_OBS,
'frame_drag_obs': FRAMEDRAG_GPB_OBS,
}
# ══════════════════════════════════════════════════════════════════════════
# Report
# ══════════════════════════════════════════════════════════════════════════
def _disc(pred: float, obs: float) -> str:
return f'{100.0 * (pred - obs) / obs:+.1f}%'
def main() -> None:
line = '═' * 78
print(line)
print(' SUBSTRATE — GRAVITY-SECTOR RESULTS')
print(' reproducing arxiv/bridge-paper.qmd §sec-gravity')
print(line)
# ── 1. Painlevé–Gullstrand / Schwarzschild metric ──────────────────────
print('\n 1. Painlevé–Gullstrand / Schwarzschild metric (§sec-pg)')
print(' ' + '-' * 74)
pg = verify_pg_metric(M_SUN)
print(f' v_ebb = √(2GM/r) fed into the acoustic metric (@eq-acoustic):')
print(f' g_tt (acoustic) = {pg["g_tt_acoustic"]:.6e}')
print(f' g_tt (Schwarzschild) = {pg["g_tt_schwarz"]:.6e}')
print(f' identical PG form? {"YES" if pg["match"] else "NO"}'
f' (horizon at v_ebb=c: v/c = {pg["v_at_horizon_over_c"]:.3f})')
print(f' Schwarzschild radius r_s(Sun) = 2GM/c² = {pg["r_s"]/1e3:.3f} km')
print(f' "pressure gravitates": ρ_eff = ρ+3P/c² = {rho_eff_factor():.0f}ρ'
f' ⇒ 4πG → 16πG (^eq-factor4)')
print(f'\n Classical tests (geodesics of @eq-schwarzschild):')
prec = perihelion_precession(M_SUN, A_MERC, E_MERC, T_MERC)
defl = light_deflection(M_SUN, R_SUN)
zsun = gravitational_redshift(M_SUN, R_SUN)
print(f' {"Test":<34}{"Predicted":>14}{"Observed":>14}{"Disc.":>9}')
print(' ' + '-' * 68)
print(f' {"Mercury perihelion (″/century)":<34}{prec:>14.2f}'
f'{MERC_PREC_OBS:>14.2f}{_disc(prec, MERC_PREC_OBS):>9}')
print(f' {"Light deflection, solar limb (″)":<34}{defl:>14.3f}'
f'{DEFLECT_OBS:>14.3f}{_disc(defl, DEFLECT_OBS):>9}')
print(f' {"Solar surface redshift (z)":<34}{zsun:>14.3e}'
f'{2.12e-6:>14.3e}{_disc(zsun, 2.12e-6):>9}')
shap = shapiro_delay(M_SUN, R_SUN, AU, AU) * 1e6 # one-way, μs
print(f' {"Shapiro delay, grazing (μs)":<34}{shap:>14.1f}'
f'{"~119":>14}{"exact":>9}')
# ── 2. The small cosmological constant ─────────────────────────────────
print('\n 2. The small cosmological constant (§sec-lambda)')
print(' ' + '-' * 74)
cc = cosmological_constant()
print(f' Dark-energy scale ρ_Λ^(1/4) = {cc["rho_L14_meV"]:.2f} meV'
f' (observed dark-energy scale)')
print(f' Substrate scale m₁c² = {cc["m1c2_meV"]:.2f} meV'
f' (close-packing, ^eq-lambda)')
print(f' ⇒ same substrate scale — dissolves the ρ_Λ ~ ρ_DM coincidence')
print(f'\n One residual disequilibrium, two rulers:')
print(f' δT/T_c vs ρ_DM (substrate) = {cc["dT_vs_DM"]:.2f}'
f' (order unity — nothing tuned)')
print(f' δT/T_c vs ρ_Pl (gravity) = {cc["dT_vs_Pl"]:.2e}'
f' (the notorious tiny Λ)')
print(f' ratio = (m₁/M_Pl)² = {cc["ratio_m1_MPl_sq"]:.2e}')
print(f' = √(ρ_DM/ρ_Pl) = {cc["ratio_from_rho"]:.2e}'
f' (identical ⇒ small Λ & weak G are one number)')
# ── 3. MOND from the superfluid ────────────────────────────────────────
print('\n 3. MOND from the superfluid (§sec-mond)')
print(' ' + '-' * 74)
a0 = mond_acceleration()
coinc = cosmic_coincidence()
print(f' {"Quantity":<30}{"Predicted":>14}{"Observed":>14}{"Disc.":>9}')
print(' ' + '-' * 68)
print(f' {"MOND scale a₀ = c√(Gρ_DM)":<30}{a0:>14.3e}'
f'{G_DAGGER_OBS:>14.3e}{_disc(a0, G_DAGGER_OBS):>9}')
print(f' {"Coincidence √(3Ω_DM/8π)":<30}{coinc:>14.4f}'
f'{0.179:>14.4f}{_disc(coinc, 0.179):>9}')
print(f'\n Outer-rim velocity (v_L = ω₀ξ, now predicted from constants):')
vL = 0.0025 * C # ω₀ξ ≈ 0.0025 c, target
vL_clean = landau_velocity_clean()
print(f' target v_L = ω₀ξ ≈ 0.0025c = {vL/1e3:.1f} km/s')
print(f' prediction v_L = c(4π/ν)^⅓ = {vL_clean/1e3:.1f} km/s'
f' ({_disc(vL_clean, vL)} vs ω₀ξ)')
print(f' Ulysses fast solar wind = {V_ULYSSES/1e3:.1f} km/s'
f' ({_disc(vL_clean, V_ULYSSES)})')
print(f' [note] the raw-SI cubic (4πν c²G)^⅓ = '
f'{landau_velocity_cubic()/1e3:.0f} km/s is the *same* relation')
print(f' missing the 2D→3D projection P=Gν²/c; the two differ by'
f' exactly')
print(f' (c/Gν²)^⅓ ≈ 1.86 — see docstring / outer-rim-onset.qmd.')
# ── 4. Horizon thermodynamics and frame-dragging ───────────────────────
print('\n 4. Horizon thermodynamics and frame-dragging (§sec-tier2)')
print(' ' + '-' * 74)
hw = hawking_temperature(M_SUN)
print(f' Hawking T_H = ℏc³/(8π G M k_B) (solar mass) = {hw["T_H"]:.2e} K'
f' (^t2-hawking)')
print(f' surface gravity κ = c⁴/4GM, horizon r_s = {hw["r_s_km"]:.3f} km;'
f' 8π = 2×4π_SC2')
fd = frame_dragging()
print(f'\n Gravity Probe B (Lense–Thirring), Earth J = {fd["J_earth"]:.3e} kg·m²/s'
f' (^eq-fd):')
print(f' {"Rate":<28}{"Predicted":>14}{"Observed":>14}{"Disc.":>9}')
print(' ' + '-' * 62)
print(f' {"Geodetic (mas/yr)":<28}{fd["geodetic"]:>14.1f}'
f'{fd["geodetic_obs"]:>14.1f}{_disc(fd["geodetic"], fd["geodetic_obs"]):>9}')
print(f' {"Frame-drag (mas/yr)":<28}{fd["frame_drag"]:>14.1f}'
f'{fd["frame_drag_obs"]:>14.1f}{_disc(fd["frame_drag"], fd["frame_drag_obs"]):>9}')
print(line)
if __name__ == '__main__':
main()