synthesizer.particle.resample_utils

Shared utilities for particle spatial resampling.

Provides kernel-based position sampling from the SPH kernel, array tiling, velocity dispersion helpers, and higher-level engine functions used by both Gas.spatially_resample and Stars.spatially_resample.

Private helpers (_tile_array, _divide_and_tile) handle the low-level numpy mechanics. Public engine functions (resample_coordinates, resample_velocities, resample_smoothing_lengths, resample_by_mode, split_by_mask, validate_required_inputs) form the stable API consumed by the class methods.

Module attributes

RESAMPLE_MODES

Valid string mode names for resample_by_mode().

Functions

synthesizer.particle.resample_utils.add_velocity_dispersion(velocities, dispersion, seed=None)[source]

Add Gaussian random velocity dispersion to velocity arrays.

Parameters:
  • velocities (np.ndarray or unyt_array) – (N_total, 3) array of velocities.

  • dispersion (float or unyt_quantity) – Standard deviation of the Gaussian noise.

  • seed (int, optional) – Random seed for reproducibility.

Returns:

Velocities with added dispersion.

Return type:

np.ndarray or unyt_array

synthesizer.particle.resample_utils.resample_by_mode(arr, mode_spec, resample_factor, rng)[source]

Resample a per-particle array according to a named mode.

Applies one of five physically-motivated splitting strategies to arr:

"duplicated"

Every child inherits the parent value unchanged. Use for intensive quantities (metallicity, optical depth, flags).

"proportional"

The parent value is divided equally: each child gets value / resample_factor. The total sum is exactly conserved. Use for extensive quantities (mass, dust mass).

"normal"

Each child is drawn from Normal(value, σ) independently. By default σ is the population standard deviation np.std(arr); pass mode_spec = ("normal", sigma) to supply a fixed σ instead.

"lognormal"

Like normal but in log-space: each child is drawn from LogNormal(log(value), σ_log) so values remain positive. By default σ_log = np.std(np.log(arr)); pass ("lognormal", sigma_log) to fix it.

"conserved_normal"

Proportional split with additive Gaussian scatter, renormalised so children sum exactly to the parent value. Default σ = np.std(arr) / resample_factor; pass ("conserved_normal", sigma) to fix it.

Parameters:
  • arr (np.ndarray or unyt_array) – Per-particle array of length N. Must not be None; check before calling.

  • mode_spec (str or tuple) – Either a mode name string or a (mode_name, sigma) tuple.

  • resample_factor (int) – Number of new particles per original.

  • rng (np.random.Generator) – A seeded numpy.random.Generator for reproducibility.

Returns:

Resampled array of length N * resample_factor, with units preserved if arr carries them.

Return type:

np.ndarray or unyt_array

Raises:

ValueError – If mode_spec is not a recognised mode name or tuple.

synthesizer.particle.resample_utils.resample_coordinates(coordinates, smoothing_lengths, kernel, resample_factor, seed=None)[source]

Resample particle coordinates by adding kernel-sampled offsets.

Each original position is replaced by resample_factor new positions offset by vectors sampled from the SPH kernel.

Note: @accepts converts coordinates and smoothing_lengths to Mpc before this function is called, so smoothing_lengths.ndview below is always in Mpc. The returned coordinates are therefore also in Mpc, matching the input unit.

Parameters:
  • coordinates (unyt_array, (N, 3)) – Original particle coordinates.

  • smoothing_lengths (unyt_array, (N,)) – Per-particle smoothing lengths.

  • kernel (Kernel) – SPH kernel for position sampling.

  • resample_factor (int) – Number of new particles per original particle.

  • seed (int, optional) – Random seed.

Returns:

Resampled coordinates in the same units as coordinates.

Return type:

unyt_array, (N * resample_factor, 3)

synthesizer.particle.resample_utils.resample_smoothing_lengths(smoothing_lengths, resample_factor)[source]

Scale smoothing lengths for volume conservation and tile.

