Legacy and Refinements

After the discovery: how close was Le Verrier, and what modern tools reveal.
Author

Jonathan Whitmore

Published

April 9, 2026

The Night of September 23

On the evening of 23 September 1846, Johann Galle opened a letter from a Frenchman he had never met. Le Verrier’s final prediction — refined in his third memoir of 31 August — gave specific coordinates for an unknown planet. That same night, Galle and his student Heinrich d’Arrest pointed the Berlin Observatory’s 9-inch Fraunhofer refractor at the predicted position. Within an hour, they found a star-like object not on their charts, very close to the predicted region of sky. Contemporary accounts describe the discovery as being within about a degree of Le Verrier’s search position.

Code
from discoverneptune.historical_values import (
    LEVERRIER_MEMOIR_1845,
    LEVERRIER_MEMOIR_1846_JUN,
    LEVERRIER_MEMOIR_1846_AUG,
    GALLE_OBSERVATION,
    MODERN_NEPTUNE,
)

# Le Verrier's published longitude vs Galle's observation
pred = LEVERRIER_MEMOIR_1846_AUG
obs = GALLE_OBSERVATION

lon_error = abs(pred.longitude_deg - obs.longitude_deg)
dist_error_pct = abs(pred.semi_major_axis_au - MODERN_NEPTUNE.semi_major_axis_au) / MODERN_NEPTUNE.semi_major_axis_au * 100

print("Le Verrier's prediction (Aug 1846):")
print(f"  Published longitude: {pred.longitude_deg:.2f}\u00b0 (quoted for 1 Jan 1847)")
print(f"  Predicted distance:  {pred.semi_major_axis_au:.3f} AU")
print()
print("Galle's observation (23 Sep 1846):")
print(f"  Observed longitude:  {obs.longitude_deg:.1f}\u00b0")
print()
print("Modern Neptune:")
print(f"  True distance:       {MODERN_NEPTUNE.semi_major_axis_au:.3f} AU")
print()
print(f"Raw longitude offset: {lon_error:.2f}\u00b0")
print("  Note: the published longitude is quoted for 1 Jan 1847,")
print("  so this is not a like-for-like comparison to 23 Sep 1846.")
print(f"Distance error:   {dist_error_pct:.1f}% — Le Verrier overestimated the distance")
Le Verrier's prediction (Aug 1846):
  Published longitude: 326.00° (quoted for 1 Jan 1847)
  Predicted distance:  36.154 AU

Galle's observation (23 Sep 1846):
  Observed longitude:  328.0°

Modern Neptune:
  True distance:       30.069 AU

Raw longitude offset: 2.00°
  Note: the published longitude is quoted for 1 Jan 1847,
  so this is not a like-for-like comparison to 23 Sep 1846.
Distance error:   20.2% — Le Verrier overestimated the distance

The published longitude remained impressively close to Neptune’s actual sky position, but the exact offset should be treated cautiously because the quoted memoir longitude is for 1 Jan 1847 rather than the night of discovery. The distance was not accurate — but as we saw in the previous chapter, this reflects the mass-distance degeneracy inherent in the inverse problem, not a failure of Le Verrier’s method.

Code
import matplotlib.pyplot as plt
import numpy as np

pred_jun = LEVERRIER_MEMOIR_1846_JUN.longitude_deg
pred_aug = LEVERRIER_MEMOIR_1846_AUG.longitude_deg
obs_galle = GALLE_OBSERVATION.longitude_deg


