def lower_bound_keogh(X_train, X_test, region):
    r"""Compute the "LB_Keogh" lower bounds between two datasets.

    Parameters
    ----------
    X_train : array-like, shape = (n_samples_train, n_timestamps)
        Training set. The warping envelopes are computed
        on this set.

    X_test: : array-like, shape = (n_samples_test, n_timestamps)
        Test set.

    region : array, shape = (2, n_timestamps)
        Constraint region. The first row consists of the starting indices
        (included) and the second row consists of the ending indices (excluded)
        of the valid rows for each column.

    Returns
    -------
    lower_bounds : array, shape = (n_samples_test, n_samples_train)
        "LB_Keogh" lower bounds.

    Notes
    -----
    The "LB_Keogh" lower bounds are computed as

    .. math:: LB_Keogh(X, Y) = \Vert X - H(X, Y) \Vert_{2}

    where :math:`X` is the test set (``X_test``), :math:`Y` is the
    training set (``X_train``), and :math:`H(X, Y)` is the projection
    of :math:`X` on :math:`Y`.

    References
    ----------
    .. [1] E. Keogh and C. A. Ratanamahatana, "Exact indexing of dynamic
           time warping". Knowledge and Information Systems, 7(3),
           358-386 (2005).

    Examples
    --------
    >>> X_train = [[0, 1, 2, 3], [1, 2, 3, 4]]
    >>> X_test = [[0, 2.5, 3.5, 6]]
    >>> region = [[0, 0, 1, 2], [2, 3, 4, 4]]
    >>> lower_bound_keogh(X_train, X_test, region)
    array([[3.08...  , 2.23...]])

    """
    X_train = check_array(X_train)
    X_test = check_array(X_test)
    _check_consistent_lengths(X_train, X_test)
    lower, upper = _warping_envelope(X_train, region)
    X_proj = _clip(X_test, lower, upper)
    squared_lb_keogh = np.sum(
        (X_test[:, None, :] - X_proj) ** 2, axis=-1
    )
    return np.sqrt(squared_lb_keogh)
Compute the "LB_Keogh" lower bounds between two datasets.

    Parameters
    ----------
    X_train : array-like, shape = (n_samples_train, n_timestamps)
        Training set. The warping envelopes are computed
        on this set.

    X_test: : array-like, shape = (n_samples_test, n_timestamps)
        Test set.

    region : array, shape = (2, n_timestamps)
        Constraint region. The first row consists of the starting indices
        (included) and the second row consists of the ending indices (excluded)
        of the valid rows for each column.

    Returns
    -------
    lower_bounds : array, shape = (n_samples_test, n_samples_train)
        "LB_Keogh" lower bounds.

    Notes
    -----
    The "LB_Keogh" lower bounds are computed as

    .. math:: LB_Keogh(X, Y) = \Vert X - H(X, Y) \Vert_{2}

    where :math:`X` is the test set (``X_test``), :math:`Y` is the
    training set (``X_train``), and :math:`H(X, Y)` is the projection
    of :math:`X` on :math:`Y`.

    References
    ----------
    .. [1] E. Keogh and C. A. Ratanamahatana, "Exact indexing of dynamic
           time warping". Knowledge and Information Systems, 7(3),
           358-386 (2005).

    Examples
    --------
    >>> X_train = [[0, 1, 2, 3], [1, 2, 3, 4]]
    >>> X_test = [[0, 2.5, 3.5, 6]]
    >>> region = [[0, 0, 1, 2], [2, 3, 4, 4]]
    >>> lower_bound_keogh(X_train, X_test, region)
    array([[3.08...  , 2.23...]])

    
pyts/metrics/lower_bounds.py