cometspec.helper

Helper utilities for cometspec.

This module groups small, self-contained utilities used across the package and the jupyter notebook examples: table, spectrum and ephemeris loaders, and some numerical and analytical helpers for seeing-dependent slit-loss estimates.

Notes

  • Packaged data files live under cometspec/data/. The get_*_path helpers resolve those locations relative to the installed package.

  • Wavelengths returned by line-list loaders are in vacuum Angstrom unless documented otherwise.

Routines

cometspec.helper.make_fwhm_lambda_bounds(*, eps_min_arcsec_500, eps_max_arcsec_500, zmin_deg, zmax_deg, lambda0_nm=500.0, alpha=-0.2, k=0.6)[source]

Build wavelength-dependent seeing bounds from the minimum and maximum seeing values (at zenith) and the minimum and maximum zenith angles. See Persson (2022) [1].

Parameters:
  • eps_min_arcsec_500 (float) – Minimum seeing at 500nm and zenith, in arcsec. It can be at any other wavelength, but lambda0_nm and alpha must be set accordingly.

  • eps_max_arcsec_500 (float) – Maximum seeing at 500nm and zenith, in arcsec.

  • zmin_deg (float) – Minimum zenith angle in degrees (zenith angle = 90° - elevation angle).

  • zmax_deg (float) – Maximum zenith angle in degrees (zenith angle = 90° - elevation angle).

  • lambda0_nm (float, optional, default 500.0) – Reference wavelength for the seeing scaling, in nm.

  • alpha (float, optional, default -1 / 5) – Wavelength scaling exponent.

  • k (float, optional, default 0.6) – Airmass scaling exponent.

Returns:

tuple[callable, callable] – A pair (fwhm_min, fwhm_max) of callables that evaluate the minimum and maximum FWHM in arcsec as a function of wavelength given the max and min seeing and zenith angles. Each callable takes x : float or ndarray of float and returns f(x) : float or ndarray of float of the same shape.

Raises:

ValueError – If a zenith angle is greater than or equal to 90 degrees.

References

cometspec.helper.frac_in_circular_aperture_gaussian(fwhm_arcsec, radius_arcsec)[source]

Compute the fraction of a 2D Gaussian inside a circular aperture.

Parameters:
  • fwhm_arcsec (float or numpy.ndarray of float) – Gaussian full width at half maximum in arcsec.

  • radius_arcsec (float) – Aperture radius in arcsec.

Returns:

numpy.ndarray or float – Fraction of the Gaussian flux inside the aperture.

cometspec.helper.frac_in_rectangular_aperture_gaussian(fwhm_arcsec, *, width_arcsec, length_arcsec)[source]

Compute the fraction of a 2D Gaussian inside a rectangular aperture.

Parameters:
  • fwhm_arcsec (float or numpy.ndarray of float) – Gaussian full width at half maximum in arcsec.

  • width_arcsec (float) – Rectangle width in arcsec.

  • length_arcsec (float) – Rectangle length in arcsec.

Returns:

numpy.ndarray or float – Fraction of the Gaussian flux inside the rectangle.

cometspec.helper.throughput_vs_lambda(*, lambda_min_nm, lambda_max_nm, eps_min_arcsec_500, eps_max_arcsec_500, zmin_deg, zmax_deg, aperture, n_points=2000)[source]

Estimate the aperture-enclosed flux fraction and slit loss as a function of wavelength.

Computes, over a wavelength grid, the fraction of a Gaussian PSF that falls within the given aperture for both the best-case (sharpest PSF) and worst-case (broadest PSF) observing conditions, defined by the seeing and zenith-angle ranges.

Note

The _min / _max suffix in the output keys refers to the input seeing/zenith extremes ( _min / _max FWHM), not to the numerical ordering of the output values. Because a sharper PSF concentrates more flux, frac_min (best seeing) is numerically larger than frac_max (worst seeing), and loss_min is numerically smaller than loss_max.