def _build(theme="light"):
    apply_style(theme)
    pal = palette(theme)
    cycle = category_cycle(theme)
    # cycle[0]: Jun prediction; cycle[1]: Aug prediction; pal.before: Galle discovery
    col_jun = cycle[0]
    col_aug = cycle[1]
    col_galle = pal.before

    fig, ax = plt.subplots(figsize=(10, 2.6))

    # The ±1° search window around Le Verrier's August prediction is shaded.
    ax.axvspan(pred_aug - 1.0, pred_aug + 1.0, color=col_galle, alpha=0.15,
               label="Galle's 1° search window")

    ax.plot([pred_jun], [1.0], marker="v", ms=14, color=col_jun,
            label="Le Verrier, Jun 1846")
    ax.plot([pred_aug], [1.0], marker="v", ms=14, color=col_aug,
            label="Le Verrier, Aug 1846 (sent to Galle)")
    ax.plot([obs_galle], [0.0], marker="*", ms=22, color=col_galle,
            markeredgecolor=pal.truth, label="Galle's observation, 23 Sep 1846")

    # Annotate each marker
    ax.annotate(f"{pred_jun:.1f}°", (pred_jun, 1.0), xytext=(0, 14),
                textcoords="offset points", ha="center", fontsize=9, color=col_jun)
    ax.annotate(f"{pred_aug:.1f}°", (pred_aug, 1.0), xytext=(0, 14),
                textcoords="offset points", ha="center", fontsize=9, color=col_aug)
    ax.annotate(f"{obs_galle:.1f}°", (obs_galle, 0.0), xytext=(0, -22),
                textcoords="offset points", ha="center", fontsize=9, color=col_galle)

    ax.set_xlim(pred_jun - 4, obs_galle + 4)
    ax.set_ylim(-0.9, 1.9)
    ax.set_yticks([])
    ax.set_xlabel("Heliocentric ecliptic longitude (degrees)")
    ax.set_title("The historical moment: prediction, search window, and discovery")
    ax.legend(loc="center left", bbox_to_anchor=(1.01, 0.5), fontsize=9,
              frameon=False)
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.spines["left"].set_visible(False)
    return fig


display(dual_render(_build, alt="Le Verrier's sky: predicted positions and Galle's discovery"))
Figure 1: Le Verrier’s sky: the predicted longitudes in his June and August 1846 memoirs, the ±1° search window Galle was told to scan, and the position where Galle and d’Arrest actually found Neptune on the night of 23 September 1846. All longitudes are heliocentric ecliptic (degrees). The quoted memoir longitudes are for 1 Jan 1847; the Sep-1846 observation is for the discovery night.

Galle found Neptune about two degrees from the published August longitude — at the edge of the search window Le Verrier had recommended. Part of that offset is because the memoir longitude is quoted for 1 January 1847 and Galle’s observation is for 23 September 1846: Neptune had not yet reached the quoted epoch. The remaining gap is genuine prediction error, but small enough that pointing the Berlin 9-inch refractor at Le Verrier’s coordinates, on the first clear night after receiving the letter, was enough.

The Adams Priority Dispute

The discovery immediately ignited a priority dispute. John Couch Adams, a young Cambridge mathematician, had independently predicted Neptune’s position and communicated his results to the Astronomer Royal, George Airy, and to James Challis at the Cambridge Observatory in late 1845 — before Le Verrier’s first publication. Adams’s predicted longitude was approximately 330 degrees, slightly less accurate than Le Verrier’s.

Challis even conducted a search in the summer of 1846, observing Neptune twice without recognizing it. He had not reduced his observations promptly enough to notice the moving object among the fixed stars.

The dispute was bitter and nationalistic — French and British partisans each claimed sole credit for their countryman. History has settled on shared credit: both men solved the same inverse problem independently, using similar methods, arriving at similar answers. Le Verrier published first and triggered the actual observation; Adams’s work, while earlier in some respects, remained unpublished and did not lead to a telescopic search until after the French announcement.

How Well Would the Prediction Have Aged?

Le Verrier’s predicted orbit matched Neptune’s true position in 1846. But his predicted semi-major axis of 36.2 AU (vs the true 30.1 AU) means his orbit had the wrong period. How quickly would the predicted and true Neptune diverge?

Two-body (Kepler) estimate

A simple Keplerian calculation shows the divergence rate. Two orbits with different semi-major axes have different periods, so their longitudes drift apart linearly:

Code
import numpy as np

# Orbital periods from Kepler's third law: P = a^(3/2) in years (for a in AU, M_sun = 1)
a_leverrier = LEVERRIER_MEMOIR_1846_AUG.semi_major_axis_au
a_true = MODERN_NEPTUNE.semi_major_axis_au

