<reponame>kingablgh/PySprint
import os

import numpy as np
from scipy.interpolate import interp1d
import scipy.stats as st

from pysprint.config import _get_config_value

__all__ = [
    "_unpack_lmfit",
    "find_nearest",
    "_handle_input",
    "_fourier_interpolate",
    "_between",
    "_get_closest",
    "run_from_ipython",
    "_calc_envelope",
    "measurement",
    "_maybe_increase_before_cwt",
    "pad_with_trailing_zeros",
    "pprint_math_or_default",
]


def pprint_math_or_default(s):
    try:
        from IPython.display import display, Math
        display(
            Math(
                s
            )
        )
    except ImportError:
        print(s)


def _maybe_increase_before_cwt(y, tolerance=0.05):
    y = np.asarray(y)
    value = y.min()
    if value - tolerance >= 0:
        return False
    if value < 0 < np.abs(value) - tolerance:
        return True
    return True


def _calc_envelope(x, ind, mode="u"):
    """
    https://stackoverflow.com/a/39662343/11751294
    """
    x_abs = np.abs(x)
    if mode == "u":
        loc = np.where(np.diff(np.sign(np.diff(x_abs))) < 0)[0] + 1
    elif mode == "l":
        loc = np.where(np.diff(np.sign(np.diff(x_abs))) > 0)[0] + 1
    else:
        raise ValueError("mode must be u or l.")
    peak = x_abs[loc]
    envelope = np.interp(ind, loc, peak)
    return envelope, peak, loc


def run_from_ipython():
    """
    Detect if code is run inside Jupyter or maybe Spyder.
    """
    try:
        __IPYTHON__  # noqa
        if any("SPYDER" in name for name in os.environ):
            return False
        return True
    except NameError:
        return False


def _get_closest(x_val, y_val, x_array, y_array):
    """
    Get the closest 2D point in array.
    """
    idx = np.argmin((np.hypot(x_array - x_val, y_array - y_val)))
    value = x_array[idx]
    return value, y_array[idx], idx


def _between(val, except_around):
    if except_around is None:
        return False
    elif len(except_around) != 2:
        raise ValueError(
            f"Invalid interval. Try [start, end] instead of {except_around}"
        )
    else:
        lower = float(min(except_around))
        upper = float(max(except_around))
    if upper >= val >= lower:
        return True
    return False


def _unpack_lmfit(r):
    dispersion, dispersion_std = [], []
    for name, par in r:
        dispersion.append(par.value)
        dispersion_std.append(par.stderr)
    return dispersion, dispersion_std


def measurement(array, confidence=0.95, silent=False):
    """
    Give the measurement results with condifence interval
    assuming the standard deviation is unknown.

    Parameters
    ----------

    array : ndarray
        The array containing the measured values

    confidence : float, optional
        The desired confidence level. Must be _between 0 and 1.

    silent : bool, optional
        Whether to print results immediately. Default is `False`.

    Returns
    -------

    mean: float
        The mean of the given array

    conf: tuple-like (interval)
        The confidence interval

    Examples
    ---------
    >>> import numpy as np
    >>> from pysprint.utils import measurement
    >>> a = np.array([123.783, 121.846, 122.248, 125.139, 122.569])
    >>> mean, interval = measurement(a, 0.99)
    123.117000 ± 2.763022
    >>> mean
    123.117
    >>> interval
    (120.35397798230359, 125.88002201769642)

    Notes
    -----

    I decided to print the results immediately, because people often don't use
    it for further code. Of course, they are also returned if needed.
    """
    precision = _get_config_value("precision")
    mean = np.mean(array)
    conf = st.t.interval(confidence, len(array) - 1, loc=mean, scale=st.sem(array))
    if not silent:
        pprint_math_or_default(
            f"{mean:.{precision}f} ± {(mean - conf[0]):.{precision}f}"
        )
    return mean, conf


def find_nearest(array, value):
    """
    Find the nearest element in array to value.

    Parameters
    ----------
    array : np.ndarray-like
        The array to search in.
    value : float
        The value to search.

    Returns
    -------
    value : float
        The closest value in array.
    idx : int
        The index of the closest element.
    """
    array = np.asarray(array)
    idx = (np.abs(value - array)).argmin()
    return array[idx], idx


