Core Functionality#

Synthetic IFU cube generator (toy model).

This module implements a compact, self-contained pipeline to create toy n_vel × ny × nx spectral cubes that mimic IFU observations of disk galaxies. It focuses on clarity and inspectability over performance, and encapsulates the following responsibilities:

Design notes#

  • Coordinates: internal grids are defined in pixels and converted to physical

    units (kpc) using a pixel scale stored per cube.

  • Velocities: rotation is computed analytically and random Gaussian scatter is

    added per voxel to mimic dispersion.

  • Output: spectral cubes are produced in units of flux per pixel and are

    optionally downsampled/averaged in the spectral axis to simulate channel binning/oversampling.

Usage and API#

  • The primary user-facing class is GalCubeCraft. Call

    g.generate_cubes() to produce one or more cubes; the method appends the generated outputs to g.results and also returns that list. Each entry in g.results is typically a tuple (spectral_cube, params) where spectral_cube has shape (n_spectral, ny, nx) and params contains metadata (beam info, pixel scale, velocity axis, etc.). The GUI and utilities in this repository expect this layout.

  • Visualisation helpers are provided as top-level functions in

    GalCubeCraft.visualise (moment0, moment1, spectrum, slice_view). Plotting is intentionally kept separate from the generation core to avoid UI/plotting dependencies here.

This file provides the GalCubeCraft helper class which encapsulates parameters, sampling choices, and the generation pipeline.

class GalCubeCraft.core.GalCubeCraft(n_gals=None, n_cubes=1, resolution='all', offset_gals=5, beam_info=[4, 4, 0], grid_size=125, n_spectral_slices=40, n_sersic=None, save=False, fname=None, verbose=True, seed=None)[source]#

Bases: object

Generator for ensembles of synthetic IFU spectral cubes (pixel units).

High-level behaviour#

  • For each requested cube the class samples (or accepts) one or more galaxy components. Each component has a 3D Sérsic + exponential vertical light distribution and a simple analytical rotation field.

  • The components are rotated to a viewing geometry and placed into a larger spatial grid. Voxels are binned by line-of-sight velocity to produce a 3D spectral cube (n_spectral x ny x nx).

  • The pipeline supports a resolution parameter that controls the effective physical size of galaxies relative to the beam; this is used to vary surface brightness and sampling across generated cubes.

Constructor arguments closely mirror the fields used throughout the implementation (see the __init__ signature). Key internal attributes are lists storing per-cube parameters (all_Re, all_Se, all_pix_spatial_scales, etc.) and the final results are appended to self.results as tuples of (spectral_cube, params).

Implementation details (what the methods do)#

  • milky_way_rot_curve_analytical() computes an analytic circular velocity as a function of radius using a simple power-law approximation scaled by a characteristic velocity v_0.

  • sersic_flux_density_3d() returns a 3D flux field for a Sérsic profile in the disk plane multiplied by an exponential vertical profile.

  • rotated_system() constructs the 3D flux and velocity cubes on a small grid, assigns tangential velocities, and applies geometric rotations (both image rotations and vector rotations) to yield a rotated flux cube and a rotated line-of-sight velocity cube.

  • make_spectral_cube() places rotated components into the final grid, computes velocity bin masks for each spectral channel, projects emission along the line of sight, and returns the assembled spectral cube together with metadata (average velocities, beam info, pixel scale, etc.).

The class operates primarily in pixel units and samples parameter values (e.g., sizes, brightness, orientations) from uniform ranges over physically viable values to produce diverse, unique systems. It intentionally focuses on clarity and inspectability rather than performance; nested Python loops are used to build fields which is adequate for moderate grid sizes used in examples.

generate_cubes()[source]#

Run the full pipeline and generate the requested spectral cubes.

This is the high-level convenience method that iterates over the pre-sampled per-cube parameters (self.all_Re, self.all_Se etc.), constructs each component via rotated_system(), assembles the spectral cube with make_spectral_cube(), applies beam convolution and light smoothing, optionally saves the cube(s) to disk when save=True (the fname argument controls the destination directory), and returns a list of (cube, params) tuples stored in self.results.

