Note
Go to the end to download the full example code.
Spatial resampling with parametric SFH and MetalDist¶
Demonstrates resampling stellar ages and metallicities from
parametrised star-formation histories and metallicity distributions
using spatially_resample().
A toy stellar population of 100 particles is resampled by a factor of 50 (→ 5 000 particles) under three scenarios:
sfh only — ages are sampled from a declining exponential SFH; metallicities are kept from the original particles.
metal_dist only — metallicities are sampled from a Gaussian MetalDist; ages are kept from the original particles.
sfh + metal_dist — ages and metallicities are jointly sampled from the 2-D SFZH formed by the outer product of the two distributions.

import matplotlib.pyplot as plt
import numpy as np
from unyt import Gyr, Mpc, Msun, Myr, km, s
from synthesizer.kernel_functions import Kernel
from synthesizer.parametric.metal_dist import Normal as ZNormal
from synthesizer.parametric.sf_hist import TruncatedExponential
from synthesizer.particle.stars import Stars
plt.rcParams["font.family"] = "DeJavu Serif"
plt.rcParams["font.serif"] = ["Times New Roman"]
# ---------------------------------------------------------------------------
# Toy data: 100 stellar particles with a basic age–metallicity distribution
# ---------------------------------------------------------------------------
NP = 100
FACTOR = 50
rng = np.random.default_rng(42)
stars = Stars(
initial_masses=np.ones(NP) * 1e6 * Msun,
ages=rng.uniform(10, 10000, NP) * Myr,
metallicities=rng.uniform(0.001, 0.03, NP),
coordinates=rng.uniform(-1, 1, (NP, 3)) * Mpc,
velocities=rng.uniform(-50, 50, (NP, 3)) * (km / s),
smoothing_lengths=np.ones(NP) * 0.3 * Mpc,
softening_lengths=np.ones(NP) * 0.1 * Mpc,
redshift=0.1,
)
# ---------------------------------------------------------------------------
# Parametric SFH and MetalDist
# ---------------------------------------------------------------------------
sfh = TruncatedExponential(tau=3 * Gyr, max_age=15 * Gyr, min_age=0 * Gyr)
metal_dist = ZNormal(mean=0.02, sigma=0.005)
# ---------------------------------------------------------------------------
# Resample under each scenario
# ---------------------------------------------------------------------------
kernel = Kernel("cubic")
s_sfh = stars.spatially_resample(
FACTOR,
kernel=kernel,
seed=1,
sfh=sfh,
)
s_md = stars.spatially_resample(
FACTOR,
kernel=kernel,
seed=1,
metal_dist=metal_dist,
)
s_both = stars.spatially_resample(
FACTOR,
kernel=kernel,
seed=1,
sfh=sfh,
metal_dist=metal_dist,
)
# ---------------------------------------------------------------------------
# Plot age and metallicity histograms
# ---------------------------------------------------------------------------
fig, axes = plt.subplots(
3, 2, figsize=(11, 9), gridspec_kw=dict(wspace=0.3, hspace=0.3)
)
age_bins = np.logspace(
np.log10((1 * Myr).to("yr").value), np.log10((15 * Gyr).to("yr").value), 60
)
z_bins = np.linspace(0.0001, 0.04, 40)
original_ages_yr = stars.ages.to("yr").value
for row_idx, (label, samp) in enumerate(
[
("sfh only", s_sfh),
("metal_dist only", s_md),
("sfh + metal_dist", s_both),
]
):
ax_age = axes[row_idx, 0]
ax_age.hist(
original_ages_yr,
bins=age_bins,
color="grey",
alpha=0.3,
label=f"Original ({len(stars.ages)} particles)",
density=False,
)
ax_age.hist(
samp.ages.to("yr").value,
bins=age_bins,
color="tab:blue",
alpha=0.6,
label=f"Resampled ({len(samp.ages)} particles)",
density=False,
)
ax_age.set_xscale("log")
ax_age.set_xlabel("Age / yr")
ax_age.set_ylabel("N")
ax_age.set_title(f"Ages — {label}")
ax_age.legend(fontsize=7.5)
ax_z = axes[row_idx, 1]
ax_z.hist(
stars.metallicities,
bins=z_bins,
color="grey",
alpha=0.3,
label="Original",
density=False,
)
ax_z.hist(
samp.metallicities,
bins=z_bins,
color="tab:orange",
alpha=0.6,
label="Resampled",
density=False,
)
ax_z.set_xlabel("Metallicity")
ax_z.set_ylabel("N")
ax_z.set_title(f"Metallicities — {label}")
ax_z.legend(fontsize=7.5)
plt.suptitle(
f"Parametric SFH + MetalDist resampling ({NP} → {NP * FACTOR} particles)",
fontsize=13,
y=1.01,
)
plt.show()
Total running time of the script: (0 minutes 0.937 seconds)