P_leverrier = a_leverrier**1.5  # years
P_true = a_true**1.5

# Mean motions (degrees per year)
n_leverrier = 360.0 / P_leverrier
n_true = 360.0 / P_true

# Divergence over 200 years
years_2body = np.arange(0, 201, 1)
divergence_2body = (n_leverrier - n_true) * years_2body  # degrees, predicted − true

print(f"Le Verrier's predicted period: {P_leverrier:.1f} years")
print(f"True Neptune period:           {P_true:.1f} years")
print(f"Mean motion (predicted − true): {n_leverrier - n_true:.4f} deg/year")
print(f"Divergence after 50 years:     {abs((n_true - n_leverrier) * 50):.1f}\u00b0")
print(f"Divergence after 100 years:    {abs((n_true - n_leverrier) * 100):.1f}\u00b0")
print(f"Divergence after 200 years:    {abs((n_true - n_leverrier) * 200):.1f}\u00b0")
Le Verrier's predicted period: 217.4 years
True Neptune period:           164.9 years
Mean motion (predicted − true): -0.5273 deg/year
Divergence after 50 years:     26.4°
Divergence after 100 years:    52.7°
Divergence after 200 years:    105.5°

N-body propagation

The Keplerian estimate ignores gravitational interactions. In reality, Jupiter and Saturn perturb Neptune’s orbit, and a Neptune at 36.2 AU experiences different perturbations than one at 30.1 AU. Let’s run two full N-body simulations to see the actual divergence:

Code
from pathlib import Path
import pandas as pd
import rebound

from discoverneptune.simulation import (
    StateVector,
    NeptuneCandidate,
    build_simulation_from_vectors,
)

# Load bundled state vectors (epoch: 1781-01-01)
from discoverneptune.data import find_bundled_data_dir

data_dir = find_bundled_data_dir()
svs = pd.read_csv(data_dir / "outer_planet_state_vectors_1781_01_01.csv")
state = {
    row.planet: StateVector(row.x, row.y, row.z, row.vx, row.vy, row.vz)
    for row in svs.itertuples(index=False)
}

# Le Verrier's Neptune: use his Aug 1846 parameters
# Mean longitude at 1781 epoch: back-propagate from 1846 using his period
# Offset: 1846 - 1781 = 65 years
years_since_epoch = 65.0
lv = LEVERRIER_MEMOIR_1846_AUG
lv_mean_motion_rad = 2 * np.pi / (lv.semi_major_axis_au**1.5)  # rad/yr
lv_longitude_1846_rad = np.radians(lv.longitude_deg)
lv_longitude_1781_rad = lv_longitude_1846_rad - lv_mean_motion_rad * years_since_epoch

leverrier_neptune = NeptuneCandidate(
    mass_solar=lv.mass_solar,
    a=lv.semi_major_axis_au,
    e=lv.eccentricity if lv.eccentricity is not None else 0.0,
    l_rad=lv_longitude_1781_rad,
)

# Modern Neptune: true orbital elements
modern_longitude_1846_rad = np.radians(GALLE_OBSERVATION.longitude_deg)
modern_mean_motion_rad = 2 * np.pi / (MODERN_NEPTUNE.semi_major_axis_au**1.5)
modern_longitude_1781_rad = modern_longitude_1846_rad - modern_mean_motion_rad * years_since_epoch

modern_neptune = NeptuneCandidate(
    mass_solar=MODERN_NEPTUNE.mass_solar,
    a=MODERN_NEPTUNE.semi_major_axis_au,
    e=MODERN_NEPTUNE.eccentricity if MODERN_NEPTUNE.eccentricity is not None else 0.0,
    l_rad=modern_longitude_1781_rad,
)

# Build two simulations
sim_lv = build_simulation_from_vectors(
    state["jupiter"], state["saturn"], state["uranus"], leverrier_neptune
)
sim_mod = build_simulation_from_vectors(
    state["jupiter"], state["saturn"], state["uranus"], modern_neptune
)