Returns:

results – A list with one entry per generated cube. Each entry is a tuple (spectral_cube_array, params_dict) where spectral_cube_array has shape (n_channels, grid_size, grid_size).

Return type:

list

Example

>>> g = GalCubeCraft(n_cubes=1, seed=42, verbose=False)
>>> results = g.generate_cubes()
>>> cube, meta = results[0]
>>> cube.shape
(40, 125, 125)

Notes

  • The method performs several stochastic choices (positions, angles, flux scalings). Use seed in the constructor to reproduce results.

  • For large numbers of cubes or larger grids, consider refactoring the inner loops to use vectorized operations or offload heavy parts to compiled code for speed.

make_spectral_cube(rotated_disks, rotated_vel_z_cubes, pix_spatial_scale)[source]#

Assemble rotated component cubes into a final spectral cube.

Projects multiple rotated galaxy components into a larger spatial grid, bins voxels by line-of-sight velocity into spectral channels, and returns a spectral cube together with metadata describing the configuration.

Parameters:
  • rotated_disks (list of ndarray) – List of 3D flux cubes (from rotated_system()) for each component. Each array shape must match (init_grid_size,)*3.

  • rotated_vel_z_cubes (list of ndarray) – Corresponding list of 3D LOS velocity fields (km/s) for each component.

  • pix_spatial_scale (float) – Physical scale (kpc/pixel) used for this cube; required for computing relative Hubble-flow offsets when placing multiple galaxies along the LOS.

Returns:

  • spectral_cube_Jy_px (ndarray) – Spectral cube with shape (n_channels, grid_size, grid_size) where the spectral axis corresponds to velocity-binned slices.

  • params_gen (dict) – Metadata with keys: ‘galaxy_centers’, ‘average_vels’, ‘beam_info’, ‘n_gals’, and ‘pix_spatial_scale’.

Notes

  • The method internally defines a symmetric velocity range (default -600 to +600 km/s) and creates self.n_spectral_slices fine bins. The code then averages groups of 5 fine bins to mimic spectral binning (i.e., 5x oversampling).

  • Components are placed at randomized centers near the cube centre and optionally offset along the LOS; small offsets are converted to a Hubble-flow velocity and added to that component’s velocity cube.

Example

>>> cube, meta = g.make_spectral_cube([disk1, disk2], [vel1, vel2], 0.1)
>>> cube.shape
(40, 125, 125)
static milky_way_rot_curve_analytical(R, v_0, R_e, n)[source]#

Analytical rotation-curve approximation.

Computes an analytic circular velocity approximation used to assign tangential velocities to voxels in the toy galaxy model. The form is a shallow power-law scaled by a characteristic velocity v_0 and a scale radius R_0 derived from the Sérsic effective radius.

Parameters:
  • R (float) – Galactocentric radius (kpc). Can be a scalar or NumPy array.

  • v_0 (float) – Characteristic rotation velocity (km/s). Typical values ~200 km/s.

  • R_e (float) – Sérsic effective radius (kpc).

  • n (float) – Sérsic index (dimensionless) used to derive the profile shape.

Returns:

vel – Circular rotation velocity (km/s) evaluated at R.

Return type:

float or ndarray

Notes

  • The function first computes the Sérsic constant b_n using a series expansion and then derives a scale radius R_0 ~ 2 * R_e / b_n^n.

  • The working formula is: v(R) = v_0 * 1.022 * (R / R_0)**0.0803

  • The exponent 0.0803 produces a gently rising/flat curve typical of disk galaxies over the radial range used here.

Edge cases#

  • For R == 0 the returned velocity will be 0 (handled naturally by the power-law when R is 0).

  • Very small R_e or extreme n values may produce R_0 values that are physically unrealistic; validate inputs when using this function.

Example

