Performs a weighted average of the harmonics found in a set of frequencies.

    Parameters
    ----------
    f_n: numpy.ndarray[float]
        The frequencies of a number of sine waves
    f_n_err: numpy.ndarray[float]
        Formal errors in the frequencies
    p_orb: float
        The orbital period
    f_tol: float
        Tolerance in the frequency for accepting harmonics

    Returns
    -------
    wavg_p_orb: float
        Weighted average orbital period
    werr_p_orb: float
        Error in the weighted average orbital period
    std_p_orb: float
        Standard deviation of the weighted average orbital period

    Notes
    -----
    The harmonics are determined using an initial period estimate that has to be
    reasonably close to the actual period.
    The orbital frequency is measured by taking a weighted average of the harmonics.
    The propagated error in the orbital frequency is calculated, and the measured
    standard deviation is also returned (as a separate handle on the error).
    All frequencies are converted to periods.


Walk up or down a slope to approach zero or to reach an extremum.

    Parameters
    ----------
    signal: numpy.ndarray[float]
        The curve to walk along
    peaks: numpy.ndarray[float]
        The starting points
    direction: numpy.ndarray[float]
        Direction to walk in, either 1 for right or -1 for left
    mode: str
        mode='up': walk until a local maximum is reached
        mode='down': walk until a local minimum is reached
        mode='up_abs': walk away from zero
        mode='down_abs': walk towards zero
        mode='up_to_zero'/'down_to_zero': same as above, but approaching zero
            as closely as possible without changing direction.
        mode='zero': walk until the sign changes

    Returns
    -------
    cur_i: numpy.ndarray[float]
        End positions of all the walkers

    Notes
    -----
    Assumes a circular curve, so that it can walk from one end
    back onto the other end.


Walk up or down a slope to approach zero or to reach an extremum.

    Parameters
    ----------
    signal: numpy.ndarray[float]
        The curve to walk along
    peaks: numpy.ndarray[float]
        The starting points
    slope_sign: numpy.ndarray[float]
        Sign of the slope of the curve at the peak locations
    mode: str
        mode='up': walk in the slope sign direction to reach a
            minimum (minus is left)
        mode='down': walk against the slope sign direction to reach
            a maximum (minus is right)
        mode='up_to_zero'/'down_to_zero': same as above, but approaching zero
            as closely as possible without changing direction.
        mode='zero': continue until the sign changes

    Returns
    -------
    cur_i: numpy.ndarray[float]
        End positions of all the walkers

    Notes
    -----
    Assumes a circular curve, so that it can walk from one end
    back onto the other end.


Walk up or down a slope to approach zero for angles

    Parameters
    ----------
    func: function
        Function that generates the curve to walk along.
    x0: numpy.ndarray[float]
        The starting positions
    walk_sign: numpy.ndarray[float]
        Sign of the direction to walk in (minus is left)
    args: tuple
        Arguments for func

    Returns
    -------
    x_interp: numpy.ndarray[float]
        Root positions for each x0

    Notes
    -----
    Assumes a circular curve with a period of 2 pi, so that it
    can walk from one end back onto the other end.


Measure the depths of the eclipses from the harmonic model given
    the timing measurements

    Parameters
    ----------
    f_h: numpy.ndarray[float]
        Frequencies of the orbital harmonics, in per day
    a_h: numpy.ndarray[float]
        Corresponding amplitudes of the sinusoids
    ph_h: numpy.ndarray[float]
        Corresponding phases of the sinusoids
    t_1: float
        Time of primary minimum with respect to the mean time
    t_2: float
        Time of secondary minimum with respect to t_1
    t_1_1: float
        Time of primary first contact
    t_1_2: float
        Time of primary last contact
    t_2_1: float
        Time of secondary first contact
    t_2_2: float
        Time of secondary last contact
    t_b_1_1: float
        Time of primary first internal tangency
    t_b_1_2: float
        Time of primary last internal tangency
    t_b_2_1: float
        Time of secondary first internal tangency
    t_b_2_2: float
        Time of secondary last internal tangency

    Returns
    -------
    depth_1: float
        Depth of primary minimum
    depth_2: float
        Depth of secondary minimum


Find and refine the prominent eclipse signatures

    Parameters
    ----------
    deriv_1: numpy.ndarray[float]
        Derivative of the sinusoids at t_model
    deriv_2: numpy.ndarray[float]
        Second derivative of the sinusoids at t_model
    noise_level: float
        The noise level (standard deviation of the residuals)

    Returns
    -------
    peaks_1: numpy.ndarray[int]
        Set of indices indicating extrema in deriv_1
    slope_sign: numpy.ndarray[int]
        Sign of deriv_1 at peaks_1 (the sign of the slope)
    zeros_1: numpy.ndarray[int]
        Set of indices indicating zero points in deriv_1
    peaks_2_n: numpy.ndarray[int]
        Set of indices indicating minima in deriv_2
    minimum_1: numpy.ndarray[int]
        Set of indices indicating local minima in deriv_1
    zeros_1_in: numpy.ndarray[int]
        Set of indices indicating inner zero points in deriv_1
    peaks_2_p: numpy.ndarray[int]
        Set of indices indicating maxima in deriv_2
    minimum_1_in: numpy.ndarray[int]
        Set of indices indicating inner local minima in deriv_1

    Notes
    -----
    Intended for use in detect_eclipses, with a fine grid of time points
    spanning two times the orbital period.


Refine existing prominent eclipse signatures

    Parameters
    ----------
    model_h: numpy.ndarray[float]
        Model of harmonic sinusoids at t_model
    deriv_1: numpy.ndarray[float]
        Derivative of the sinusoids at t_model
    deriv_2: numpy.ndarray[float]
        Second derivative of the sinusoids at t_model
    peaks_1: numpy.ndarray[int]
        Set of indices indicating extrema in deriv_1
    minimum_1: numpy.ndarray[int]
        Set of indices indicating local minima in deriv_1
    zeros_1_in: numpy.ndarray[int]
        Set of indices indicating inner zero points in deriv_1

    Returns
    -------
    peaks_1: numpy.ndarray[int]
        Set of indices indicating extrema in deriv_1
    slope_sign: numpy.ndarray[int]
        Sign of deriv_1 at peaks_1 (the sign of the slope)
    zeros_1: numpy.ndarray[int]
        Set of indices indicating zero points in deriv_1
    peaks_2_n: numpy.ndarray[int]
        Set of indices indicating minima in deriv_2
    minimum_1: numpy.ndarray[int]
        Set of indices indicating local minima in deriv_1
    zeros_1_in: numpy.ndarray[int]
        Set of indices indicating inner zero points in deriv_1
    peaks_2_p: numpy.ndarray[int]
        Set of indices indicating maxima in deriv_2
    minimum_1_in: numpy.ndarray[int]
        Set of indices indicating inner local minima in deriv_1

    Notes
    -----
    Intended for use in detect_eclipses, with a fine grid of time points
    spanning two times the orbital period.

    This version is more robust against high frequency jitter, although
    the initial positions zeros_1_in do need to not shift by too much in
    the larger number of harmonics used here.


Make a list of indices indicating eclipses out of lists of peaks

    Parameters
    ----------
    p_orb: float
        Orbital period of the eclipsing binary in days
    t_model: numpy.ndarray[float]
        Set of time points for a model of sinusoids
    model_h: numpy.ndarray[float]
        Model of harmonic sinusoids at t_model
    t_gaps: numpy.ndarray[float]
        Gap timestamps in pairs
    peaks_1: numpy.ndarray[int]
        Set of indices indicating extrema in deriv_1
    slope_sign: numpy.ndarray[int]
        Sign of deriv_1 at peaks_1 (the sign of the slope)
    zeros_1: numpy.ndarray[int]
        Set of indices indicating zero points in deriv_1
    peaks_2_n: numpy.ndarray[int]
        Set of indices indicating minima in deriv_2
    minimum_1: numpy.ndarray[int]
        Set of indices indicating local minima in deriv_1
    zeros_1_in: numpy.ndarray[int]
        Set of indices indicating inner zero points in deriv_1
    peaks_2_p: numpy.ndarray[int]
        Set of indices indicating maxima in deriv_2
    minimum_1_in: numpy.ndarray[int]
        Set of indices indicating inner local minima in deriv_1

    Returns
    -------
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.

    Notes
    -----
    Intended for use in detect_eclipses,
    in conjunction with mark_eclipse_peaks.

    ecl_indices:
    ecl = [zeros_1, minimum_1, peaks_2_n, peaks_1, peaks_2_p,
           minimum_1_in, zeros_1_in, minimum_1_in_mid, zeros_1_in, minimum_1_in,
           peaks_2_p, peaks_1, peaks_2_n, minimum_1, zeros_1]


Backtrack to low harmonic positions of existing prominent eclipse signatures

    Parameters
    ----------
    deriv_1: numpy.ndarray[float]
        Derivative of the sinusoids at t_model
        This one includes only low harmonics.
    deriv_2: numpy.ndarray[float]
        Second derivative of the sinusoids at t_model
        This one includes only low harmonics.
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.

    Returns
    -------
    ecl_indices_lh: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.

    Notes
    -----
    Intended for use in detect_eclipses, with a fine grid of time points
    spanning two times the orbital period.


Measure the times, durations and depths of the eclipses

    Parameters
    ----------
    t_model: numpy.ndarray[float]
        Set of time points for a model of sinusoids
    model_h: numpy.ndarray[float]
        Model of harmonic sinusoids at t_model
    deriv_2: numpy.ndarray[float]
        Second derivative of the sinusoids at t_model
        This one includes only low harmonics.
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.
    noise_level: float
        The noise level (standard deviation of the residuals)

    Returns
    -------
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.
    ecl_min: numpy.ndarray[float]
        Times of the eclipse minima
    ecl_mid: numpy.ndarray[float]
        Times of the eclipse midpoints
    widths: numpy.ndarray[float]
        Durations of the eclipses
    depths: numpy.ndarray[float]
        Depths of the eclipses
    ecl_mid_b: numpy.ndarray[float]
        Times of the flat bottom midpoints
    widths_b: numpy.ndarray[float]
        Durations of the flat bottoms
    t_i_1_err: numpy.ndarray[float]
        Timing error estimates for the eclipse edges
    t_i_2_err: numpy.ndarray[float]
        Timing error estimates for the eclipse edges
    t_b_i_1_err: numpy.ndarray[float]
        Timing error estimates for the eclipse bottoms
    t_b_i_2_err: numpy.ndarray[float]
        Timing error estimates for the eclipse bottoms

    Notes
    -----
    Intended for use in detect_eclipses, in conjunction
    with mark_eclipse_peaks.
    Will remove eclipses below noise_level/4


