# Colab Setup (Run this first)
!pip install litebird_sim rich
Requirement already satisfied: litebird_sim in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (0.16.0)
Requirement already satisfied: rich in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (14.2.0)
Requirement already satisfied: asciimatics>=1.15.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (1.15.0)
Requirement already satisfied: astropy>=6.1.7 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (6.1.7)
Requirement already satisfied: deprecated>=1.2.18 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (1.3.1)
Requirement already satisfied: deprecation>=2.1.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (2.1.0)
Requirement already satisfied: ducc0>=0.40.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (0.40.0)
Requirement already satisfied: h5py>=3.9 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (3.15.1)
Requirement already satisfied: healpy>=1.18.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (1.19.0)
Requirement already satisfied: jinja2>=3.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (3.1.6)
Requirement already satisfied: jplephem>=2.22 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (2.23)
Requirement already satisfied: libinsdb>=0.9.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (0.9.0)
Requirement already satisfied: markdown-katex>=202406.1035 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (202406.1035)
Requirement already satisfied: markdown>=3.9 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (3.10)
Requirement already satisfied: matplotlib>=3.10.3 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (3.10.7)
Requirement already satisfied: numba>=0.63.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (0.65.0)
Requirement already satisfied: numpy>=1.26.4 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (1.26.4)
Requirement already satisfied: pygithub>=2.6.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (2.8.1)
Requirement already satisfied: pyperclip>=1.9.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (1.11.0)
Requirement already satisfied: pysm3>=3.4.2 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (3.4.3)
Requirement already satisfied: pyyaml>=6.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (6.0.3)
Requirement already satisfied: requests>=2.32.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (2.32.5)
Requirement already satisfied: scipy>=1.14.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (1.14.1)
Requirement already satisfied: tomlkit>=0.12.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from litebird_sim) (0.12.5)
Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from rich) (4.0.0)
Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from rich) (2.19.2)
Requirement already satisfied: pyfiglet>=0.7.2 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from asciimatics>=1.15.0->litebird_sim) (1.0.4)
Requirement already satisfied: Pillow>=2.7.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from asciimatics>=1.15.0->litebird_sim) (12.0.0)
Requirement already satisfied: wcwidth in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from asciimatics>=1.15.0->litebird_sim) (0.2.14)
Requirement already satisfied: pyerfa>=2.0.1.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from astropy>=6.1.7->litebird_sim) (2.0.1.5)
Requirement already satisfied: astropy-iers-data>=0.2024.10.28.0.34.7 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from astropy>=6.1.7->litebird_sim) (0.2025.11.24.0.39.11)
Requirement already satisfied: packaging>=19.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from astropy>=6.1.7->litebird_sim) (25.0)
Requirement already satisfied: wrapt<3,>=1.10 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from deprecated>=1.2.18->litebird_sim) (2.0.1)
Requirement already satisfied: MarkupSafe>=2.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from jinja2>=3.1->litebird_sim) (3.0.3)
Requirement already satisfied: coverage<8,>=7.3.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from libinsdb>=0.9.0->litebird_sim) (7.12.0)
Requirement already satisfied: python-dateutil<3,>=2.8.2 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from libinsdb>=0.9.0->litebird_sim) (2.9.0.post0)
Requirement already satisfied: requests-mock<2,>=1.11.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from libinsdb>=0.9.0->litebird_sim) (1.12.1)
Requirement already satisfied: types-pyyaml<7,>=6.0.12.12 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from libinsdb>=0.9.0->litebird_sim) (6.0.12.20250915)
Requirement already satisfied: six>=1.5 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from python-dateutil<3,>=2.8.2->libinsdb>=0.9.0->litebird_sim) (1.17.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from requests>=2.32.0->litebird_sim) (3.4.4)
Requirement already satisfied: idna<4,>=2.5 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from requests>=2.32.0->litebird_sim) (3.11)
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from requests>=2.32.0->litebird_sim) (2.5.0)
Requirement already satisfied: certifi>=2017.4.17 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from requests>=2.32.0->litebird_sim) (2025.11.12)
Requirement already satisfied: mdurl~=0.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich) (0.1.2)
Requirement already satisfied: setuptools in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from markdown-katex>=202406.1035->litebird_sim) (80.9.0)
Requirement already satisfied: contourpy>=1.0.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from matplotlib>=3.10.3->litebird_sim) (1.3.3)
Requirement already satisfied: cycler>=0.10 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from matplotlib>=3.10.3->litebird_sim) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from matplotlib>=3.10.3->litebird_sim) (4.60.1)
Requirement already satisfied: kiwisolver>=1.3.1 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from matplotlib>=3.10.3->litebird_sim) (1.4.9)
Requirement already satisfied: pyparsing>=3 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from matplotlib>=3.10.3->litebird_sim) (3.2.5)
Requirement already satisfied: llvmlite<0.48,>=0.47.0dev0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from numba>=0.63.0->litebird_sim) (0.47.0)
Requirement already satisfied: pynacl>=1.4.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from pygithub>=2.6.1->litebird_sim) (1.6.1)
Requirement already satisfied: pyjwt>=2.4.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from pyjwt[crypto]>=2.4.0->pygithub>=2.6.1->litebird_sim) (2.10.1)
Requirement already satisfied: typing-extensions>=4.5.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from pygithub>=2.6.1->litebird_sim) (4.15.0)
Requirement already satisfied: cryptography>=3.4.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from pyjwt[crypto]>=2.4.0->pygithub>=2.6.1->litebird_sim) (46.0.3)
Requirement already satisfied: cffi>=2.0.0 in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from cryptography>=3.4.0->pyjwt[crypto]>=2.4.0->pygithub>=2.6.1->litebird_sim) (2.0.0)
Requirement already satisfied: pycparser in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from cffi>=2.0.0->cryptography>=3.4.0->pyjwt[crypto]>=2.4.0->pygithub>=2.6.1->litebird_sim) (2.23)
Requirement already satisfied: toml in /opt/homebrew/Caskroom/miniconda/base/envs/litebird_sim/lib/python3.13/site-packages (from pysm3>=3.4.2->litebird_sim) (0.10.2)
litebird_sim noise simulation notebook#
To run this notebook, you have several options:
If you are running this under Binder, you should already be set!
If you are running this under Google Colab, be sure to run the cell with
!pip install…that is right above the title.If you are running this locally, you should first create and activate a new virtual environment with the commands
python -m venv ./my_venv source ./my_venv/bin/activate
(you can use Conda environments, if you prefer) and install Jupyter and litebird_sim in it:
pip install jupyter litebird_sim
If you have a local copy of the
litebird_simrepository cloned from litebird/litebird_sim (e.g., because you’re part of the Simulation Team!), you can use a development install instead:cd /my/local/copy/litebird_sim pip install -e .
Set up the environment#
# Using this file, we can use "import litebird_sim" even if it is not installed system-wide
We start by importing a few libraries that will be useful in this notebook.
import litebird_sim as lbs
import numpy as np
import matplotlib.pylab as plt
import scipy.signal
%matplotlib inline
Simulation parameters#
We define a small set of detectors representing a two-detector pair at 140 GHz. All noise parameters are set explicitly below.
# Observation parameters
duration_s = 3600.0 # 1 hour of data
sampling_hz = 19.0 # LFT-like sampling rate
random_seed = 42
# Noise parameters (typical LFT 140 GHz values)
net_ukrts = 50.0 # µK√s
fknee_mhz = 20.0 # knee frequency in mHz
fmin_hz = 1e-5 # minimum frequency in Hz
alpha = 1.0 # 1/f spectral slope
1. White noise#
The simplest noise model is uncorrelated Gaussian (white) noise. Each sample is drawn with standard deviation
$$\sigma = \frac{\mathrm{NET} \cdot \sqrt{f_\mathrm{samp}}}{10^6}\ \mathrm{K}$$
The one-sided power spectral density (as returned by scipy.signal.welch) integrates to the total variance, and for a flat spectrum this gives:
$$P_\mathrm{white} = \frac{2,\sigma^2}{f_\mathrm{samp}} = 2,\mathrm{NET}^2\ (\mu\mathrm{K}^2/\mathrm{Hz})$$
The factor of 2 arises because welch folds the negative-frequency half of the two-sided spectrum onto the positive axis.
det = lbs.DetectorInfo(
name="det_white",
net_ukrts=net_ukrts,
sampling_rate_hz=sampling_hz,
)
sim_white = lbs.Simulation(
base_path="./output_noise_nb",
start_time=0,
duration_s=duration_s,
random_seed=random_seed,
)
obs_white = sim_white.create_observations(detectors=[det])
lbs.noise.add_noise_to_observations(
obs_white,
noise_type="white",
dets_random=sim_white.dets_random,
)
tod_white = obs_white[0].tod[0] # first (and only) detector
print(f"Samples: {len(tod_white)}, RMS: {tod_white.std():.3e} K")
Samples: 68400, RMS: 2.180e-04 K
t = np.arange(len(tod_white)) / sampling_hz
fig, axes = plt.subplots(1, 2, figsize=(13, 4))
# Time domain
axes[0].plot(t[:500], tod_white[:500] * 1e6, lw=0.8)
axes[0].set_xlabel("Time (s)")
axes[0].set_ylabel("Signal (µK)")
axes[0].set_title("White noise — time domain (first 500 samples)")
# Power spectral density
f, psd = scipy.signal.welch(tod_white, fs=sampling_hz, nperseg=4096)
axes[1].loglog(f[1:], psd[1:] * 1e12) # convert K² to µK²
axes[1].set_xlabel("Frequency (Hz)")
axes[1].set_ylabel("PSD (µK² / Hz)")
axes[1].set_title("White noise — power spectrum")
# Expected white noise level.
# scipy.signal.welch returns the one-sided PSD, which folds both positive
# and negative frequencies onto the positive axis, giving a factor of 2:
# PSD_white = 2 * sigma² / fs = 2 * NET² (µK²/Hz)
sigma_K = net_ukrts * np.sqrt(sampling_hz) / 1e6
expected_psd = 2 * sigma_K**2 / sampling_hz
axes[1].axhline(
expected_psd * 1e12,
color="red",
ls="--",
label=f"Expected = 2·NET² = {2 * net_ukrts**2:.0f} µK²/Hz",
)
axes[1].legend()
plt.tight_layout()
plt.show()
2. 1/f noise#
Detector readout chains introduce low-frequency (1/f) noise characterised by the knee frequency $f_\mathrm{knee}$ and spectral index $\alpha$. Two PSD models are available:
Model |
PSD shape |
|---|---|
|
$P(f) \propto (f^\alpha + f_\mathrm{knee}^\alpha),/,(f^\alpha + f_\mathrm{min}^\alpha)$ |
|
$P(f) \propto \left[(f^2 + f_\mathrm{knee}^2),/,(f^2 + f_\mathrm{min}^2)\right]^{\alpha/2}$ |
and two computational engines: "fft" (default, works with both models) and "ducc" (IIR filtering, "keshner" only).
det_oof = lbs.DetectorInfo(
name="det_oof",
net_ukrts=net_ukrts,
sampling_rate_hz=sampling_hz,
fknee_mhz=fknee_mhz,
fmin_hz=fmin_hz,
alpha=alpha,
)
sim_oof = lbs.Simulation(
base_path="./output_noise_nb",
start_time=0,
duration_s=duration_s,
random_seed=random_seed,
)
obs_oof = sim_oof.create_observations(detectors=[det_oof])
lbs.noise.add_noise_to_observations(
obs_oof,
noise_type="one_over_f",
dets_random=sim_oof.dets_random,
)
tod_oof = obs_oof[0].tod[0]
print(f"Samples: {len(tod_oof)}, RMS: {tod_oof.std():.3e} K")
Samples: 68400, RMS: 2.207e-04 K
fig, axes = plt.subplots(1, 2, figsize=(13, 4))
axes[0].plot(t[:500], tod_oof[:500] * 1e6, lw=0.8, color="C1")
axes[0].set_xlabel("Time (s)")
axes[0].set_ylabel("Signal (µK)")
axes[0].set_title("1/f noise — time domain (first 500 samples)")
f_oof, psd_oof = scipy.signal.welch(tod_oof, fs=sampling_hz, nperseg=4096)
axes[1].loglog(f_oof[1:], psd_oof[1:] * 1e12, color="C1", label="Simulated")
# Overlay the expected 'toast' PSD shape.
# Factor of 2 accounts for the one-sided (folded) PSD returned by welch.
fknee_hz = fknee_mhz / 1000.0
sigma_K = net_ukrts * np.sqrt(sampling_hz) / 1e6
f_th = f_oof[1:]
psd_white = 2 * sigma_K**2 / sampling_hz # one-sided white-noise floor
psd_theory = (
psd_white * (f_th**alpha + fknee_hz**alpha) / (f_th**alpha + fmin_hz**alpha)
)
axes[1].loglog(f_th, psd_theory * 1e12, "r--", label="Expected (toast model)")
axes[1].axvline(
fknee_hz, color="grey", ls=":", label=f"$f_\\mathrm{{knee}}$ = {fknee_mhz} mHz"
)
axes[1].set_xlabel("Frequency (Hz)")
axes[1].set_ylabel("PSD (µK² / Hz)")
axes[1].set_title("1/f noise — power spectrum")
axes[1].legend()
plt.tight_layout()
plt.show()
Comparing PSD models and engines#
Let’s overlay the two models and both engines on the same plot to check their consistency.
def make_oof_tod(model, engine, seed):
"""Helper: return a 1/f TOD for the given model/engine combination."""
sim_ = lbs.Simulation(
base_path="./output_noise_nb",
start_time=0,
duration_s=duration_s,
random_seed=seed,
)
obs_ = sim_.create_observations(detectors=[det_oof])
lbs.noise.add_noise_to_observations(
obs_,
noise_type="one_over_f",
dets_random=sim_.dets_random,
engine=engine,
model=model,
)
return obs_[0].tod[0]
tod_toast_fft = make_oof_tod("toast", "fft", seed=1)
tod_keshner_fft = make_oof_tod("keshner", "fft", seed=2)
tod_keshner_ducc = make_oof_tod("keshner", "ducc", seed=3)
fig, ax = plt.subplots(figsize=(8, 5))
for tod, label, color in [
(tod_toast_fft, "toast / fft", "C0"),
(tod_keshner_fft, "keshner / fft", "C1"),
(tod_keshner_ducc, "keshner / ducc", "C2"),
]:
f_, p_ = scipy.signal.welch(tod, fs=sampling_hz, nperseg=4096)
ax.loglog(f_[1:], p_[1:] * 1e12, label=label)
ax.axvline(fknee_hz, color="grey", ls=":", label="$f_\\mathrm{knee}$")
ax.set_xlabel("Frequency (Hz)")
ax.set_ylabel("PSD (µK² / Hz)")
ax.set_title("1/f — model and engine comparison")
ax.legend()
plt.tight_layout()
plt.show()
3. Using Simulation.add_noise#
The high-level Simulation.add_noise method is the easiest entry point. It reads noise parameters directly from the detector objects and adds noise in-place to all observations.
dets_pair = [
lbs.DetectorInfo(
name=f"det_{i}",
net_ukrts=net_ukrts,
sampling_rate_hz=sampling_hz,
fknee_mhz=fknee_mhz,
fmin_hz=fmin_hz,
alpha=alpha,
)
for i in range(4)
]
sim = lbs.Simulation(
base_path="./output_noise_nb",
start_time=0,
duration_s=duration_s,
random_seed=random_seed,
)
sim.create_observations(detectors=dets_pair)
# Add 1/f noise to all detectors in one call
sim.add_noise(noise_type="one_over_f")
obs = sim.observations[0]
for i in range(obs.n_detectors):
print(f" det_{i} RMS = {obs.tod[i].std():.3e} K")
det_0 RMS = 2.207e-04 K
det_1 RMS = 2.196e-04 K
det_2 RMS = 2.197e-04 K
det_3 RMS = 2.215e-04 K
5. Reproducibility#
Passing the same random_seed to Simulation guarantees identical noise realisations across runs. The dets_random list can also be saved and restored for fine-grained control.
def run_sim(seed):
sim_ = lbs.Simulation(
base_path="./output_noise_nb",
start_time=0,
duration_s=100,
random_seed=seed,
)
sim_.create_observations(detectors=[det_oof])
sim_.add_noise(noise_type="one_over_f")
return sim_.observations[0].tod[0].copy()
tod_a = run_sim(seed=99)
tod_b = run_sim(seed=99) # same seed → identical
tod_c = run_sim(seed=100) # different seed → different
print(f"Same seed — max |diff|: {np.abs(tod_a - tod_b).max():.2e} (should be 0)")
print(f"Diff seed — max |diff|: {np.abs(tod_a - tod_c).max():.2e} (should be > 0)")
Same seed — max |diff|: 0.00e+00 (should be 0)
Diff seed — max |diff|: 1.27e-03 (should be > 0)
Summary#
Noise type |
Function / method |
Key parameters |
|---|---|---|
White |
|
|
1/f |
|
|
Correlated (common-mode) |
|
|
Correlated (Cholesky) |
|
|
For full API details see the noise documentation.