>>> GalCubeCraft.milky_way_rot_curve_analytical(np.array([0.1,1,10]), 200, 5.0, 1.0)
array([...])  # velocities in km/s

References

See discussion in Lahiry et al. and empirical approximations used for compact rotation-curve modelling.

rotated_system(params_gal_rot)[source]#

Create a rotated 3D galaxy flux cube and LOS velocity cube.

This routine constructs an isolated galaxy on a small cubic grid of size self.init_grid_size and performs the following steps:

  1. Build a 3D flux density using sersic_flux_density_3d().

  2. Compute a tangential velocity magnitude at each (x,y) using milky_way_rot_curve_analytical() and assign vector components in the local tangent direction.

  3. Add a Gaussian random LOS velocity component with standard deviation gal_vz_sigma to mimic dispersion.

  4. Rotate both the scalar flux cube and the velocity vector field to the requested viewing angles (inclination and position angle).

Parameters:

params_gal_rot (dict) – Dictionary with the following keys (units in parentheses): - ‘pix_spatial_scale’ (kpc/pixel) - ‘Re’ (pixels) - ‘hz’ (pixels) - ‘Se’ (flux units) - ‘n’ (dimensionless, Sérsic index) - ‘gal_x_angle’ (degrees, inclination) - ‘gal_y_angle’ (degrees, position angle) - ‘gal_vz_sigma’ (km/s, LOS dispersion) - ‘v_0’ (km/s, characteristic rotation velocity)

Returns:

  • rotated_disk_xy (ndarray) – 3D flux cube after rotations (shape (init_grid_size,)*3).

  • rotated_vel_z_cube_xy (ndarray) – 3D line-of-sight velocity cube (same shape) giving LOS velocity (km/s) at each voxel after rotation and projection.

  • Performance and memory

  • ———————-

  • - The method uses explicit Python loops to assign velocities and to – rotate vectors voxel-by-voxel; this is clear but not optimal for very large grids. self.init_grid_size is chosen to be small to keep runtime reasonable for examples.

Example

>>> params = {'pix_spatial_scale':0.1, 'Re':20, 'hz':2, 'Se':0.1, 'n':1.0,
...           'gal_x_angle':45, 'gal_y_angle':30, 'gal_vz_sigma':40, 'v_0':200}
>>> disk, vel = g.rotated_system(params)
>>> disk.shape, vel.shape
((31,31,31), (31,31,31))

Notes

  • The method currently assumes circular disks (axis ratio q=1) and a simple form for the rotation curve. Replace parts of the pipeline if you need more physical realism.

static sersic_flux_density_3d(x, y, z, Se, Re, n, hz)[source]#

Compute the 3D Sérsic + exponential vertical flux density.

The returned array represents the intrinsic 3D flux distribution of a disk galaxy in physical units (same units as the coordinate grids).

Parameters:
  • x (ndarray) – Coordinate grids (kpc). These should have identical shapes (for example produced by np.meshgrid with indexing='ij').

  • y (ndarray) – Coordinate grids (kpc). These should have identical shapes (for example produced by np.meshgrid with indexing='ij').

  • z (ndarray) – Coordinate grids (kpc). These should have identical shapes (for example produced by np.meshgrid with indexing='ij').

  • Se (float) – Flux density at the effective radius (arbitrary flux units).

  • Re (float) – Effective (half-light) radius in kpc.

  • n (float) – Sérsic index; lower values produce disk-like profiles.

  • hz (float) – Vertical exponential scale height in kpc.

Returns:

S – 3D array with the same shape as the input coordinate grids giving flux density at each voxel.

Return type:

ndarray

Notes

  • The radial Sérsic profile is evaluated using the standard series expansion for the constant b_n.

  • The profile assumes circular symmetry in the disk plane (axis ratio q = 1). To model elliptical disks, scale one of the axes before calling this routine.

  • The vertical structure is a symmetric exponential: exp(-|z|/hz).

Example