Check duplication of primary and secondary for wrong period

    Parameters
    ----------
    n_fold: int
        Divisor for the period
    p_orb: float
        Orbital period of the eclipsing binary in days
    ecl_min: numpy.ndarray[float]
        Times of the eclipse minima
    widths: numpy.ndarray[float]
        Durations of the eclipses
    depths: numpy.ndarray[float]
        Depths of the eclipses

    Returns
    -------
    div_p: int
        Divisor for the period after check
    n_group: numpy.ndarray[int]
        Number of matches per eclipse group


Check for (very) wide overlapping eclipses and some more

    Parameters
    ----------
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.
    model_h: numpy.ndarray[float]
        Model of harmonic sinusoids at t_model
    model_lh: numpy.ndarray[float]
        Model of low harmonic sinusoids at t_model

    Returns
    -------
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.


Checks whether eclipses decrease too much in depth
    between harmonic models with different number of harmonics

    Parameters
    ----------
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.
    model_lh: numpy.ndarray[float]
        Harmonic sinusoid model of the light curve
        This one includes only low harmonics.
    model_h: numpy.ndarray[float]
        Harmonic sinusoid model of the light curve.

    Returns
    -------
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.


Checks whether eclipses decrease or increase too much in depth
    between harmonic models with different number of harmonics

    Parameters
    ----------
    ecl_indices_lh: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.
        This one corresponds to only low harmonics.
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.
    model_lh: numpy.ndarray[float]
        Harmonic sinusoid model of the light curve
        This one includes only low harmonics.
    model_h: numpy.ndarray[float]
        Harmonic sinusoid model of the light curve.
    cur_model_h: numpy.ndarray[float]
        Harmonic sinusoid model of the light curve,
        restricted to the current iteration of number
        of harmonics.

    Returns
    -------
    ecl_indices: numpy.ndarray[int]
        Two dimensional array of eclipse indices.
        Each eclipse has indices corresponding to
        several prominent points.


Select the best combination of primary and secondary

    Parameters
    ----------
    p_orb: float
        Orbital period of the eclipsing binary in days
    ecl_mid: numpy.ndarray[float]
        Times of the eclipse midpoints
    widths: numpy.ndarray[float]
        Durations of the eclipses
    depths: numpy.ndarray[float]
        Depths of the eclipses

    Returns
    -------
    best_comb: numpy.ndarray[int]
        Best combination of eclipses based on
        the combined depth.


Determine the eclipse midpoints, depths and widths from the derivatives
    of the harmonic model.

    Parameters
    ----------
    p_orb: float
        Orbital period of the eclipsing binary in days
    f_n: numpy.ndarray[float]
        The frequencies of a number of sine waves
    a_n: numpy.ndarray[float]
        The amplitudes of a number of sine waves
    ph_n: numpy.ndarray[float]
        The phases of a number of sine waves
    noise_level: float
        The noise level (standard deviation of the residuals)
    t_gaps: numpy.ndarray[float]
        Gap timestamps in pairs

    Returns
    -------
    ecl_indices: numpy.ndarray[int]
        Indices of several important points in the harmonic model
        as generated here (see function for details)
    n_fold: int
        If there are too many eclipses found with
        depths and widths within 1% at the right phases,
        this suggests dividing the period by this number.

    Notes
    -----
    The result is ordered according to depth so that the deepest eclipse is first.

    The code in this function utilises a similar idea to find the eclipses
    as ECLIPSR (except somewhat simpler due to analytic functions instead
    of raw data). See IJspeert 2021.


Translate the eclipse indices to timings and depths

    Parameters
    ----------
    ecl_indices: numpy.ndarray[int], None
        Indices of several important points in the harmonic model
    p_orb: float
        Orbital period of the eclipsing binary in days
    f_n: numpy.ndarray[float]
        The frequencies of a number of sine waves
    a_n: numpy.ndarray[float]
        The amplitudes of a number of sine waves
    ph_n: numpy.ndarray[float]
        The phases of a number of sine waves

    Returns
    -------
    t_1: float, None
        Time of primary minimum with respect to the mean time
    t_2: float, None
        Time of secondary minimum with respect to the mean time
    t_contacts: tuple[float], None
        Measurements of the times of contact:
        t_1_1, t_1_2, t_2_1, t_2_2
    t_int_tan: tuple[float], None
        Measurements of the times near internal tangency:
        t_b_1_1, t_b_1_2, t_b_2_1, t_b_2_2
    depths: numpy.ndarray[float], None
        Measurements of the eclipse depths in units of a_n
    t_i_1_err: numpy.ndarray[float], None
        Measurement error estimates of the first contacts
    t_i_2_err: numpy.ndarray[float], None
        Measurement error estimates of the last contacts
    t_b_i_1_err: numpy.ndarray[float]
        Measurement error estimates of the first internal tangencies
    t_b_i_2_err: numpy.ndarray[float]
        Measurement error estimates of the last internal tangencies


Calculates the linear regression errors on period and t_zero

    Parameters
    ---------
    p_orb: float
        Orbital period of the eclipsing binary in days
    t_tot: float
        Total time base of observations
    sigma_t: float
        Error in the individual time measurements

    Returns
    -------
    p_err: float
        Error in the period
    t_err: float
        Error in t_zero
    p_t_cov: float
        Covariance between the period and t_zero

    Notes
    -----
    The number of eclipses, computed from the period and
    time base, is taken to be a contiguous set.
    var_matrix:
    [[std[0]**2          , std[0]*std[1]*corr],
     [std[0]*std[1]*corr,           std[1]**2]]


Eccentric anomaly in terms of true anomaly and eccentricity

    Parameters
    ----------
    nu: float, numpy.ndarray[float]
        True anomaly
    e: float, numpy.ndarray[float]
        Eccentricity of the orbit

    Returns
    -------
    : float, numpy.ndarray[float]
        Eccentric anomaly


Derivative of the projected normalised distance between the centres of the stars

    Parameters
    ----------
    theta: float, numpy.ndarray[float]
        Phase angle of the eclipse minimum
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit

    Returns
    -------
    minimize: float, numpy.ndarray[float]
        Numeric result of the function that should equal 0

    Notes
    -----
    For circular orbits, delta has minima at 0 and 180 degrees, but this will deviate for
    eccentric *and* inclined orbits due to conjunction no longer lining up with the minimum
    projected separation between the stars.

    Minimize this function w.r.t. theta near zero to get the phase angle of minimum separation
    at primary eclipse (eclipse maximum), or near pi to get it for the secondary eclipse.


Second derivative of the projected normalised distance between the centres of the stars

    Parameters
    ----------
    theta: float, numpy.ndarray[float]
        Phase angle of the eclipse minimum
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit

    Returns
    -------
    deriv: float, numpy.ndarray[float]
        Derivative value of the delta_deriv function


Determine the phase angles of minima for given e, w, i

    Parameters
    ----------
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit

    Returns
    -------
    theta_1: float
        Phase angle of primary minimum
    theta_2: float
        Phase angle of secondary minimum
    theta_3: float
        Phase angle of maximum separation between 1 and 2
    theta_4: float
        Phase angle of maximum separation between 2 and 1


Determine the phase angles of minima for given e, w, i

    Parameters
    ----------
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit

    Returns
    -------
    theta_1: float
        Phase angle of primary minimum
    theta_2: float
        Phase angle of secondary minimum
    theta_3: float
        Phase angle of maximum separation between 1 and 2
    theta_4: float
        Phase angle of maximum separation between 2 and 1

    Notes
    -----
    Other implementation for minima_phase_angles that can be JIT-ted.
    On its own it is 10x slower, but as part of other functions it can be faster
    if it means that other function can then also be JIT-ted


Find the root of this function to obtain the phase angle between first/last contact
    and eclipse minimum for the primary/secondary eclipse

    Parameters
    ----------
    phi: float, numpy.ndarray[float]
        Angle between first contact and eclipse minimum
    ecosw: float
        Eccentricity times cosine omega
    esinw: float
        Eccentricity times sine omega
    cosi: float
        Cosine of the inclination of the orbit
    phi_0: float
        Auxiliary angle (see Kopal 1959)
    ecl: int
        Primary or secondary eclipse (1 or 2)
    contact: int
        First or last contact (1 or 2)

    Returns
    -------
     eqn: float, numpy.ndarray[float]
        Numeric result of the function that should equal 0


Find the root of this function to obtain the phase angle between first/last contact
    and eclipse minimum for the primary/secondary eclipse

    Parameters
    ----------
    phi: float, numpy.ndarray[float]
        Angle between first contact and eclipse minimum
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    phi_0: float
        Auxiliary angle (see Kopal 1959)
    ecl: int
        Primary or secondary eclipse (1 or 2)
    contact: int
        First or last contact (1 or 2)

    Returns
    -------
     eqn: float, numpy.ndarray[float]
        Numeric result of the function that should equal 0


Find the root of this function to obtain the phase angle between first/last contact
    and eclipse minimum for the primary/secondary eclipse using the radii

    Parameters
    ----------
    phi: float, numpy.ndarray[float]
        Angle between first contact and eclipse minimum
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum_sma: float
        Sum of radii in units of the semi-major axis
        (can also be r_dif_sma for internal tangency)
    ecl: int
        Primary or secondary eclipse (1 or 2)
    contact: int
        First or last contact (1 or 2)

    Returns
    -------
     eqn: float, numpy.ndarray[float]
        Numeric result of the function that should equal 0


Determine the contact angles for given e, w, i, phi_0

    Parameters
    ----------
    ecosw: float
        Eccentricity times cosine omega
    esinw: float
        Eccentricity times sine omega
    cosi: float
        Cosine of the inclination of the orbit
    phi_0: float
        Auxiliary angle (see Kopal 1959)

    Returns
    -------
    phi_1_1: float
        First contact angle of primary eclipse
    phi_1_2: float
        Last contact angle of primary eclipse
    phi_2_1: float
        First contact angle of secondary eclipse
    phi_2_2: float
        Last contact angle of secondary eclipse


Determine the contact angles for given e, w, i, phi_0

    Parameters
    ----------
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    phi_0: float
        Auxiliary angle (see Kopal 1959)

    Returns
    -------
    phi_1_1: float
        First contact angle of primary eclipse
    phi_1_2: float
        Last contact angle of primary eclipse
    phi_2_1: float
        First contact angle of secondary eclipse
    phi_2_2: float
        Last contact angle of secondary eclipse