Parameters:
  • lambda_min_nm (float) – Minimum wavelength of the evaluation grid, in nm.

  • lambda_max_nm (float) – Maximum wavelength of the evaluation grid, in nm.

  • eps_min_arcsec_500 (float) – Best (minimum) seeing FWHM at 500 nm and zenith, in arcsec.

  • eps_max_arcsec_500 (float) – Worst (maximum) seeing FWHM at 500 nm and zenith, in arcsec.

  • zmin_deg (float) – Minimum (best) zenith angle during observations, in degrees (zenith angle = 90° - elevation angle).

  • zmax_deg (float) – Maximum (worst) zenith angle during observations, in degrees (zenith angle = 90° - elevation angle).

  • aperture (dict) – Aperture definition. Keys:

    • type ({‘circular’, ‘rectangular’}) – Aperture shape.

    • radius_arcsec (float, optional) – Radius in arcsec. Required if type='circular'.

    • width_arcsec (float, optional) – Width in arcsec. Required if type='rectangular'.

    • length_arcsec (float, optional) – Length in arcsec. Required if type='rectangular'.

  • n_points (int, optional, default 2000) – Number of wavelength points in the evaluation grid. Must be >= 2. Defaults to 2000.

Returns:

dict – Dictionary with the following keys. Each value is an ndarray of length n_points:

  • lambda_nm (numpy.ndarray of float) – Wavelength grid, in nm.

  • fwhm_min_arcsec (numpy.ndarray of float) – PSF FWHM at best seeing (eps_min, zmin) as a function of wavelength, in arcsec. Numerically the smallest FWHM values.

  • fwhm_max_arcsec (numpy.ndarray of float) – PSF FWHM at worst seeing (eps_max, zmax) as a function of wavelength, in arcsec. Numerically the largest FWHM values.

  • frac_min (numpy.ndarray of float) – Enclosed flux fraction at best seeing conditions. Numerically the largest fraction (sharpest PSF → most flux within aperture).

  • frac_max (numpy.ndarray of float) – Enclosed flux fraction at worst seeing conditions. Numerically the smallest fraction (broadest PSF → least flux within aperture).

  • loss_min (numpy.ndarray of float) – Slit loss at best seeing, i.e. 1 - frac_min. Numerically the smallest loss.

  • loss_max (numpy.ndarray of float) – Slit loss at worst seeing, i.e. 1 - frac_max. Numerically the largest loss.

Raises:

ValueError – If n_points is smaller than 2 or aperture['type'] is not 'circular' or 'rectangular'.

cometspec.helper.add_slit_loss_error_scalar(q_err, *, lambda_nm, aperture, eps_min_arcsec_500=0.7, eps_max_arcsec_500=1.2, zmin_deg=45.0, zmax_deg=45.0, n_points=2000)[source]

Add slit-loss uncertainty to the log10 error of a log10 quantity.

Evaluates the aperture-enclosed flux fraction over a narrow wavelength window centred on lambda_nm for both the best- and worst-case seeing/zenith conditions (see throughput_vs_lambda()). From those two flux fractions it derives a symmetric systematic uncertainty in log10 space via a geometric-mean scaling, then adds it in quadrature to the input statistical error.

The systematic term is computed as follows:

\[\begin{split}\begin{aligned} \sigma_\mathrm{sys} &= \tfrac{1}{2}\log_{10} \left( \frac{\bar{f}_\mathrm{max}}{\bar{f}_\mathrm{min}} \right) \\[4pt] \sigma_\mathrm{tot} &= \sqrt{\sigma_\mathrm{stat}^{2} + \sigma_\mathrm{sys}^{2}} \end{aligned}\end{split}\]

Where \(\sigma_\mathrm{stat}\) is the input statistical error q_err, and \(\bar{f}_\mathrm{min}\) and \(\bar{f}_\mathrm{max}\) are the mean flux fractions over the wavelength window for the best and worst seeing/zenith conditions, respectively.

Important

q_err must be the error of a log10 quantity proportional to the aperture-collected flux (e.g., log10(F), log10(N) for a column density inferred from line flux, or any derived quantity Q such that Q F). The slit loss multiplies the true flux by a fraction f (0, 1], so on a log scale it acts as an additive shift Δlog10(Q) = log10(f). The systematic-error derivation in this function assumes exactly that additive structure; feeding it a linear-space value or a quantity not proportional to flux will produce an incorrect uncertainty.

Note

The wavelength window used for the flux-fraction estimate is [lambda_nm - 0.01, lambda_nm + 0.01] nm, sampled with n_points points, and the result flux fraction is averaged over that window.