# Integrate both for 265 years (1781 + 265 = 2046, i.e. 200 years past 1846)
# Record Neptune longitude at each step
integration_years = 265  # 65 years to reach 1846, then 200 more
sample_times = np.linspace(0, integration_years, 500)

lon_lv = np.empty(len(sample_times))
lon_mod = np.empty(len(sample_times))

for i, t in enumerate(sample_times):
    sim_lv.integrate(t)
    nep_lv = sim_lv.particles[-1]
    sun_lv = sim_lv.particles[0]
    lon_lv[i] = np.degrees(np.arctan2(nep_lv.y - sun_lv.y, nep_lv.x - sun_lv.x))

    sim_mod.integrate(t)
    nep_mod = sim_mod.particles[-1]
    sun_mod = sim_mod.particles[0]
    lon_mod[i] = np.degrees(np.arctan2(nep_mod.y - sun_mod.y, nep_mod.x - sun_mod.x))

# Compute divergence (unwrap to handle 360-deg wrapping)
years_since_1846 = sample_times - years_since_epoch
divergence_nbody = np.unwrap(np.radians(lon_lv - lon_mod))
divergence_nbody = np.degrees(divergence_nbody)

print(f"N-body divergence at 1846 (t=0):   {divergence_nbody[np.argmin(np.abs(years_since_1846))]:.1f}\u00b0")
idx_50 = np.argmin(np.abs(years_since_1846 - 50))
idx_100 = np.argmin(np.abs(years_since_1846 - 100))
idx_200 = np.argmin(np.abs(years_since_1846 - 200))
print(f"N-body divergence after 50 years:   {divergence_nbody[idx_50]:.1f}\u00b0")
print(f"N-body divergence after 100 years:  {divergence_nbody[idx_100]:.1f}\u00b0")
print(f"N-body divergence after 200 years:  {divergence_nbody[idx_200]:.1f}\u00b0")
N-body divergence at 1846 (t=0):   -9.2°
N-body divergence after 50 years:   -19.3°
N-body divergence after 100 years:  -46.4°
N-body divergence after 200 years:  -119.9°
Code
import matplotlib.pyplot as plt

mask = years_since_1846 >= 0


def _build(theme="light"):
    apply_style(theme)
    pal = palette(theme)
    fig, axes = plt.subplots(1, 2, figsize=(12, 5), sharey=True)

    # Two-body panel: use pal.after (reference/analytic)
    ax = axes[0]
    ax.plot(years_2body, divergence_2body, color=pal.after, linewidth=2)
    ax.axhline(0, color=pal.spine, linewidth=0.5)
    ax.axvline(0, color=pal.spine, linewidth=0.5, linestyle="--", alpha=0.3)
    ax.set_xlabel("Years after 1846")
    ax.set_ylabel("Predicted − true longitude (degrees)")
    ax.set_title("Two-body (Kepler)")
    ax.set_xlim(0, 200)

    # N-body panel: use pal.before (actual/observed divergence)
    ax = axes[1]
    ax.plot(years_since_1846[mask], divergence_nbody[mask], color=pal.before, linewidth=2)
    ax.axhline(0, color=pal.spine, linewidth=0.5)
    ax.set_xlabel("Years after 1846")
    ax.set_title("N-body (with Jupiter & Saturn)")
    ax.set_xlim(0, 200)

    return fig


display(dual_render(_build, alt="Le Verrier prediction degradation: two-body vs N-body longitude divergence"))
Figure 2: How Le Verrier’s predicted Neptune orbit diverges from reality. Left: simple two-body (Kepler) estimate. Right: full N-body simulation including Jupiter and Saturn perturbations.

Le Verrier’s prediction was tuned to match the 1846 sky position. The wrong orbital period means his predicted Neptune would have drifted away from the true Neptune within a few decades — a consequence of fitting the perturbation signal with an orbit that has the right phase but wrong frequency. The N-body curve does not start at zero in 1846: back-propagating both orbits to the 1781 epoch with Keplerian mean motions and re-integrating with Jupiter and Saturn re-introduces a small phase offset by 1846. The trend, not the intercept, is the point.

How Close Was He?

Code
import pandas as pd