Determine the contact angles for given e, w, i, r_sum_sma

    Parameters
    ----------
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum_sma: float
        Sum of radii in units of the semi-major axis
        (can also be r_dif_sma for internal tangency)

    Returns
    -------
    phi_1_1: float
        First contact angle of primary eclipse
    phi_1_2: float
        Last contact angle of primary eclipse
    phi_2_1: float
        First contact angle of secondary eclipse
    phi_2_2: float
        Last contact angle of secondary eclipse


Calculate the eccentricity and argument of periastron from the
    analytic formulae for small eccentricities (among other approximations).

    Parameters
    ----------
    p_orb: float
        Orbital period of the eclipsing binary in days
    t_1: float, numpy.ndarray[float]
        Time of primary minimum in domain [0, p_orb)
        and t_1 < t_2
    t_2: float, numpy.ndarray[float]
        Time of secondary minimum in domain [0, p_orb)
    tau_1_1: float, numpy.ndarray[float]
        Duration of primary first contact to minimum
    tau_1_2: float, numpy.ndarray[float]
        Duration of primary minimum to last contact
    tau_2_1: float, numpy.ndarray[float]
        Duration of secondary first contact to minimum
    tau_2_2: float, numpy.ndarray[float]
        Duration of secondary minimum to last contact
    cosi: float
        Cosine of the inclination of the orbit
    phi_0: float
        Auxiliary angle (see Kopal 1959)

    Returns
    -------
    e: float, numpy.ndarray[float]
        Eccentricity for each set of input parameters
    w: float, numpy.ndarray[float]
        Argument of periastron for each set of input parameters
    ecosw: float
        Eccentricity times cosine of omega
    esinw: float
        Eccentricity times sine of omega

    Notes
    -----
    Only for small e


Find the root to solve for psi

    Parameters
    ----------
    psi: float
        Auxilary parameter to solve for
    displacement: float
        Phase difference between primary and secondary
        eclipse: 2pi/P(t1 - t2)

    Returns
    -------
    eqn: float
        Equation to find the root of

    Notes
    -----
    Iterative form of Newton's second law as in Kopal (1959, eq. 9-9)


Formula for the sum of radii in units of the semi-major axis
     from the angle phi_0

    Parameters
    ----------
    e: float, numpy.ndarray[float]
        Eccentricity
    i: float, numpy.ndarray[float]
        Inclination of the orbit
    phi_0: float, numpy.ndarray[float]
        Auxiliary angle, see Kopal 1959

    Returns
    -------
    r_sum_sma: float, numpy.ndarray[float]
        Sum of radii in units of the semi-major axis

    Notes
    -----
    Becomes 0 for e = 1, and then negative when e > 1.


Formula for the angle phi_0 from the sum of radii in units
    of the semi-major axis

    Parameters
    ----------
    e: float, numpy.ndarray[float]
        Eccentricity
    i: float, numpy.ndarray[float]
        Inclination of the orbit
    r_sum_sma: float, numpy.ndarray[float]
        Sum of radii in units of the semi-major axis

    Returns
    -------
    phi_0: float, numpy.ndarray[float]
        Auxiliary angle, see Kopal 1959

    Raises
    ------
    ZeroDivisionError
        If either e == 1 or i == 0 and all inputs are float.
        If inputs involve arrays, gives RuntimeWarning instead.


Formula for the sum of radii in units of the semi-major axis
     from the angle phi_0

    Parameters
    ----------
    e: float, numpy.ndarray[float]
        Eccentricity
    w: float, numpy.ndarray[float]
        Argument of periastron
    i: float, numpy.ndarray[float]
        Inclination of the orbit
    phi_0: float, numpy.ndarray[float]
        Auxiliary angle, see Kopal 1959
    rho_0: float, numpy.ndarray[float]
        Auxiliary scaled distance

    Returns
    -------
    r_ratio: float, numpy.ndarray[float]
        Radius ratio r_2/r_1

    Notes
    -----
    Using rho_0 can be advantageous in case of partial eclipses, however,
    some information is given up: we don't know which star is which size.
    (if we assume main sequence, star 1 - the primary - is the bigger one)


Formula for the angle phi_0 from the sum of radii in units
    of the semi-major axis

    Parameters
    ----------
    e: float, numpy.ndarray[float]
        Eccentricity
    w: float, numpy.ndarray[float]
        Argument of periastron
    i: float, numpy.ndarray[float]
        Inclination of the orbit
    r_sum_sma: float, numpy.ndarray[float]
        Sum of radii in units of the semi-major axis
    r_ratio: float, numpy.ndarray[float]
        Radius ratio r_2/r_1

    Returns
    -------
    rho_0: float, numpy.ndarray[float]
        Auxiliary scaled distance


Projected separation between the centres of the two components
    at a given phase theta

    Parameters
    ----------
    e: float, numpy.ndarray[float]
        Eccentricity
    w: float, numpy.ndarray[float]
        Argument of periastron
    i: float, numpy.ndarray[float]
        Inclination of the orbit
    theta: float, numpy.ndarray[float]
        Phase angle (0 or pi at conjunction)

    Returns
    -------
    sep: float, numpy.ndarray[float]
        The projected separation in units of the
        semi-major axis.

    Notes
    -----
    delta^2 = a^2 (1-e^2)^2(1 - sin^2(i)cos^2(theta))/(1 - e sin(theta - w))^2
    sep = delta/a


Area covered for two overlapping circles separated by a certain distance

    Parameters
    ----------
    d: numpy.ndarray[float]
        Separation between the centres of the two circles
    r_1: float
        Radius of circle 1
    r_2: float
        Radius of circle 2

    Returns
    -------
    area: float
        Area covered by one circle overlapping the other

    Notes
    -----
    For d between |r_1 - r_2| and r_1 + r_2:
    area = r_1^2 * arccos((d^2 + r_1^2 - r2^2)/(2 d r_1))
           + r_2^2 * arccos((d^2 + r_2^2 - r_1^2)/(2 d r_2))
           - r_1 r_2 sqrt(1 - ((r_1^2 + r_2^2 - d^2)/(2 r_1 r_2))^2)


Surface brightness ratio from the ratio of eclipse depths

    Parameters
    ----------
    d_ratio: float
        Measured depth ratio d_2 / d_1
        (d_2 < d_1 by definition)
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum_sma: float
        Sum of radii in units of the semi-major axis
    r_ratio: float
        Radius ratio r_2/r_1
    theta_1: float
        Phase angle of primary minimum
    theta_2: float
        Phase angle of secondary minimum

    Returns
    -------
    sb_ratio: float
        Surface brightness ratio sb_2/sb_1

    Notes
    -----
    In terms of the eclipse depths (d_i) and the areas
    covered in either eclipse (area_i):
    sb_ratio = (d_2 area_1)/(d_1 area_2)


Theoretical total normalised flux in the assumption of uniform brightness

    Parameters
    ----------
    theta: numpy.ndarray[float]
        Phase angle (0 or pi degrees at conjunction)
        Around 0, the light of the primary is blocked,
        around pi, the light of the secondary is blocked.
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum_sma: float
        Sum of radii in units of the semi-major axis
    r_ratio: float
        Radius ratio r_2/r_1
    sb_ratio: float
        Surface brightness ratio sb_2/sb_1
    theta_3: float
        Phase angle of maximum separation between 1 and 2
    theta_4: float
        Phase angle of maximum separation between 2 and 1

    Returns
    -------
    light_lost: float
        Fractional loss of light at the given phase angle

    Notes
    -----
    light_lost(1) = covered_area / (pi r_1^2 + pi r_2^2 sb_ratio)
    light_lost(2) = covered_area sb_ratio / (pi r_1^2 + pi r_2^2 sb_ratio)


Theoretical eclipse timings

    Parameters
    ----------
    p_orb: float
        Orbital period of the eclipsing binary in days
    t_zero: float
        Time of the deepest minimum with respect to the mean time
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum_sma: float
        Sum of radii in units of the semi-major axis
    r_ratio: float
        Radius ratio r_2/r_1

    Returns
    -------
    t_1: float
        Time of primary minimum in domain [0, p_orb)
    t_2: float
        Time of secondary minimum in domain [0, p_orb)
    t_1_1: float
        Duration of primary first contact to minimum
    t_1_2: float
        Duration of primary minimum to last contact
    t_2_1: float
        Duration of secondary first contact to minimum
    t_2_2: float
        Duration of secondary minimum to last contact
    t_b_1_1: float
        Time of primary first internal tangency to minimum
    t_b_1_2: float
        Time of primary minimum to second internal tangency
    t_b_2_1: float
        Time of secondary first internal tangency to minimum
    t_b_2_2: float
        Time of secondary minimum to second internal tangency


Theoretical eclipse depths at minimum

    Parameters
    ----------
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum_sma: float
        Sum of radii in units of the semi-major axis
    r_ratio: float
        Radius ratio r_2/r_1
    sb_ratio: float
        Surface brightness ratio sb_2/sb_1

    Returns
    -------
    depth_1: float
        Depth of primary minimum
    depth_2: float
        Depth of secondary minimum

    Notes
    -----
    Not to be confused with eclipse_depth


Minimise this function to obtain an estimate of cosi,
    log ratio of radii and log ratio of surface brightness

    Parameters
    ----------
    phi_0: float
        Auxiliary angle, see Kopal 1959
    p_orb: float
        Orbital period of the eclipsing binary in days
    ecosw: float
        Eccentricity times cosine of omega
    esinw: float
        Eccentricity times sine of omega
    cosi: float
        Cosine of the inclination of the orbit
    timings_tau: numpy.ndarray[float]
        Eclipse timings of minima, durations from/to first/last contact,
        and durations from/to first/last internal tangency:
        t_1, t_2, tau_1_1, tau_1_2, tau_2_1, tau_2_2,
        tau_b_1_1, tau_b_1_2, tau_b_2_1, tau_b_2_2
    timings_err: numpy.ndarray[float]
        Error estimates for the eclipse timings,
        t_1_err, t_2_err, t_1_1_err, t_1_2_err, t_2_1_err, t_2_2_err
        t_b_1_1_err, t_b_1_2_err, t_b_2_1_err, t_b_2_2_err

    Returns
    -------
    likelihood: float
        Minus the likelihood with weighted sum of squared deviations of
        five outcomes of integrals of Kepler's second law for different
        time spans of the eclipses compared to the measured values.