Parameters:
  • q_err (float) – One-sigma statistical uncertainty in log10 space from a quantity proportional to the aperture-collected flux (e.g., the error of log10(column density)).

  • lambda_nm (float) – Central wavelength at which to evaluate the slit-loss systematic, in nm.

  • aperture (dict) – Aperture definition. Must contain the key 'type' with value 'circular' or 'rectangular'. For circular apertures, also requires 'radius_arcsec' (float). For rectangular apertures, requires 'width_arcsec' and 'length_arcsec' (both float).

  • eps_min_arcsec_500 (float, optional, default 0.7) – Best (minimum) seeing FWHM at 500 nm and zenith, in arcsec. Defaults to 0.7.

  • eps_max_arcsec_500 (float, optional, default 1.2) – Worst (maximum) seeing FWHM at 500 nm and zenith, in arcsec. Defaults to 1.2.

  • zmin_deg (float, optional, default 45.0) – Minimum (best) zenith angle during observations, in degrees. Defaults to 45.0.

  • zmax_deg (float, optional, default 45.0) – Maximum (worst) zenith angle during observations, in degrees. Defaults to 45.0.

  • n_points (int, optional, default 2000) – Number of wavelength points used to sample the narrow window around lambda_nm. Must be >= 2. Defaults to 2000.

Returns:

float – Total one-sigma uncertainty on q_log10 in dex, equal to the quadrature sum of the input statistical error q_err and the symmetric slit-loss systematic sigma_sys:

Raises:

ValueError – If n_points is smaller than 2 or aperture['type'] is not 'circular' or 'rectangular'.

cometspec.helper.open_table(file_path, *, header_row=0, units_row=1, data_start=2, delimiter=',')[source]

Read a CSV table with an optional units row.

Note

The units_row expect strings that can be parsed by astropy.units.Unit. If a unit string cannot be parsed, a warning is issued and the column is left unitless. If the units row contains empty strings or placeholders like "-" or "None", those columns are also left unitless.

Parameters:
  • file_path (os.PathLike or str) – Path to the table file.

  • header_row (int, optional, default 0) – Zero-based row index containing the column names.

  • units_row (int, optional, default 1) – Zero-based row index containing the units row, or None to skip unit parsing.

  • data_start (int, optional, default 2) – Zero-based row index where table data begin.

  • delimiter (str, optional, default ",") – Delimiter used in the CSV file.

Returns:

astropy.table.Table – The loaded table.

Raises:

ValueError – If units_row is provided but is greater than or equal to data_start.

cometspec.helper.find_spectrum_file(dir_path, *, night, fibre, suffix='.csv')[source]

Find the first spectrum file matching a night and fibre.

Parameters:
  • dir_path (os.PathLike or str) – Directory to search.

  • night (str) – Night substring to match in the filename.

  • fibre (str) – Fibre substring to match in the filename. Technically the function checks if both night and fibre are substrings of the filename, so they can be any distinctive part of the filename as long as they uniquely identify the file. If multiple files match, the first one when sorted by name is returned.

  • suffix (str, optional, default ".csv") – Filename suffix to accept. Defaults to ".csv".

Returns:

pathlib.Path or None – The first matching path, or None if no file matches.

cometspec.helper.load_spectrum(dir_path, *, night, fibre, header_row=0, units_row=1, data_start=2)[source]

Load a stacked spectrum for a given night and fibre.

Note

  • The units_row expect strings that can be parsed by astropy.units.Unit. If a unit string cannot be parsed, a warning is issued and the column is left unitless. If the units row contains empty strings or placeholders like “-” or “None”, those columns are also left unitless.

  • Technically the function checks if both night and fibre are substrings of the filename, so they can be any distinctive part of the filename as long as they uniquely identify the file. If multiple files match, the first one when sorted by name is returned.

Parameters:
  • dir_path (os.PathLike or str) – Directory containing the spectrum files.

  • night (str) – Night substring used to locate the file.

  • fibre (str) – Fibre substring used to locate the file.

  • header_row (int, optional, default 0) – Zero-based row index containing the column names.

  • units_row (int, optional, default 1) – Zero-based row index containing the units row.

  • data_start (int, optional, default 2) – Zero-based row index where table data begin.

Returns:

astropy.table.Table – The loaded spectrum table.

Raises:
  • FileNotFoundError – If no matching spectrum file is found.

  • ValueError – If units_row is provided but is greater than or equal to data_start.

