Skip to content

Understanding Magnitudes: From Pogson to Gaia

AstroLatent

With the Pleiades coordinate pipeline working (previous post), I wanted to go deeper: how bright are these stars, really? And what can brightness alone tell us about their physics? Let’s take a look.

The goal: build a Color-Magnitude Diagram from scratch and see if I can read stellar evolution directly off the plot.

Table of contents

Open Table of contents

Magnitudes — a 2000-year-old scale

The magnitude system dates back to Hipparchus (2nd century BCE), who ranked stars from 1st magnitude (brightest) to 6th magnitude (faintest visible to the naked eye). In 1856, Norman Pogson formalized the relationship:

m1m2=2.5log10(F1F2)m_1 - m_2 = -2.5 \log_{10}\left(\frac{F_1}{F_2}\right)

The scale is inverted, smaller magnitudes mean brighter. The Sun sits at mV=26.7m_V = -26.7, Vega at mV0m_V \approx 0, and the naked-eye limit is around m6m \approx 6. Each magnitude step is a factor 100.42.51210^{0.4} \approx 2.512 in flux, and 5 magnitudes = factor 100 in flux, that’s the definition (2.5125=1002.512^5 = 100).

Why logarithmic? Stellar fluxes span 101010^{10} between the Sun and Gaia’s faintest targets and the log scale compresses this into a manageable range (27-27 to +21+21 mag).

Magnitude scale of familiar astronomical objects, and Pogson's law showing flux ratio vs magnitude difference

The Sun at 26.7-26.7, Sirius at 1.5-1.5, a faint Pleiades member around G=18G = 18, almost 45 magnitudes of range. On the right, Pogson’s law: flux ratio vs Δm\Delta m. On a log scale it’s a straight line, Δm=5×100\Delta m = 5 \rightarrow \times 100, Δm=10×10000\Delta m = 10 \rightarrow \times 10\,000.

Apparent vs absolute magnitude

The problem with apparent magnitude mm: it mixes intrinsic brightness and distance. A dim nearby star and a brilliant distant one can look identical. The fix is absolute magnitude MM, the magnitude an object would have at exactly 10 pc.

The distance modulus connects them:

mM=5log10(d10pc)m - M = 5 \log_{10}\left(\frac{d}{10\,\text{pc}}\right)

With Gaia parallax ϖ\varpi (in mas), d=1000/ϖd = 1000/\varpi pc, so:

M=m+5log10(ϖ/1000)+5M = m + 5 \log_{10}(\varpi / 1000) + 5

For a cluster, all stars are at roughly the same distance, so the distance modulus is a single number that shifts everything vertically:

Distance modulusDistance
0 mag10 pc
5 mag100 pc
5.67 mag136 pc (Pleiades)
10 mag1,000 pc
15 mag10,000 pc

But, this formula assumes zero extinction (no interstellar dust absorbing light) on the way.. The full version adds AλA_\lambda: mM=5log10(d)5+Aλm - M = 5\log_{10}(d) - 5 + A_\lambda. For the Pleiades at b24°b \approx -24°, extinction is negligible (~0.1 mag in G). For clusters embedded in the galactic plane, it becomes the dominant error source.

Same Pleiades, more columns

I start from the same Pleiades members as the previous post, same quality filters, same member selection. Two changes to the query this time: individual BP and RP magnitudes (not just the combined color index), and flux signal-to-noise columns to compute photometric uncertainties.

I also added a parallax BETWEEN 5.0 AND 10.0 pre-filter in the ADQL. This targets the Pleiades distance range (~100–200 pc) directly in the database and cuts the returned data volume dramatically. Without it I was hitting the server row limit on some runs.

query = """
SELECT TOP 5000 source_id, ra, dec, parallax, parallax_error,
       pmra, pmdec,
       phot_g_mean_mag,
       phot_bp_mean_mag,
       phot_rp_mean_mag,
       bp_rp,
       phot_g_mean_flux_over_error,
       phot_bp_mean_flux_over_error,
       phot_rp_mean_flux_over_error,
       ruwe
FROM gaiadr3.gaia_source
WHERE 1 = CONTAINS(
    POINT('ICRS', ra, dec),
    CIRCLE('ICRS', 56.75, 24.12, 3.0)
)
AND parallax BETWEEN 5.0 AND 10.0
AND parallax_over_error > 5
AND ruwe < 1.4
AND phot_bp_rp_excess_factor BETWEEN 1.0 AND 1.8
AND phot_g_mean_mag < 18
"""

