Repository: aeon-toolkit/aeon
Pull request #770 (2023-09-26 20:03:24)

[MNT] Add pre-commit bot which pushes fixes to PRs
As the title says. It should ease the burden a bit for those who have not set up properly yet. Won't catch everything, though.


Patch:
diff --git a/.github/workflows/pr_pre_commit.yml b/.github/workflows/pr_pre_commit.yml
index 79398e9c..8aadc4bb 100644
--- a/.github/workflows/pr_pre_commit.yml
+++ b/.github/workflows/pr_pre_commit.yml
@@ -36,3 +36,6 @@ jobs:
       - name: Check for missing init files
         run: build_tools/fail_on_missing_init_files.sh
         shell: bash
+
+      - uses: pre-commit-ci/lite-action@v1.0.1
+        if: always()
diff --git a/aeon/clustering/metrics/averaging/_barycenter_averaging.py b/aeon/clustering/metrics/averaging/_barycenter_averaging.py
index 75a72a49..c38d0201 100644
--- a/aeon/clustering/metrics/averaging/_barycenter_averaging.py
+++ b/aeon/clustering/metrics/averaging/_barycenter_averaging.py
@@ -8,6 +8,7 @@ from numba import njit
 
 from aeon.clustering.metrics.medoids import medoids
 from aeon.distances import (
+    adtw_alignment_path,
     ddtw_alignment_path,
     dtw_alignment_path,
     edr_alignment_path,
@@ -113,6 +114,7 @@ def _ba_update(
     c: float = 1.0,
     descriptor: str = "identity",
     reach: int = 30,
+    warp_penalty: float = 1.0,
 ) -> Tuple[np.ndarray, float]:
     X_size, X_dims, X_timepoints = X.shape
     sum = np.zeros(X_timepoints)
@@ -142,6 +144,10 @@ def _ba_update(
             curr_alignment, _ = shape_dtw_alignment_path(
                 curr_ts, center, window=window, descriptor=descriptor, reach=reach
             )
+        elif metric == "adtw":
+            curr_alignment, _ = adtw_alignment_path(
+                curr_ts, center, window=window, warp_penalty=warp_penalty
+            )
         else:
             # When numba version > 0.57 add more informative error with what metric
             # was passed.
diff --git a/aeon/distances/__init__.py b/aeon/distances/__init__.py
index a0903985..ef4b3ad2 100644
--- a/aeon/distances/__init__.py
+++ b/aeon/distances/__init__.py
@@ -10,6 +10,10 @@ __all__ = [
     "euclidean_pairwise_distance",
     "manhattan_distance",
     "manhattan_pairwise_distance",
+    "adtw_distance",
+    "adtw_pairwise_distance",
+    "adtw_cost_matrix",
+    "adtw_alignment_path",
     "dtw_distance",
     "dtw_pairwise_distance",
     "dtw_cost_matrix",
@@ -61,6 +65,12 @@ __all__ = [
 ]
 
 
+from aeon.distances._adtw import (
+    adtw_alignment_path,
+    adtw_cost_matrix,
+    adtw_distance,
+    adtw_pairwise_distance,
+)
 from aeon.distances._bounding_matrix import create_bounding_matrix
 from aeon.distances._ddtw import (
     ddtw_alignment_path,
diff --git a/aeon/distances/_adtw.py b/aeon/distances/_adtw.py
new file mode 100644
index 00000000..e5f82f8f
--- /dev/null
+++ b/aeon/distances/_adtw.py
@@ -0,0 +1,372 @@
+# -*- coding: utf-8 -*-
+r"""Amercing dynamic time warping (ADTW) between two time series."""
+__author__ = ["chrisholder", "TonyBagnall"]
+
+from typing import List, Tuple
+
+import numpy as np
+from numba import njit
+
+from aeon.distances._alignment_paths import compute_min_return_path
+from aeon.distances._bounding_matrix import create_bounding_matrix
+from aeon.distances._squared import _univariate_squared_distance
+from aeon.distances._utils import reshape_pairwise_to_multiple
+
+
+@njit(cache=True, fastmath=True)
+def adtw_distance(
+    x: np.ndarray,
+    y: np.ndarray,
+    window: float = None,
+    itakura_max_slope: float = None,
+    warp_penalty: float = 1.0,
+) -> float:
+    r"""Compute the ADTW distance between two time series.
+
+    Amercing Dynamic Time Warping (ADTW) [1]_ is a variant of DTW that uses a
+    explicit warping penalty to encourage or discourage warping. The warping
+    penalty is a constant value that is added to the cost of warping. A high
+    value will encourage the algorithm to warp less and if the value is low warping
+    is more likely.
+
+    Parameters
+    ----------
+    x : np.ndarray
+        First time series, either univariate, shape ``(n_timepoints,)``, or
+        multivariate, shape ``(n_channels, n_timepoints)``.
+    y : np.ndarray
+        Second time series, either univariate, shape ``(n_timepoints,)``, or
+        multivariate, shape ``(n_channels, n_timepoints)``.
+    window : float or None, default=None
+        The window to use for the bounding matrix. If None, no bounding matrix
+        is used. window is a percentage deviation, so if ``window = 0.1`` then
+        10% of the series length is the max warping allowed.
+        is used.
+    itakura_max_slope : float, default=None
+        Maximum slope as a proportion of the number of time points used to create
+        Itakura parallelogram on the bounding matrix. Must be between 0. and 1.
+    warp_penalty: float, default=1.0
+        Penalty for warping. A high value will mean less warping.
+
+    Returns
+    -------
+    float
+        ADTW distance between x and y, minimum value 0.
+
+    Raises
+    ------
+    ValueError
+        If x and y are not 1D or 2D arrays.
+
+    References
+    ----------
+    .. [1] Matthieu Herrmann, Geoffrey I. Webb: Amercing: An intuitive and
+    effective constraint for dynamic time warping, Pattern Recognition,
+    Volume 137, 2023.
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from aeon.distances import adtw_distance
+    >>> x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    >>> y = np.array([11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
+    >>> adtw_distance(x, y) # 1D series
+    783.0
+    >>> x = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [0, 1, 0, 2, 0]])
+    >>> y = np.array([[11, 12, 13, 14],[7, 8, 9, 20],[1, 3, 4, 5]] )
+    >>> adtw_distance(x, y) # 2D series with 3 channels, unequal length
+    565.0
+    """
+    if x.ndim == 1 and y.ndim == 1:
+        _x = x.reshape((1, x.shape[0]))
+        _y = y.reshape((1, y.shape[0]))
+        bounding_matrix = create_bounding_matrix(
+            _x.shape[1], _y.shape[1], window, itakura_max_slope
+        )
+        return _adtw_distance(_x, _y, bounding_matrix, warp_penalty)
+    if x.ndim == 2 and y.ndim == 2:
+        bounding_matrix = create_bounding_matrix(
+            x.shape[1], y.shape[1], window, itakura_max_slope
+        )
+        return _adtw_distance(x, y, bounding_matrix, warp_penalty)
+    raise ValueError("x and y must be 1D or 2D")
+
+
+@njit(cache=True, fastmath=True)
+def adtw_cost_matrix(
+    x: np.ndarray,
+    y: np.ndarray,
+    window: float = None,
+    itakura_max_slope: float = None,
+    warp_penalty: float = 1.0,
+) -> np.ndarray:
+    r"""Compute the ADTW cost matrix between two time series.
+
+    Parameters
+    ----------
+    x : np.ndarray
+        First time series, either univariate, shape ``(n_timepoints,)``, or
+        multivariate, shape ``(n_channels, n_timepoints)``.
+    y : np.ndarray
+        Second time series, either univariate, shape ``(n_timepoints,)``, or
+        multivariate, shape ``(n_channels, n_timepoints)``.
+    window : float, default=None
+        The window to use for the bounding matrix. If None, no bounding matrix
+        is used. window is a percentage deviation, so if ``window = 0.1``,
+        10% of the series length is the max warping allowed.
+        is used.
+    itakura_max_slope : float, default=None
+        Maximum slope as a proportion of the number of time points used to create
+        Itakura parallelogram on the bounding matrix. Must be between 0. and 1.
+    warp_penalty: float, default=1.0
+        Penalty for warping. A high value will mean less warping.
+
+    Returns
+    -------
+    np.ndarray (n_timepoints, m_timepoints)
+        adtw cost matrix between x and y.
+
+    Raises
+    ------
+    ValueError
+        If x and y are not 1D or 2D arrays.
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from aeon.distances import adtw_cost_matrix
+    >>> x = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]])
+    >>> y = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]])
+    >>> adtw_cost_matrix(x, y)
+    array([[  0.,   2.,   7.,  17.,  34.,  60.,  97., 147., 212., 294.],
+           [  2.,   0.,   2.,   7.,  17.,  34.,  60.,  97., 147., 212.],
+           [  7.,   2.,   0.,   2.,   7.,  17.,  34.,  60.,  97., 147.],
+           [ 17.,   7.,   2.,   0.,   2.,   7.,  17.,  34.,  60.,  97.],
+           [ 34.,  17.,   7.,   2.,   0.,   2.,   7.,  17.,  34.,  60.],
+           [ 60.,  34.,  17.,   7.,   2.,   0.,   2.,   7.,  17.,  34.],
+           [ 97.,  60.,  34.,  17.,   7.,   2.,   0.,   2.,   7.,  17.],
+           [147.,  97.,  60.,  34.,  17.,   7.,   2.,   0.,   2.,   7.],
+           [212., 147.,  97.,  60.,  34.,  17.,   7.,   2.,   0.,   2.],
+           [294., 212., 147.,  97.,  60.,  34.,  17.,   7.,   2.,   0.]])
+
+    """
+    if x.ndim == 1 and y.ndim == 1:
+        _x = x.reshape((1, x.shape[0]))
+        _y = y.reshape((1, y.shape[0]))
+        bounding_matrix = create_bounding_matrix(
+            _x.shape[1], _y.shape[1], window, itakura_max_slope
+        )
+        return _adtw_cost_matrix(_x, _y, bounding_matrix, warp_penalty)
+    if x.ndim == 2 and y.ndim == 2:
+        bounding_matrix = create_bounding_matrix(
+            x.shape[1], y.shape[1], window, itakura_max_slope
+        )
+        return _adtw_cost_matrix(x, y, bounding_matrix, warp_penalty)
+    raise ValueError("x and y must be 1D or 2D")
+
+
+@njit(cache=True, fastmath=True)
+def _adtw_distance(
+    x: np.ndarray, y: np.ndarray, bounding_matrix: np.ndarray, warp_penalty: float
+) -> float:
+    return _adtw_cost_matrix(x, y, bounding_matrix, warp_penalty)[
+        x.shape[1] - 1, y.shape[1] - 1
+    ]
+
+
+@njit(cache=True, fastmath=True)
+def _adtw_cost_matrix(
+    x: np.ndarray, y: np.ndarray, bounding_matrix: np.ndarray, warp_penalty: float
+) -> np.ndarray:
+    x_size = x.shape[1]
+    y_size = y.shape[1]
+    cost_matrix = np.full((x_size + 1, y_size + 1), np.inf)
+    cost_matrix[0, 0] = 0.0
+
+    for i in range(x_size):
+        for j in range(y_size):
+            if bounding_matrix[i, j]:
+                cost_matrix[i + 1, j + 1] = _univariate_squared_distance(
+                    x[:, i], y[:, j]
+                ) + min(
+                    cost_matrix[i, j + 1] + warp_penalty,
+                    cost_matrix[i + 1, j] + warp_penalty,
+                    cost_matrix[i, j],
+                )
+
+    return cost_matrix[1:, 1:]
+
+
+@njit(cache=True, fastmath=True)
+def adtw_pairwise_distance(
+    X: np.ndarray,
+    y: np.ndarray = None,
+    window: float = None,
+    itakura_max_slope: float = None,
+    warp_penalty: float = 1.0,
+) -> np.ndarray:
+    r"""Compute the ADTW pairwise distance between a set of time series.
+
+    Parameters
+    ----------
+    X : np.ndarray
+        A collection of time series instances  of shape ``(n_instances, n_timepoints)``
+        or ``(n_instances, n_channels, n_timepoints)``.
+    y : np.ndarray or None, default=None
+        A single series or a collection of time series of shape ``(m_timepoints,)`` or
+        ``(m_instances, m_timepoints)`` or ``(m_instances, m_channels, m_timepoints)``.
+    window : float or None, default=None
+        The window to use for the bounding matrix. If None, no bounding matrix
+        is used.
+    itakura_max_slope : float, default=None
+        Maximum slope as a proportion of the number of time points used to create
+        Itakura parallelogram on the bounding matrix. Must be between 0. and 1.
+    warp_penalty: float, default=1.0
+        Penalty for warping. A high value will mean less warping.
+        warp less and if value is low then will encourage algorithm to warp
+        more.
+
+    Returns
+    -------
+    np.ndarray
+        ADTW pairwise matrix between the instances of X of shape
+        ``(n_instances, n_instances)`` or between X and y of shape ``(n_instances,
+        n_instances)``.
+
+    Raises
+    ------
+    ValueError
+        If X is not 2D or 3D array and if y is not 1D, 2D or 3D arrays when passing y.
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from aeon.distances import adtw_pairwise_distance
+    >>> # Distance between each time series in a collection of time series
+    >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]])
+    >>> adtw_pairwise_distance(X)
+    array([[  0.,  27., 108.],
+           [ 27.,   0.,  27.],
+           [108.,  27.,   0.]])
+
+    >>> # Distance between two collections of time series
+    >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]])
+    >>> y = np.array([[[11, 12, 13]],[[14, 15, 16]], [[17, 18, 19]]])
+    >>> adtw_pairwise_distance(X, y)
+    array([[300., 507., 768.],
+           [147., 300., 507.],
+           [ 48., 147., 300.]])
+
+    >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]])
+    >>> y_univariate = np.array([[11, 12, 13],[14, 15, 16], [17, 18, 19]])
+    >>> adtw_pairwise_distance(X, y_univariate)
+    array([[300.],
+           [147.],
+           [ 48.]])
+    """
+    if y is None:
+        # To self
+        if X.ndim == 3:
+            return _adtw_pairwise_distance(X, window, itakura_max_slope, warp_penalty)
+        if X.ndim == 2:
+            _X = X.reshape((X.shape[0], 1, X.shape[1]))
+            return _adtw_pairwise_distance(_X, window, itakura_max_slope, warp_penalty)
+        raise ValueError("x and y must be 2D or 3D arrays")
+    _x, _y = reshape_pairwise_to_multiple(X, y)
+    return _adtw_from_multiple_to_multiple_distance(
+        _x, _y, window, itakura_max_slope, warp_penalty
+    )
+
+
+@njit(cache=True, fastmath=True)
+def _adtw_pairwise_distance(
+    X: np.ndarray, window: float, itakura_max_slope: float, warp_penalty: float
+) -> np.ndarray:
+    n_instances = X.shape[0]
+    distances = np.zeros((n_instances, n_instances))
+    bounding_matrix = create_bounding_matrix(
+        X.shape[2], X.shape[2], window, itakura_max_slope
+    )
+
+    for i in range(n_instances):
+        for j in range(i + 1, n_instances):
+            distances[i, j] = _adtw_distance(X[i], X[j], bounding_matrix, warp_penalty)
+            distances[j, i] = distances[i, j]
+
+    return distances
+
+
+@njit(cache=True, fastmath=True)
+def _adtw_from_multiple_to_multiple_distance(
+    x: np.ndarray,
+    y: np.ndarray,
+    window: float,
+    itakura_max_slope: float,
+    warp_penalty: float,
+) -> np.ndarray:
+    n_instances = x.shape[0]
+    m_instances = y.shape[0]
+    distances = np.zeros((n_instances, m_instances))
+    bounding_matrix = create_bounding_matrix(
+        x.shape[2], y.shape[2], window, itakura_max_slope
+    )
+
+    for i in range(n_instances):
+        for j in range(m_instances):
+            distances[i, j] = _adtw_distance(x[i], y[j], bounding_matrix, warp_penalty)
+    return distances
+
+
+@njit(cache=True, fastmath=True)
+def adtw_alignment_path(
+    x: np.ndarray,
+    y: np.ndarray,
+    window: float = None,
+    itakura_max_slope: float = None,
+    warp_penalty: float = 1.0,
+) -> Tuple[List[Tuple[int, int]], float]:
+    """Compute the ADTW alignment path between two time series.
+
+    Parameters
+    ----------
+    x : np.ndarray
+        First time series, shape ``(n_channels, n_timepoints)`` or ``(n_timepoints,)``.
+    y : np.ndarray
+        Second time series, shape ``(m_channels, m_timepoints)`` or ``(m_timepoints,)``.
+    window : float, default=None
+        The window to use for the bounding matrix. If None, no bounding matrix
+        is used.
+    itakura_max_slope : float, default=None
+        Maximum slope as a proportion of the number of time points used to create
+        Itakura parallelogram on the bounding matrix. Must be between 0. and 1.
+    warp_penalty: float, default=1.0
+        Penalty for warping. A high value will mean less warping.
+
+    Returns
+    -------
+    List[Tuple[int, int]]
+        The alignment path between the two time series where each element is a tuple
+        of the index in x and the index in y that have the best alignment according
+        to the cost matrix.
+    float
+        The ADTW distance betweeen the two time series.
+
+    Raises
+    ------
+    ValueError
+        If x and y are not 1D or 2D arrays.
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from aeon.distances import adtw_alignment_path
+    >>> x = np.array([[1, 2, 3, 6]])
+    >>> y = np.array([[1, 2, 3, 4]])
+    >>> adtw_alignment_path(x, y)
+    ([(0, 0), (1, 1), (2, 2), (3, 3)], 4.0)
+    """
+    cost_matrix = adtw_cost_matrix(x, y, window, itakura_max_slope, warp_penalty)
+    return (
+        compute_min_return_path(cost_matrix),
+        cost_matrix[x.shape[-1] - 1, y.shape[-1] - 1],
+    )
diff --git a/aeon/distances/_distance.py b/aeon/distances/_distance.py
index 37c7e2b6..e879e4c7 100644
--- a/aeon/distances/_distance.py
+++ b/aeon/distances/_distance.py
@@ -5,6 +5,12 @@ from typing import Any, Callable, List, Tuple, Union
 
 import numpy as np
 
+from aeon.distances._adtw import (
+    adtw_alignment_path,
+    adtw_cost_matrix,
+    adtw_distance,
+    adtw_pairwise_distance,
+)
 from aeon.distances._ddtw import (
     ddtw_alignment_path,
     ddtw_cost_matrix,
@@ -205,6 +211,14 @@ def distance(
         )
     elif metric == "mpdist":
         return mpdist(x, y, **kwargs)
+    elif metric == "adtw":
+        return adtw_distance(
+            x,
+            y,
+            kwargs.get("window"),
+            kwargs.get("itakura_max_slope"),
+            kwargs.get("warp_penalty", 1.0),
+        )
     else:
         if isinstance(metric, Callable):
             return metric(x, y, **kwargs)
@@ -357,6 +371,14 @@ def pairwise_distance(
         )
     elif metric == "mpdist":
         return _custom_func_pairwise(x, y, mpdist, **kwargs)
+    elif metric == "adtw":
+        return adtw_pairwise_distance(
+            x,
+            y,
+            kwargs.get("window"),
+            kwargs.get("itakura_max_slope"),
+            kwargs.get("warp_penalty", 1.0),
+        )
     else:
         if isinstance(metric, Callable):
             return _custom_func_pairwise(x, y, metric, **kwargs)
@@ -530,6 +552,14 @@ def alignment_path(
             kwargs.get("c", 1.0),
             kwargs.get("itakura_max_slope"),
         )
+    elif metric == "adtw":
+        return adtw_alignment_path(
+            x,
+            y,
+            kwargs.get("window"),
+            kwargs.get("itakura_max_slope"),
+            kwargs.get("warp_penalty", 1.0),
+        )
     else:
         raise ValueError("Metric must be one of the supported strings")
 
@@ -661,6 +691,14 @@ def cost_matrix(
             kwargs.get("c", 1.0),
             kwargs.get("itakura_max_slope"),
         )
+    elif metric == "adtw":
+        return adtw_cost_matrix(
+            x,
+            y,
+            kwargs.get("window"),
+            kwargs.get("itakura_max_slope"),
+            kwargs.get("warp_penalty", 1.0),
+        )
     else:
         raise ValueError("Metric must be one of the supported strings")
 
@@ -676,6 +714,7 @@ def get_distance_function(metric: Union[str, DistanceFunction]) -> DistanceFunct
     'ddtw'          distance.ddtw_distance
     'wdtw'          distance.wdtw_distance
     'wddtw'         distance.wddtw_distance
+    'adtw'          distance.adtw_distance
     'erp'           distance.erp_distance
     'edr'           distance.edr_distance
     'msm'           distance.msm_distance
@@ -729,6 +768,7 @@ def get_pairwise_distance_function(
     'ddtw'          distance.ddtw_pairwise_distance
     'wdtw'          distance.wdtw_pairwise_distance
     'wddtw'         distance.wddtw_pairwise_distance
+    'adtw'          distance.adtw_pairwise_distance
     'erp'           distance.erp_pairwise_distance
     'edr'           distance.edr_pairwise_distance
     'msm'           distance.msm_pairiwse_distance
@@ -783,6 +823,7 @@ def get_alignment_path_function(metric: str) -> AlignmentPathFunction:
     'ddtw'          distance.ddtw_alignment_path
     'wdtw'          distance.wdtw_alignment_path
     'wddtw'         distance.wddtw_alignment_path
+    'adtw'          distance.adtw_alignment_path
     'erp'           distance.erp_alignment_path
     'edr'           distance.edr_alignment_path
     'msm'           distance.msm_alignment_path
@@ -830,6 +871,7 @@ def get_cost_matrix_function(metric: str) -> CostMatrixFunction:
     'ddtw'          distance.ddtw_cost_matrix
     'wdtw'          distance.wdtw_cost_matrix
     'wddtw'         distance.wddtw_cost_matrix
+    'adtw'          distance.adtw_cost_matrix
     'erp'           distance.erp_cost_matrix
     'edr'           distance.edr_cost_matrix
     'msm'           distance.msm_cost_matrix
@@ -956,6 +998,13 @@ DISTANCES = [
         "cost_matrix": msm_cost_matrix,
         "alignment_path": msm_alignment_path,
     },
+    {
+        "name": "adtw",
+        "distance": adtw_distance,
+        "pairwise_distance": adtw_pairwise_distance,
+        "cost_matrix": adtw_cost_matrix,
+        "alignment_path": adtw_alignment_path,
+    },
     {
         "name": "shape_dtw",
         "distance": shape_dtw_distance,


Test patch:
diff --git a/aeon/clustering/metrics/tests/test_dba.py b/aeon/clustering/metrics/tests/test_dba.py
index 8a42d4cd..3a2fd127 100644
--- a/aeon/clustering/metrics/tests/test_dba.py
+++ b/aeon/clustering/metrics/tests/test_dba.py
@@ -143,7 +143,18 @@ def test_dba():
 
 
 def test_elastic_dba_variations():
-    distances = ["dtw", "ddtw", "wdtw", "wddtw", "erp", "edr", "twe", "msm"]
+    distances = [
+        "dtw",
+        "ddtw",
+        "wdtw",
+        "wddtw",
+        "erp",
+        "edr",
+        "twe",
+        "msm",
+        "shape_dtw",
+        "adtw",
+    ]
     for dist in distances:
         X_train = create_test_distance_numpy(10, 10, 10)
 
diff --git a/aeon/distances/tests/_expected_results.py b/aeon/distances/tests/_expected_results.py
index e9751aa0..40cad787 100644
--- a/aeon/distances/tests/_expected_results.py
+++ b/aeon/distances/tests/_expected_results.py
@@ -77,6 +77,13 @@ _expected_distance_results = {
         12.461233755089522,
         36.0265974253265,
     ],
+    "adtw": [
+        25.0,
+        5.163699519307769,
+        49.59969618998147,
+        9.360373075383167,
+        49.86527164702194,
+    ],
 }
 
 _expected_distance_results_params = {
@@ -128,4 +135,9 @@ _expected_distance_results_params = {
         [6.5373486105638134, 50.88045781961115],
         [3.6922226886554435, 38.923480043064245],
     ],
+    "adtw": [
+        [5.163699519307769, 49.59969618998147],
+        [5.231335579254712, 50.31911284774053],
+        [6.932617853961479, 50.31911284774053],
+    ],
 }
diff --git a/aeon/distances/tests/test_numba_distance_parameters.py b/aeon/distances/tests/test_numba_distance_parameters.py
index ca27910e..648840a7 100644
--- a/aeon/distances/tests/test_numba_distance_parameters.py
+++ b/aeon/distances/tests/test_numba_distance_parameters.py
@@ -67,6 +67,7 @@ DIST_PARAMS = {
     "wddtw": BASIC_BOUNDING_PARAMS + [{"g": 1.0}],
     "twe": BASIC_BOUNDING_PARAMS + [{"lmbda": 0.5}, {"nu": 0.9}],
     "msm": BASIC_BOUNDING_PARAMS + [{"independent": False}, {"c": 0.1}],
+    "adtw": BASIC_BOUNDING_PARAMS + [{"warp_penalty": 5.0}],
 }