>>> nx = ny = nz = 21
>>> x = np.arange(nx) - (nx-1)/2
>>> X,Y,Z = np.meshgrid(x,x,x,indexing='ij')
>>> S = GalCubeCraft.sersic_flux_density_3d(X, Y, Z, Se=0.1, Re=5.0, n=1.0, hz=0.5)
>>> S.shape
(21, 21, 21)

References

Sérsic (1963) and standard approximations for b_n (see Ciotti & Bertin, 1999 for derivations and series expansions).

class GalCubeCraft.core.GalCubeCraft_Phy(n_gals=None, n_cubes=1, spatial_resolution=4.5, spectral_resolution=10, offset_gals=20, beam_info=[18, 18, 0], fov=125, n_sersic=None, save=False, fname=None, verbose=True, seed=None)[source]#

Bases: object

Generator for single-system synthetic IFU spectral cubes (physical units).

High-level behaviour#

  • Uses explicit physical-unit parametrisation (e.g. kpc, km s⁻¹) to

    construct highly specific galaxies, typically a single system intended for interactive use (GUI). Each component has a 3D Sérsic + exponential vertical light distribution and a simple analytical rotation field.

  • Components are rotated to a viewing geometry and placed into a larger

    spatial grid. Voxels are binned by line-of-sight velocity to produce a 3D spectral cube.

Constructor arguments closely mirror the fields used throughout the implementation (see the __init__ signature). Key internal attributes are lists storing per-cube parameters (all_Re, all_Se, all_pix_spatial_scales, etc.) and the final results are appended to self.results as tuples of (spectral_cube, params).

Implementation details (what the methods do)#

  • milky_way_rot_curve_analytical() computes an analytic circular velocity as a function of radius using a simple power-law approximation scaled by a characteristic velocity v_0.

  • sersic_flux_density_3d() returns a 3D flux field for a Sérsic profile in the disk plane multiplied by an exponential vertical profile.

  • rotated_system() constructs the 3D flux and velocity cubes on a small grid, assigns tangential velocities, and applies geometric rotations (both image rotations and vector rotations) to yield a rotated flux cube and a rotated line-of-sight velocity cube.

  • make_spectral_cube() places rotated components into the final grid, computes velocity bin masks for each spectral channel, projects emission along the line of sight, and returns the assembled spectral cube together with metadata (average velocities, beam info, pixel scale, etc.).

Parameters are provided explicitly (not randomly sampled) to enable controlled, reproducible experiments. The implementation prioritises clarity and inspectability over performance.

all_gal_v_0#
if isinstance(self.resolution, float) and n_cubes==1:

# Directly use the float as r r = np.full(n_cubes, self.resolution)

else:
if self.resolution == ‘all’:

r_min, r_max = 0.25, 4

elif self.resolution == ‘unresolved’:

r_min, r_max = 0.25, 1

elif self.resolution == ‘resolved’:

r_min, r_max = 1, 4

log_r = np.random.uniform(np.log10(r_min), np.log10(r_max), size=n_cubes) r = 10 ** log_r

# Convert resolution ratio to effective radius in pixels # Re = r * (beam minor axis / 2) gives effective radius Re_central = r * (self.beam_info[0]/self.spatial_resolution) /2

generate_cubes()[source]#

Run the full pipeline and generate the requested spectral cubes.

This is the high-level convenience method that iterates over the pre-sampled per-cube parameters (self.all_Re, self.all_Se etc.), constructs each component via rotated_system(), assembles the spectral cube with make_spectral_cube(), applies beam convolution and light smoothing, optionally saves the cube(s) to disk when save=True (the fname argument controls the destination directory), and returns a list of (cube, params) tuples stored in self.results.

Returns:

results – A list with one entry per generated cube. Each entry is a tuple (spectral_cube_array, params_dict) where spectral_cube_array has shape (n_channels, grid_size, grid_size).

Return type:

list

Example