Minimise this function to obtain an estimate of cosi,
    log ratio of radii and log ratio of surface brightness

    Parameters
    ----------
    params: array-like[float]
        cosi, log_rr, log_sb
    p_orb: float
        Orbital period of the eclipsing binary in days
    ecosw: float
        Eccentricity times cosine of omega
    esinw: float
        Eccentricity times sine of omega
    phi_0: float
        Auxiliary angle, see Kopal 1959
    timings_tau: numpy.ndarray[float]
        Eclipse timings of minima, durations from/to first/last contact,
        and durations from/to first/last internal tangency:
        t_1, t_2, tau_1_1, tau_1_2, tau_2_1, tau_2_2,
        tau_b_1_1, tau_b_1_2, tau_b_2_1, tau_b_2_2
    depths: numpy.ndarray[float]
        Eclipse depth of the primary and secondary, depth_1, depth_2
    timings_err: numpy.ndarray[float]
        Error estimates for the eclipse timings,
        t_1_err, t_2_err, t_1_1_err, t_1_2_err, t_2_1_err, t_2_2_err
        t_b_1_1_err, t_b_1_2_err, t_b_2_1_err, t_b_2_2_err
    depths_err: numpy.ndarray[float]
        Error estimates for the depths

    Returns
    -------
    likelihood: float
        Minus the likelihood with weighted sum of squared deviations of
        five outcomes of integrals of Kepler's second law for different
        time spans of the eclipses compared to the measured values.


Minimise this function to obtain an estimate of all eclipse parameters

    Parameters
    ----------
    params: array-like[float]
        ecosw, esinw, cosi, phi_0, log_rr, log_sb
    p_orb: float
        Orbital period of the eclipsing binary in days
    timings_tau: numpy.ndarray[float]
        Eclipse timings of minima, durations from/to first/last contact,
        and durations from/to first/last internal tangency:
        t_1, t_2, tau_1_1, tau_1_2, tau_2_1, tau_2_2,
        tau_b_1_1, tau_b_1_2, tau_b_2_1, tau_b_2_2
    depths: numpy.ndarray[float]
        Eclipse depth of the primary and secondary, depth_1, depth_2
    timings_err: numpy.ndarray[float]
        Error estimates for the eclipse timings,
        t_1_err, t_2_err, t_1_1_err, t_1_2_err, t_2_1_err, t_2_2_err
        t_b_1_1_err, t_b_1_2_err, t_b_2_1_err, t_b_2_2_err
    depths_err: numpy.ndarray[float]
        Error estimates for the depths

    Returns
    -------
    likelihood: float
        Minus the likelihood with weighted sum of squared deviations of
        five outcomes of integrals of Kepler's second law for different
        time spans of the eclipses compared to the measured values.


Determine all eclipse parameters using approximate formulae
    and partially with an iterative procedure of the exact formulae

    Parameters
    ----------
    p_orb: float
        The orbital period
    timings_tau: numpy.ndarray[float]
        Eclipse timings of minima, durations from/to first/last contact,
        and durations from/to first/last internal tangency:
        t_1, t_2, tau_1_1, tau_1_2, tau_2_1, tau_2_2,
        tau_b_1_1, tau_b_1_2, tau_b_2_1, tau_b_2_2
    depths: numpy.ndarray[float]
        Eclipse depth of the primary and secondary, depth_1, depth_2
    timings_err: numpy.ndarray[float]
        Error estimates for the eclipse timings,
        t_1_err, t_2_err, t_1_1_err, t_1_2_err, t_2_1_err, t_2_2_err
        t_b_1_1_err, t_b_1_2_err, t_b_2_1_err, t_b_2_2_err
    depths_err: numpy.ndarray[float]
        Error estimates for the depths
    verbose: bool
        If set to True, this function will print some information

    Returns
    -------
    ecosw: float
        Eccentricity times cosine of omega
    esinw: float
        Eccentricity times sine of omega
    cosi: float
        Cosine of the inclination of the orbit
    phi_0: float
        Auxiliary angle, see Kopal 1959
    log_rr: float
        Logarithm of the radius ratio r_2/r_1
    log_sb: float
        Logarithm of the surface brightness ratio sb_2/sb_1
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum: float
        Sum of radii in units of the semi-major axis
    r_rat: float
        Radius ratio r_2/r_1
    sb_rat: float
        Surface brightness ratio sb_2/sb_1

    Notes
    -----
    ecosw, esinw, phi_0 are determined with approximate formulae,
    cosi, log_rr, log_sb are determined with an iterative procedure
    of the exact formulae.

    phi_0: Auxiliary angle (see Kopal 1959)
    psi_0: Auxiliary angle like phi_0 but for the eclipse bottoms


Determine all eclipse parameters using approximate formulae
    and partially with an iterative procedure of the exact formulae

    Parameters
    ----------
    p_orb: float
        The orbital period
    timings_tau: numpy.ndarray[float]
        Eclipse timings of minima, durations from/to first/last contact,
        and durations from/to first/last internal tangency:
        t_1, t_2, tau_1_1, tau_1_2, tau_2_1, tau_2_2,
        tau_b_1_1, tau_b_1_2, tau_b_2_1, tau_b_2_2
    depths: numpy.ndarray[float]
        Eclipse depth of the primary and secondary, depth_1, depth_2
    timings_err: numpy.ndarray[float]
        Error estimates for the eclipse timings,
        t_1_err, t_2_err, t_1_1_err, t_1_2_err, t_2_1_err, t_2_2_err
        t_b_1_1_err, t_b_1_2_err, t_b_2_1_err, t_b_2_2_err
    depths_err: numpy.ndarray[float]
        Error estimates for the depths
    verbose: bool
        If set to True, this function will print some information

    Returns
    -------
    ecosw: float
        Eccentricity times cosine of omega
    esinw: float
        Eccentricity times sine of omega
    cosi: float
        Cosine of the inclination of the orbit
    phi_0: float
        Auxiliary angle, see Kopal 1959
    log_rr: float
        Logarithm of the radius ratio r_2/r_1
    log_sb: float
        Logarithm of the surface brightness ratio sb_2/sb_1
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum: float
        Sum of radii in units of the semi-major axis
    r_rat: float
        Radius ratio r_2/r_1
    sb_rat: float
        Surface brightness ratio sb_2/sb_1

    Notes
    -----
    ecosw, esinw, phi_0 are determined with approximate,
    iterative but very accurate formulae,
    cosi, log_rr, log_sb are determined with an iterative procedure
    of the exact formulae.

    phi_0: Auxiliary angle (see Kopal 1959)


Determine all eclipse parameters using an iterative procedure
    with exact formulae and a global minimiser

    Parameters
    ----------
    p_orb: float
        The orbital period
    timings_tau: numpy.ndarray[float]
        Eclipse timings of minima, durations from/to first/last contact,
        and durations from/to first/last internal tangency:
        t_1, t_2, tau_1_1, tau_1_2, tau_2_1, tau_2_2,
        tau_b_1_1, tau_b_1_2, tau_b_2_1, tau_b_2_2
    depths: numpy.ndarray[float]
        Eclipse depth of the primary and secondary, depth_1, depth_2
    timings_err: numpy.ndarray[float]
        Error estimates for the eclipse timings,
        t_1_err, t_2_err, t_1_1_err, t_1_2_err, t_2_1_err, t_2_2_err
        t_b_1_1_err, t_b_1_2_err, t_b_2_1_err, t_b_2_2_err
    depths_err: numpy.ndarray[float]
        Error estimates for the depths
    verbose: bool
        If set to True, this function will print some information

    Returns
    -------
    ecosw: float
        Eccentricity times cosine of omega
    esinw: float
        Eccentricity times sine of omega
    cosi: float
        Cosine of the inclination of the orbit
    phi_0: float
        Auxiliary angle, see Kopal 1959
    log_rr: float
        Logarithm of the radius ratio r_2/r_1
    log_sb: float
        Logarithm of the surface brightness ratio sb_2/sb_1
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    r_sum: float
        Sum of radii in units of the semi-major axis
    r_rat: float
        Radius ratio r_2/r_1
    sb_rat: float
        Surface brightness ratio sb_2/sb_1


Calculates the uncorrelated (formal) uncertainties for the extracted
    parameters (e, w, phi_0, r_sum_sma).

    Parameters
    ----------
    e: float
        Eccentricity of the orbit
    w: float
        Argument of periastron
    i: float
        Inclination of the orbit
    p_orb: float
        Orbital period of the eclipsing binary in days
    t_1: float
        Time of primary minimum in domain [0, p_orb)
        and t_1 < t_2
    t_2: float
        Time of secondary minimum in domain [0, p_orb)
    tau_1_1: float
        Duration of primary first contact to minimum
    tau_1_2: float
        Duration of primary minimum to last contact
    tau_2_1: float
        Duration of secondary first contact to minimum
    tau_2_2: float
        Duration of secondary minimum to last contact
    p_err: float
        Error in the orbital period (used as error in the times of minima)
    i_err: float
        Error in the orbital inclination
    t_1_err: float
        Error in t_1 (the time of primary minimum)
    t_2_err: float
        Error in t_1 (the time of secondary minimum)
    t_1_1_err: float
        Error in tau_1_1 (or in the time of primary first contact)
    t_1_2_err: float
        Error in tau_1_2 (or in the time of primary last contact)
    t_2_1_err: float
        Error in tau_2_1 (or in the time of secondary first contact)
    t_2_2_err: float
        Error in tau_2_2 (or in the time of secondary last contact)

    Returns
    -------
    sigma_e: float
        Formal error in eccentricity
    sigma_w: float
        Formal error in argument of periastron
    sigma_phi_0: float
        Formal error in auxiliary angle
    sigma_r_sum_sma: float
        Formal error in sum of radii in units of the semi-major axis
    sigma_ecosw: float
        Formal error in e*cos(w)
    sigma_esinw: float
        Formal error in e*sin(w)


