Source code for hwoutils.conversions

"""Unit conversion functions using centralized constants.

Pure JAX implementations — no astropy dependency. Functions are intentionally
NOT JIT-compiled so JAX can fuse them into larger computation graphs.
"""

import jax.numpy as jnp

from hwoutils import constants as const

# ---------------------------------------------------------------------------
# Flux conversions
# ---------------------------------------------------------------------------


[docs] def jy_to_photons_per_nm_per_m2(flux_jy, wavelength_nm): """Convert flux density from Janskys to photons/s/nm/m². Args: flux_jy: Flux density in Janskys. wavelength_nm: Wavelength in nanometers. Returns: Flux density in photons/s/nm/m². """ return flux_jy * const.Jy / (wavelength_nm * const.h)
[docs] def photons_per_nm_per_m2_to_jy(flux_phot, wavelength_nm): """Convert flux density from photons/s/nm/m² to Janskys. Args: flux_phot: Flux density in photons/s/nm/m². wavelength_nm: Wavelength in nanometers. Returns: Flux density in Janskys. """ return flux_phot * (wavelength_nm * const.h) / const.Jy
[docs] def mag_per_arcsec2_to_jy_per_arcsec2(mag_per_arcsec2): """Convert surface brightness from mag/arcsec² to Jy/arcsec² (AB). Args: mag_per_arcsec2: Surface brightness in magnitudes per arcsec². Returns: Surface brightness in Jy/arcsec². """ # Astropy calculates `to(u.Jy)` as `10**( -0.4 * (mag + 48.600000) )` # and `1 Jy = 1e-23 erg/s/cm^2/Hz` # which comes out to 3630.7805477010028 Jy, not the nominal 3631.0 Jy. f0_jy = 3630.7805477010028 return f0_jy * 10 ** (-0.4 * mag_per_arcsec2)
# --------------------------------------------------------------------------- # Length conversions # ---------------------------------------------------------------------------
[docs] def nm_to_um(length_nm): """Convert nanometers to micrometers.""" return length_nm * const.nm2um
[docs] def um_to_nm(length_um): """Convert micrometers to nanometers.""" return length_um * const.um2nm
[docs] def nm_to_wavenumber_cm(wavelength_nm): """Convert wavelength [nm] to wavenumber [cm^-1] (nu = 1 / lambda). Args: wavelength_nm: Wavelength in nanometers. Returns: Wavenumber in inverse centimeters. """ return const.cm2nm / wavelength_nm
[docs] def wavenumber_cm_to_nm(wavenumber_cm): """Convert wavenumber [cm^-1] to wavelength [nm]. Args: wavenumber_cm: Wavenumber in inverse centimeters. Returns: Wavelength in nanometers. """ return const.cm2nm / wavenumber_cm
[docs] def au_to_m(length_au): """Convert AU to meters.""" return length_au * const.AU2m
[docs] def m_to_au(length_m): """Convert meters to AU.""" return length_m * const.m2AU
[docs] def Rearth_to_m(length_Rearth): """Convert Earth radii to meters.""" return length_Rearth * const.Rearth2m
# --------------------------------------------------------------------------- # Velocity conversions # ---------------------------------------------------------------------------
[docs] def au_per_yr_to_m_per_s(velocity_au_per_yr): """Convert AU/yr to m/s.""" return velocity_au_per_yr * const.AU2m / const.yr2s
# --------------------------------------------------------------------------- # Angular conversions # ---------------------------------------------------------------------------
[docs] def arcsec_to_rad(angle_arcsec): """Convert arcseconds to radians.""" return angle_arcsec * const.arcsec2rad
[docs] def rad_to_arcsec(angle_rad): """Convert radians to arcseconds.""" return angle_rad * const.rad2arcsec
[docs] def mas_to_arcsec(angle_mas): """Convert milliarcseconds to arcseconds.""" return angle_mas * const.mas2arcsec
[docs] def arcsec_to_mas(angle_arcsec): """Convert arcseconds to milliarcseconds.""" return angle_arcsec * const.arcsec2mas
[docs] def arcsec_to_lambda_d(angle_arcsec, wavelength_nm, diameter_m): """Convert angular separation to lambda/D units. Args: angle_arcsec: Angular separation in arcseconds. wavelength_nm: Wavelength in nanometers. diameter_m: Telescope diameter in meters. Returns: Angular separation in lambda/D. """ angle_rad = angle_arcsec * const.arcsec2rad wavelength_m = wavelength_nm * const.nm2m lambda_d_rad = wavelength_m / diameter_m return angle_rad / lambda_d_rad
[docs] def lambda_d_to_arcsec(angle_lambda_d, wavelength_nm, diameter_m): """Convert lambda/D units to angular separation in arcseconds. Args: angle_lambda_d: Angular separation in lambda/D. wavelength_nm: Wavelength in nanometers. diameter_m: Telescope diameter in meters. Returns: Angular separation in arcseconds. """ wavelength_m = wavelength_nm * const.nm2m lambda_d_rad = wavelength_m / diameter_m angle_rad = angle_lambda_d * lambda_d_rad return angle_rad * const.rad2arcsec
# --------------------------------------------------------------------------- # Mass conversions # ---------------------------------------------------------------------------
[docs] def Msun_to_kg(mass_solar): """Convert solar masses to kilograms.""" return mass_solar * const.Msun2kg
[docs] def Mearth_to_kg(mass_earth): """Convert Earth masses to kilograms.""" return mass_earth * const.Mearth2kg
# --------------------------------------------------------------------------- # Distance conversions # ---------------------------------------------------------------------------
[docs] def au_to_arcsec(distance_au, distance_pc): """Convert physical distance in AU to angular separation. Args: distance_au: Physical distance in AU. distance_pc: Distance to system in parsecs. Returns: Angular separation in arcseconds. """ return distance_au / distance_pc
[docs] def arcsec_to_au(angle_arcsec, distance_pc): """Convert angular separation to physical distance. Args: angle_arcsec: Angular separation in arcseconds. distance_pc: Distance to system in parsecs. Returns: Physical distance in AU. """ return angle_arcsec * distance_pc
# --------------------------------------------------------------------------- # Time conversions # ---------------------------------------------------------------------------
[docs] def years_to_days(time_years): """Convert years to days.""" return time_years * 365.25
[docs] def days_to_years(time_days): """Convert days to years.""" return time_days / 365.25
[docs] def is_leap_year(year): """Determine if a year is a leap year. Args: year: The year to check. Returns: True if the year is a leap year. """ return (year % 4 == 0) & ((year % 100 != 0) | (year % 400 == 0))
[docs] def days_in_year(year): """Return the number of days in a year (365 or 366). Args: year: The year to check. Returns: Number of days. """ return 365 + is_leap_year(year)
[docs] def gregorian_to_jd(year, month, day): """Convert a Gregorian date to a Julian day. Args: year: The year. month: The month. day: The day. Returns: The Julian day. """ a = jnp.floor((14 - month) / 12) y = year + 4800 - a m = month + 12 * a - 3 jdn = ( day + jnp.floor((153 * m + 2) / 5) + 365 * y + jnp.floor(y / 4) - jnp.floor(y / 100) + jnp.floor(y / 400) - 32045 ) return jdn - 0.5
[docs] def jd_to_decimal_year(jd): """Convert a Julian day to a decimal year. Args: jd: The Julian day. Returns: The decimal year. """ year_approx = 1970.0 + (jd - 2440587.5) / 365.2425 year = jnp.floor(year_approx) jd_start = gregorian_to_jd(year, 1, 1) jd_end = gregorian_to_jd(year + 1, 1, 1) year = jnp.where(jd < jd_start, year - 1, year) jd_start = gregorian_to_jd(year, 1, 1) jd_end = gregorian_to_jd(year + 1, 1, 1) return year + (jd - jd_start) / (jd_end - jd_start)
[docs] def decimal_year_to_jd(decimal_year): """Convert a decimal year to a Julian day. Args: decimal_year: The decimal year. Returns: The Julian day. """ year = jnp.floor(decimal_year) year_fraction = decimal_year - year jd_start = gregorian_to_jd(year, 1, 1) jd_end = gregorian_to_jd(year + 1, 1, 1) return jd_start + year_fraction * (jd_end - jd_start)
# --------------------------------------------------------------------------- # Albedo conventions # --------------------------------------------------------------------------- # Lambertian-sphere ratio of geometric to spherical (Bond) albedo. # Seager 2010, eq 3.36: a uniformly Lambertian sphere has A_g = (2/3) A_S. SPHERICAL_TO_GEOMETRIC_ALBEDO_LAMBERTIAN: float = 2.0 / 3.0
[docs] def spherical_to_geometric_albedo(A_S): """Convert spherical albedo to geometric albedo (Lambertian sphere). Args: A_S: Spherical (Bond) albedo, dimensionless. Returns: Geometric albedo A_g, dimensionless. Notes: Exact for a uniformly Lambertian-reflecting sphere (Seager 2010, eq 3.36): A_g = (2/3) * A_S. For atmospheres with anisotropic scattering (Mie clouds, etc.) the actual A_g / A_S ratio depends on the scattering phase function; this is the leading-order approximation that the ExoJaxPhysicalModel uses to convert its 2-stream plane-parallel reflectivity to a disk-integrated geometric albedo. """ return SPHERICAL_TO_GEOMETRIC_ALBEDO_LAMBERTIAN * A_S
[docs] def geometric_to_spherical_albedo(A_g): """Convert geometric albedo to spherical albedo (Lambertian sphere). Inverse of :func:`spherical_to_geometric_albedo`: A_S = (3/2) * A_g. """ return A_g / SPHERICAL_TO_GEOMETRIC_ALBEDO_LAMBERTIAN