SPT-3G D1 high-ℓ CMB power spectra vs. Planck 2018
AI-generated notebook — This notebook was created by GitHub Copilot (Claude Sonnet 4.6) from the following prompts:
- “read https://lambda.gsfc.nasa.gov/product/spt/spt3g_d1_high_ell_info.html and the relative paper, then create a jupyter notebook based post that loads the spectra and compares them with the latest spectra from planck. cite stuff properly. be didactic. run it using a uv venv. find a way of specifying requirements inside the notebook itself. the most standard way.”
- “I look at the band powers. In the original paper, the error bars are a lot larger. They are increased by a factor of 10, do that also here and mention it.”
SPT-3G D1 high-ℓ CMB power spectra vs. Planck 2018
Background
The Cosmic Microwave Background (CMB) temperature anisotropy power spectrum is usually expressed as in units of . It encodes the statistical properties of tiny temperature fluctuations imprinted on the CMB at recombination () and provides our most precise probe of cosmological parameters.
The power spectrum can be divided into two regimes by the physical processes responsible for the signal:
Primary anisotropies ()
These arise from density and velocity perturbations in the primordial plasma. The characteristic acoustic peaks at are perfectly measured by Planck (Planck Collaboration 2020).
Secondary anisotropies ()
At smaller angular scales, primary CMB fluctuations are exponentially suppressed by Silk damping. A rich variety of physical processes contributes instead:
| Effect | Abbreviation | Physical origin |
|---|---|---|
| Thermal Sunyaev–Zel’dovich | tSZ | Inverse Compton scattering off hot ICM electrons |
| Kinematic Sunyaev–Zel’dovich | kSZ | Doppler shift from bulk motions of ionized gas |
| Cosmic Infrared Background | CIB | Cumulative emission from dusty star-forming galaxies |
| Radio galaxies | RG | Synchrotron emission from AGN |
The tSZ effect has a distinctive frequency spectrum (decreasing below 217 GHz, rising above), which means multi-frequency observations can separate it from the CIB. The kSZ effect has the same frequency spectrum as the CMB and can only be separated statistically.
The SPT-3G D1 data set
The South Pole Telescope (SPT) is a 10-meter microwave telescope at the South Pole, operated by a collaboration led by the University of Chicago. The SPT-3G receiver observes simultaneously at 95, 150, and 220 GHz with ~16,000 detectors.
Chaubal et al. (2026) present the SPT-3G D1 TT high-ℓ power spectra using observations from 2019 and 2020 of the 1646 deg² SPT-3G Main field. The measurements span 1700 ≤ ℓ ≤ 11,000 across six frequency auto- and cross-spectra: 95×95, 95×150, 95×220, 150×150, 150×220, 220×220 GHz.
Citation: Chaubal P. et al. 2026, SPT-3G D1: A Measurement of Secondary Cosmic Microwave Background Anisotropy Power, arXiv:2601.20551. Data hosted at LAMBDA and UChicago SPT site.
In this notebook we:
- Download and parse the SPT-3G D1 band powers.
- Download the Planck 2018 (PR3) binned TT spectrum.
- Compare the two datasets to visualise how SPT-3G extends the CMB power spectrum into the secondary-anisotropy regime.
Requirements
This notebook uses only the standard scientific Python stack.
The cell below installs the dependencies with pip (the standard way to
declare and install notebook requirements).
To run this notebook in an isolated uv virtual environment:
uv venv .venv
source .venv/bin/activate
uv pip install notebook
jupyter execute 2026-04-21-spt3g-d1-planck-comparison.ipynb
The %pip install cell below is the standard Jupyter mechanism for
embedding package requirements directly inside a notebook.
%pip install numpy matplotlib requests
/home/zonca/p/software/zonca.dev/posts/.venv-spt3g/bin/python: No module named pip
Note: you may need to restart the kernel to use updated packages.
import io
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import requests
# Consistent, publication-friendly style
plt.rcParams.update({
'figure.dpi': 120,
'axes.grid': True,
'grid.alpha': 0.3,
'font.size': 11,
})
1. Load the SPT-3G D1 band powers
The released text file dls.txt contains 144 rows (24 per frequency
combination) with four columns:
| Column | Meaning |
|---|---|
| index | Band index (0-based within each spectrum) |
| ℓ | Effective multipole of the band |
| Band power in | |
| uncertainty (diagonal covariance) |
Rows are ordered: all 95×95 bands, then 95×150, 95×220, 150×150, 150×220, 220×220.
SPT_URL = "https://pole.uchicago.edu/public/data/chaubal26/dls.txt"
resp = requests.get(SPT_URL, timeout=30)
resp.raise_for_status()
raw = np.loadtxt(io.StringIO(resp.text))
print(f"Shape: {raw.shape} → {raw.shape[0]} rows, {raw.shape[1]} columns")
print(f"Rows per frequency combination: {raw.shape[0] // 6}")
Shape: (144, 4) → 144 rows, 4 columns
Rows per frequency combination: 24
FREQS = ["95×95", "95×150", "95×220", "150×150", "150×220", "220×220"]
N_BANDS = raw.shape[0] // len(FREQS) # 24 bands per spectrum
# Split into a dict keyed by frequency label
spt = {}
for i, name in enumerate(FREQS):
chunk = raw[i * N_BANDS : (i + 1) * N_BANDS]
spt[name] = {
"ell": chunk[:, 1],
"Dl": chunk[:, 2],
"sigmaDl": chunk[:, 3],
}
# Sanity check
for name, d in spt.items():
print(f"{name}: ℓ = {d['ell'][0]:.0f} – {d['ell'][-1]:.0f} "
f"({N_BANDS} bands)")
95×95: ℓ = 1751 – 10379 (24 bands)
95×150: ℓ = 1752 – 10402 (24 bands)
95×220: ℓ = 1751 – 10411 (24 bands)
150×150: ℓ = 1752 – 10430 (24 bands)
150×220: ℓ = 1751 – 10449 (24 bands)
220×220: ℓ = 1752 – 10436 (24 bands)
2. Visualise all six SPT-3G D1 spectra
Each panel shows with error bars multiplied by 10 (following the convention of Fig. 2 in Chaubal et al. 2026, where uncertainties are inflated by a factor of ten to be visible on a log scale). The colour coding follows frequency: blue → 95 GHz, green → 150 GHz, red → 220 GHz.
Key features to notice:
-
Declining slope at low ℓ (~2000–3000): residual primary CMB power (the damping tail).
-
Minimum near ℓ ≈ 3000: tSZ has a null at ~217 GHz and suppresses the 150×150 spectrum, creating a local minimum.
-
Rising power at high ℓ (>4000): CIB dominates the 220 GHz channels; radio galaxies (flat or rising spectra) appear in lower frequency channels.
-
Cross-spectra help separate components: the 95×220 cross-spectrum,
for example, is sensitive to the CIB without the noise penalty of a 220×220 auto-spectrum.
# Colour map: one colour per unique frequency pair
palette = {
"95×95": "#1f77b4", # blue
"95×150": "#2ca02c", # green
"95×220": "#9467bd", # purple
"150×150": "#ff7f0e", # orange
"150×220": "#d62728", # red
"220×220": "#8c564b", # brown
}
fig, axes = plt.subplots(2, 3, figsize=(14, 7), sharey=False, sharex=True)
axes = axes.flatten()
for ax, name in zip(axes, FREQS):
d = spt[name]
ax.errorbar(
d["ell"], d["Dl"], yerr=10 * d["sigmaDl"],
fmt="o", ms=3, lw=0.8, color=palette[name], label=name,
capsize=2, elinewidth=0.8,
)
ax.set_title(f"{name} GHz", fontsize=12)
ax.set_yscale("log")
ax.set_xlabel(r"Multipole $\ell$")
ax.set_ylabel(r"$D_\ell\;[\mu\mathrm{K}^2]$")
ax.set_xlim(1500, 11500)
fig.suptitle(
"SPT-3G D1 high-ℓ TT band powers (Chaubal et al. 2026, arXiv:2601.20551)\nError bars ×10 for visibility",
fontsize=13, y=1.01,
)
fig.tight_layout()
plt.savefig("spt3g_d1_six_spectra.png", bbox_inches="tight")
plt.show()
3. Load the Planck 2018 TT spectrum
We use the Planck PR3 binned TT spectrum, which covers with five columns:
| Column | Meaning |
|---|---|
| ℓ | Effective multipole |
| Measured band power () | |
| Lower 1σ error | |
| Upper 1σ error | |
| BestFit | Best-fit ΛCDM model |
Citation: Planck Collaboration V (2020), Planck 2018 results. V. Power spectra and likelihoods, A&A 641, A5. DOI: 10.1051/0004-6361/201936386. Data: Planck Legacy Archive (IRSA).
PLANCK_URL = (
"https://irsa.ipac.caltech.edu/data/Planck/release_3/"
"ancillary-data/cosmoparams/COM_PowerSpect_CMB-TT-binned_R3.01.txt"
)
resp_pl = requests.get(PLANCK_URL, timeout=30)
resp_pl.raise_for_status()
planck = np.loadtxt(io.StringIO(resp_pl.text), comments="#")
pl = {
"ell": planck[:, 0],
"Dl": planck[:, 1],
"err_lo": planck[:, 2],
"err_hi": planck[:, 3],
"bestfit": planck[:, 4],
}
print(f"Planck PR3 TT spectrum: {len(pl['ell'])} bins")
print(f"ℓ range: {pl['ell'][0]:.0f} – {pl['ell'][-1]:.0f}")
Planck PR3 TT spectrum: 83 bins
ℓ range: 48 – 2499
4. Full ℓ range: Planck + SPT-3G D1 (150×150 GHz)
Planck’s 143 GHz channel is the closest match to SPT-3G’s 150 GHz band. Although the two instruments use slightly different effective frequencies and source-masking thresholds, the 150×150 SPT-3G spectrum is the most natural counterpart to the Planck TT spectrum for a direct comparison.
The plot below shows:
- Grey shaded region (Planck PR3): primary CMB spectrum measured with full-sky coverage and multiple frequencies for foreground separation. Plotted up to ℓ ≈ 2500 where Planck signal-to-noise remains adequate.
- Blue curve (Planck best-fit ΛCDM): smooth theoretical prediction continuing into the damping tail.
- Orange points (SPT-3G D1 150×150): SPT-3G measurements at 1700 ≤ ℓ ≤ 11,000, showing both the damping tail region and the foreground-dominated regime.
Note on error bars: Following Fig. 2 of Chaubal et al. (2026), all plotted uncertainties are inflated by a factor of 10 to be visible on the logarithmic scale. The actual errors are 10× smaller.
Note the transition near ℓ ≈ 3000–4000: below this range, SPT-3G roughly above the pure-CMB prediction.
follows the Planck best-fit damping tail; above it, foreground power (CIBfrom dusty galaxies, SZ effects) begins to dominate and push the measured
fig, ax = plt.subplots(figsize=(12, 5))
# ── Planck PR3 band powers (error bars ×10 for visibility) ──────────────────
ax.errorbar(
pl["ell"], pl["Dl"],
yerr=[10 * pl["err_lo"], 10 * pl["err_hi"]],
fmt="none", ecolor="#aaaaaa", elinewidth=0.6, alpha=0.7,
label="Planck PR3 TT (2018)",
)
ax.scatter(
pl["ell"], pl["Dl"],
s=5, color="#555555", zorder=3,
)
# ── Planck best-fit ΛCDM (extended to high ℓ as reference) ──────────────────
ax.plot(
pl["ell"], pl["bestfit"],
color="royalblue", lw=1.4, ls="--",
label="Planck best-fit ΛCDM",
)
# ── SPT-3G D1 150×150 GHz (error bars ×10 for visibility) ──────────────────
d150 = spt["150×150"]
ax.errorbar(
d150["ell"], d150["Dl"], yerr=10 * d150["sigmaDl"],
fmt="o", ms=4, color="#ff7f0e", elinewidth=0.9,
capsize=2, label="SPT-3G D1 150×150 GHz (err ×10)",
zorder=5,
)
# ── Annotations ──────────────────────────────────────────────────────────────
ax.axvspan(1700, 2500, alpha=0.05, color="green", label="Overlap region")
ax.annotate(
"tSZ minimum\n@ 150 GHz",
xy=(3200, 32), xytext=(4200, 60),
fontsize=9, color="#cc4400",
arrowprops=dict(arrowstyle="->", color="#cc4400", lw=0.8),
)
ax.annotate(
"CIB + radio\ngalaxies rising",
xy=(7000, 180), xytext=(6000, 450),
fontsize=9, color="#cc4400",
arrowprops=dict(arrowstyle="->", color="#cc4400", lw=0.8),
)
ax.set_yscale("log")
ax.set_xlabel(r"Multipole $\ell$", fontsize=13)
ax.set_ylabel(r"$D_\ell = \ell(\ell+1)C_\ell / 2\pi\;\;[\mu\mathrm{K}^2]$", fontsize=13)
ax.set_title(
"CMB TT power spectrum: Planck 2018 PR3 + SPT-3G D1 (150×150 GHz)",
fontsize=13,
)
ax.set_xlim(30, 12000)
ax.set_ylim(10, 1e5)
ax.legend(fontsize=10, loc="upper right")
# Secondary x-axis showing angular scale θ ≈ 180°/ℓ
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
tick_ells = [100, 500, 1000, 3000, 6000, 11000]
ax2.set_xticks(tick_ells)
ax2.set_xticklabels([f"{180/l:.1f}°" for l in tick_ells], fontsize=9)
ax2.set_xlabel(r"Angular scale $\theta = 180°/\ell$", fontsize=11)
plt.tight_layout()
plt.savefig("spt3g_d1_planck_comparison.png", bbox_inches="tight")
plt.show()
5. Zoom into the overlap region
Planck measures up to and SPT-3G D1 starts at , giving a ~800-multipole overlap window where both experiments should agree on the primary CMB power.
Any systematic offset between the two would signal calibration differences or residual foreground contamination in one (or both) data sets.
fig, ax = plt.subplots(figsize=(9, 4))
# Planck points in overlap range (error bars ×10 for visibility)
mask_pl = pl["ell"] >= 1600
ax.errorbar(
pl["ell"][mask_pl], pl["Dl"][mask_pl],
yerr=[10 * pl["err_lo"][mask_pl], 10 * pl["err_hi"][mask_pl]],
fmt="s", ms=4, color="#555555", elinewidth=0.8, capsize=2,
label="Planck PR3 TT (2018) (err ×10)",
)
ax.plot(pl["ell"][mask_pl], pl["bestfit"][mask_pl],
color="royalblue", lw=1.2, ls="--", label="Planck best-fit ΛCDM")
# SPT-3G D1 low-ell portion (error bars ×10 for visibility)
mask_spt = d150["ell"] <= 2700
ax.errorbar(
d150["ell"][mask_spt], d150["Dl"][mask_spt],
yerr=10 * d150["sigmaDl"][mask_spt],
fmt="o", ms=5, color="#ff7f0e", elinewidth=1.0, capsize=3,
label="SPT-3G D1 150×150 GHz (err ×10)",
)
ax.axvspan(1700, 2500, alpha=0.08, color="green", label="Overlap window")
ax.set_xlabel(r"Multipole $\ell$", fontsize=12)
ax.set_ylabel(r"$D_\ell\;[\mu\mathrm{K}^2]$", fontsize=12)
ax.set_title("Overlap region: Planck PR3 vs. SPT-3G D1 (150×150 GHz)", fontsize=12)
ax.set_xlim(1550, 2800)
ax.legend(fontsize=10)
plt.tight_layout()
plt.savefig("spt3g_d1_planck_overlap.png", bbox_inches="tight")
plt.show()
6. Multi-frequency view: foreground separation by frequency
A key advantage of SPT-3G D1 is the simultaneous measurement at three frequencies. The plot below overlays all six spectra on a single panel to illustrate frequency-dependent behaviour:
- tSZ imprint: the tSZ effect decreases the temperature at 95 and 150 GHz (relative to the CIB) and has a spectral null at ~217 GHz. The 150×150 spectrum is most suppressed relative to 220×220 near ℓ ≈ 3000.
- CIB rise: The CIB has a steep, rising spectrum, making the 220 GHz channels the most CIB-dominated at high ℓ.
- Radio galaxies: They appear brightest at 95 GHz and their power remains relatively flat or slightly rising toward low ℓ within the SPT range.
These frequency dependencies allow the SPT-3G collaboration to fit for the individual foreground components and extract, e.g., the tSZ amplitude and kSZ amplitude .
fig, ax = plt.subplots(figsize=(11, 5))
for name in FREQS:
d = spt[name]
ax.errorbar(
d["ell"], d["Dl"], yerr=10 * d["sigmaDl"],
fmt="o-", ms=3, lw=0.8, color=palette[name],
elinewidth=0.5, capsize=1, label=f"{name} GHz",
)
# Show where tSZ roughly creates its minimum in the 150×150 spectrum
ax.axvline(3000, color="grey", ls=":", lw=1)
ax.text(3100, 8, r"$\ell = 3000$", fontsize=9, color="grey")
ax.set_yscale("log")
ax.set_xlabel(r"Multipole $\ell$", fontsize=13)
ax.set_ylabel(r"$D_\ell\;[\mu\mathrm{K}^2]$", fontsize=13)
ax.set_title(
"SPT-3G D1: all six frequency spectra — foreground separation by colour\n(error bars ×10 for visibility)",
fontsize=12,
)
ax.set_xlim(1500, 11500)
ax.legend(ncol=2, fontsize=10)
plt.tight_layout()
plt.savefig("spt3g_d1_all_spectra_overlay.png", bbox_inches="tight")
plt.show()
7. Key results from the paper
Chaubal et al. (2026) use these spectra to constrain the amplitudes of the secondary signal components. Selected highlights:
| Parameter | Value | Notes |
|---|---|---|
| Thermal SZ at 143 GHz | ||
| Kinematic SZ | ||
| Duration of reionization | (95%) | From kSZ |
| Duration of reionization | (95%) | AMBER simulations |
The kSZ power provides the most constraining current bounds on the epoch and duration of reionization from the small-scale CMB power spectrum.
8. Summary
This notebook demonstrated how to:
- Download and parse the SPT-3G D1 band-power file from the UChicago public data archive and the Planck Legacy Archive (IRSA mirror).
- Compare the high-resolution SPT-3G spectra with the Planck 2018 PR3 measurements in the overlap multipole range .
- Visualise the frequency-dependent foreground signatures (tSZ minimum, CIB rise) that allow SPT-3G D1 to separate astrophysical components.
Together, Planck (primary CMB, large scales) and SPT-3G D1 (secondary CMB, small scales) map the CMB temperature power spectrum across nearly four decades in angular scale, from the Sachs–Wolfe plateau at to the Poisson-noise floor of extragalactic sources at .
References
-
Chaubal P. et al. (2026): SPT-3G D1: A Measurement of Secondary Cosmic Microwave Background Anisotropy Power, arXiv:2601.20551, submitted to JCAP. Data: https://pole.uchicago.edu/public/data/chaubal26/ (mirrored at LAMBDA: https://lambda.gsfc.nasa.gov/product/spt/spt3g_d1_high_ell_info.html)
-
Planck Collaboration V (2020): Planck 2018 results. V. Power spectra and likelihoods, A&A 641, A5. DOI: https://doi.org/10.1051/0004-6361/201936386. Data: Planck Legacy Archive (IRSA).
-
Sunyaev & Zel’dovich (1972): The Observations of Relic Radiation as a Test of the Nature of X-Ray Radiation from the Clusters of Galaxies, Comm. Astrophys. Space Phys. 4, 173.