Estimate errors using importance sampling and
    the highest density interval (HDI)

    Parameters
    ----------
    ecosw: float
        Eccentricity times cosine of omega
    esinw: float
        Eccentricity times sine of omega
    cosi: float
        Cosine of the inclination of the orbit
    phi_0: float
        Auxiliary angle, see Kopal 1959
    log_rr: float
        Logarithm of the radius ratio r_2/r_1
    log_sb: float
        Logarithm of the surface brightness ratio sb_2/sb_1
    p_orb: float
        Orbital period of the eclipsing binary in days
    timings: numpy.ndarray[float]
        Eclipse timings of minima and first and last contact points,
        Timings of the possible flat bottom (internal tangency),
        t_1, t_2, t_1_1, t_1_2, t_2_1, t_2_2
        t_b_1_1, t_b_1_2, t_b_2_1, t_b_2_2
    depths: numpy.ndarray[float]
        Primary and secondary eclipse depth
    p_err: float
        Error in the orbital period
    timings_err: numpy.ndarray[float]
        Error estimates for the eclipse timings,
        t_1_err, t_2_err, t_1_1_err, t_1_2_err, t_2_1_err, t_2_2_err,
        t_b_1_1_err, t_b_1_2_err, t_b_2_1_err, t_b_2_2_err
    depths_err: numpy.ndarray[float]
        Error estimates for the depths
    p_t_corr: float
        Correlation between orbital period and t_zero/t_1/t_2
    verbose: bool
        If set to True, this function will print some information

    Returns
    -------
    errors: tuple[numpy.ndarray[float]]
        The (non-symmetric) errors for the same parameters as intervals.
        These are computed from the intervals.
    dists_in: tuple[numpy.ndarray[float]]
        Full input distributions for: p, t_1, t_2, t_1_1, t_1_2, t_2_1, t_2_2,
        t_b_1_1, t_b_1_2, t_b_2_1, t_b_2_2, d_1, d_2
    dists_out: tuple[numpy.ndarray[float]]
        Full output distributions for the same parameters as intervals

    Notes
    -----
    The HDI is the minimum width Bayesian credible interval (BCI).
    https://arviz-devs.github.io/arviz/api/generated/arviz.hdi.html
    The interval for w can consist of two disjunct intervals due to the
    degeneracy between angles around 90 degrees and 270 degrees.


 This function creates a masked xarray dataset containing modis data for one day
         Inputs are:
         ds: Raw Modis netcdf file
         geo: The modis geo file (values already rotated)
         date: The day of the modis file
         mask: The regionmask mask (watersheds)
         variable_name: The name of the variable we're extracting from the modis file. Ex.: 'R_median_filter_fsca_GFD'


Convert 3-hourly watershed data to daily values.


Read the newly generated CARRA data for a specific watershed.


Read the comparison data for a specific watershed.


Create a detailed daily ET comparison for June-July 2013.


Create comparison plots for all variable groups.


Create cumulative precipitation comparison plot.


Create detailed temperature comparison plots.


Create plots for variables that have comparison data.


Create plots for standard variable groups.


Create enhanced wind direction comparison plots.


Create radiation comparison plots.


Calculate wind direction statistics considering wind speed.


Calculate comparison statistics for all variables.


Create water balance analysis plots.


Handle extreme values in radiation and sublimation columns by replacing them with previous valid values.


Combine monthly CSV files for each watershed into continuous time series.


Load CARRA daily timeseries for a watershed.


Load LAMAH-ICE daily timeseries for a watershed.


Merge CARRA data into LAMAH dataframe with appropriate column renaming.


    Ensure pyproj/GDAL can locate PROJ data in this conda env.
    Safe to call multiple times.


Generate list of dates from start_date to end_date (format: YYYYMM).


Download a file and return True if successful.


    Ensure a weights file exists for this grid+watershed combo and return its path.
    We cache the expensive step (building weights) to a NetCDF file.


Reconstruct a SpatialAverager cheaply from on-disk weights in the worker process.


    Use the first available CARRA file to:
    - build lon/lat coordinates
    - read/simplify watersheds
    - compute and cache averager weights (once)
    Returns: (weights_path, gdf, id_to_index, index_to_id)


    Try to decode times from either WRF-like byte strings in ds.Times
    or a 'Time' coordinate; fall back safely.


    Process one month of CARRA data.
    args: (date, carra_dir, weights_path, gdf, id_to_index, index_to_id, output_dir)


times for each crust profile


Returns a copy of the entire step class.


!
        \brief Type-saver function to prevent the user from having to call copy.deepcopy


We ignore SIGINT.

    It's up to our parent to kill us in the typical condition of this arising from ``^C`` on a
    terminal. If someone is manually killing us with that signal, well... nothing will happen.