cometspec.helper.load_ephemeris_summary(path, *, key_column='date_obs', delimiter=',')[source]

Read a csv file into a nested dictionary where the key of the outer dictionary is determined by key_column and the inner dictionary contains the row values with the column names as key.

Parameters:
  • path (os.PathLike or str) – Path to ephemeris csv file.

  • key_column (str, optional, default "date_obs") – Column used as the dictionary key.

  • delimiter (str, optional, default ",") – Delimiter used in the CSV file. Defaults to ",".

Returns:

dict[str, dict[str, Any]] – A mapping from observation key to row values.

Raises:

FileNotFoundError – If the ephemeris summary file does not exist.

cometspec.helper.get_ephemeris_for_night(ephemeris, night)[source]

Return the ephemeris record for a single night given the night and the ephemeris dictionary (output of load_ephemeris_summary()).

Parameters:
  • ephemeris (dict[str, dict[str, Any]]) – Nested ephemeris mapping returned by load_ephemeris_summary().

  • night (str) – Night key to retrieve. It can be any distinctive substring of the key used in the ephemeris dictionary.

Returns:

dict[str, Any] – The matching record, or an empty dictionary if the night is missing.

cometspec.helper.get_kurucz_irradiance_path()[source]

Return the path to the packaged Kurucz solar irradiance file.

Returns:

pathlib.Path – Path to kurucz_irradiance.txt.

Raises:

FileNotFoundError – If the packaged file is missing.

cometspec.helper.open_kurucz_irradiance()[source]

Load the packaged Kurucz [2] solar irradiance file. See Kurucz.

Returns:

pandas.DataFrame – A DataFrame with columns WAVE in units of \(\AA\) and FLUX in units of \(\mathrm{erg\,s^{-1}\,cm^{-2}\,\AA^{-1}}\).

Raises:

FileNotFoundError – If the packaged Kurucz file cannot be found.

References

cometspec.helper.get_hall_anderson_irradiance_path()[source]

Return the path to the packaged Hall & Anderson UV solar irradiance file. See Hall & Anderson.

Returns:

pathlib.Path – Path to Hall_Anderson.txt.

Raises:

FileNotFoundError – If the packaged file is missing.

cometspec.helper.open_hall_anderson_irradiance(wave_max_AA=2990.0)[source]

Load the packaged Hall & Anderson [3] UV solar irradiance file.

The on-disk file has wavelength in Angstrom and irradiance in \(\mathrm{photons\,s^{-1}\,cm^{-2}\,\AA^{-1}}\). The output is converted to the same units as open_kurucz_irradiance() and truncated at wave_max_AA so the two spectra concatenate without overlap. See Hall & Anderson.

Parameters:

wave_max_AA (float, optional, default 2990.0) – Upper wavelength cutoff in Angstrom (inclusive). Default 2990.0 matches the Kurucz file’s lower bound.

Returns:

pandas.DataFrame – A DataFrame with columns WAVE in units of \(\AA\) and FLUX in units of \(\mathrm{erg\,s^{-1}\,cm^{-2}\,\AA^{-1}}\).

Raises:

FileNotFoundError – If the packaged Hall & Anderson file cannot be found.

References

cometspec.helper.get_meftah_irradiance_path()[source]

Return the path to the packaged Meftah et al. (2023) solar irradiance file. See Meftah et al. (2023).

Returns:

pathlib.Path – Path to Meftah_MPS_ATLAS_Kurucz_LATMOS_Meftah_V1.txt.

Raises:

FileNotFoundError – If the packaged file is missing.

cometspec.helper.open_meftah_irradiance()[source]

Load the packaged Meftah et al. (2023) solar irradiance file.

We used the disk integrated MPS-ATLAS-Kurucz spectrum which has units of \(\mathrm{nm}\) and \(\mathrm{W.m^(-2).nm^(-1)}\), but it is converted to the same units as open_kurucz_irradiance(). See Meftah et al. (2023).

Returns:

pandas.DataFrame – A DataFrame with columns WAVE in units of \(\AA\) and FLUX in units of \(\mathrm{erg\,s^{-1}\,cm^{-2}\,\AA^{-1}}\).

Raises:

FileNotFoundError – If the packaged Meftah et al. (2023) file cannot be found.

References

cometspec.helper.get_bromley_irradiance_path()[source]

Return the path to the packaged Bromley solar irradiance file.