>>> g = GalCubeCraft(n_cubes=1, seed=42, verbose=False)
>>> results = g.generate_cubes()
>>> cube, meta = results[0]
>>> cube.shape
(40, 125, 125)

Notes

  • The method performs several stochastic choices (positions, angles,

    flux scalings). Use seed in the constructor to reproduce results.

  • For large numbers of cubes or larger grids, consider refactoring the

    inner loops to use vectorized operations or offload heavy parts to compiled code for speed.

make_spectral_cube(rotated_disks, rotated_vel_z_cubes, pix_spatial_scale)[source]#

Assemble rotated component cubes into a final spectral cube.

Projects multiple rotated galaxy components into a larger spatial grid, bins voxels by line-of-sight velocity into spectral channels, and returns a spectral cube together with metadata describing the configuration.

Parameters:
  • rotated_disks (list of ndarray) – List of 3D flux cubes (from rotated_system()) for each component. Each array shape must match (init_grid_size,)*3.

  • rotated_vel_z_cubes (list of ndarray) – Corresponding list of 3D LOS velocity fields (km/s) for each component.

  • pix_spatial_scale (float) – Physical scale (kpc/pixel) used for this cube; required for computing relative Hubble-flow offsets when placing multiple galaxies along the LOS.

Returns:

  • spectral_cube_Jy_px (ndarray) – Spectral cube with shape (n_channels, grid_size, grid_size) where the spectral axis corresponds to velocity-binned slices.

  • params_gen (dict) – Metadata with keys: ‘galaxy_centers’, ‘average_vels’, ‘beam_info’, ‘n_gals’, and ‘pix_spatial_scale’.

Notes

  • The method internally defines a symmetric velocity range (default -600 to +600 km/s) and creates self.n_spectral_slices fine bins. The code then averages groups of 5 fine bins to mimic spectral binning (i.e., 5x oversampling).

  • Components are placed at randomized centers near the cube centre and optionally offset along the LOS; small offsets are converted to a Hubble-flow velocity and added to that component’s velocity cube.

Example

>>> cube, meta = g.make_spectral_cube([disk1, disk2], [vel1, vel2], 0.1)
>>> cube.shape
(40, 125, 125)
static milky_way_rot_curve_analytical(R, v_0, R_e, n)[source]#

Analytical rotation-curve approximation.

Computes an analytic circular velocity approximation used to assign tangential velocities to voxels in the toy galaxy model. The form is a shallow power-law scaled by a characteristic velocity v_0 and a scale radius R_0 derived from the Sérsic effective radius.

Parameters:
  • R (float) – Galactocentric radius (kpc). Can be a scalar or NumPy array.

  • v_0 (float) – Characteristic rotation velocity (km/s). Typical values ~200 km/s.

  • R_e (float) – Sérsic effective radius (kpc).

  • n (float) – Sérsic index (dimensionless) used to derive the profile shape.

Returns:

vel – Circular rotation velocity (km/s) evaluated at R.

Return type:

float or ndarray

Notes

  • The function first computes the Sérsic constant b_n using a series expansion and then derives a scale radius R_0 ~ 2 * R_e / b_n^n.

  • The working formula is: v(R) = v_0 * 1.022 * (R / R_0)**0.0803

  • The exponent 0.0803 produces a gently rising/flat curve typical of disk galaxies over the radial range used here.

Edge cases#

  • For R == 0 the returned velocity will be 0 (handled naturally by the power-law when R is 0).

  • Very small R_e or extreme n values may produce R_0 values that are physically unrealistic; validate inputs when using this function.

Example

>>> GalCubeCraft.milky_way_rot_curve_analytical(np.array([0.1,1,10]), 200, 5.0, 1.0)
array([...])  # velocities in km/s

References

See discussion in Lahiry et al. and empirical approximations used for compact rotation-curve modelling.

rotated_system(params_gal_rot)[source]#

Create a rotated 3D galaxy flux cube and LOS velocity cube.