rows = [
    {
        "Source": LEVERRIER_MEMOIR_1845.source,
        "Semi-major axis (AU)": LEVERRIER_MEMOIR_1845.semi_major_axis_au,
        "Longitude (deg)": LEVERRIER_MEMOIR_1845.longitude_deg,
        "Mass (1/M_sun)": f"1/{1/LEVERRIER_MEMOIR_1845.mass_solar:.0f}",
    },
    {
        "Source": LEVERRIER_MEMOIR_1846_JUN.source,
        "Semi-major axis (AU)": LEVERRIER_MEMOIR_1846_JUN.semi_major_axis_au,
        "Longitude (deg)": LEVERRIER_MEMOIR_1846_JUN.longitude_deg,
        "Mass (1/M_sun)": f"1/{1/LEVERRIER_MEMOIR_1846_JUN.mass_solar:.0f}",
    },
    {
        "Source": LEVERRIER_MEMOIR_1846_AUG.source,
        "Semi-major axis (AU)": LEVERRIER_MEMOIR_1846_AUG.semi_major_axis_au,
        "Longitude (deg)": LEVERRIER_MEMOIR_1846_AUG.longitude_deg,
        "Mass (1/M_sun)": f"1/{1/LEVERRIER_MEMOIR_1846_AUG.mass_solar:.0f}",
    },
    {
        "Source": GALLE_OBSERVATION.source,
        "Semi-major axis (AU)": GALLE_OBSERVATION.semi_major_axis_au,
        "Longitude (deg)": GALLE_OBSERVATION.longitude_deg,
        "Mass (1/M_sun)": f"1/{1/GALLE_OBSERVATION.mass_solar:.0f}",
    },
    {
        "Source": MODERN_NEPTUNE.source,
        "Semi-major axis (AU)": MODERN_NEPTUNE.semi_major_axis_au,
        "Longitude (deg)": MODERN_NEPTUNE.longitude_deg,
        "Mass (1/M_sun)": f"1/{1/MODERN_NEPTUNE.mass_solar:.0f}",
    },
]

df = pd.DataFrame(rows)
df
Table 1: Neptune predictions and observations compared.
Source Semi-major axis (AU) Longitude (deg) Mass (1/M_sun)
0 Comptes Rendus vol 21, 10 Nov 1845 (preliminar... 35.000 325.0 1/20000
1 Comptes Rendus vol 22, 1 Jun 1846 36.154 325.0 1/9300
2 Comptes Rendus vol 23, 31 Aug 1846 36.154 326.0 1/9300
3 Astronomische Nachrichten No. 576, 25 Sep 1846... 30.070 328.0 1/19314
4 Modern JPL Horizons / IAU 2009 30.069 NaN 1/19314

Later Career

After Neptune, Le Verrier was appointed director of the Paris Observatory in 1854. His tenure was productive but turbulent — he was an exacting administrator, and his management style eventually led to his dismissal in 1870. He was reinstated in 1873 after the accidental death of his successor, Charles-Eugène Delaunay.

His later work focused on comprehensive planetary tables — painstaking computations of the orbits of all known planets, including the Mercury anomaly that would later require Einstein’s general relativity to explain (see the previous chapter).

Le Verrier died on 23 September 1877 — the 31st anniversary of Galle’s discovery of Neptune at the position Le Verrier had predicted.

References

  • Le Verrier, U. “Sur la planète qui produit les anomalies observées dans le mouvement d’Uranus.” Comptes Rendus 23 (31 Aug 1846).
  • Galle, J. G. “Account of the Discovery of the Planet of Le Verrier at Berlin.” Monthly Notices of the Royal Astronomical Society 7 (1846): 153.
  • Smart, W. M. “John Couch Adams and the Discovery of Neptune.” Occasional Notes of the Royal Astronomical Society 2 (1947): 33–55.
  • Grosser, M. The Discovery of Neptune (1962).
  • Standage, T. The Neptune File (2000).
  • Kollerstrom, N. “An Hiatus in History: The British Claim for Neptune’s Co-prediction, 1845–1846.” History of Science 44 (2006): 349–371.