Returns:

pathlib.Path – Path to Bromley.npz.

Raises:

FileNotFoundError – If the packaged file is missing.

cometspec.helper.open_bromley_irradiance()[source]

Load the packaged Bromley solar irradiance file.

Bromley et. al. (2024) [5]. file, stored as a compressed .npz archive with two float32 arrays: wavelength (nm) and flux (\(\mathrm{W\,m^{-2}\,nm^{-1}}\)). The output is converted to the same units as open_kurucz_irradiance().We used the 0-14000_vac.tar.gz file.

Returns:

pandas.DataFrame – A DataFrame with columns WAVE in units of \(\AA\) and FLUX in units of \(\mathrm{erg\,s^{-1}\,cm^{-2}\,\AA^{-1}}\).

Raises:

FileNotFoundError – If the packaged Bromley file cannot be found.

References

cometspec.helper.load_cn_linelist(path_or_text)[source]

Load a Brooke- or Sneden-style CN line list.

Parses CN molecular line lists distributed in the fixed-width “machine-readable table” format used by ApJS, as produced by Brooke et al. (2014) [6] and Sneden et al. (2014) [7]. Three isotopologues are supported, each available from the journal’s online supplementary materials:

  • 12C14N (Brooke et al. 2014) — \(^{12}\mathrm{C}^{14}\mathrm{N}\)

  • 13C14N (Sneden et al. 2014) — \(^{13}\mathrm{C}^{14}\mathrm{N}\)

  • 12C15N (Sneden et al. 2014) — \(^{12}\mathrm{C}^{15}\mathrm{N}\)

Parameters:

path_or_text (str or os.PathLike) – Path to the line-list file, or the file contents as a string.

Returns:

pandas.DataFrame – Parsed line list. Each row corresponds to one rovibronic transition. The columns reproduce the fields of the source machine-readable tables, plus two derived wavelength columns appended at the end:

Electronic state and vibrational quantum numbers

  • eS' (str) – Upper electronic state label (A, B, or X).

  • eS'' (str) – Lower electronic state label (A, B, or X).

  • v' (int) – Upper vibrational quantum number \(v'\).

  • v'' (int) – Lower vibrational quantum number \(v''\).

Rotational quantum numbers and fine-structure / parity labels

  • J' (float) – Upper total angular momentum \(J'\) (excluding nuclear spin).

  • J'' (float) – Lower total angular momentum \(J''\).

  • F' (int) – Upper-state spin/parity component: in \(A^{2}\Pi\), 1 for \(\Omega = 1/2\), 2 for \(\Omega = 3/2\); in \(B^{2}\Sigma^{+}\) and \(X^{2}\Sigma^{+}\), 1 for \(e\), 2 for \(f\) parity.

  • F'' (int) – Lower-state spin/parity component, same encoding as F'.

  • p' (str) – Upper-state parity / e-f label (e or f).

  • p'' (str) – Lower-state parity / e-f label (e or f).

  • N' (float) – Upper \(N\) quantum number as defined in Brooke et al. (2014) [6]. Stored as text in the source file (allowing blank entries) and coerced to numeric; NaN where blank.

  • N'' (int) – Lower \(N\) quantum number.

  • Obs (float) – Observed transition wavenumber, in cm-1. May be NaN for lines without a measured position (predicted-only).

  • Cal (float) – Calculated transition wavenumber from the term-value fit, in cm-1.

  • Res (float) – Residual Obs - Cal, in cm-1.

  • E'' (float) – Lower-state energy \(E''\) relative to v"=0, in cm-1.

  • A (float) – Einstein \(A\) coefficient (spontaneous emission rate), in s-1.

  • f (float) – Absorption oscillator strength (dimensionless).

  • Des (str) – Transition description from Brooke/Sneden line list [6] [7].

  • lambda_vac_A_from_Cal (float) – Vacuum wavelength in Å, computed as \(10^{8}/\mathrm{Cal}\).

  • lambda_vac_A_from_Obs (float) – Vacuum wavelength in Å, computed as \(10^{8}/\mathrm{Obs}\). NaN where Obs is missing.

Raises:

ValueError – If no Brooke/Sneden-style data lines are found in the input.

References

cometspec.helper.PACKAGE_DIR

Package path

cometspec.helper.DATA_DIR

PACKAGE_DIR / “data”