This routine constructs an isolated galaxy on a small cubic grid of size self.init_grid_size and performs the following steps:

  1. Build a 3D flux density using sersic_flux_density_3d().

  2. Compute a tangential velocity magnitude at each (x,y) using milky_way_rot_curve_analytical() and assign vector components in the local tangent direction.

  3. Add a Gaussian random LOS velocity component with standard deviation gal_vz_sigma to mimic dispersion.

  4. Rotate both the scalar flux cube and the velocity vector field to the requested viewing angles (inclination and position angle).

Parameters:

params_gal_rot (dict) – Dictionary with the following keys (units in parentheses): - ‘pix_spatial_scale’ (kpc/pixel) - ‘Re’ (pixels) - ‘hz’ (pixels) - ‘Se’ (flux units) - ‘n’ (dimensionless, Sérsic index) - ‘gal_x_angle’ (degrees, inclination) - ‘gal_y_angle’ (degrees, position angle) - ‘gal_vz_sigma’ (km/s, LOS dispersion) - ‘v_0’ (km/s, characteristic rotation velocity)

Returns:

  • rotated_disk_xy (ndarray) – 3D flux cube after rotations (shape (init_grid_size,)*3).

  • rotated_vel_z_cube_xy (ndarray) – 3D line-of-sight velocity cube (same shape) giving LOS velocity (km/s) at each voxel after rotation and projection.

  • Performance and memory

  • ———————-

  • - The method uses explicit Python loops to assign velocities and to – rotate vectors voxel-by-voxel; this is clear but not optimal for very large grids. self.init_grid_size is chosen to be small to keep runtime reasonable for examples.

Example

>>> params = {'pix_spatial_scale':0.1, 'Re':20, 'hz':2, 'Se':0.1, 'n':1.0,
...           'gal_x_angle':45, 'gal_y_angle':30, 'gal_vz_sigma':40, 'v_0':200}
>>> disk, vel = g.rotated_system(params)
>>> disk.shape, vel.shape
((31,31,31), (31,31,31))

Notes

  • The method currently assumes circular disks (axis ratio q=1) and a simple form for the rotation curve. Replace parts of the pipeline if you need more physical realism.

static sersic_flux_density_3d(x, y, z, Se, Re, n, hz)[source]#

Compute the 3D Sérsic + exponential vertical flux density.

The returned array represents the intrinsic 3D flux distribution of a disk galaxy in physical units (same units as the coordinate grids).

Parameters:
  • x (ndarray) – Coordinate grids (kpc). These should have identical shapes (for example produced by np.meshgrid with indexing='ij').

  • y (ndarray) – Coordinate grids (kpc). These should have identical shapes (for example produced by np.meshgrid with indexing='ij').

  • z (ndarray) – Coordinate grids (kpc). These should have identical shapes (for example produced by np.meshgrid with indexing='ij').

  • Se (float) – Flux density at the effective radius (arbitrary flux units).

  • Re (float) – Effective (half-light) radius in kpc.

  • n (float) – Sérsic index; lower values produce disk-like profiles.

  • hz (float) – Vertical exponential scale height in kpc.

Returns:

S – 3D array with the same shape as the input coordinate grids giving flux density at each voxel.

Return type:

ndarray

Notes

  • The radial Sérsic profile is evaluated using the standard series expansion for the constant b_n.

  • The profile assumes circular symmetry in the disk plane (axis ratio q = 1). To model elliptical disks, scale one of the axes before calling this routine.

  • The vertical structure is a symmetric exponential: exp(-|z|/hz).

Example

>>> nx = ny = nz = 21
>>> x = np.arange(nx) - (nx-1)/2
>>> X,Y,Z = np.meshgrid(x,x,x,indexing='ij')
>>> S = GalCubeCraft.sersic_flux_density_3d(X, Y, Z, Se=0.1, Re=5.0, n=1.0, hz=0.5)
>>> S.shape
(21, 21, 21)

References

Sérsic (1963) and standard approximations for b_n (see Ciotti & Bertin, 1999 for derivations and series expansions).