for attempt in range(3):
    try:
        job = Gaia.launch_job(query)
        data = job.get_results()
        break
    except Exception as e:
        if attempt < 2:
            print(f"Gaia error (attempt {attempt+1}/3), retrying...")
            time.sleep(5)
        else:
            raise RuntimeError(f"Gaia unavailable: {e}")

# Same member selection as the coordinates post
dpm = np.sqrt((data['pmra'] - 19.99)**2 + (data['pmdec'] + 45.55)**2)
mask = (dpm < 3.0) & (np.abs(data['parallax'] - 7.35) < 1.5)
members = data[mask]

One thing I learned the hard way: Gaia.launch_job_async() was returning intermittent 500 errors from the ESA server. Switching to launch_job() (synchronous) with a retry loop fixed it. The async version is meant for large queries that take minutes, for a few thousand rows, sync seems to be more reliable.

Same ~870 members as before.

Magnitude uncertainties from flux SNR

Gaia doesn’t report magnitude errors directly, it reports flux signal-to-noise ratios. The conversion comes from error propagation on m=2.5log10(F)+constm = -2.5 \log_{10}(F) + \text{const}:

σm=1.0857SNR\sigma_m = \frac{1.0857}{\text{SNR}}

where 1.0857=2.5/ln(10)1.0857 = 2.5 / \ln(10). At SNR = 100, that’s σm0.01\sigma_m \approx 0.01 mag. At SNR = 1000, σm0.001\sigma_m \approx 0.001 mag.

members['phot_g_mean_mag_error']  = 1.0857 / members['phot_g_mean_flux_over_error']
members['phot_bp_mean_mag_error'] = 1.0857 / members['phot_bp_mean_flux_over_error']
members['phot_rp_mean_mag_error'] = 1.0857 / members['phot_rp_mean_flux_over_error']

I initially looked for phot_g_mean_mag_error columns in the Gaia catalog. They don’t exist, you have to compute them yourself from the flux columns. Not obvious if you’re just browsing the schema.

Distance modulus

First real test: computing the absolute magnitude and distance modulus for every member, then comparing against the published value.

members['abs_g'] = (
    members['phot_g_mean_mag']
    + 5 * np.log10(members['parallax'] / 1000)
    + 5
)
members['dist_mod'] = members['phot_g_mean_mag'] - members['abs_g']

Distance modulus and parallax histograms for Pleiades members

The distance modulus peaks at 5.66 mag, matching Babusiaux et al. (2018) at 5.67 ± 0.02. The parallax distribution centers on 7.37 mas, giving d136d \approx 136 pc. Both are narrow and unimodal, confirming the member selection is clean. The slight asymmetry of the distance histogram is expected — d=1/ϖd = 1/\varpi is a non-linear transformation, so symmetric parallax errors become asymmetric in distance.

Gaia’s three bands

Gaia doesn’t observe in a single filter. It uses these three:

BandWavelengthWhat it measures
G330–1050 nm (ultra-wide)Total visible + near-IR flux
G_BP330–680 nmBlue flux
G_RP630–1050 nmRed flux

The color index BPRP=GBPGRPBP - RP = G_{BP} - G_{RP} is a direct proxy for stellar temperature:

The physics is Wien’s displacement law: λmax=2898/T\lambda_\text{max} = 2898 / T. A hotter star emits more blue photons → brighter in BP → smaller BPRPBP - RP.

Histograms of G, BP, and RP magnitudes for Pleiades members

Other photometric systems exist, like Johnson-Cousins (U, B, V, R, I) from the 1950s, SDSS (u, g, r, i, z), and 2MASS (J, H, Ks) in the near-IR, but Gaia’s three bands cover 1.8 billion sources, so that’s what I’m working with.

Bolometric luminosity

A star radiates across the entire spectrum, not just the visible. The bolometric luminosity LL is the total power:

L=4πR2σTeff4L = 4\pi R^2 \sigma T_\text{eff}^4

The T4T^4 dependence is steep: doubling the temperature gives 16× the luminosity. A bolometric correction BCλBC_\lambda bridges single-band magnitudes to bolometric: Mbol=Mλ+BCλM_\text{bol} = M_\lambda + BC_\lambda. For the Sun, BCV0.07BC_V \approx -0.07 (it emits mostly in the visible, so the correction is tiny). For an M dwarf, BCV2.5BC_V \approx -2.5 — the V band misses most of the flux because these stars peak in the infrared.

Building the CMD

The Color-Magnitude Diagram (CMD) is the observational counterpart of the HR diagram, arguably the most important single plot in stellar astrophysics.

I knew what to expect from textbooks, but seeing it emerge from real data was different. The features jumped out immediately:

Color-Magnitude Diagram of the Pleiades

