| name | bagpipes-sed-fitting |
| description | Bayesian SED fitting with BAGPIPES using default models and nautilus sampler |
| category | astronomy |
| tags | ["sed-fitting","bagpipes","bayesian","nautilus","galaxy","stellar-population","python"] |
| author | Song Huang |
| date | "2026-03-30T00:00:00.000Z" |
| version | 1.0.0 |
| requirements | ["bagpipes>=1.3.5","numpy","matplotlib","astropy","nautilus-sampler (auto-installed with bagpipes)"] |
BAGPIPES SED Fitting Skill
BAGPIPES (Bayesian Analysis of Galaxies for Physical Inference and Parameter EStimation) is a state-of-the-art Python code for generating realistic model galaxy spectra and fitting spectroscopic and photometric observations.
Overview
BAGPIPES uses Bayesian inference with nested sampling (MultiNest or nautilus) to fit spectral energy distributions (SEDs) and extract physical galaxy parameters.
Key Features
- Model components: Flexible star-formation histories, dust attenuation, nebular emission
- Stellar population models: BC03, MILES, BPASS (via model_components)
- Photometric & spectroscopic fitting: Support for both data types
- Bayesian inference: Posterior sampling with evidence calculation
- Default sampler: Nautilus (pure Python, auto-installed with bagpipes)
Installation
pip install bagpipes
Note: Do not clone the repository directly - the large model grids are not included. Use pip install.
Quick Start
Basic Workflow
import bagpipes as pipes
import numpy as np
def load_photometry(ID):
"""Load photometry for a given object ID."""
fluxes = np.array([...])
fluxerrs = np.array([...])
photometry = np.c_[fluxes, fluxerrs]
return photometry
fit_instructions = {
"redshift": (0., 10.),
"tau": {
"age": (0.1, 15.),
"tau": (0.3, 10.),
"massformed": (1., 15.),
"metallicity": (0., 2.5),
},
"dust": {
"type": "Calzetti",
"Av": (0., 2.),
}
}
galaxy = pipes.galaxy("object_id", load_photometry,
spectrum_exists=False,
filt_list=filter_list)
fit = pipes.fit(galaxy, fit_instructions, sampler="nautilus")
fit.fit(verbose=False)
print(fit.posterior.samples.keys())
print(fit.posterior.median)
Model Components
Star Formation History (SFH)
Available SFH components with their parameters:
| Component | Description | Key Parameters |
|---|
burst | Instantaneous burst | age, massformed, metallicity |
constant | Constant SFH | age, massformed, metallicity |
tau | Exponential decay | age, tau, massformed, metallicity |
delayed | Delayed exponential | age, tau, massformed, metallicity |
lognormal | Log-normal SFH | age, tau, massformed, metallicity |
dblplaw | Double power-law | age, tau, alpha, beta, massformed, metallicity |
Example - Exponential tau model:
exp = {
"age": (0.1, 15.),
"tau": (0.3, 10.),
"massformed": (1., 15.),
"metallicity": (0., 2.5),
}
fit_instructions["tau"] = exp
Example - Double power-law (more flexible):
dblplaw = {
"tau": (0., 15.),
"alpha": (0.01, 1000.),
"beta": (0.01, 1000.),
"alpha_prior": "log_10",
"beta_prior": "log_10",
"massformed": (1., 15.),
"metallicity": (0., 2.5),
}
fit_instructions["dblplaw"] = dblplaw
Dust Attenuation
| Type | Description | Parameters |
|---|
Calzetti | Calzetti et al. (2000) | Av, optional eta |
CF00 | Charlot & Fall (2000) two-component | Av, mu |
Salim | Salim et al. (2018) | Av, delta, B, Rv |
Example:
dust = {
"type": "Calzetti",
"Av": (0., 2.),
}
fit_instructions["dust"] = dust
Nebular Emission
nebular = {
"logU": -3.0,
}
nebular = {
"logU": (-4., -1.),
}
fit_instructions["nebular"] = nebular
AGN Component (optional)
agn = {
"alpha": (-2., 2.),
"mass": (6., 12.),
}
fit_instructions["agn"] = agn
Priors
Specify priors using parameter_prior keyword:
component["metallicity"] = (0., 2.5)
component["metallicity"] = (0.01, 5.)
component["metallicity_prior"] = "log_10"
component["parameter_prior"] = "log_e"
component["parameter_prior"] = "recip"
component["redshift"] = (0., 1.)
component["redshift_prior"] = "Gaussian"
component["redshift_prior_mu"] = 0.7
component["redshift_prior_sigma"] = 0.2
Available priors:
"uniform" - Uniform in linear space (default)
"log_10" - Uniform in log10 space
"log_e" - Uniform in natural log space
"pow_10" - Uniform in 10^parameter
"recip" - Uniform in 1/parameter
"recipsq" - Uniform in 1/parameter^2
"Gaussian" - Gaussian with mu and sigma
Loading Observational Data
Photometry
def load_photometry(ID):
"""
Load photometry for object ID.
Returns: 2D array of shape (N_bands, 2) with flux and error.
Fluxes should be in micro-Janskys (uJy).
"""
fluxes = catalog[ID]['fluxes']
fluxerrs = catalog[ID]['errors']
for i in range(len(fluxes)):
if fluxes[i] == 0 or fluxerrs[i] <= 0:
fluxes[i] = 0.
fluxerrs[i] = 9.9e99
max_snr = 20.
for i in range(len(fluxes)):
if fluxes[i] / fluxerrs[i] > max_snr:
fluxerrs[i] = fluxes[i] / max_snr
return np.c_[fluxes, fluxerrs]
filt_list = np.loadtxt("filters.txt", dtype=str)
galaxy = pipes.galaxy(ID, load_photometry,
spectrum_exists=False,
filt_list=filt_list)
Spectroscopy
def load_spectrum(ID):
"""
Load spectrum for object ID.
Returns: 2D array of shape (N_pixels, 3) with wavelength, flux, error.
Wavelength in Angstroms, flux in erg/s/cm^2/A.
"""
wavelength = spectrum['wave']
flux = spectrum['flux']
error = spectrum['error']
mask = np.ones(len(wavelength), dtype=bool)
spectrum_array = np.c_[wavelength, flux, error]
return spectrum_array, mask
galaxy = pipes.galaxy(ID, load_photometry, load_spectrum,
spectrum_exists=True,
filt_list=filt_list)
Fitting with Nautilus Sampler
fit = pipes.fit(galaxy, fit_instructions, sampler="nautilus")
fit.fit(verbose=False)
fit = pipes.fit(
galaxy,
fit_instructions,
sampler="nautilus",
n_live=1000,
n_eff=10000,
)
Nautilus advantages:
- Pure Python (no compilation needed)
- Often faster than MultiNest
- Handles high-dimensional problems well
- Built-in with bagpipes installation
Extracting Results
samples = fit.posterior.samples
print(samples.keys())
median = fit.posterior.median
conf_int = fit.posterior.conf_int
mass = samples['stellar_mass']
print(f"log M* = {np.median(mass):.2f} +/- {np.std(mass):.2f}")
evidence = fit.posterior.ln_evidence
max_like_params = fit.posterior.max_like_params
Visualization
fig = fit.plot_spectrum_posterior(save=False, show=True)
fig = fit.plot_sfh_posterior(save=False, show=True)
fig = fit.plot_corner(save=False, show=True)
fig = fit.plot_calibration(save=False, show=True)
fig = galaxy.plot()
Fitting Catalogs
ids = ["001", "002", "003", ...]
fit_catalogue = pipes.fit_catalogue(
ids,
fit_instructions,
load_photometry,
spectrum_exists=False,
filt_list=filt_list,
run="my_run",
sampler="nautilus",
)
fit_catalogue.fit(verbose=False, n_cores=4)
for id in ids:
fit = fit_catalogue.fits[id]
mass = fit.posterior.samples['stellar_mass']
print(f"{id}: log M* = {np.median(mass):.2f}")
Common Model Setups
Simple Exponential SFH
fit_instructions = {
"redshift": (0., 10.),
"tau": {
"age": (0.1, 15.),
"tau": (0.3, 10.),
"massformed": (1., 15.),
"metallicity": (0., 2.5),
},
"dust": {
"type": "Calzetti",
"Av": (0., 2.),
}
}
Complex SFH with Nebular
fit_instructions = {
"redshift": (0., 10.),
"dblplaw": {
"tau": (0., 15.),
"alpha": (0.01, 1000.),
"beta": (0.01, 1000.),
"alpha_prior": "log_10",
"beta_prior": "log_10",
"massformed": (1., 15.),
"metallicity": (0., 2.5),
},
"dust": {
"type": "Calzetti",
"Av": (0., 3.),
},
"nebular": {
"logU": (-4., -1.),
}
}
Fixed Redshift Fit
fit_instructions = {
"redshift": 1.5,
"tau": {
"age": (0.1, 13.5),
"tau": (0.3, 10.),
"massformed": (1., 15.),
"metallicity": (0., 2.5),
},
"dust": {
"type": "Calzetti",
"Av": (0., 2.),
}
}
Best Practices
Data Preparation
- Flux units: Photometry in uJy, spectroscopy in erg/s/cm^2/A
- Missing data: Set flux=0 and large error (e.g., 9.9e99)
- SNR limits: Cap maximum SNR to ~20-30 to prevent overfitting
- Filters: Use consistent filter transmission curves
Model Selection
- Start simple: Begin with tau model, add complexity as needed
- Prior ranges: Use physically motivated ranges
- Redshift: Fix if known from spectroscopy
- Nebular emission: Include for emission-line galaxies
Convergence Checks
print(f"N_eff = {fit.posterior.n_eff}")
if fit.posterior.n_eff < 1000:
print("Warning: Low effective sample size!")
Citation
If using BAGPIPES, cite:
- Carnall et al. (2018): Primary code paper (MNRAS, 480, 4379)
- Carnall et al. (2019): Spectroscopic fitting (MNRAS, 483, 3636)
- Lange et al. (2023): Nautilus sampler (MNRAS, 525, 3181)
External Resources