Each smoothing length is scaled by resample_factor ** (-1/3) so that the total kernel volume is conserved across the resampled particles, then tiled resample_factor times. The returned smoothing lengths are therefore resample_factor ** (1/3) times smaller than the originals with the same total volume.

Parameters:
  • smoothing_lengths (unyt_array, (N,)) – Original smoothing lengths.

  • resample_factor (int) – Number of new particles per original.

Returns:

Resampled smoothing lengths.

Return type:

unyt_array, (N * resample_factor,)

synthesizer.particle.resample_utils.resample_velocities(velocities, resample_factor, velocity_dispersion=None, seed=None)[source]

Resample velocities: tile each velocity and optionally add dispersion.

Parameters:
  • velocities (unyt_array, (N, 3), optional) – Original velocities. Returns None if None is passed.

  • resample_factor (int) – Number of new particles per original.

  • velocity_dispersion (float or unyt_quantity, optional) – Std. dev. of Gaussian velocity noise to add after tiling.

  • seed (int, optional) – Random seed for the dispersion noise.

Returns:

Resampled velocities (None if velocities is None).

Return type:

unyt_array or None

synthesizer.particle.resample_utils.sample_kernel_positions(kernel, smoothing_lengths, n_samples, seed=None)[source]

Sample 3D offsets from an SPH kernel for many particles.

For each particle with smoothing length h, n_samples 3D offsets are sampled from the kernel W(r) treated as a PDF. The radial part is sampled via inverse-CDF from p(q) ∝ 4π q² f(q) where q = r/h and f(q) is the dimensionless kernel. Angles are sampled uniformly on the sphere.

All operations are vectorised across particles × samples.

Parameters:
  • kernel (Kernel) – A Kernel instance providing kernel.f for the dimensionless kernel function.

  • smoothing_lengths (np.ndarray) – (N,) array of raw smoothing lengths (numeric values in whatever length unit the caller uses — units are not attached).

  • n_samples (int) – Number of samples per particle.

  • seed (int, optional) – Random seed for reproducibility.

Returns:

(N, n_samples, 3) array of raw Cartesian offset vectors in the same units as smoothing_lengths.

Return type:

np.ndarray

synthesizer.particle.resample_utils.split_by_mask(mask, **arrays)[source]

Split arrays by a boolean mask, preserving input order.

Each array is either indexed by the mask (if it is a 1-D array matching the mask length), or left unchanged if it is a scalar, None, or a shorter/longer array.

Input order is preserved in the returned lists:

m, u = split_by_mask(mask, mass=..., met=..., sfr=...)
# m[0] is masked mass, u[0] is unmasked mass, etc.
Parameters:
  • mask (array-like) – Boolean mask.

  • **arrays – Named arrays, scalars, or None to split.

Returns:

(to_resample, no_resample) — two lists, each of the same length as the number of input arrays, in input order.

Return type:

tuple[list, list]

synthesizer.particle.resample_utils.validate_mask(mask, nparticles)[source]

Validate a boolean mask array and convert to bool ndarray.

Parameters:
  • mask (array-like) – Boolean mask of length nparticles.

  • nparticles (int) – Expected length.

Returns:

bool array.

Return type:

np.ndarray

Raises:

InconsistentArguments – If mask is the wrong length.

synthesizer.particle.resample_utils.validate_required_inputs(coordinates, smoothing_lengths, kernel)[source]

Validate that required arrays for spatial resampling are provided.

Parameters:
  • coordinates (unyt_array or None) – The particle coordinates array.

  • smoothing_lengths (unyt_array or None) – The particle smoothing lengths array.

  • kernel (Kernel or None) – The SPH kernel for position sampling.

Raises:

InconsistentArguments – If anything is missing.

synthesizer.particle.resample_utils.validate_resample_factor(resample_factor)[source]

Validate that resample_factor >= 2.

Raises:

ValueError – If resample_factor < 2.