The Pleiades CMD is clean. Main sequence from bright B-type stars at the top-left down to faint M dwarfs at the bottom-right. The turn-off region sits near MG0.5M_G \approx -0.5, BPRP0BP-RP \approx 0 , B6-B7 stars, about 5 MM_\odot. For a ~125 Myr cluster, that’s where it should be: every star more massive than this has already evolved off the main sequence.

The turn-off as a clock

The turn-off position is what makes CMDs powerful for dating clusters. The main-sequence lifetime scales roughly as tMSM2.5×1010t_\text{MS} \propto M^{-2.5} \times 10^{10} yr, massive stars burn through their fuel much faster:

Mass (MM_\odot)TypeTeffT_\text{eff} (K)MS lifetime
0.5M3,800> 80 Gyr
1.0G (Sun)5,780~10 Gyr
2.0A8,500~1.5 Gyr
5.0B15,000~100 Myr
10.0B28,000~20 Myr

In the Pleiades (~125 Myr), every star above ~5 MM_\odot has already left the main sequence. The turn-off sits at BPRP0BP-RP \approx 0, MG0.5M_G \approx -0.5. After the MS, the helium core contracts while the hydrogen envelope expands → the star moves onto the red giant branch (RGB): luminous but red, upper-right on the CMD. The Pleiades are too young to show a red giant branch — that’ll appear when I look at older clusters.

The key insight: the lower the turn-off on the CMD, the older the cluster. An older cluster (say, 625 Myr for the Hyades) would have its turn-off at MG2M_G \approx 2, the A-type stars have run out of hydrogen. At 6.8 Gyr (NGC 188), even solar-type stars are leaving the MS, with the turn-off down at MG3.5M_G \approx 3.5. Querying these clusters to see it for myself is on the list for a future post.

Apparent vs absolute CMD

I plotted both the apparent CMD (GG vs BPRPBP-RP, no distance correction) and the absolute CMD (MGM_G vs BPRPBP-RP, corrected). For the Pleiades the difference is just a vertical shift of 5.67 mag, the shape is identical because all stars are at essentially the same distance.

Side-by-side comparison of apparent and absolute Color-Magnitude Diagrams for the Pleiades

The absolute version is what you need when comparing clusters at different distances and they’ll overlap in MGM_G. The apparent version is useful when fitting isochrones that you shift in distance modulus.

Uncertainties on the CMD

The CMD sequences aren’t infinitely thin. Part of the broadening is real, binaries, rotation, metallicity spread, but part is measurement noise so I wanted to see how much.

The full error propagation on absolute magnitude:

σMG=σG2+(5ln10)2(σϖϖ)2\sigma_{M_G} = \sqrt{\sigma_G^2 + \left(\frac{5}{\ln 10}\right)^2 \left(\frac{\sigma_\varpi}{\varpi}\right)^2}

For the color index, both band errors add in quadrature: σBPRP=σBP2+σRP2\sigma_{BP-RP} = \sqrt{\sigma_{BP}^2 + \sigma_{RP}^2}.

sigma_mG = np.sqrt(
    members['phot_g_mean_mag_error']**2 +
    (5/np.log(10))**2 * (members['parallax_error']/members['parallax'])**2
)
sigma_col = np.sqrt(
    members['phot_bp_mean_mag_error']**2 + members['phot_rp_mean_mag_error']**2
)

Pleiades CMD with propagated uncertainty error bars shown on every 5th star

The median errors are tiny, σ(MG)0.003\sigma(M_G) \approx 0.003 mag, σ(BPRP)0.004\sigma(BP-RP) \approx 0.004 mag. The MS broadening is not measurement noise. It’s dominated by unresolved binaries (+0.75 mag for equal-mass pairs), differential reddening (minimal for the Pleiades), and possibly stellar rotation. For distant clusters behind the galactic plane, extinction would dwarf all of this, dust both dims and reddens starlight, shifting the entire CMD toward fainter and redder.

Validation

Same approach as the coordinates post, compare every derived quantity against published values:

QuantityMy resultLiteratureMatch?
Distance (pc)~136136 ± 1Ok
Distance modulus5.665.67 ± 0.02ok
Turn-off MGM_G~−0.5~−0.5ok
Members~870~1000–1400Low but seems clean

Pleiades CMD with turn-off region highlighted and literature MSTO line

The member count is still lower than the literature, same reasons as the coordinates post: magnitude cut at G<18G < 18 and conservative proper motion radius miss the faintest and outermost members. But the derived physical quantities all match, which means the selection is clean if incomplete.

What’s next

References

Siguiente
Navigating the Sky with Gaia DR3 and Astropy SkyCoord