Source code for mhkit.utils.upcrossing

"""
Upcrossing Analysis Functions
=============================
This module contains a collection of functions that facilitate upcrossing 
analyses.

Key Functions:
--------------
- `upcrossing`: Finds the zero upcrossing points.
- `peaks`: Finds the peaks between zero crossings.
- `troughs`: Finds the troughs between zero crossings.
- `heights`: Calculates the height between zero crossings.
- `periods`: Calculates the period between zero crossings.
- `custom`: Applies a custom, user-defined function between zero crossings.
   
Author: 
-------
mbruggs
akeeste

Date:
-----
2023-10-10


"""

from typing import Callable, Optional
import numpy as np


def _apply(
    t: np.ndarray,
    data: np.ndarray,
    f: Callable[[int, int], float],
    inds: Optional[np.ndarray] = None,
) -> np.ndarray:
    """
    Apply a function `f` over intervals defined by `inds`. If `inds` is None,
    compute the indices using the upcrossing function.

    Parameters
    ----------
    t : np.ndarray
        Time array.
    data : np.ndarray
        Data array.
    f : Callable[[int, int], float]
        A function to apply to pairs of indices (start, end).
    inds : np.ndarray, optional
        Indices that define the intervals. If None, `upcrossing` is used to generate them.

    Returns
    -------
    np.ndarray
        Array of values resulting from applying `f` over the intervals.
    """
    if inds is None:
        inds = upcrossing(t, data)

    n = inds.size - 1

    vals = np.empty(n)
    for i in range(n):
        vals[i] = f(inds[i], inds[i + 1])

    return vals


[docs] def upcrossing(t: np.ndarray, data: np.ndarray) -> np.ndarray: """ Finds the zero upcrossing points. Parameters ---------- t: np.array Time array. data: np.array Signal time series. Returns ------- inds: np.array Zero crossing indices """ # Check data types if not isinstance(t, np.ndarray): raise TypeError(f"t must be of type np.ndarray. Got: {type(t)}") if not isinstance(data, np.ndarray): raise TypeError(f"data must be of type np.ndarray. Got: {type(data)}") if len(data.shape) != 1: raise ValueError("only 1D data supported, try calling squeeze()") # eliminate zeros zero_mask = data == 0 data[zero_mask] = 0.5 * np.min(np.abs(data)) # zero up-crossings diff = np.diff(np.sign(data)) zero_upcrossings_mask = (diff == 2) | (diff == 1) zero_upcrossings_index = np.where(zero_upcrossings_mask)[0] return zero_upcrossings_index
[docs] def peaks( t: np.ndarray, data: np.ndarray, inds: Optional[np.ndarray] = None ) -> np.ndarray: """ Finds the peaks between zero crossings. Parameters ---------- t: np.array Time array. data: np.array Signal time-series. inds : np.ndarray, optional Optional indices for the upcrossing. Useful when using several of the upcrossing methods to avoid repeating the upcrossing analysis each time. Returns ------- peaks: np.array Peak values of the time-series """ # Check data types if not isinstance(t, np.ndarray): raise TypeError(f"t must be of type np.ndarray. Got: {type(t)}") if not isinstance(data, np.ndarray): raise TypeError(f"data must be of type np.ndarray. Got: {type(data)}") return _apply(t, data, lambda ind1, ind2: np.max(data[ind1:ind2]), inds)
[docs] def troughs( t: np.ndarray, data: np.ndarray, inds: Optional[np.ndarray] = None ) -> np.ndarray: """ Finds the troughs between zero crossings. Parameters ---------- t: np.array Time array. data: np.array Signal time-series. inds: np.array, optional Optional indices for the upcrossing. Useful when using several of the upcrossing methods to avoid repeating the upcrossing analysis each time. Returns ------- troughs: np.array Trough values of the time-series """ # Check data types if not isinstance(t, np.ndarray): raise TypeError(f"t must be of type np.ndarray. Got: {type(t)}") if not isinstance(data, np.ndarray): raise TypeError(f"data must be of type np.ndarray. Got: {type(data)}") return _apply(t, data, lambda ind1, ind2: np.min(data[ind1:ind2]), inds)
[docs] def heights( t: np.ndarray, data: np.ndarray, inds: Optional[np.ndarray] = None ) -> np.ndarray: """ Calculates the height between zero crossings. The height is defined as the max value - min value between the zero crossing points. Parameters ---------- t: np.array Time array. data: np.array Signal time-series. inds: np.array, optional Optional indices for the upcrossing. Useful when using several of the upcrossing methods to avoid repeating the upcrossing analysis each time. Returns ------- heights: np.array Height values of the time-series """ # Check data types if not isinstance(t, np.ndarray): raise TypeError(f"t must be of type np.ndarray. Got: {type(t)}") if not isinstance(data, np.ndarray): raise TypeError(f"data must be of type np.ndarray. Got: {type(data)}") def func(ind1, ind2): return np.max(data[ind1:ind2]) - np.min(data[ind1:ind2]) return _apply(t, data, func, inds)
[docs] def periods( t: np.ndarray, data: np.ndarray, inds: Optional[np.ndarray] = None ) -> np.ndarray: """ Calculates the period between zero crossings. Parameters ---------- t: np.array Time array. data: np.array Signal time-series. inds: np.array, optional Optional indices for the upcrossing. Useful when using several of the upcrossing methods to avoid repeating the upcrossing analysis each time. Returns ------- periods: np.array Period values of the time-series """ # Check data types if not isinstance(t, np.ndarray): raise TypeError(f"t must be of type np.ndarray. Got: {type(t)}") if not isinstance(data, np.ndarray): raise TypeError(f"data must be of type np.ndarray. Got: {type(data)}") return _apply(t, data, lambda ind1, ind2: t[ind2] - t[ind1], inds)
[docs] def custom( t: np.ndarray, data: np.ndarray, func: Callable[[int, int], np.ndarray], inds: Optional[np.ndarray] = None, ) -> np.ndarray: """ Applies a custom function to the timeseries data between upcrossing points. Parameters ---------- t: np.array Time array. data: np.array Signal time-series. func: Callable[[int, int], np.ndarray] Function to apply between the zero crossing periods given t[ind1], t[ind2], where ind1 < ind2, correspond to the start and end of an upcrossing section. inds: np.array, optional Optional indices for the upcrossing. Useful when using several of the upcrossing methods to avoid repeating the upcrossing analysis each time. Returns ------- values: np.array Custom values of the time-series """ # Check data types if not isinstance(t, np.ndarray): raise TypeError(f"t must be of type np.ndarray. Got: {type(t)}") if not isinstance(data, np.ndarray): raise TypeError(f"data must be of type np.ndarray. Got: {type(data)}") if not callable(func): raise ValueError("func must be callable") return _apply(t, data, func, inds)