def _handle_input(x, y, ref, sam):
    """
    Instead of handling the inputs in every function, there is this private method.

    Parameters
    ----------

    x: array-like
    x-axis data

    y: array-like
    y-axis data

    ref, sam: array-like
    reference and sample arm spectrum evaluated at x

    Returns
    -------
    x: array-like
    unchanged x data

    Ydata: array-like
    the transformed y data

    """
    if len(x) == 0:
        raise ValueError("No values for x.")

    if len(y) == 0:
        raise ValueError("No values for y.")

    if (len(x) > 0) and (len(ref) > 0) and (len(sam) > 0):
        y_data = (y - ref - sam) / (2 * np.sqrt(ref * sam))
    elif (len(ref) == 0) or (len(sam) == 0):
        y_data = y
    else:
        raise TypeError("Input types are wrong.\n")
    return np.asarray(x), np.asarray(y_data)


def _fourier_interpolate(x, y):
    """ Simple linear interpolation for FFTs"""
    xs = np.linspace(x[0], x[-1], len(x))
    intp = interp1d(x, y, kind="linear", fill_value="extrapolate")
    ys = intp(xs)
    return xs, ys


def pad_with_trailing_zeros(array, shape):
    """
    Pad an array with trailing zeros to be the desired shape
    """
    c = shape - len(array)
    if c < 1:
        return np.asarray(array)
    return np.pad(array, pad_width=(0, c), mode="constant")

import os

import numpy as np
from scipy.interpolate import interp1d
import scipy.stats as st

from pysprint.config import _get_config_value

__all__ = [
    "_unpack_lmfit",
    "find_nearest",
    "_handle_input",
    "_fourier_interpolate",
    "_between",
    "_get_closest",
    "run_from_ipython",
    "_calc_envelope",
    "measurement",
    "_maybe_increase_before_cwt",
    "pad_with_trailing_zeros",
    "pprint_math_or_default",
]


def pprint_math_or_default(s):
    try:
        from IPython.display import display, Math
        display(
            Math(
                s
            )
        )
    except ImportError:
        print(s)


def _maybe_increase_before_cwt(y, tolerance=0.05):
    y = np.asarray(y)
    value = y.min()
    if value - tolerance >= 0:
        return False
    if value < 0 < np.abs(value) - tolerance:
        return True
    return True


def _calc_envelope(x, ind, mode="u"):
    """
    https://stackoverflow.com/a/39662343/11751294
    """
    x_abs = np.abs(x)
    if mode == "u":
        loc = np.where(np.diff(np.sign(np.diff(x_abs))) < 0)[0] + 1
    elif mode == "l":
        loc = np.where(np.diff(np.sign(np.diff(x_abs))) > 0)[0] + 1
    else:
        raise ValueError("mode must be u or l.")
    peak = x_abs[loc]
    envelope = np.interp(ind, loc, peak)
    return envelope, peak, loc


def run_from_ipython():
    """
    Detect if code is run inside Jupyter or maybe Spyder.
    """
    try:
        __IPYTHON__  # noqa
        if any("SPYDER" in name for name in os.environ):
            return False
        return True
    except NameError:
        return False


def _get_closest(x_val, y_val, x_array, y_array):
    """
    Get the closest 2D point in array.
    """
    idx = np.argmin((np.hypot(x_array - x_val, y_array - y_val)))
    value = x_array[idx]
    return value, y_array[idx], idx


def _between(val, except_around):
    if except_around is None:
        return False
    elif len(except_around) != 2:
        raise ValueError(
            f"Invalid interval. Try [start, end] instead of {except_around}"
        )
    else:
        lower = float(min(except_around))
        upper = float(max(except_around))
    if upper >= val >= lower:
        return True
    return False


def _unpack_lmfit(r):
    dispersion, dispersion_std = [], []
    for name, par in r:
        dispersion.append(par.value)
        dispersion_std.append(par.stderr)
    return dispersion, dispersion_std


