# -*- coding: utf-8 -*-
from warnings import warn
import numpy as np
from ..misc import NeuroKitWarning
from ..signal import signal_sanitize
from .utils_complexity_attractor import (_attractor_equation,
                                         complexity_attractor)
[docs]
def complexity_embedding(signal, delay=1, dimension=3, show=False, **kwargs):
    """**Time-delay Embedding of a Signal**
    Time-delay embedding is one of the key concept of complexity science. It is based on the idea
    that a dynamical system can be described by a vector of numbers, called its *'state'*, that
    aims to provide a complete description of the system at some point in time. The set of all
    possible states is called the *'state space'*.
    Takens's (1981) embedding theorem suggests that a sequence of measurements of a dynamic system
    includes in itself all the information required to completely reconstruct the state space.
    Time-delay embedding attempts to identify the state *s* of the system at some time *t* by
    searching the past history of observations for similar states, and, by studying the evolution
    of similar states, infer information about the future of the system.
    **Attractors**
    How to visualize the dynamics of a system? A sequence of state values over time is called a
    trajectory. Depending on the system, different trajectories can evolve to a common subset of
    state space called an attractor. The presence and behavior of attractors gives intuition about
    the underlying dynamical system. We can visualize the system and its attractors by plotting the
    trajectory of many different initial state values and numerically integrating them to
    approximate their continuous time evolution on discrete computers.
    Parameters
    ----------
    signal : Union[list, np.array, pd.Series]
        The signal (i.e., a time series) in the form of a vector of values. Can also be a string,
        such as ``"lorenz"`` (Lorenz attractor), ``"rossler"`` (Rössler attractor), or
        ``"clifford"`` (Clifford attractor) to obtain a pre-defined attractor.
    delay : int
        Time delay (often denoted *Tau* :math:`\\tau`, sometimes referred to as *lag*) in samples.
        See :func:`complexity_delay` to estimate the optimal value for this parameter.
    dimension : int
        Embedding Dimension (*m*, sometimes referred to as *d* or *order*). See
        :func:`complexity_dimension` to estimate the optimal value for this parameter.
    show : bool
        Plot the reconstructed attractor. See :func:`complexity_attractor` for details.
    **kwargs
        Other arguments to be passed to :func:`complexity_attractor`.
    Returns
    -------
    array
        Embedded time-series, of shape ``length - (dimension - 1) * delay``
    See Also
    ------------
    complexity_delay, complexity_dimension, complexity_attractor
    Examples
    ---------
    **Example 1**: Understanding the output
    .. ipython
      import neurokit2 as nk
      # Basic example
      signal = [1, 2, 3, 2.5, 2.0, 1.5]
      embedded = nk.complexity_embedding(signal, delay = 2, dimension = 2)
      embedded
    The first columns contains the beginning of the signal, and the second column contains the
    values at *t+2*.
    **Example 2**: 2D, 3D, and "4D" Attractors. Note that 3D attractors are slow to plot.
    .. ipython
      # Artifical example
      signal = nk.signal_simulate(duration=4, sampling_rate=200, frequency=5, noise=0.01)
      @savefig p_complexity_embedding1.png scale=100%
      embedded = nk.complexity_embedding(signal, delay=50, dimension=2, show=True)
      @suppress
      plt.close()
    .. ipython
      @savefig p_complexity_embedding2.png scale=100%
      embedded = nk.complexity_embedding(signal, delay=50, dimension=3, show=True)
      @suppress
      plt.close()
    .. ipython
      @savefig p_complexity_embedding3.png scale=100%
      embedded = nk.complexity_embedding(signal, delay=50, dimension=4, show=True)
      @suppress
      plt.close()
    In the last 3D-attractor, the 4th dimension is represented by the color.
    **Example 3**: Attractor of heart rate
      ecg = nk.ecg_simulate(duration=60*4, sampling_rate=200)
      peaks, _ = nk.ecg_peaks(ecg, sampling_rate=200)
      signal = nk.ecg_rate(peaks, sampling_rate=200, desired_length=len(ecg))
      @savefig p_complexity_embedding4.png scale=100%
      embedded = nk.complexity_embedding(signal, delay=250, dimension=2, show=True)
      @suppress
      plt.close()
    References
    -----------
    * Gautama, T., Mandic, D. P., & Van Hulle, M. M. (2003, April). A differential entropy based
      method for determining the optimal embedding parameters of a signal. In 2003 IEEE
      International Conference on Acoustics, Speech, and Signal Processing, 2003. Proceedings.
      (ICASSP'03). (Vol. 6, pp. VI-29). IEEE.
    * Takens, F. (1981). Detecting strange attractors in turbulence. In Dynamical systems and
      turbulence, Warwick 1980 (pp. 366-381). Springer, Berlin, Heidelberg.
    """
    # If string
    if isinstance(signal, str):
        return _attractor_equation(signal, **kwargs)
    N = len(signal)
    signal = signal_sanitize(signal)
    # Sanity checks
    if isinstance(delay, float):
        warn("`delay` must be an integer. Running `int(delay)`", category=NeuroKitWarning)
        delay = int(delay)
    if isinstance(dimension, float):
        warn("`dimension` must be an integer. Running `int(dimension)`", category=NeuroKitWarning)
        dimension = int(dimension)
    if dimension * delay > N:
        raise ValueError(
            "NeuroKit error: complexity_embedding(): dimension * delay should be lower than",
            " the length of the signal.",
        )
    if delay < 1:
        raise ValueError("NeuroKit error: complexity_embedding(): 'delay' has to be at least 1.")
    Y = np.zeros((dimension, N - (dimension - 1) * delay))
    for i in range(dimension):
        Y[i] = signal[i * delay : i * delay + Y.shape[1]]
    embedded = Y.T
    if show is True:
        complexity_attractor(embedded, **kwargs)
    return embedded