Parse input arguments


    Function for loading of CAT annotations, FITS file, and creation of numpy array
    with all star cluster candidates. Size of output 'slices' is (N x 1 x sz xsz),
    were N is the number of valid star cluster candidates of the current galaxy.

    input:
      - cat:       CAT complete filename
      - target:    target galaxy (e.g.: eso486-g021, ngc4449, etc.)
      - sz:        window size for visualization (window size: sz x sz)

    output:
      - slices:    numpy array of size (N x 1 x sz x sz) with star cluster candidates
      - coords:    numpy array of size (N,) with star cluster candidate coordinates

    [GPS - 01/22/2019]


    Function for loading dataset from .dat dictionary.

    input:
      - file_name: file name and directory of current galaxy dataset file

    output:
      - dset['data']: numpy array with image data of size (N x 5 x sz x sz)
      - dset['label']: numpy array with labels of size (N,)
      - dset['coordinates']: numpy array with coordinates of each object of size (N, 2)

    where N is the number of candidates of current galaxy and sz is the slice size.

    [GPS - 01/22/2019]


    This is the lambda handler function that acts as a proxy
    to the main function handle_event

    :param event: Event data passed from the lambda trigger
    :type event: dict
    :param context: Lambda context
    :type context: dict
    :return: Returns a 200 (Successful) / 500 (Error) HTTP response
    :rtype: dict


    Handles the event passed to the lambda function to initialize the FileProcessor.

    :param event: Event data passed from the Lambda.
    :type event: dict
    :param context: Lambda context.
    :type context: object
    :return: Returns a 200 (Successful) / 500 (Error) HTTP response.
    :rtype: dict


        This method serves as the main entry point for the FileProcessor class.
        It will then determine which instrument library to use to process the file.

        :return: None
        :rtype: None


        Calibrates the file using the appropriate instrument library.
        This involves dynamic import of the calibration module and
        processing of the file.

        :param instrument: The name of the instrument used for calibration.
        :type instrument: str
        :param file_path: The path to the file that needs to be calibrated.
        :type file_path: Path
        :param dry_run: Indicates whether the operation is a dry run.
        :type dry_run: bool
        :return: The filename of the calibrated file.
        :rtype: string


        Tracks processed science product in the CDF Tracker file database.
        It involves initializing the database engine, setting up database tables,
        and tracking both the original and processed files.

        :param science_filename_parser: The parser function to process file names.
        :type science_filename_parser: function
        :param file_path: The path of the original file.
        :type file_path: Path


        Builds a status dictionary for MetaTracker tracking.

        :param start_time: Timestamp when processing began (from `time.time()`).
        :type start_time: float
        :param success: Whether processing succeeded.
        :type success: bool
        :param message: Message to include with the status.
        :type message: str
        :param origin_file_ids: Optional IDs of the original file if this is a processed result.
        :type origin_file_ids: list
        :return: Dictionary representing processing status.
        :rtype: dict


    Set the url for the ALMA TAP service.

    Parameters
    ----------
    tap_service : str, optional
         (Default value = 'ESO')
         The TAP service to use. Options are:
         'ESO' for Europe (https://almascience.eso.org/tap),
         'NRAO' for North America (https://almascience.nrao.edu/tap), or
         'NAOJ' for East Asia (https://almascience.nrao.edu/tap)


    Get TAP metadata in order to set units properly.

    Parameters
    ----------
    tap_service : str, optional
         (Default value = 'ESO')
         The TAP service to use. Options are:
         'ESO' for Europe (https://almascience.eso.org/tap),
         'NRAO' for North America (https://almascience.nrao.edu/tap), or
         'NAOJ' for East Asia (https://almascience.nao.ac.jp/tap)


    Query the ALMA archive for a given position and radius around it.

    Parameters
    ----------
    ra : float
         Right ascension in degrees (ICRS).
    dec : float
         Declination in degrees (ICRS).
    search_radius : float, optional
         (Default value = 1. arcmin)
         Search radius (in arcmin) around the source coordinates.
    tap_service : str, optional
         (Default value = 'ESO')
         The TAP service to use. Options are:
         'ESO' for Europe (https://almascience.eso.org/tap),
         'NRAO' for North America (https://almascience.nrao.edu/tap), or
         'NAOJ' for East Asia (https://almascience.nao.ac.jp/tap)
    point : bool, optional
         (Default value = True)
         Search whether the specified position (ra, dec) is contained within any ALMA observations (point=True)
         or query all ALMA observations that overlap with a cone centred at the specified position (ra, dec) and
         extending the search_radius (point=False). In the case of point=True, the search_radius parameter is ignored.
    public : bool, optional
         (Default value = True)
         Search for public data (public=True), proprietary data (public=False),
         or both public and proprietary data (public=None).
    published : bool, optional
         (Default value = None)
         Search for published data only (published=True), unpublished data only (published=False),
         or both published and unpublished data (published=None).
    print_query : bool, optional
         (Default value = True)
         Print the ADQL TAP query to the terminal.
    print_targets : bool, optional
         (Default value = False)
         Print a list of targets with ALMA data (ALMA source names) to the terminal.

    Returns
    -------
    pandas.DataFrame containing the query results


    Run the TAP query through PyVO service.

    Parameters
    ----------
    query_str : str
         ADQL query to send to the PyVO TAP service
    tap_service : str, optional
         (Default value = 'ESO')
         The TAP service to use. Options are:
         'ESO' for Europe (https://almascience.eso.org/tap),
         'NRAO' for North America (https://almascience.nrao.edu/tap), or
         'NAOJ' for East Asia (https://almascience.nao.ac.jp/tap)
    Returns
    -------
    pandas.DataFrame containing the query results


    Query targets by name.

    This is done by using the astropy SESAME resolver to get the target's coordinates and then the ALMA archive
    is queried for those coordinates and a search_radius around them. The SESAME resolver searches multiple databases
    (Simbad, NED, VizieR) to parse names commonly found throughout literature and returns their coordinates. If the
    target is not resolved in any of these databases, consider using the 'keysearch' function and query the archive
    using the 'target_name' keyword (e.g. keysearch({'target_name': sources})).

    Parameters
    ----------
    sources : str or list of str
         list of sources by name.
         (IMPORTANT: source names must be identified by at least one of Simbad, NED, or Vizier)
    search_radius : float, optional
         (Default value = 1. arcmin)
         Search radius (in arcmin) around the source coordinates.
    tap_service : str, optional
         (Default value = 'ESO')
         The TAP service to use. Options are:
         'ESO' for Europe (https://almascience.eso.org/tap),
         'NRAO' for North America (https://almascience.nrao.edu/tap), or
         'NAOJ' for East Asia (https://almascience.nao.ac.jp/tap)
    point : bool, optional
         (Default value = True)
         Search whether the specified position (ra, dec) is contained within any ALMA observations (point=True)
         or query all ALMA observations that overlap with a cone centred at the specified position (ra, dec) and
         extending the search_radius (point=False). In the case of point=True, the search_radius parameter is ignored.
    public : bool, optional
         (Default value = True)
         Search for public data (public=True), proprietary data (public=False),
         or both public and proprietary data (public=None).
    published : bool, optional
         (Default value = None)
         Search for published data only (published=True), unpublished data only (published=False),
         or both published and unpublished data (published=None).
    print_query : bool, optional
         (Default value = True)
         Print the ADQL TAP query to the terminal.
    print_targets : bool, optional
         (Default value = False)
         Print a list of targets with ALMA data (ALMA source names) to the terminal.

    Returns
    -------
    pandas.DataFrame containing the query results.

    See Also
    --------
    keysearch : Query the ALMA archive for any (string-type) keywords defined in ALMA TAP system.


    Query the ALMA archive for a list of coordinates or a catalog of sources based on their coordinates.

    Parameters
    ----------
    target_df : pandas.DataFrame
         Source names and coordinates.

         Index:
            RangeIndex
         Columns:
            Name: Name, dtype: str, description: target name (can be numbers or dummy names)
            Name: RAJ2000, dtype: float64, description: right ascension in degrees (ICRS)
            Name: DEJ2000, dtype: float64, description: declination in degrees (ICRS)
    search_radius : float, optional
         (Default value = 1. arcmin)
         Search radius (in arcmin) around the source coordinates.
    tap_service : str, optional
         (Default value = 'ESO')
         The TAP service to use. Options are:
         'ESO' for Europe (https://almascience.eso.org/tap),
         'NRAO' for North America (https://almascience.nrao.edu/tap), or
         'NAOJ' for East Asia (https://almascience.nao.ac.jp/tap)
    point : bool, optional
         (Default value = True)
         Search whether the specified position (ra, dec) is contained within any ALMA observations (point=True)
         or query all ALMA observations that overlap with a cone centred at the specified position (ra, dec) and
         extending the search_radius (point=False). In the case of point=True, the search_radius parameter is ignored.
    public : bool, optional
         (Default value = True)
         Search for public data (public=True), proprietary data (public=False),
         or both public and proprietary data (public=None).
    published : bool, optional
         (Default value = None)
         Search for published data only (published=True), unpublished data only (published=False),
         or both published and unpublished data (published=None).
    print_query : bool, optional
         (Default value = True)
         Print the ADQL TAP query to the terminal.
    print_targets : bool, optional
         (Default value = False)
         Print a list of targets with ALMA data (ALMA source names) to the terminal.

    Returns
    -------
    pandas.DataFrame containing the query results.


    Query the ALMA archive for any (string-type) keywords defined in ALMA TAP system.

    Parameters
    ----------
    search_dict : dict[str, list of str]
         Dictionary of keywords in the ALMA archive and their values. Values must be formatted as a list.
         A list of valid keywords are stored in VALID_KEYWORDS_STR variable.
    tap_service : str, optional
         (Default value = 'ESO')
         The TAP service to use. Options are:
         'ESO' for Europe (https://almascience.eso.org/tap),
         'NRAO' for North America (https://almascience.nrao.edu/tap), or
         'NAOJ' for East Asia (https://almascience.nao.ac.jp/tap)
    public : bool, optional
         (Default value = True)
         Search for public data (public=True), proprietary data (public=False),
         or both public and proprietary data (public=None).
    published : bool, optional
         (Default value = None)
         Search for published data only (published=True), unpublished data only (published=False),
         or both published and unpublished data (published=None).
    print_query : bool, optional
         (Default value = True)
         Print the ADQL TAP query to the terminal.
    print_targets : bool, optional
         (Default value = False)
         Print a list of targets with ALMA data (ALMA source names) to the terminal.

    Returns
    -------
    pandas.DataFrame containing the query results.

    Notes
    -----
    The power of this function is in combining keywords. When multiple keywords are provided, they are
    queried using 'AND' logic, but when multiple values are provided for a given keyword, they are queried using
    'OR' logic. If a given value contains spaces, its constituents are queried using 'AND' logic. Words encapsulated
     in quotation marks (either ' or ") are queried as phrases. Values for the 'target_name' keyword
     are queried with 'OR' logic.

    Examples
    --------
    keysearch({"proposal_abstract": ["high-mass star formation outflow disk"]})
         will query the archive for projects with the words
         "high-mass" AND "star" AND "formation" AND "outflow" AND "disk" in their proposal abstracts.

    keysearch({"proposal_abstract": ["high-mass", "star", "formation", "outflow", "disk"]})
         will query the archive for projects with the words
         "high-mass" OR "star" OR "formation" OR "outflow" OR "disk" in their proposal abstracts.

    keysearch({"proposal_abstract": ["'high-mass star formation' outflow disk"]})
         will query the archive for projects with the phrase
         "high-mass star formation" AND the words "outflow" AND "disk" in their proposal abstracts.

    keysearch({"proposal_abstract": ["'star formation'"], "scientific_category":['Galaxies']})
         will query the archive for projects with the phrase
         "star formation" in their proposal abstracts AND
         projects that are within the scientific_category of 'Galaxies'.


Given the minimum and maximum frequency, parse the 'frequency_support' and extract the frequency resolution.


Set the plot parameters like titles, labels, and scaling.


Convert the size of the dota to be downloaded in human-readable format.


    Add a few new useful columns to the pandas.DataFrame with the query results from the PyVO TAP service and
    return the full query DataFrame and optionally a summary of the results.

    Parameters
    ----------
    TAP_df : pandas.DataFrame
         This is likely the output of 'run_query' function.
    print_targets : bool, optional
         (Default value = True)
         Print a list of targets with ALMA data (ALMA source names) to the terminal.

    Returns
    -------
    pandas.DataFrame containing the query results.


    Print a summary of the observations.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    print_targets : bool, optional
         (Default value = True)
         Print a list of targets with ALMA data (ALMA source names) to the terminal.


    Control how much of the pandas.DataFrame with the query results is presented in the displayed table.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    allcols : bool, optional
         (Default value = False)
         Show all 81 columns (allcols=True), or the first 18 columns (allcols=False).
    allrows : bool, optional
         (Default value = False)
         Show all rows in the DataFrame (allrows=True), or just a summary (allrows=False).

    Returns
    -------
    pandas.DataFrame containing the query results displayed to the user interface as specified by the user.


    Determine how many observations were observed at a given frequency (+redshift).

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    line_freq : float64
         Frequency of the line of interest in GHz.
    z : float64, optional
         (Default value = 0.)
         Redshift by which the frequency given in 'line_freq' parameter should be shifted.
    line_name : str, optional
         (Default value = '')
         Name of the line specified in 'line_freq'.
    print_summary : bool, optional
         (Default value = True)
         Print a summary of the observations to the terminal.
    print_targets : bool, optional
         (Default value = True)
         Print a list of targets with ALMA data (ALMA source names) to the terminal.

    Returns
    -------
    pandas.DataFrame containing all observations of line of interest.


    Determine how many CO, 13CO, and C18O lines were observed in the provided query DataFrame.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    z : float64, optional
         (Default value = 0.)
         Redshift by which the frequencies should be shifted.
    print_summary : bool, optional
         (Default value = True)
         Print a summary of the observations for each (redshifted) CO, 13CO, and C18O line to the terminal.
    print_targets : bool, optional
         (Default value = True)
         Print the target names (ALMA source names) with ALMA data for each (redshifted) CO, 13CO, and C18O line
         to the terminal.

    Returns
    -------
    pandas.DataFrame containing all observations of (redshifted) CO, 13CO, and C18O lines.


Draw a horizontal/vertical dashed line at a given (redshifted) frequency on the requested axis.


    Draw a horizontal/vertical dashed line at (redshifted) frequencies of CO, 13CO, and C18O lines on the requested
    axis.


    Obtain a subset of the provided pandas.DataFrame that contains observations in the range of minimum frequency
    (min_freq) to maximum frequency (max_freq) in GHz. This is used to define the frequency range of various ALMA bands.


    Create overview plots of observed frequencies, angular resolution, LAS, frequency and velocity resolutions.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    mark_freq : list of float64, optional
         (Default value = '')
         A list of frequencies to mark on the plot with dashed lines.
    z : float64, optional
         (Default value = 0.)
         Redshift by which the frequencies given in 'mark_freq' and 'mark_CO' parameters should be shifted.
         Currently only one redshift can be given for all targets.
    mark_CO : bool, optional
         (Default value = False)
         Mark CO, 13CO, and C18O frequencies on the plot with dashed lines.
    showfig : bool, optional
         (Default value = True)
         Display the plot (showfig=True) or not (showfig=False).
    savefig : str, optional
         (Default value = None)
         Filename (without an extension) for the plot to be saved as.
         Default file extension is PDF. Figure is saved in a subdirectory called 'reports' within the current
         working directory. If the directory doesn't exist, it will be created. Default quality is dpi=300.


    Create overview and detailed plots of observed frequencies in each band.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    mark_freq : list of float64, optional
         (Default value = '')
         A list of frequencies to mark on the plot with dashed lines.
    z : float64, optional
         (Default value = 0.)
         Redshift by which the frequencies given in 'mark_freq' and 'mark_CO' parameters should be shifted.
         Currently only one redshift can be given for all targets.
    mark_CO : bool, optional
         (Default value = False)
         Mark CO, 13CO, and C18O frequencies on the plot with dashed lines.
    showfig : bool, optional
         (Default value = True)
         Display the plot (showfig=True) or not (showfig=False).
    savefig : str, optional
         (Default value = None)
         Filename (without an extension) for the plot to be saved as.
         Default file extension is PDF. Figure is saved in a subdirectory called 'reports' within the current
         working directory. If the directory doesn't exist, it will be created. Default quality is dpi=300.


    Create detailed plots of observations in each band. The x-axis displays the observation number 'Obs' column in
    the input DataFrame.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    mark_freq : list of float64, optional
         (Default value = '')
         A list of frequencies to mark on the plot with dashed lines.
    z : float64, optional
         (Default value = 0.)
         Redshift by which the frequencies given in 'mark_freq' and 'mark_CO' parameters should be shifted.
         Currently only one redshift can be given for all targets.
    mark_CO : bool, optional
         (Default value = False)
         Mark CO, 13CO, and C18O frequencies on the plot with dashed lines.
    showfig : bool, optional
         (Default value = True)
         Display the plot (showfig=True) or not (showfig=False).
    savefig : str, optional
         (Default value = None)
         Filename (without an extension) for the plot to be saved as.
         Default file extension is PDF. Figure is saved in a subdirectory called 'reports' within the current
         working directory. If the directory doesn't exist, it will be created. Default quality is dpi=300.


    Create overview plots of observed frequencies, angular resolution, LAS, frequency and velocity resolutions,
    highlighting the observations of a give (redshifted) frequency with hatches on the bar plots.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    line_freq : float64
         Frequency of the line of interest in GHz.
    z : float64, optional
         (Default value = 0.)
         Redshift by which the frequency given in 'line_freq' parameter should be shifted.
    line_name : str, optional
         (Default value = '')
         Name of the line specified in 'line_freq'.
    showfig : bool, optional
         (Default value = True)
         Display the plot (showfig=True) or not (showfig=False).
    savefig : str, optional
         (Default value = None)
         Filename (without an extension) for the plot to be saved as.
         Default file extension is PDF. Figure is saved in a subdirectory called 'reports' within the current
         working directory. If the directory doesn't exist, it will be created. Default quality is dpi=300.


    Plot the distribution of the targets on the sky.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    showfig : bool, optional
         (Default value = True)
         Display the plot (showfig=True) or not (showfig=False).
    savefig : str, optional
         (Default value = None)
         Filename (without an extension) for the plot to be saved as.
         Default file extension is PDF. Figure is saved in a subdirectory called 'reports' within the current
         working directory. If the directory doesn't exist, it will be created. Default quality is dpi=300.


Save the current plot in the 'reports' subdirectory in the current working directory.
    If the directory doesn't exist, it will be created.


Write the DataFrame with the query results to a table in CSV format.

    The table will be saved in the 'tables' subdirectory within the current working directory.
    If the directory doesn't exist, it will be created.

    Parameters
    ----------
    observations : pandas.DataFrame
    filename : str
         (Default value = "mytable")
         Name of the table to be saved in the 'tables' subdirectory.


    Create overview plots of observed frequencies, angular resolution, LAS, frequency and velocity resolutions for
    each source in the provided DataFrame and save them in PDF format in the 'reports' subdirectory.
    If the directory doesn't exist, it will be created.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    mark_freq : list of float64, optional
         (Default value = '')
         A list of frequencies to mark on the plot with dashed lines.
    z : float64, optional
         (Default value = 0.)
         Redshift by which the frequencies given in 'mark_freq' and 'mark_CO' parameters should be shifted.
         Currently only one redshift can be given for all targets.
    mark_CO : bool, optional
         (Default value = False)
         Mark CO, 13CO, and C18O frequencies on the plot with dashed lines.

    Notes
    -----
    Reports will be grouped by ALMA target names, therefore the same source with many different ALMA names will be
    treated as individual unique targets (e.g. TW_Hya, TW Hya, twhya).


    Download ALMA data from the archive to a location on the local machine.

    Parameters
    ----------
    observations : pandas.DataFrame
         This is likely the output of e.g. 'conesearch', 'target', 'catalog', & 'keysearch' functions.
    fitsonly : bool, optional
         (Default value = False)
         Download individual fits files only (fitsonly=True). This option will not download the raw data
         (e.g. 'asdm' files), weblogs, or README files.
    dryrun : bool, optional
         (Default value = False)
         Allow the user to do a test run to check the size and number of files to download without actually
         downloading the data (dryrun=True). To download the data, set dryrun=False.
    print_urls : bool, optional
         (Default value = False)
         Write the list of urls to be downloaded from the archive to the terminal.
    filename_must_include : list of str, optional
         (Default value = '')
         A list of strings the user wants to be contained in the url filename. This is useful to restrict the
         download further, for example, to data that have been primary beam corrected ('.pbcor') or that have
         the science target or calibrators (by including their names). The choice is largely dependent on the
         cycle and type of reduction that was performed and data products that exist on the archive as a result.
         In most recent cycles, the science target can be filtered out with the flag '_sci' or its ALMA target name.
    location : str, optional
         (Default value = ./data)
         directory where the downloaded data should be placed.
    archive_mirror : str, optional
         (Default value = 'ESO')
         The archive service to use. Options are:
         'ESO' for Europe (https://almascience.eso.org),
         'NRAO' for North America (https://almascience.nrao.edu), or
         'NAOJ' for East Asia (https://almascience.nao.ac.jp)


Find the (un)weighted pair counts between reference and unknown
        catalogs.

        Parameters
        ----------
        reference_catalog : `dict`
            Catalog of objects with known redshift to count pairs around.
            Dictionary contains:

            ``"ra"``
                RA position in degrees (`numpy.ndarray`, (N,))
            ``"dec"``
                Dec position in degrees (`numpy.ndarray`, (N,))
            ``"id"``
                Unique identifier in the catalog (`numpy.ndarray`, (N,))
            ``"redshift"``
                Redshift of the reference object (`numpy.ndarray`, (N,))
        unknown_catalog : `dict`
            Catalog of objects with unknown redshift to count around the
            reference objects.
            Dictionary contains:

            ``"ra"``
                RA position in degrees (`numpy.ndarray`, (N,))
            ``"dec"``
                Dec position in degrees (`numpy.ndarray`, (N,))
            ``"id"``
                Unique identifier in the catalog (`numpy.ndarray`, (N,))
            ``"weight"``
                OPTIONAL: If setting use_unkn_weights flag, weight to apply
                to each unknown object. (`numpy.ndarray`, (N,))

        Returns
        -------
        output_data : `pandas.DataFrame`
            Summary data produced from the pair finding, cross-correlation.
            Contains a summary of the N_pairs and requested distance weights
            per reference object.


Convert RA/DEC positions to points on the unit sphere.

        Parameters
        ----------
        ras : `numpy.ndarray`, (N,)
            Right assertion coordinate in radians
        decs : `numpy.ndarray`, (N,)
            Declination coordinate in radians

        Returns
        -------
        vectors : `numpy.ndarray`, (N, 3)
            Array of points on the unit sphere.


Query the kdtree for all points within the maximum r value at a
        given redshift/distance.

        Parameters
        ----------
        ref_vecct : `numpy.ndarray`, (3,)
            Position to center ball tree search on.
        unkn_tree : `scipy.spatial.cKDTree`
            Searchable kdtree containing points to correlate with.
        dist : `float`
            Distance from observer to the reference object at redshift, z.

        Returns
        -------
        output_indexes : `list` of `int`s
            List of integer index lookups into the array the tree was created
            with.


Bin data and construct output dict.

        If an output data file is specified, send the raw pairs off to be
        written to disk.

        Parameters
        ----------
        ref_id : `int`
            Unique identifier for the reference object.
        redshift : `float`
            Redshift of the reference object.
        unkn_ids : `numpy.ndarray`, (N,)
            Unique ids of all objects with unknown redshift that are within
            the distance r_min to r_max
        unkn_dists : `numpy.ndarray`, (N,)
            Distances in Mpc from the reference to the unknown objects between
            r_min and r_max.
        unkn_weights : `numpy.ndarray`, (N,)
            Weights for each object with unknown redshift.

        Returns
        -------
        output_row : `dict`
            Dictionary containing the values:

            ``"ref_id"``
                Unique reference id (`int`)
            ``"redshift"``
                Reference redshift (`float`)
            ``"region"``
                Spatial region the reference belongs to (`int`)
            ``"[scale_name]_count"``
                Number of unknown objects with the annulus around the
                reference for annulus [scale_name]. (`int`)
            ``"[scale_name]_weight"``
                Weighted number  unknown objects with the annulus around the
                reference for annulus [scale_name]. (`float`)


Construct a dataformate of values to be written to disk via a
        subprocess.

        Parameters
        ----------
        ref_id : `int`
            Unique identifier for the reference object.
        region : `int`
            Spatial region this reference belongs to.
        unkn_ids : `numpy.ndarray`, (N,)
            Unique ids of all objects with unknown redshift that are within
            the distance r_min to r_max
        unkn_dists : `numpy.ndarray`, (N,)
            Distances in Mpc from the reference to the unknown objects between
            r_min and r_max.


Convert raw distances into a signal matched weight for the
    correlation.

    All weights with distances below 0.001 Mpc will be set to a value of
    0.001 ** ``weight_power``.

    Parameters
    ----------
    dists : `numpy.ndarray`, (N,)
        Distances in Mpc.
    power : `float`
        Exponent to raise distance to the power of.

    Returns
    -------
    weights : `numpy.ndarray`, (N,)
        Output weights.


Initializer for enabling locking for multiprocessing.Pool.

    Copies a dict of locks into a global to prevent multiple writes to the
    same output file.

    Parameters
    ----------
    locks : `dict`
        A dictionary with integer keys mapping to `multiprocessing.Lock`
        objects.


Write raw pairs produced by pair maker to disk.

    Ids are loss-lessly compressed distances are stored as log, keeping 3
    decimal digits.

    Parameters
    ----------
    data : `dict`
        Dictionary of data produced by the PairMaker class.
        Dictionary has should have following keys:

        ``"file_name"``
            File name of the file to write to. (`str`)
        ``"ref_id"``
            Id of the reference object. (`int`)
        ``"scale_names"``
            Names of the scales run in pair_maker. Formated e.g.
            'Mpc1.00t10.00' (`list`)
        ``"'scale_name'_ids"``
            Unique ids of unknown objects within annulus 'scale_name' around
            the reference object (`numpy.ndarray`, (N,))
        ``"'scale_name'_dists"``
            Distance to unknown object with id in 'scale_name'_ids
            (`numpy.ndarray`, (N,))


Simple function to propagate errors from multiprocessing.Process
    objects.

    Parameters
    ----------
    exception : `Exception`


Log and convert distances to int type.

    Compression is lossy, keeping only 4 decimals in the log.

    Parameters
    ----------
    dists : `numpy.ndarray`
        Distances in Mpc to convert to int for compression.

    Returns
    -------
    comp_dists : `numpy.ndarray`
        Integer array representing Mpc distances. Can be converted back to
        distance by using `decompress_distances`.


Convert dists from int to float and unlog them.

    Parameters
    ----------
    comp_dists : `numpy.ndarray`
        Integer representations of log distances produced by the function
        `compres_distances`.

    Returns
    -------
    dists : `numpy.ndarray`
        Float uncompressed data.


Create bins linear or redshift.

    Parameters
    ----------
    z_min : `float`
        Minimum redshift.
    z_max : `float`
        Maximum redshift
    n_bins : `int`
        Number of redshift bins to create

    Returns
    -------
    bin_edges : `numpy.ndarray`, (n_bins + 1,)
        Redshift bin edges.


Create bins equally spaced in log(1 + z).

    Parameters
    ----------
    z_min : `float`
        Minimum redshift.
    z_max : `float`
        Maximum redshift
    n_bins : `int`
        Number of redshift bins to create

    Returns
    -------
    bin_edges : `numpy.ndarray`, (n_bins + 1,)
        Redshift bin edges.


Create bins equally spaced in comoving distance.

    Assumes a Planck2015 cosmology.

    Parameters
    ----------
    z_min : `float`
        Minimum redshift.
    z_max : `float`
        Maximum redshift
    n_bins : `int`
        Number of redshift bins to create

    Returns
    -------
    bin_edges : `numpy.ndarray`, (n_bins + 1,)
        Redshift bin edges.


Compute bin edges in redshift using different prescriptions.


Combine pairs of reference against unknown with reference against
        random to create the output over-densities.

        Optionally, can apply weights per-reference object in the correlation.
        Additionally can provide an reference sample "auto-correlation" run
        though pair_maker or pair_collapser that allows for the production of
        an estimator that produces a quantity closer to n(z).
        (n(z) * b_u / b_r assuming delta function reference bins/redshifts.)

        Parameters
        ----------
        ref_unkn : `pandas.DataFrame`
            DataFrame output from pair_maker or pair_collapser run methods
            containing the counts of objects with unknown redshifts around
            reference objects with known redshifts.
        ref_rand : `pandas.DataFrame`
            DataFrame output from pair_maker or pair_collapser run methods
            containing the counts of randoms around reference objects with
            known redshifts.
        weight_rand : `bool`
            If randoms correlated against the the reference objects did not
            already have weights, this flag uses the average weight of the
            unknown sample times the random counts to compute a weight.
        ref_weights : `numpy.ndarray` or `None`
            Optional weights to apply to each reference object in the
            calculated correlations.
        ref_ref : `pandas.DataFrame`
            DataFrame output from pair_maker or pair_collapser run methods
            counting the number of reference objects around themselves.
            Optional for producing outputs with the dark-matter clustering
            mitigated.
        ref_ref_rand : `pandas.DataFrame`
            DataFrame output from pair_maker or pair_collapser run methods
            counting the number of reference objects around randoms. This can
            be the same data as ref_rand or a specific set of randoms sampling
            the reference objects. Optional for producing outputs with the
            dark-matter clustering mitigated.
        ref_ref_rand_weights : `numpy.ndarray` or `None`
            Optional weights to apply to each reference object in the
            calculated correlations. This can be the same as ref_weights when
            using a DD / DR estimator or, if using randoms sampling the
            reference objects, a set of weights for those randoms.

        Returns
        -------
        output_dataframe : `pandas.DataFrame`
            Output clustering redshift calculations as a function of redshift.
            Contains raw columns for additional jackknife/bootstrapping using
            independent samples on the sky.


Combine pairs of reference against unknown as in the run method and
        additionally bootstrap over spatial regions to estimate errors.

        Parameters
        ----------
        ref_unkn : `pandas.DataFrame`
            DataFrame output from pair_maker or pair_collapser run methods
            containing the counts of objects with unknown redshifts around
            reference objects with known redshifts.
        ref_rand : `pandas.DataFrame`
            DataFrame output from pair_maker or pair_collapser run methods
            containing the counts of randoms around reference objects with
            known redshifts.
        weight_rand : `bool`
            If randoms correlated against the the reference objects did not
            already have weights, this flag uses the average weight of the
            unknown sample times the random counts to compute a weight.
        ref_weights : `numpy.ndarray` or `None`
            Optional weights to apply to each reference object in the
            calculated correlations.
        ref_ref : `pandas.DataFrame`
            DataFrame output from pair_maker or pair_collapser run methods
            counting the number of reference objects around themselves.
            Optional for producing outputs with the dark-matter clustering
            mitigated.
        ref_ref_rand : `pandas.DataFrame`
            DataFrame output from pair_maker or pair_collapser run methods
            counting the number of reference objects around randoms. This can
            be the same data as ref_rand or a specific set of randoms sampling
            the reference objects. Optional for producing outputs with the
            dark-matter clustering mitigated.
        ref_ref_rand_weights : `numpy.ndarray` or `None`
            Optional weights to apply to each reference object in the
            calculated correlations. This can be the same as ref_weights when
            using a DD / DR estimator or, if using randoms sampling the
            reference objects, a set of weights for those randoms.
        bootstraps : `int` or `numpy.ndarray`
            Number of bootstrap realizations to create or
        output_bootstraps : `str`
            Name output pickle file to write raw bootstraps to.

        Returns
        -------
        output_dataframe : `pandas.DataFrame`
            Output clustering redshift calculations as a function of redshift
            with errorbars derived from spatial bootstrapping.


Bin the reference data in the requested redshift bins.

        Parameters
        ----------
        data : `pandas.DataFrame`
            DataFrame containing the raw point from pair_maker or
            pair_collapser.
        ref_weights : `numpy.ndarray`
            Optional weights to apply to each object. Used in computing the
            total weighted pairs and the average redshift of the bin.

        Returns
        -------
        output_data : `pandas.DataFrame`
            Binned pair counts as a function of redshift.


Compute the correlation function for the input data pairs using
        the w_2 estimator from Landy & Szalay 93.

        Parameters
        ----------
        data : `pandas.DataFrame`
            Data binned in reference redshifts to create correlation functions.
        random : `pandas.DataFrame`
            Randoms binned in reference redshifts to create correlation
            functions.
        weight_rand : `bool`
            Use average weight on randoms.

        Returns
        -------
        count_corr, weight_corr : (`pandas.Series`, 'pandas.Series')
            Simple correlation estimators (DD / DR - 1) for pure counts and
            weighted counts.


Compute the over-density around reference objects stored in the
        pair file given the ids and weights in the input unknown_catalog.

        Parameters
        ----------
        unknown_catalog : `dict`
            Catalog of objects with unknown redshift to cross-correlate against
            the set of reference objects stored in the pair file. Input catalog
            should cover the same area as that covered by the references and
            be regionated similarly.
            Dictionary should contain contains:

            ``"id"``
                Unique identifier in the catalog (`numpy.ndarray`, (N,))
            ``"region"``
                Spatial region the unknown objects belong to. Optional only
                if the pair file contains one region.
                (`numpy.ndarray`, (N,))
            ``"weight"``
                OPTIONAL: If setting use_unkn_weights flag, weight to apply
                to each unknown object. (`numpy.ndarray`, (N,))

        Returns
        -------
        output : `pandas.DataFrame`
            Summary data produced from the pair finding, cross-correlation.
            Contains a summary of the N_pairs and requested distance weights
            per reference object.


Generate packages of work for subprocesses.

        Parameters
        ----------
        unique_regions : `numpy.ndarray`, (M,)
            Number of unique regions in the input dataset.
        unkn_regions : `numpy.ndarray`, (N,)
            Unique ID of the region an unkn object belongs to.
        unkn_ids : `nunpy.ndarray`, (N,)
            Unique identifier for each unknown object.
        unkn_weights `numpy.ndarray`, (N,)
            Weight to apply to pair counts.

        Yields
        ------
        work_packet : `dict`
            Dictionary containing the following data:

            ``"region"``
                Unique ID of the region the data in the dict belong to (`int`)
            ``"unkn_ids"``
                Unique ids of the objects withing this single region.
                (`numpy.ndarray`, (N,))
            ``"tot_sample"``
                Total number of unknown objects within the region (`int`)
            ``"ave_unkn_weight"``
                Average value of the weights of the unknown objects in the
                regions.
            ``"r_mins"``
                Minimum radii to compute raw counts in. (`list` of `int`)
            ``"r_maxes"``
                Maximum radii to compute raw counts in. (`list` of `int`)
            ``"file_name"``
                Location of complete pair file. (`str`)
            ``"z_bins"``
                List of redshift binned data to load in this work packet.
                (`list` of `str`)
            ``"weight_power"``
                Weight function to apply in distance weighted correlation.
                (`float`)


Retrieve paths of all parquet files stored in a region.

        Parameters
        ----------
        region : `int`
            ID of spatial region to retrieve.

        Returns
        -------
        files : `list` of `str`
            Names of parquet files in region ``region``.


Collapse unknown pairs into a single estimate for each reference in
    a redshift bin in in the pair file.

    Parameters
    ----------
    data : `dict`
        Dictionary of data to process.
        Dictionary contains:

        ``"unkn_ids"``
            Integer ids of objects with unknown redshift.
            (`numpy.ndarray`, (N,))
        ``"unkn_weights"``
            Weights for each individual object with unknown redshift.
            (`numpy.ndarray`, (N,))
        ``"tot_sample"``
            Number of unknown objects in this region. (`int`)
        ``"r_mins"``
            Minimum distance in Mpc of the annuli. (`list` of `float`)
        ``"r_maxes"``
            Maximum distance in Mpc of the annuli. (`list` of `float`)
        ``"file_name"``
            Location of the parquet directory. (`str`)
        ``"z_bin"``
            Full path name to the parquet file, redshift bin to be run. (`str`)
        ``"weight_power"``
            Power law exponent to weight galaxies by distance. (`float`)

    Returns
    -------
    output : `pandas.DataFrame`
        Over-densities around each of the reference objects in this redshift
        file.


Collapse pairs around one reference object by masking in the ids of the
    input objects with unknown redshift.

    Parameters
    ----------
    ref_row : `pandas.DataFrame`
        DataFrame representing one reference object.
    pair_data : `pandas.DataFrame`
        Ids and distances for all the unknown objects around this reference.
    unkn_ids : `numpy.ndarray`
        Integer ids of unknown objects to mask into pair data.
    unkn_weights : `numpy.ndarray`
        Weights of unknown objects.
    r_mins : `list` of `float`
        Minimum distance for bin annuli
    r_maxes : `list` of `float`
        Maximum distance for bin annuli
    weight_power : `float`
        Power law slope to weight pair distances by.

    Returns
    -------
    output : `dict`
        Output values for a individual reference object.
        Dictionary containing:

        ``"redshift"``
            Redshift of the reference object. (`float`)
        ``"region"``
            Region this reference object belongs to. (`int`)
        ``"[scale_name]_count"``
            Number of pairs around the reference within the annulus
            ``scale_name`` (`int`)
        ``"[scale_name]_weight"``
            Sum of weights of pairs around the reference within the annulus
            ``scale_name`` (`float`)


Find indices of trim_to where it is fully contained in trim_from.

    Parameters
    ----------
    trim_to : `numpy.ndarray`
        Sorted integer array to find locations of the min and max value in
        ``trim_from``
    trim_from : `numpy.ndarray`
        Sorted integer array to trim to the min and max value of ``trim_to``.

    Returns
    -------
    output : `tuple`, (`int`, `int`)
        Min and max index where the values of trim_to will be fully contained
        within trim_from.