def measurement(array, confidence=0.95, silent=False):
    """
    Give the measurement results with condifence interval
    assuming the standard deviation is unknown.

    Parameters
    ----------

    array : ndarray
        The array containing the measured values

    confidence : float, optional
        The desired confidence level. Must be _between 0 and 1.

    silent : bool, optional
        Whether to print results immediately. Default is `False`.

    Returns
    -------

    mean: float
        The mean of the given array

    conf: tuple-like (interval)
        The confidence interval

    Examples
    ---------
    >>> import numpy as np
    >>> from pysprint.utils import measurement
    >>> a = np.array([123.783, 121.846, 122.248, 125.139, 122.569])
    >>> mean, interval = measurement(a, 0.99)
    123.117000 ± 2.763022
    >>> mean
    123.117
    >>> interval
    (120.35397798230359, 125.88002201769642)

    Notes
    -----

    I decided to print the results immediately, because people often don't use
    it for further code. Of course, they are also returned if needed.
    """
    precision = _get_config_value("precision")
    mean = np.mean(array)
    conf = st.t.interval(confidence, len(array) - 1, loc=mean, scale=st.sem(array))
    if not silent:
        pprint_math_or_default(
            f"{mean:.{precision}f} ± {(mean - conf[0]):.{precision}f}"
        )
    return mean, conf


def find_nearest(array, value):
    """
    Find the nearest element in array to value.

    Parameters
    ----------
    array : np.ndarray-like
        The array to search in.
    value : float
        The value to search.

    Returns
    -------
    value : float
        The closest value in array.
    idx : int
        The index of the closest element.
    """
    array = np.asarray(array)
    idx = (np.abs(value - array)).argmin()
    return array[idx], idx


def _handle_input(x, y, ref, sam):
    """
    Instead of handling the inputs in every function, there is this private method.

    Parameters
    ----------

    x: array-like
    x-axis data

    y: array-like
    y-axis data

    ref, sam: array-like
    reference and sample arm spectrum evaluated at x

    Returns
    -------
    x: array-like
    unchanged x data

    Ydata: array-like
    the transformed y data

    """
    if len(x) == 0:
        raise ValueError("No values for x.")

    if len(y) == 0:
        raise ValueError("No values for y.")

    if (len(x) > 0) and (len(ref) > 0) and (len(sam) > 0):
        y_data = (y - ref - sam) / (2 * np.sqrt(ref * sam))
    elif (len(ref) == 0) or (len(sam) == 0):
        y_data = y
    else:
        raise TypeError("Input types are wrong.\n")
    return np.asarray(x), np.asarray(y_data)


def _fourier_interpolate(x, y):
    """ Simple linear interpolation for FFTs"""
    xs = np.linspace(x[0], x[-1], len(x))
    intp = interp1d(x, y, kind="linear", fill_value="extrapolate")
    ys = intp(xs)
    return xs, ys


def pad_with_trailing_zeros(array, shape):
    """
    Pad an array with trailing zeros to be the desired shape
    """
    c = shape - len(array)
    if c < 1:
        return np.asarray(array)
    return np.pad(array, pad_width=(0, c), mode="constant")
https://stackoverflow.com/a/39662343/11751294 Detect if code is run inside Jupyter or maybe Spyder. # noqa Get the closest 2D point in array. Give the measurement results with condifence interval     assuming the standard deviation is unknown.      Parameters     ----------      array : ndarray         The array containing the measured values      confidence : float, optional         The desired confidence level. Must be _between 0 and 1.      silent : bool, optional         Whether to print results immediately. Default is `False`.      Returns     -------      mean: float         The mean of the given array      conf: tuple-like (interval)         The confidence interval      Examples     ---------     >>> import numpy as np     >>> from pysprint.utils import measurement     >>> a = np.array([123.783, 121.846, 122.248, 125.139, 122.569])     >>> mean, interval = measurement(a, 0.99)     123.117000 ± 2.763022     >>> mean     123.117     >>> interval     (120.35397798230359, 125.88002201769642)      Notes     -----      I decided to print the results immediately, because people often don't use     it for further code. Of course, they are also returned if needed. Find the nearest element in array to value.      Parameters     ----------     array : np.ndarray-like         The array to search in.     value : float         The value to search.      Returns     -------     value : float         The closest value in array.     idx : int         The index of the closest element. Instead of handling the inputs in every function, there is this private method.      Parameters     ----------      x: array-like     x-axis data      y: array-like     y-axis data      ref, sam: array-like     reference and sample arm spectrum evaluated at x      Returns     -------     x: array-like     unchanged x data      Ydata: array-like     the transformed y data Simple linear interpolation for FFTs Pad an array with trailing zeros to be the desired shape