Repository: aeon-toolkit/aeon
Pull request #1326 (2024-03-19 20:24:05)

[MNT] Type hint distance module
<!--
Thanks for contributing a pull request! Please ensure you have taken a look at our
contribution guide: https://www.aeon-toolkit.org/en/stable/contributing.html

Feel free to delete sections of this template if they do not apply to your PR,
avoid submitting a blank template or empty sections.
-->

#### Reference Issues/PRs

<!--
Example: Fixes #1234. See also #3456.
Please use keywords (e.g., Fixes) to create link to the issues or pull requests
you resolved, so that they will automatically be closed when your pull request
is merged. See https://github.com/blog/1506-closing-issues-via-pull-requests
-->

#### What does this implement/fix? Explain your changes.

<!--
A clear and concise description of what you have implemented.
-->
This PR adds correct type hinting to the entire distance module.

#### Does your contribution introduce a new dependency? If yes, which one?

<!--
If your contribution does add a dependency, we may suggest adding it as an
optional/soft dependency to keep external dependencies of the core aeon package
to a minimum.
-->

#### Any other comments?

<!--
Please be aware that we are a loose team of volunteers so patience is
necessary; assistance handling other issues is very welcome. We value all
user contributions, no matter how minor they are. If we are slow to
review, either the pull request needs some benchmarking, tinkering,
convincing, etc. or more likely the reviewers are simply busy. In either
case, we ask for your understanding during the review process.
-->

### PR checklist

<!--
Please go through the checklist below. Please feel free to remove points if they are not applicable.
-->

##### For all contributions
- [x] I've added myself to the [list of contributors](https://github.com/aeon-toolkit/aeon/blob/main/.all-contributorsrc). Alternatively, you can use the [@all-contributors](https://allcontributors.org/docs/en/bot/usage) bot to do this for you.
- [x] The PR title starts with either [ENH], [MNT], [DOC], [BUG], [REF], [DEP] or [GOV] indicating whether the PR topic is related to enhancement, maintenance, documentation, bugs, refactoring, deprecation or governance.

##### For new estimators and functions
- [x] I've added the estimator to the online [API documentation](https://www.aeon-toolkit.org/en/latest/api_reference.html).
- [x] (OPTIONAL) I've added myself as a `__maintainer__` at the top of relevant files and want to be contacted regarding its maintenance. Unmaintained files may be removed.

##### For developers with write access
- [x] (OPTIONAL) I've updated aeon's [CODEOWNERS](https://github.com/aeon-toolkit/aeon/blob/main/CODEOWNERS) to receive notifications about future changes to these files.


<!--
Thanks for contributing!
-->


chrisholder: Depends on PR #1326 
MatthewMiddlehurst: Yes this should wait until a decision is made there. The failures seem unrelated however, as the dependency is already installed via (I think) the developer install packages.
chrisholder: Will hold merging this until the typing-extension PR has gone through as it uses a feature of that dependency (anyone who just is running aeon with minimum dependency will get errors)

Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index bf9b1945..63ba03af 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -52,7 +52,7 @@ Please go through the checklist below. Please feel free to remove points if they
 
 ##### For new estimators and functions
 - [ ] I've added the estimator to the online [API documentation](https://www.aeon-toolkit.org/en/latest/api_reference.html).
-- [ ] (OPTIONAL) I've added myself as a `__maintainer__` at the top of relevant files and want to be contacted regarding its maintenance. Unmaintained files may be removed.
+- [ ] (OPTIONAL) I've added myself as a `__maintainer__` at the top of relevant files and want to be contacted regarding its maintenance. Unmaintained files may be removed. This is for the full file, and you should not add yourself if you are just making minor changes or do not want to help maintain its contents.
 
 ##### For developers with write access
 - [ ] (OPTIONAL) I've updated aeon's [CODEOWNERS](https://github.com/aeon-toolkit/aeon/blob/main/CODEOWNERS) to receive notifications about future changes to these files.
diff --git a/aeon/base/_base.py b/aeon/base/_base.py
index 55fec1dc..f96f91d6 100644
--- a/aeon/base/_base.py
+++ b/aeon/base/_base.py
@@ -116,6 +116,10 @@ class BaseObject(_BaseEstimator):
         attrs = [attr for attr in dir(self) if "__" not in attr]
         cls_attrs = [attr for attr in dir(type(self))]
         self_attrs = set(attrs).difference(cls_attrs)
+
+        # keep a test flag if it exists
+        self_attrs.discard("_unit_test_flag")
+
         for attr in self_attrs:
             delattr(self, attr)
 
diff --git a/aeon/base/estimator/interval_based/base_interval_forest.py b/aeon/base/estimator/interval_based/base_interval_forest.py
index b5c8e0bf..2f52d924 100644
--- a/aeon/base/estimator/interval_based/base_interval_forest.py
+++ b/aeon/base/estimator/interval_based/base_interval_forest.py
@@ -126,12 +126,6 @@ class BaseIntervalForest(metaclass=ABCMeta):
         Default of 0 means n_estimators are used.
     contract_max_n_estimators : int, default=500
         Max number of estimators when time_limit_in_minutes is set.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -161,7 +155,6 @@ class BaseIntervalForest(metaclass=ABCMeta):
     intervals_ : list of shape (n_estimators) of BaseTransformer
         Stores the interval extraction transformer for all estimators.
 
-
     References
     ----------
     .. [1] H.Deng, G.Runger, E.Tuv and M.Vladimir, "A time series forest for
@@ -188,7 +181,6 @@ class BaseIntervalForest(metaclass=ABCMeta):
         replace_nan=None,
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
-        save_transformed_data="deprecated",
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -205,21 +197,10 @@ class BaseIntervalForest(metaclass=ABCMeta):
         self.replace_nan = replace_nan
         self.time_limit_in_minutes = time_limit_in_minutes
         self.contract_max_n_estimators = contract_max_n_estimators
-        self.save_transformed_data = save_transformed_data
         self.random_state = random_state
         self.n_jobs = n_jobs
         self.parallel_backend = parallel_backend
 
-        # TODO remove 'save_transformed_data' and 'transformed_data_' in v0.8.0
-        self.transformed_data_ = []
-        self.save_transformed_data = save_transformed_data
-        if save_transformed_data != "deprecated":
-            warnings.warn(
-                "the save_transformed_data parameter is deprecated and will be "
-                "removed in v0.8.0. transformed_data_ will also be removed.",
-                stacklevel=2,
-            )
-
         super().__init__()
 
     # if subsampling attributes, an interval_features transformer must contain a
@@ -237,12 +218,11 @@ class BaseIntervalForest(metaclass=ABCMeta):
     transformer_feature_skip = ["transform_features_", "_transform_features"]
 
     def _fit(self, X, y):
-        b = (
-            False
-            if isinstance(self.save_transformed_data, str)
-            else self.save_transformed_data
-        )
-        self.transformed_data_ = self._fit_forest(X, y, save_transformed_data=b)
+        if getattr(self, "_unit_test_flag", False):
+            self._transformed_data = self._fit_forest(X, y, save_transformed_data=True)
+        else:
+            self._fit_forest(X, y)
+
         return self
 
     def _predict(self, X):
diff --git a/aeon/classification/convolution_based/_arsenal.py b/aeon/classification/convolution_based/_arsenal.py
index e403a789..788a7dcc 100644
--- a/aeon/classification/convolution_based/_arsenal.py
+++ b/aeon/classification/convolution_based/_arsenal.py
@@ -7,7 +7,6 @@ __maintainer__ = []
 __all__ = ["Arsenal"]
 
 import time
-import warnings
 
 import numpy as np
 from joblib import Parallel, delayed
@@ -54,12 +53,6 @@ class Arsenal(BaseClassifier):
         Default of 0 means n_estimators is used.
     contract_max_n_estimators : int, default=100
         Max number of estimators when time_limit_in_minutes is set.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     n_jobs : int, default=1
         The number of jobs to run in parallel for both `fit` and `predict`.
         ``-1`` means using all processors.
@@ -131,7 +124,6 @@ class Arsenal(BaseClassifier):
         n_features_per_kernel=4,
         time_limit_in_minutes=0.0,
         contract_max_n_estimators=100,
-        save_transformed_data="deprecated",
         n_jobs=1,
         random_state=None,
     ):
@@ -154,16 +146,6 @@ class Arsenal(BaseClassifier):
 
         self._weight_sum = 0
 
-        # TODO remove 'save_transformed_data' and 'transformed_data_' in v0.8.0
-        self.transformed_data_ = []
-        self.save_transformed_data = save_transformed_data
-        if save_transformed_data != "deprecated":
-            warnings.warn(
-                "the save_transformed_data parameter is deprecated and will be"
-                "removed in v0.8.0. transformed_data_ will also be removed.",
-                stacklevel=2,
-            )
-
         super().__init__()
 
     def _fit(self, X, y):
@@ -186,12 +168,8 @@ class Arsenal(BaseClassifier):
         Changes state by creating a fitted model that updates attributes
         ending in "_" and sets is_fitted flag to True.
         """
-        b = (
-            False
-            if isinstance(self.save_transformed_data, str)
-            else self.save_transformed_data
-        )
-        self.transformed_data_ = self._fit_arsenal(X, y, keep_transformed_data=b)
+        self._fit_arsenal(X, y)
+
         return self
 
     def _predict(self, X) -> np.ndarray:
diff --git a/aeon/classification/feature_based/_fresh_prince.py b/aeon/classification/feature_based/_fresh_prince.py
index 81c64855..17b1ba2c 100644
--- a/aeon/classification/feature_based/_fresh_prince.py
+++ b/aeon/classification/feature_based/_fresh_prince.py
@@ -7,7 +7,6 @@ RotationForestClassifier.
 __maintainer__ = []
 __all__ = ["FreshPRINCEClassifier"]
 
-import warnings
 
 import numpy as np
 from sklearn.utils import check_random_state
@@ -32,12 +31,6 @@ class FreshPRINCEClassifier(BaseClassifier):
         "comprehensive".
     n_estimators : int, default=200
         Number of estimators for the RotationForestClassifier ensemble.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     verbose : int, default=0
         Level of output printed to the console (for information only).
     n_jobs : int, default=1
@@ -81,7 +74,6 @@ class FreshPRINCEClassifier(BaseClassifier):
         self,
         default_fc_parameters="comprehensive",
         n_estimators=200,
-        save_transformed_data="deprecated",
         verbose=0,
         n_jobs=1,
         chunksize=None,
@@ -103,16 +95,6 @@ class FreshPRINCEClassifier(BaseClassifier):
         self._rotf = None
         self._tsfresh = None
 
-        # TODO remove 'save_transformed_data' and 'transformed_data_' in v0.8.0
-        self.transformed_data_ = []
-        self.save_transformed_data = save_transformed_data
-        if save_transformed_data != "deprecated":
-            warnings.warn(
-                "the save_transformed_data parameter is deprecated and will be"
-                "removed in v0.8.0. transformed_data_ will also be removed.",
-                stacklevel=2,
-            )
-
         super().__init__()
 
     def _fit(self, X, y):
@@ -135,14 +117,8 @@ class FreshPRINCEClassifier(BaseClassifier):
         Changes state by creating a fitted model that updates attributes
         ending in "_" and sets is_fitted flag to True.
         """
-        b = (
-            False
-            if isinstance(self.save_transformed_data, str)
-            else self.save_transformed_data
-        )
-        self.transformed_data_ = self._fit_fresh_prince(X, y)
-        if not b:
-            self.transformed_data_ = []
+        self._fit_fresh_prince(X, y)
+
         return self
 
     def _predict(self, X) -> np.ndarray:
@@ -188,15 +164,15 @@ class FreshPRINCEClassifier(BaseClassifier):
         )
 
     def _fit_predict_proba(self, X, y) -> np.ndarray:
-        Xt = self._fit_fresh_prince(X, y)
+        Xt = self._fit_fresh_prince(X, y, save_rotf_data=True)
         return self._rotf._get_train_probs(Xt, y)
 
-    def _fit_fresh_prince(self, X, y):
+    def _fit_fresh_prince(self, X, y, save_rotf_data=False):
         self.n_cases_, self.n_channels_, self.n_timepoints_ = X.shape
 
         self._rotf = RotationForestClassifier(
             n_estimators=self.n_estimators,
-            save_transformed_data=self.save_transformed_data,
+            save_transformed_data=save_rotf_data,
             n_jobs=self._n_jobs,
             random_state=self.random_state,
         )
diff --git a/aeon/classification/interval_based/_cif.py b/aeon/classification/interval_based/_cif.py
index c8ee534c..f18a6724 100644
--- a/aeon/classification/interval_based/_cif.py
+++ b/aeon/classification/interval_based/_cif.py
@@ -96,12 +96,6 @@ class CanonicalIntervalForestClassifier(BaseIntervalForest, BaseClassifier):
         Wraps the C based pycatch22 implementation for aeon.
         (https://github.com/DynamicsAndNeuralSystems/pycatch22). This requires the
         ``pycatch22`` package to be installed if True.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -134,10 +128,6 @@ class CanonicalIntervalForestClassifier(BaseIntervalForest, BaseClassifier):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of TransformerMixin
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all classifiers. Only saved when
-        save_transformed_data is true.
 
     See Also
     --------
@@ -188,7 +178,6 @@ class CanonicalIntervalForestClassifier(BaseIntervalForest, BaseClassifier):
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
         use_pycatch22=False,
-        save_transformed_data="deprecated",
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -222,7 +211,6 @@ class CanonicalIntervalForestClassifier(BaseIntervalForest, BaseClassifier):
             replace_nan=replace_nan,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
diff --git a/aeon/classification/interval_based/_drcif.py b/aeon/classification/interval_based/_drcif.py
index c0c8e4e1..cb339d8b 100644
--- a/aeon/classification/interval_based/_drcif.py
+++ b/aeon/classification/interval_based/_drcif.py
@@ -113,12 +113,6 @@ class DrCIFClassifier(BaseIntervalForest, BaseClassifier):
     use_pyfftw : bool, default=False
         Whether to use the pyfftw library for FFT calculations. Requires the pyfftw
         package to be installed.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -151,10 +145,6 @@ class DrCIFClassifier(BaseIntervalForest, BaseClassifier):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of TransformerMixin
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all classifiers. Only saved when
-        save_transformed_data is true.
 
     See Also
     --------
@@ -206,7 +196,6 @@ class DrCIFClassifier(BaseIntervalForest, BaseClassifier):
         contract_max_n_estimators=500,
         use_pycatch22=False,
         use_pyfftw=False,
-        save_transformed_data="deprecated",
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -257,7 +246,6 @@ class DrCIFClassifier(BaseIntervalForest, BaseClassifier):
             replace_nan=replace_nan,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
diff --git a/aeon/classification/interval_based/_interval_forest.py b/aeon/classification/interval_based/_interval_forest.py
index 8ca3f3d1..aac5ee32 100644
--- a/aeon/classification/interval_based/_interval_forest.py
+++ b/aeon/classification/interval_based/_interval_forest.py
@@ -113,12 +113,6 @@ class IntervalForestClassifier(BaseIntervalForest, BaseClassifier):
         Default of 0 means n_estimators are used.
     contract_max_n_estimators : int, default=500
         Max number of estimators when time_limit_in_minutes is set.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -147,10 +141,6 @@ class IntervalForestClassifier(BaseIntervalForest, BaseClassifier):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of BaseTransformer
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all classifiers. Only saved when
-        save_transformed_data is true.
 
     References
     ----------
@@ -197,7 +187,6 @@ class IntervalForestClassifier(BaseIntervalForest, BaseClassifier):
         replace_nan=None,
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
-        save_transformed_data="deprecated",
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -215,7 +204,6 @@ class IntervalForestClassifier(BaseIntervalForest, BaseClassifier):
             replace_nan=replace_nan,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
diff --git a/aeon/classification/interval_based/_rise.py b/aeon/classification/interval_based/_rise.py
index 23f31ca0..34ef2a3a 100644
--- a/aeon/classification/interval_based/_rise.py
+++ b/aeon/classification/interval_based/_rise.py
@@ -63,12 +63,6 @@ class RandomIntervalSpectralEnsembleClassifier(BaseIntervalForest, BaseClassifie
     use_pyfftw : bool, default=False
         Whether to use the pyfftw library for FFT calculations. Requires the pyfftw
         package to be installed.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -101,10 +95,6 @@ class RandomIntervalSpectralEnsembleClassifier(BaseIntervalForest, BaseClassifie
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of TransformerMixin
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all estimators. Only saved when
-        save_transformed_data is true.
 
     See Also
     --------
@@ -156,7 +146,6 @@ class RandomIntervalSpectralEnsembleClassifier(BaseIntervalForest, BaseClassifie
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
         use_pyfftw=False,
-        save_transformed_data="deprecated",
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -193,7 +182,6 @@ class RandomIntervalSpectralEnsembleClassifier(BaseIntervalForest, BaseClassifie
             replace_nan=replace_nan,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
diff --git a/aeon/classification/interval_based/_stsf.py b/aeon/classification/interval_based/_stsf.py
index 41c20ac9..b720975c 100644
--- a/aeon/classification/interval_based/_stsf.py
+++ b/aeon/classification/interval_based/_stsf.py
@@ -63,12 +63,6 @@ class SupervisedTimeSeriesForest(BaseIntervalForest, BaseClassifier):
     use_pyfftw : bool, default=True
         Whether to use the pyfftw library for FFT calculations. Requires the pyfftw
         package to be installed.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -101,10 +95,6 @@ class SupervisedTimeSeriesForest(BaseIntervalForest, BaseClassifier):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of TransformerMixin
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all estimators. Only saved when
-        save_transformed_data is true.
 
     Notes
     -----
@@ -146,7 +136,6 @@ class SupervisedTimeSeriesForest(BaseIntervalForest, BaseClassifier):
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
         use_pyfftw=False,
-        save_transformed_data="deprecated",
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -184,7 +173,6 @@ class SupervisedTimeSeriesForest(BaseIntervalForest, BaseClassifier):
             replace_nan=0,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
diff --git a/aeon/classification/interval_based/_tsf.py b/aeon/classification/interval_based/_tsf.py
index bdd04335..1a9397e0 100644
--- a/aeon/classification/interval_based/_tsf.py
+++ b/aeon/classification/interval_based/_tsf.py
@@ -81,12 +81,6 @@ class TimeSeriesForestClassifier(BaseIntervalForest, BaseClassifier):
         Default of 0 means n_estimators are used.
     contract_max_n_estimators : int, default=500
         Max number of estimators when time_limit_in_minutes is set.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -119,10 +113,6 @@ class TimeSeriesForestClassifier(BaseIntervalForest, BaseClassifier):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of BaseTransformer
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all classifiers. Only saved when
-        save_transformed_data is true.
 
     Notes
     -----
@@ -165,7 +155,6 @@ class TimeSeriesForestClassifier(BaseIntervalForest, BaseClassifier):
         max_interval_length=np.inf,
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
-        save_transformed_data="deprecated",
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -188,7 +177,6 @@ class TimeSeriesForestClassifier(BaseIntervalForest, BaseClassifier):
             replace_nan=replace_nan,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
diff --git a/aeon/classification/shapelet_based/_stc.py b/aeon/classification/shapelet_based/_stc.py
index 5e33bf24..1ae9dd5b 100644
--- a/aeon/classification/shapelet_based/_stc.py
+++ b/aeon/classification/shapelet_based/_stc.py
@@ -7,7 +7,6 @@ transform then builds (by default) a rotation forest classifier on the output.
 __maintainer__ = []
 __all__ = ["ShapeletTransformClassifier"]
 
-import warnings
 
 import numpy as np
 from sklearn.model_selection import cross_val_predict
@@ -59,12 +58,6 @@ class ShapeletTransformClassifier(BaseClassifier):
     contract_max_n_shapelet_samples : int, default=np.inf
         Max number of shapelets to extract when contracting the transform with
         ``transform_limit_in_minutes`` or ``time_limit_in_minutes``.
-    save_transformed_data : bool, default="deprecated"
-        Save the data transformed in ``fit``.
-
-        Deprecated and will be removed in v0.8.0. Use ``fit_predict`` and
-        ``fit_predict_proba`` to generate train estimates instead.
-        ``transformed_data_`` will also be removed.
     n_jobs : int, default=1
         The number of jobs to run in parallel for both ``fit`` and ``predict``.
         `-1` means using all processors.
@@ -146,7 +139,6 @@ class ShapeletTransformClassifier(BaseClassifier):
         transform_limit_in_minutes=0,
         time_limit_in_minutes=0,
         contract_max_n_shapelet_samples=np.inf,
-        save_transformed_data="deprecated",
         n_jobs=1,
         batch_size=100,
         random_state=None,
@@ -173,16 +165,6 @@ class ShapeletTransformClassifier(BaseClassifier):
         self._transform_limit_in_minutes = 0
         self._classifier_limit_in_minutes = 0
 
-        # TODO remove 'save_transformed_data' and 'transformed_data_' in v0.8.0
-        self.transformed_data_ = []
-        self.save_transformed_data = save_transformed_data
-        if save_transformed_data != "deprecated":
-            warnings.warn(
-                "the save_transformed_data parameter is deprecated and will be"
-                "removed in v0.8.0. transformed_data_ will also be removed.",
-                stacklevel=2,
-            )
-
         super().__init__()
 
     def _fit(self, X, y):
@@ -205,14 +187,8 @@ class ShapeletTransformClassifier(BaseClassifier):
         Changes state by creating a fitted model that updates attributes
         ending in "_".
         """
-        b = (
-            False
-            if isinstance(self.save_transformed_data, str)
-            else self.save_transformed_data
-        )
-        self.transformed_data_ = self._fit_stc(X, y)
-        if not b:
-            self.transformed_data_ = []
+        self._fit_stc(X, y)
+
         return self
 
     def _predict(self, X) -> np.ndarray:
@@ -267,7 +243,7 @@ class ShapeletTransformClassifier(BaseClassifier):
         )
 
     def _fit_predict_proba(self, X, y) -> np.ndarray:
-        Xt = self._fit_stc(X, y)
+        Xt = self._fit_stc(X, y, save_rotf_data=True)
 
         if (isinstance(self.estimator, RotationForestClassifier)) or (
             self.estimator is None
@@ -300,7 +276,7 @@ class ShapeletTransformClassifier(BaseClassifier):
                 n_jobs=self._n_jobs,
             )
 
-    def _fit_stc(self, X, y):
+    def _fit_stc(self, X, y, save_rotf_data=False):
         self.n_cases_, self.n_channels_, self.n_timepoints_ = X.shape
 
         if self.time_limit_in_minutes > 0:
@@ -329,7 +305,7 @@ class ShapeletTransformClassifier(BaseClassifier):
         )
 
         if isinstance(self._estimator, RotationForestClassifier):
-            self._estimator.save_transformed_data = self.save_transformed_data
+            self._estimator.save_transformed_data = save_rotf_data
 
         m = getattr(self._estimator, "n_jobs", None)
         if m is not None:
diff --git a/aeon/clustering/averaging/_averaging.py b/aeon/clustering/averaging/_averaging.py
index 5d6c7957..b306ec81 100644
--- a/aeon/clustering/averaging/_averaging.py
+++ b/aeon/clustering/averaging/_averaging.py
@@ -2,7 +2,7 @@
 
 __maintainer__ = []
 
-from typing import Callable, Dict
+from typing import Callable, Dict, Union
 
 import numpy as np
 
@@ -34,7 +34,7 @@ _AVERAGE_DICT = {
 
 
 def _resolve_average_callable(
-    averaging_method: [str, Callable[[np.ndarray], np.ndarray]]
+    averaging_method: Union[str, Callable[[np.ndarray, Dict], np.ndarray]]
 ) -> Callable[[np.ndarray, Dict], np.ndarray]:
     """Resolve a string or callable to a averaging callable.
 
@@ -57,5 +57,4 @@ def _resolve_average_callable(
                 _AVERAGE_DICT.keys(),
             )
         return _AVERAGE_DICT[averaging_method]
-
     return averaging_method
diff --git a/aeon/clustering/averaging/_barycenter_averaging.py b/aeon/clustering/averaging/_barycenter_averaging.py
index ef8db079..71ab6c7b 100644
--- a/aeon/clustering/averaging/_barycenter_averaging.py
+++ b/aeon/clustering/averaging/_barycenter_averaging.py
@@ -1,6 +1,6 @@
 __maintainer__ = []
 
-from typing import Tuple
+from typing import Optional, Tuple, Union
 
 import numpy as np
 from numba import njit
@@ -23,7 +23,7 @@ from aeon.distances import (
 
 def _medoids(
     X: np.ndarray,
-    precomputed_pairwise_distance: np.ndarray = None,
+    precomputed_pairwise_distance: Union[np.ndarray, None] = None,
     distance: str = "dtw",
     **kwargs,
 ):
@@ -46,7 +46,7 @@ def elastic_barycenter_average(
     distance: str = "dtw",
     max_iters: int = 30,
     tol=1e-5,
-    precomputed_medoids_pairwise_distance: np.ndarray = None,
+    precomputed_medoids_pairwise_distance: Optional[np.ndarray] = None,
     verbose: bool = False,
     **kwargs,
 ) -> np.ndarray:
@@ -137,9 +137,9 @@ def _ba_update(
     center: np.ndarray,
     X: np.ndarray,
     distance: str = "dtw",
-    window: float = None,
+    window: Union[float, None] = None,
     g: float = 0.0,
-    epsilon: float = None,
+    epsilon: Union[float, None] = None,
     nu: float = 0.001,
     lmbda: float = 1.0,
     independent: bool = True,
diff --git a/aeon/datatypes/_adapter/gluonts_to_pd_multiindex.py b/aeon/datatypes/_adapter/gluonts_to_pd_multiindex.py
index edb8ab8d..6331b58f 100644
--- a/aeon/datatypes/_adapter/gluonts_to_pd_multiindex.py
+++ b/aeon/datatypes/_adapter/gluonts_to_pd_multiindex.py
@@ -6,7 +6,7 @@ from aeon.datatypes import convert_to
 
 # TODO: move in v0.9.0
 @deprecated(
-    version="0.6.0",
+    version="0.8.0",
     reason="convert_gluonts_result_to_multiindex will be moved from datatypes in "
     "to utils.conversion in v0.9.0",
     category=FutureWarning,
diff --git a/aeon/datatypes/_adapter/pd_multiindex_to_list_dataset.py b/aeon/datatypes/_adapter/pd_multiindex_to_list_dataset.py
index fb4b4785..20c9e77c 100644
--- a/aeon/datatypes/_adapter/pd_multiindex_to_list_dataset.py
+++ b/aeon/datatypes/_adapter/pd_multiindex_to_list_dataset.py
@@ -5,7 +5,7 @@ from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 # TODO: move in v0.9.0
 @deprecated(
-    version="0.6.0",
+    version="0.8.0",
     reason="convert_from_multiindex_to_listdataset will be moved from datatypes to "
     "utils.conversion in v0.9.0",
     category=FutureWarning,
diff --git a/aeon/distances/_adtw.py b/aeon/distances/_adtw.py
index 8df23ce1..618fb778 100644
--- a/aeon/distances/_adtw.py
+++ b/aeon/distances/_adtw.py
@@ -2,7 +2,7 @@ r"""Amercing dynamic time warping (ADTW) between two time series."""
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -17,8 +17,8 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 def adtw_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
-    itakura_max_slope: float = None,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
     warp_penalty: float = 1.0,
 ) -> float:
     r"""Compute the ADTW distance between two time series.
@@ -94,8 +94,8 @@ def adtw_distance(
 def adtw_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
-    itakura_max_slope: float = None,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
     warp_penalty: float = 1.0,
 ) -> np.ndarray:
     r"""Compute the ADTW cost matrix between two time series.
@@ -198,9 +198,9 @@ def _adtw_cost_matrix(
 @njit(cache=True, fastmath=True)
 def adtw_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
-    itakura_max_slope: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
     warp_penalty: float = 1.0,
 ) -> np.ndarray:
     r"""Compute the ADTW pairwise distance between a set of time series.
@@ -260,7 +260,7 @@ def adtw_pairwise_distance(
            [ 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> adtw_pairwise_distance(X, y_univariate)
     array([[300.],
            [147.],
@@ -273,7 +273,7 @@ def adtw_pairwise_distance(
         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")
+        raise ValueError("x and y must be 1D, 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
@@ -282,7 +282,10 @@ def adtw_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _adtw_pairwise_distance(
-    X: np.ndarray, window: float, itakura_max_slope: float, warp_penalty: float
+    X: np.ndarray,
+    window: Optional[float],
+    itakura_max_slope: Optional[float],
+    warp_penalty: float,
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -302,8 +305,8 @@ def _adtw_pairwise_distance(
 def _adtw_from_multiple_to_multiple_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float,
-    itakura_max_slope: float,
+    window: Optional[float],
+    itakura_max_slope: Optional[float],
     warp_penalty: float,
 ) -> np.ndarray:
     n_cases = x.shape[0]
@@ -323,8 +326,8 @@ def _adtw_from_multiple_to_multiple_distance(
 def adtw_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
-    itakura_max_slope: float = None,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
     warp_penalty: float = 1.0,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the ADTW alignment path between two time series.
diff --git a/aeon/distances/_bounding_matrix.py b/aeon/distances/_bounding_matrix.py
index 473a18c2..f5b58e3a 100644
--- a/aeon/distances/_bounding_matrix.py
+++ b/aeon/distances/_bounding_matrix.py
@@ -1,6 +1,7 @@
 __maintainer__ = []
 
 import math
+from typing import Optional
 
 import numpy as np
 from numba import njit
@@ -8,7 +9,10 @@ from numba import njit
 
 @njit(cache=True)
 def create_bounding_matrix(
-    x_size: int, y_size: int, window: float = None, itakura_max_slope: float = None
+    x_size: int,
+    y_size: int,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ):
     """Create a bounding matrix for an elastic distance.
 
diff --git a/aeon/distances/_ddtw.py b/aeon/distances/_ddtw.py
index 1806a101..96f0ed62 100644
--- a/aeon/distances/_ddtw.py
+++ b/aeon/distances/_ddtw.py
@@ -2,7 +2,7 @@
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -14,7 +14,10 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 
 @njit(cache=True, fastmath=True)
 def ddtw_distance(
-    x: np.ndarray, y: np.ndarray, window: float = None, itakura_max_slope: float = None
+    x: np.ndarray,
+    y: np.ndarray,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Compute the DDTW distance between two time series.
 
@@ -89,7 +92,10 @@ def ddtw_distance(
 
 @njit(cache=True, fastmath=True)
 def ddtw_cost_matrix(
-    x: np.ndarray, y: np.ndarray, window: float = None, itakura_max_slope: float = None
+    x: np.ndarray,
+    y: np.ndarray,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     r"""Compute the DDTW cost matrix between two time series.
 
@@ -158,9 +164,9 @@ def ddtw_cost_matrix(
 @njit(cache=True, fastmath=True)
 def ddtw_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
-    itakura_max_slope: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the DDTW pairwise distance between a set of time series.
 
@@ -211,7 +217,7 @@ def ddtw_pairwise_distance(
            [4.73062500e+02, 1.34560000e+04, 5.40078010e+07]])
 
     >>> X = np.array([[[10, 22, 399]],[[41, 500, 1316]], [[117, 18, 9]]])
-    >>> y_univariate = np.array([[100, 11, 199],[10, 15, 26], [170, 108, 1119]])
+    >>> y_univariate = np.array([100, 11, 199])
     >>> ddtw_pairwise_distance(X, y_univariate)
     array([[ 15129.    ],
            [322624.    ],
@@ -224,14 +230,14 @@ def ddtw_pairwise_distance(
         if X.ndim == 2:
             _X = X.reshape((X.shape[0], 1, X.shape[1]))
             return _ddtw_pairwise_distance(_X, window, itakura_max_slope)
-        raise ValueError("x and y must be 2D or 3D arrays")
+        raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     _x, _y = reshape_pairwise_to_multiple(X, y)
     return _ddtw_from_multiple_to_multiple_distance(_x, _y, window, itakura_max_slope)
 
 
 @njit(cache=True, fastmath=True)
 def _ddtw_pairwise_distance(
-    X: np.ndarray, window: float, itakura_max_slope: float
+    X: np.ndarray, window: Optional[float], itakura_max_slope: Optional[float]
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -255,7 +261,10 @@ def _ddtw_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _ddtw_from_multiple_to_multiple_distance(
-    x: np.ndarray, y: np.ndarray, window: float, itakura_max_slope: float
+    x: np.ndarray,
+    y: np.ndarray,
+    window: Optional[float],
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
@@ -281,7 +290,10 @@ def _ddtw_from_multiple_to_multiple_distance(
 
 @njit(cache=True, fastmath=True)
 def ddtw_alignment_path(
-    x: np.ndarray, y: np.ndarray, window: float = None, itakura_max_slope: float = None
+    x: np.ndarray,
+    y: np.ndarray,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the ddtw alignment path between two time series.
 
diff --git a/aeon/distances/_distance.py b/aeon/distances/_distance.py
index 1083f4b4..f8f13ac9 100644
--- a/aeon/distances/_distance.py
+++ b/aeon/distances/_distance.py
@@ -1,8 +1,9 @@
 __maintainer__ = []
 
-from typing import Any, Callable, List, Tuple, Union
+from typing import Any, Callable, List, Optional, Tuple, TypedDict, Union
 
 import numpy as np
+from typing_extensions import Unpack
 
 from aeon.distances._adtw import (
     adtw_alignment_path,
@@ -78,6 +79,26 @@ from aeon.distances._wdtw import (
 )
 from aeon.distances.mpdist import mpdist
 
+
+class DistanceKwargs(TypedDict, total=False):
+    window: Optional[float]
+    itakura_max_slope: Optional[float]
+    p: float
+    w: np.ndarray
+    g: float
+    descriptor: str
+    reach: int
+    epsilon: float
+    g_arr: np.ndarray
+    nu: float
+    lmbda: float
+    independent: bool
+    c: float
+    warp_penalty: float
+    standardize: bool
+    m: int
+
+
 DistanceFunction = Callable[[np.ndarray, np.ndarray, Any], float]
 AlignmentPathFunction = Callable[
     [np.ndarray, np.ndarray, Any], Tuple[List[Tuple[int, int]], float]
@@ -90,7 +111,7 @@ def distance(
     x: np.ndarray,
     y: np.ndarray,
     metric: Union[str, DistanceFunction],
-    **kwargs: Any,
+    **kwargs: Unpack[DistanceKwargs],
 ) -> float:
     """Compute the distance between two time series.
 
@@ -214,14 +235,14 @@ def distance(
             kwargs.get("itakura_max_slope"),
         )
     elif metric == "mpdist":
-        return mpdist(x, y, **kwargs)
+        return mpdist(x, y, kwargs.get("m", 0))
     elif metric == "adtw":
         return adtw_distance(
             x,
             y,
-            kwargs.get("window"),
-            kwargs.get("itakura_max_slope"),
-            kwargs.get("warp_penalty", 1.0),
+            itakura_max_slope=kwargs.get("itakura_max_slope"),
+            window=kwargs.get("window"),
+            warp_penalty=kwargs.get("warp_penalty", 1.0),
         )
     elif metric == "sbd":
         return sbd_distance(x, y, kwargs.get("standardize", True))
@@ -233,9 +254,9 @@ def distance(
 
 def pairwise_distance(
     x: np.ndarray,
-    y: np.ndarray = None,
-    metric: Union[str, DistanceFunction] = None,
-    **kwargs: Any,
+    y: Optional[np.ndarray] = None,
+    metric: Union[str, DistanceFunction, None] = None,
+    **kwargs: Unpack[DistanceKwargs],
 ) -> np.ndarray:
     """Compute the pairwise distance matrix between two time series.
 
@@ -288,7 +309,7 @@ def pairwise_distance(
            [ 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> pairwise_distance(X, y_univariate, metric='dtw')
     array([[300.],
            [147.],
@@ -399,11 +420,13 @@ def pairwise_distance(
 
 
 def _custom_func_pairwise(
-    X: Union[np.ndarray, List[np.ndarray]],
-    y: Union[np.ndarray, List[np.ndarray]] = None,
-    dist_func: DistanceFunction = None,
-    **kwargs: Any,
+    X: Optional[Union[np.ndarray, List[np.ndarray]]],
+    y: Optional[Union[np.ndarray, List[np.ndarray]]] = None,
+    dist_func: Union[DistanceFunction, None] = None,
+    **kwargs: Unpack[DistanceKwargs],
 ) -> np.ndarray:
+    if dist_func is None:
+        raise ValueError("dist_func must be a callable")
     if y is None:
         # To self
         if isinstance(X, np.ndarray):
@@ -426,7 +449,9 @@ def _custom_func_pairwise(
 
 
 def _custom_pairwise_distance(
-    X: Union[np.ndarray, List[np.ndarray]], dist_func: DistanceFunction, **kwargs
+    X: Union[np.ndarray, List[np.ndarray]],
+    dist_func: DistanceFunction,
+    **kwargs: Unpack[DistanceKwargs],
 ) -> np.ndarray:
     n_cases = len(X)
     distances = np.zeros((n_cases, n_cases))
@@ -443,7 +468,7 @@ def _custom_from_multiple_to_multiple_distance(
     x: Union[np.ndarray, List[np.ndarray]],
     y: Union[np.ndarray, List[np.ndarray]],
     dist_func: DistanceFunction,
-    **kwargs,
+    **kwargs: Unpack[DistanceKwargs],
 ) -> np.ndarray:
     n_cases = len(x)
     m_cases = len(y)
@@ -459,7 +484,7 @@ def alignment_path(
     x: np.ndarray,
     y: np.ndarray,
     metric: str,
-    **kwargs: Any,
+    **kwargs: Unpack[DistanceKwargs],
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the alignment path and distance between two time series.
 
@@ -594,7 +619,7 @@ def cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
     metric: str,
-    **kwargs: Any,
+    **kwargs: Unpack[DistanceKwargs],
 ) -> np.ndarray:
     """Compute the alignment path and distance between two time series.
 
diff --git a/aeon/distances/_dtw.py b/aeon/distances/_dtw.py
index b67ff4b7..4a6e317e 100644
--- a/aeon/distances/_dtw.py
+++ b/aeon/distances/_dtw.py
@@ -2,7 +2,7 @@ r"""Dynamic time warping (DTW) between two time series."""
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -15,7 +15,10 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 
 @njit(cache=True, fastmath=True)
 def dtw_distance(
-    x: np.ndarray, y: np.ndarray, window: float = None, itakura_max_slope: float = None
+    x: np.ndarray,
+    y: np.ndarray,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Compute the DTW distance between two time series.
 
@@ -125,7 +128,10 @@ def dtw_distance(
 
 @njit(cache=True, fastmath=True)
 def dtw_cost_matrix(
-    x: np.ndarray, y: np.ndarray, window: float = None, itakura_max_slope: float = None
+    x: np.ndarray,
+    y: np.ndarray,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     r"""Compute the DTW cost matrix between two time series.
 
@@ -223,9 +229,9 @@ def _dtw_cost_matrix(
 @njit(cache=True, fastmath=True)
 def dtw_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
-    itakura_max_slope: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     r"""Compute the DTW pairwise distance between a set of time series.
 
@@ -294,7 +300,7 @@ def dtw_pairwise_distance(
            [ 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> dtw_pairwise_distance(X, y_univariate)
     array([[300.],
            [147.],
@@ -307,14 +313,14 @@ def dtw_pairwise_distance(
         if X.ndim == 2:
             _X = X.reshape((X.shape[0], 1, X.shape[1]))
             return _dtw_pairwise_distance(_X, window, itakura_max_slope)
-        raise ValueError("x and y must be 2D or 3D arrays")
+        raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     _x, _y = reshape_pairwise_to_multiple(X, y)
     return _dtw_from_multiple_to_multiple_distance(_x, _y, window, itakura_max_slope)
 
 
 @njit(cache=True, fastmath=True)
 def _dtw_pairwise_distance(
-    X: np.ndarray, window: float, itakura_max_slope: float
+    X: np.ndarray, window: Optional[float], itakura_max_slope: Optional[float]
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -332,7 +338,10 @@ def _dtw_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _dtw_from_multiple_to_multiple_distance(
-    x: np.ndarray, y: np.ndarray, window: float, itakura_max_slope: float
+    x: np.ndarray,
+    y: np.ndarray,
+    window: Optional[float],
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
@@ -349,7 +358,10 @@ def _dtw_from_multiple_to_multiple_distance(
 
 @njit(cache=True, fastmath=True)
 def dtw_alignment_path(
-    x: np.ndarray, y: np.ndarray, window: float = None, itakura_max_slope: float = None
+    x: np.ndarray,
+    y: np.ndarray,
+    window: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the DTW alignment path between two time series.
 
diff --git a/aeon/distances/_edr.py b/aeon/distances/_edr.py
index dc1aeed0..a2861694 100644
--- a/aeon/distances/_edr.py
+++ b/aeon/distances/_edr.py
@@ -2,7 +2,7 @@
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -20,9 +20,9 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 def edr_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
-    epsilon: float = None,
-    itakura_max_slope: float = None,
+    window: Optional[float] = None,
+    epsilon: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Compute the EDR distance between two time series.
 
@@ -115,9 +115,9 @@ def edr_distance(
 def edr_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
-    epsilon: float = None,
-    itakura_max_slope: float = None,
+    window: Optional[float] = None,
+    epsilon: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the EDR cost matrix between two time series.
 
@@ -182,7 +182,10 @@ def edr_cost_matrix(
 
 @njit(cache=True, fastmath=True)
 def _edr_distance(
-    x: np.ndarray, y: np.ndarray, bounding_matrix: np.ndarray, epsilon: float = None
+    x: np.ndarray,
+    y: np.ndarray,
+    bounding_matrix: np.ndarray,
+    epsilon: Optional[float] = None,
 ) -> float:
     distance = _edr_cost_matrix(x, y, bounding_matrix, epsilon)[
         x.shape[1] - 1, y.shape[1] - 1
@@ -192,12 +195,15 @@ def _edr_distance(
 
 @njit(cache=True, fastmath=True)
 def _edr_cost_matrix(
-    x: np.ndarray, y: np.ndarray, bounding_matrix: np.ndarray, epsilon: float = None
+    x: np.ndarray,
+    y: np.ndarray,
+    bounding_matrix: np.ndarray,
+    epsilon: Optional[float] = None,
 ) -> np.ndarray:
     x_size = x.shape[1]
     y_size = y.shape[1]
     if epsilon is None:
-        epsilon = max(np.std(x), np.std(y)) / 4
+        epsilon = float(max(np.std(x), np.std(y))) / 4
 
     cost_matrix = np.zeros((x_size + 1, y_size + 1))
 
@@ -219,10 +225,10 @@ def _edr_cost_matrix(
 @njit(cache=True, fastmath=True)
 def edr_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
-    epsilon: float = None,
-    itakura_max_slope: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
+    epsilon: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the pairwise EDR distance between a set of time series.
 
@@ -278,7 +284,7 @@ def edr_pairwise_distance(
            [1., 1., 1.]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> edr_pairwise_distance(X, y_univariate)
     array([[1.],
            [1.],
@@ -291,7 +297,7 @@ def edr_pairwise_distance(
         if X.ndim == 2:
             _X = X.reshape((X.shape[0], 1, X.shape[1]))
             return _edr_pairwise_distance(_X, window, epsilon, itakura_max_slope)
-        raise ValueError("x and y must be 2D or 3D arrays")
+        raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     _x, _y = reshape_pairwise_to_multiple(X, y)
     return _edr_from_multiple_to_multiple_distance(
         _x, _y, window, epsilon, itakura_max_slope
@@ -300,7 +306,10 @@ def edr_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _edr_pairwise_distance(
-    X: np.ndarray, window: float, epsilon: float = None, itakura_max_slope: float = None
+    X: np.ndarray,
+    window: Optional[float] = None,
+    epsilon: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -320,9 +329,9 @@ def _edr_pairwise_distance(
 def _edr_from_multiple_to_multiple_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float,
-    epsilon: float = None,
-    itakura_max_slope: float = None,
+    window: Optional[float] = None,
+    epsilon: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
@@ -341,9 +350,9 @@ def _edr_from_multiple_to_multiple_distance(
 def edr_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
-    epsilon: float = None,
-    itakura_max_slope: float = None,
+    window: Optional[float] = None,
+    epsilon: Optional[float] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the EDR alignment path between two time series.
 
diff --git a/aeon/distances/_erp.py b/aeon/distances/_erp.py
index 9f111a1e..6b00f3ec 100644
--- a/aeon/distances/_erp.py
+++ b/aeon/distances/_erp.py
@@ -2,7 +2,7 @@ r"""Edit real penalty (erp) distance between two time series."""
 
 __maintainer__ = []
 
-from typing import List, Tuple, Union
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -20,10 +20,10 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 def erp_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     g: float = 0.0,
-    g_arr: np.ndarray = None,
-    itakura_max_slope: float = None,
+    g_arr: Optional[np.ndarray] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Compute the ERP distance between two time series.
 
@@ -115,10 +115,10 @@ def erp_distance(
 def erp_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
-    g: Union[float, np.ndarray] = 0.0,
-    g_arr: np.ndarray = None,
-    itakura_max_slope: float = None,
+    window: Optional[float] = None,
+    g: float = 0.0,
+    g_arr: Optional[np.ndarray] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the ERP cost matrix between two time series.
 
@@ -190,7 +190,7 @@ def _erp_distance(
     y: np.ndarray,
     bounding_matrix: np.ndarray,
     g: float,
-    g_arr: np.ndarray,
+    g_arr: Optional[np.ndarray],
 ) -> float:
     return _erp_cost_matrix(x, y, bounding_matrix, g, g_arr)[
         x.shape[1] - 1, y.shape[1] - 1
@@ -203,7 +203,7 @@ def _erp_cost_matrix(
     y: np.ndarray,
     bounding_matrix: np.ndarray,
     g: float,
-    g_arr: np.ndarray,
+    g_arr: Optional[np.ndarray],
 ) -> np.ndarray:
     x_size = x.shape[1]
     y_size = y.shape[1]
@@ -231,7 +231,7 @@ def _erp_cost_matrix(
 
 @njit(cache=True, fastmath=True)
 def _precompute_g(
-    x: np.ndarray, g: float, g_array: np.ndarray
+    x: np.ndarray, g: float, g_array: Optional[np.ndarray]
 ) -> Tuple[np.ndarray, float]:
     gx_distance = np.zeros(x.shape[1])
     if g_array is None:
@@ -252,11 +252,11 @@ def _precompute_g(
 @njit(cache=True, fastmath=True)
 def erp_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
     g: float = 0.0,
-    g_arr: np.ndarray = None,
-    itakura_max_slope: float = None,
+    g_arr: Optional[np.ndarray] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the ERP pairwise distance between a set of time series.
 
@@ -318,7 +318,7 @@ def erp_pairwise_distance(
            [12., 21., 30.]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> erp_pairwise_distance(X, y_univariate)
     array([[30.],
            [21.],
@@ -331,7 +331,7 @@ def erp_pairwise_distance(
         if X.ndim == 2:
             _X = X.reshape((X.shape[0], 1, X.shape[1]))
             return _erp_pairwise_distance(_X, window, g, g_arr, itakura_max_slope)
-        raise ValueError("x and y must be 2D or 3D arrays")
+        raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     _x, _y = reshape_pairwise_to_multiple(X, y)
     return _erp_from_multiple_to_multiple_distance(
         _x, _y, window, g, g_arr, itakura_max_slope
@@ -341,10 +341,10 @@ def erp_pairwise_distance(
 @njit(cache=True, fastmath=True)
 def _erp_pairwise_distance(
     X: np.ndarray,
-    window: float,
+    window: Optional[float],
     g: float,
-    g_arr: np.ndarray,
-    itakura_max_slope: float,
+    g_arr: Optional[np.ndarray],
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -364,10 +364,10 @@ def _erp_pairwise_distance(
 def _erp_from_multiple_to_multiple_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float,
+    window: Optional[float],
     g: float,
-    g_arr: np.ndarray,
-    itakura_max_slope: float,
+    g_arr: Optional[np.ndarray],
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
@@ -386,10 +386,10 @@ def _erp_from_multiple_to_multiple_distance(
 def erp_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     g: float = 0.0,
-    g_arr: np.ndarray = None,
-    itakura_max_slope: float = None,
+    g_arr: Optional[np.ndarray] = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the ERP alignment path between two time series.
 
diff --git a/aeon/distances/_euclidean.py b/aeon/distances/_euclidean.py
index 5092bd30..ac760340 100644
--- a/aeon/distances/_euclidean.py
+++ b/aeon/distances/_euclidean.py
@@ -1,5 +1,7 @@
 __maintainer__ = []
 
+from typing import Optional
+
 import numpy as np
 from numba import njit
 
@@ -63,7 +65,9 @@ def _univariate_euclidean_distance(x: np.ndarray, y: np.ndarray) -> float:
 
 
 @njit(cache=True, fastmath=True)
-def euclidean_pairwise_distance(X: np.ndarray, y: np.ndarray = None) -> np.ndarray:
+def euclidean_pairwise_distance(
+    X: np.ndarray, y: Optional[np.ndarray] = None
+) -> np.ndarray:
     """Compute the Euclidean pairwise distance between a set of time series.
 
     Parameters
@@ -106,7 +110,7 @@ def euclidean_pairwise_distance(X: np.ndarray, y: np.ndarray = None) -> np.ndarr
            [ 6.92820323, 12.12435565, 17.32050808]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> euclidean_pairwise_distance(X, y_univariate)
     array([[17.32050808],
            [12.12435565],
diff --git a/aeon/distances/_lcss.py b/aeon/distances/_lcss.py
index f9ac1202..770cc104 100644
--- a/aeon/distances/_lcss.py
+++ b/aeon/distances/_lcss.py
@@ -2,7 +2,7 @@ r"""Longest common subsequence (LCSS) between two time series."""
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -17,9 +17,9 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 def lcss_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     epsilon: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Return the LCSS distance between x and y.
 
@@ -120,9 +120,9 @@ def lcss_distance(
 def lcss_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     epsilon: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     r"""Return the LCSS cost matrix between x and y.
 
@@ -220,10 +220,10 @@ def _lcss_cost_matrix(
 @njit(cache=True, fastmath=True)
 def lcss_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
     epsilon: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the LCSS pairwise distance between a set of time series.
 
@@ -278,7 +278,7 @@ def lcss_pairwise_distance(
            [1., 1., 1.]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> lcss_pairwise_distance(X, y_univariate)
     array([[1.],
            [1.],
@@ -291,7 +291,7 @@ def lcss_pairwise_distance(
         if X.ndim == 2:
             _X = X.reshape((X.shape[0], 1, X.shape[1]))
             return _lcss_pairwise_distance(_X, window, epsilon, itakura_max_slope)
-        raise ValueError("x and y must be 2D or 3D arrays")
+        raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     _x, _y = reshape_pairwise_to_multiple(X, y)
     return _lcss_from_multiple_to_multiple_distance(
         _x, _y, window, epsilon, itakura_max_slope
@@ -300,7 +300,10 @@ def lcss_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _lcss_pairwise_distance(
-    X: np.ndarray, window: float, epsilon: float, itakura_max_slope: float
+    X: np.ndarray,
+    window: Optional[float],
+    epsilon: float,
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -320,9 +323,9 @@ def _lcss_pairwise_distance(
 def _lcss_from_multiple_to_multiple_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float,
+    window: Optional[float],
     epsilon: float,
-    itakura_max_slope: float,
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
@@ -341,9 +344,9 @@ def _lcss_from_multiple_to_multiple_distance(
 def lcss_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     epsilon: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the LCSS alignment path between two time series.
 
diff --git a/aeon/distances/_manhattan.py b/aeon/distances/_manhattan.py
index 05c6551c..c82bf3a1 100644
--- a/aeon/distances/_manhattan.py
+++ b/aeon/distances/_manhattan.py
@@ -1,5 +1,7 @@
 __maintainer__ = []
 
+from typing import Optional
+
 import numpy as np
 from numba import njit
 
@@ -71,7 +73,9 @@ def _univariate_manhattan_distance(x: np.ndarray, y: np.ndarray) -> float:
 
 
 @njit(cache=True, fastmath=True)
-def manhattan_pairwise_distance(X: np.ndarray, y: np.ndarray = None) -> np.ndarray:
+def manhattan_pairwise_distance(
+    X: np.ndarray, y: Optional[np.ndarray] = None
+) -> np.ndarray:
     """Compute the manhattan pairwise distance between a set of time series.
 
     Parameters
@@ -115,7 +119,7 @@ def manhattan_pairwise_distance(X: np.ndarray, y: np.ndarray = None) -> np.ndarr
            [12., 21., 30.]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> manhattan_pairwise_distance(X, y_univariate)
     array([[30.],
            [21.],
diff --git a/aeon/distances/_minkowski.py b/aeon/distances/_minkowski.py
index 4f69e58a..8d9283a3 100644
--- a/aeon/distances/_minkowski.py
+++ b/aeon/distances/_minkowski.py
@@ -1,5 +1,7 @@
 __maintainer__ = []
 
+from typing import Optional
+
 import numpy as np
 from numba import njit
 
@@ -8,7 +10,7 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 
 @njit(cache=True, fastmath=True)
 def minkowski_distance(
-    x: np.ndarray, y: np.ndarray, p: float = 2.0, w: np.ndarray = None
+    x: np.ndarray, y: np.ndarray, p: float = 2.0, w: Optional[np.ndarray] = None
 ) -> float:
     r"""Compute the Minkowski distance between two time series.
 
@@ -77,15 +79,13 @@ def minkowski_distance(
             raise ValueError("Weights w must have the same shape as x")
         if np.any(_w < 0):
             raise ValueError("Input weights should be all non-negative")
+    else:
+        _w = np.ones_like(x)
 
     if x.ndim == 1 and y.ndim == 1:
-        return _univariate_minkowski_distance(
-            x, y, p, np.ones_like(x) if w is None else _w
-        )
+        return _univariate_minkowski_distance(x, y, p, _w)
     if x.ndim == 2 and y.ndim == 2:
-        return _multivariate_minkowski_distance(
-            x, y, p, np.ones_like(x) if w is None else _w
-        )
+        return _multivariate_minkowski_distance(x, y, p, _w)
 
     raise ValueError("Inconsistent dimensions.")
 
@@ -102,7 +102,7 @@ def _univariate_minkowski_distance(
 
     dist = np.sum(w * (np.abs(x - y) ** p))
 
-    return dist ** (1.0 / p)
+    return float(dist ** (1.0 / p))
 
 
 @njit(cache=True, fastmath=True)
@@ -126,7 +126,10 @@ def _multivariate_minkowski_distance(
 
 @njit(cache=True, fastmath=True)
 def minkowski_pairwise_distance(
-    X: np.ndarray, y: np.ndarray = None, p: float = 2.0, w: np.ndarray = None
+    X: np.ndarray,
+    y: Optional[np.ndarray] = None,
+    p: float = 2.0,
+    w: Optional[np.ndarray] = None,
 ) -> np.ndarray:
     """Compute the Minkowski pairwise distance between a set of time series.
 
@@ -186,8 +189,8 @@ def minkowski_pairwise_distance(
            [ 60.62177826,  86.60254038]])
 
     >>> 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]])
-    >>> minkowski_pairwise_distance(X, y, p=1)
+    >>> y_univariate = np.array([11, 12, 13])
+    >>> minkowski_pairwise_distance(X, y_univariate, p=1)
     array([[30.],
            [21.],
            [12.]])
@@ -205,7 +208,7 @@ def minkowski_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _minkowski_pairwise_distance(
-    X: np.ndarray, p: float, w: np.ndarray = None
+    X: np.ndarray, p: float, w: Optional[np.ndarray] = None
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -226,7 +229,7 @@ def _minkowski_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _minkowski_from_multiple_to_multiple_distance(
-    x: np.ndarray, y: np.ndarray, p: float, w: np.ndarray = None
+    x: np.ndarray, y: np.ndarray, p: float, w: Optional[np.ndarray] = None
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
diff --git a/aeon/distances/_msm.py b/aeon/distances/_msm.py
index f8f7c539..5ae057bc 100644
--- a/aeon/distances/_msm.py
+++ b/aeon/distances/_msm.py
@@ -21,10 +21,10 @@ from aeon.distances._utils import _convert_to_list
 def msm_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     independent: bool = True,
     c: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Compute the MSM distance between two time series.
 
@@ -134,10 +134,10 @@ def msm_distance(
 def msm_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     independent: bool = True,
     c: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the MSM cost matrix between two time series.
 
@@ -348,10 +348,10 @@ def _cost_independent(x: float, y: float, z: float, c: float) -> float:
 def msm_pairwise_distance(
     X: Union[np.ndarray, List[np.ndarray]],
     y: Optional[Union[np.ndarray, List[np.ndarray]]] = None,
-    window: float = None,
+    window: Optional[float] = None,
     independent: bool = True,
     c: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the msm pairwise distance between a set of time series.
 
@@ -408,11 +408,11 @@ def msm_pairwise_distance(
            [10., 13., 16.]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> msm_pairwise_distance(X, y_univariate)
-    array([[16., 19., 22.],
-           [13., 16., 19.],
-           [10., 13., 16.]])
+    array([[16.],
+           [13.],
+           [10.]])
 
     >>> # Distance between each TS in a collection of unequal-length time series
     >>> X = [np.array([1, 2, 3]), np.array([4, 5, 6, 7]), np.array([8, 9, 10, 11, 12])]
@@ -436,10 +436,10 @@ def msm_pairwise_distance(
 @njit(cache=True, fastmath=True)
 def _msm_pairwise_distance(
     X: NumbaList[np.ndarray],
-    window: float,
+    window: Optional[float],
     independent: bool,
     c: float,
-    itakura_max_slope: float,
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = len(X)
     distances = np.zeros((n_cases, n_cases))
@@ -466,10 +466,10 @@ def _msm_pairwise_distance(
 def _msm_from_multiple_to_multiple_distance(
     x: NumbaList[np.ndarray],
     y: NumbaList[np.ndarray],
-    window: float,
+    window: Optional[float],
     independent: bool,
     c: float,
-    itakura_max_slope: float,
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = len(x)
     m_cases = len(y)
@@ -495,10 +495,10 @@ def _msm_from_multiple_to_multiple_distance(
 def msm_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     independent: bool = True,
     c: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the msm alignment path between two time series.
 
diff --git a/aeon/distances/_sbd.py b/aeon/distances/_sbd.py
index 51d75c7f..a41ba5dd 100644
--- a/aeon/distances/_sbd.py
+++ b/aeon/distances/_sbd.py
@@ -174,11 +174,11 @@ def sbd_pairwise_distance(
            [0., 0., 0.]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> sbd_pairwise_distance(X, y_univariate)
-    array([[0., 0., 0.],
-           [0., 0., 0.],
-           [0., 0., 0.]])
+    array([[0.],
+           [0.],
+           [0.]])
 
     >>> # Distance between each TS in a collection of unequal-length time series
     >>> X = [np.array([1, 2, 3]), np.array([4, 5, 6, 7]), np.array([8, 9, 10, 11, 12])]
diff --git a/aeon/distances/_shape_dtw.py b/aeon/distances/_shape_dtw.py
index 08a28e3d..cd51e61d 100644
--- a/aeon/distances/_shape_dtw.py
+++ b/aeon/distances/_shape_dtw.py
@@ -2,7 +2,7 @@ r"""Shape Dynamic time warping (ShapeDTW) between two time series."""
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -39,6 +39,8 @@ def _pad_ts_edges(x: np.ndarray, reach: int) -> np.ndarray:
         x_padded[:, :, reach : reach + n_timepoints] = x
         x_padded[:, :, :reach] = np.expand_dims(x[:, :, 0], axis=-1)
         x_padded[:, :, reach + n_timepoints :] = np.expand_dims(x[:, :, -1], axis=-1)
+    else:
+        raise ValueError("x must be either 2D or 3D array.")
 
     return x_padded
 
@@ -86,6 +88,8 @@ def _transform_subsequences(
     """
     if descriptor == "identity":
         descriptor_function = _identity_descriptor
+    else:
+        raise ValueError("Descriptor invalid. Descriptor must be 'identity'.")
 
     sliding_window = reach * 2 + 1
     sliding_window = int(sliding_window)
@@ -115,10 +119,10 @@ def _transform_subsequences(
 def shape_dtw_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     descriptor: str = "identity",
     reach: int = 30,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     """Compute the ShapeDTW distance function between two series x and y.
 
@@ -260,11 +264,11 @@ def _get_shape_dtw_distance_from_cost_mat(
 def shape_dtw_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     descriptor: str = "identity",
     reach: int = 30,
-    itakura_max_slope: float = None,
-) -> float:
+    itakura_max_slope: Optional[float] = None,
+) -> np.ndarray:
     """Compute the ShapeDTW cost matrix between two series x and y.
 
     Parameters
@@ -349,7 +353,7 @@ def _shape_dtw_cost_matrix(
     bounding_matrix: np.ndarray,
     descriptor: str = "identity",
     reach: int = 30,
-) -> float:
+) -> np.ndarray:
     new_x = _transform_subsequences(x=x, descriptor=descriptor, reach=reach)
     new_y = _transform_subsequences(x=y, descriptor=descriptor, reach=reach)
 
@@ -364,10 +368,10 @@ def _shape_dtw_cost_matrix(
 def shape_dtw_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     descriptor: str = "identity",
     reach: int = 30,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the ShapeDTW alignment path between two series x and y.
 
@@ -426,10 +430,11 @@ def shape_dtw_alignment_path(
 
         x_pad = _pad_ts_edges(x=_x, reach=reach)
         y_pad = _pad_ts_edges(x=_y, reach=reach)
-
-    if x.ndim == 2 and y.ndim == 2:
+    elif x.ndim == 2 and y.ndim == 2:
         x_pad = _pad_ts_edges(x=x, reach=reach)
         y_pad = _pad_ts_edges(x=y, reach=reach)
+    else:
+        raise ValueError("x and y must be 1D or 2D")
 
     shapedtw_dist = _get_shape_dtw_distance_from_cost_mat(
         x=x_pad, y=y_pad, reach=reach, shape_dtw_cost_mat=cost_matrix
@@ -441,11 +446,11 @@ def shape_dtw_alignment_path(
 @njit(cache=True, fastmath=True)
 def shape_dtw_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
     descriptor: str = "identity",
     reach: int = 30,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the ShapeDTW pairwise distance among a set of series.
 
@@ -488,6 +493,32 @@ def shape_dtw_pairwise_distance(
     ------
     ValueError
         If x and y are not 1D or 2D arrays.
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from aeon.distances import shape_dtw_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]]])
+    >>> shape_dtw_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]]])
+    >>> shape_dtw_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])
+    >>> shape_dtw_pairwise_distance(X, y_univariate)
+    array([[300.],
+           [147.],
+           [ 48.]])
     """
     if y is None:
         if X.ndim == 3:
@@ -528,11 +559,11 @@ def shape_dtw_pairwise_distance(
 @njit(cache=True, fastmath=True)
 def _shape_dtw_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
     descriptor: str = "identity",
     reach: int = 30,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     if y is None:
         y = np.copy(X)
diff --git a/aeon/distances/_squared.py b/aeon/distances/_squared.py
index 6e4e872d..69a95bff 100644
--- a/aeon/distances/_squared.py
+++ b/aeon/distances/_squared.py
@@ -1,5 +1,7 @@
 __maintainer__ = []
 
+from typing import Optional
+
 import numpy as np
 from numba import njit
 
@@ -70,12 +72,14 @@ def _univariate_squared_distance(x: np.ndarray, y: np.ndarray) -> float:
 
 
 @njit(cache=True, fastmath=True)
-def squared_pairwise_distance(X: np.ndarray, y: np.ndarray = None) -> np.ndarray:
+def squared_pairwise_distance(
+    X: np.ndarray, y: Optional[np.ndarray] = None
+) -> np.ndarray:
     """Compute the squared pairwise distance between a set of time series.
 
     Parameters
     ----------
-    x : np.ndarray
+    X : np.ndarray
         First time series, either univariate, shape ``(n_timepoints,)``, or
         multivariate, shape ``(n_channels, n_timepoints)``.
     y : np.ndarray
@@ -113,7 +117,7 @@ def squared_pairwise_distance(X: np.ndarray, y: np.ndarray = None) -> np.ndarray
            [ 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> squared_pairwise_distance(X, y_univariate)
     array([[300.],
            [147.],
diff --git a/aeon/distances/_twe.py b/aeon/distances/_twe.py
index 8d77fca6..254fdcbb 100644
--- a/aeon/distances/_twe.py
+++ b/aeon/distances/_twe.py
@@ -2,7 +2,7 @@
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -20,10 +20,10 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 def twe_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     nu: float = 0.001,
     lmbda: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Compute the TWE distance between two time series.
 
@@ -114,10 +114,10 @@ def twe_distance(
 def twe_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     nu: float = 0.001,
     lmbda: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the TWE cost matrix between two time series.
 
@@ -248,11 +248,11 @@ def _pad_arrs(x: np.ndarray) -> np.ndarray:
 @njit(cache=True, fastmath=True)
 def twe_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
     nu: float = 0.001,
     lmbda: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the TWE pairwise distance between a set of time series.
 
@@ -309,11 +309,11 @@ def twe_pairwise_distance(
            [12.004, 15.004, 18.004]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> twe_pairwise_distance(X, y_univariate)
-    array([[19.46810162],
-           [16.46810162],
-           [13.46810162]])
+    array([[18.004],
+           [15.004],
+           [12.004]])
     """
     if y is None:
         # To self
@@ -322,7 +322,7 @@ def twe_pairwise_distance(
         if X.ndim == 2:
             _X = X.reshape((X.shape[0], 1, X.shape[1]))
             return _twe_pairwise_distance(_X, window, nu, lmbda, itakura_max_slope)
-        raise ValueError("x and y must be 2D or 3D arrays")
+        raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     _x, _y = reshape_pairwise_to_multiple(X, y)
     return _twe_from_multiple_to_multiple_distance(
         _x, _y, window, nu, lmbda, itakura_max_slope
@@ -332,10 +332,10 @@ def twe_pairwise_distance(
 @njit(cache=True, fastmath=True)
 def _twe_pairwise_distance(
     X: np.ndarray,
-    window: float,
+    window: Optional[float],
     nu: float,
     lmbda: float,
-    itakura_max_slope: float,
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -362,10 +362,10 @@ def _twe_pairwise_distance(
 def _twe_from_multiple_to_multiple_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float,
+    window: Optional[float],
     nu: float,
     lmbda: float,
-    itakura_max_slope: float,
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
@@ -395,10 +395,10 @@ def _twe_from_multiple_to_multiple_distance(
 def twe_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     nu: float = 0.001,
     lmbda: float = 1.0,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the TWE alignment path between two time series.
 
diff --git a/aeon/distances/_wddtw.py b/aeon/distances/_wddtw.py
index fb3da8ae..00c44649 100644
--- a/aeon/distances/_wddtw.py
+++ b/aeon/distances/_wddtw.py
@@ -2,7 +2,7 @@
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -18,9 +18,9 @@ from aeon.distances._wdtw import _wdtw_cost_matrix, _wdtw_distance
 def wddtw_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     g: float = 0.05,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Compute the WDDTW distance between two time series.
 
@@ -103,9 +103,9 @@ def wddtw_distance(
 def wddtw_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     g: float = 0.05,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the WDDTW cost matrix between two time series.
 
@@ -172,10 +172,10 @@ def wddtw_cost_matrix(
 @njit(cache=True, fastmath=True)
 def wddtw_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
     g: float = 0.05,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the WDDTW pairwise distance between a set of time series.
 
@@ -226,7 +226,7 @@ def wddtw_pairwise_distance(
            [2.33574763e+02, 6.64390438e+03, 2.66663693e+07]])
 
     >>> X = np.array([[[10, 22, 399]],[[41, 500, 1316]], [[117, 18, 9]]])
-    >>> y_univariate = np.array([[100, 11, 199],[10, 15, 26], [170, 108, 1119]])
+    >>> y_univariate = np.array([100, 11, 199])
     >>> wddtw_pairwise_distance(X, y_univariate)
     array([[  7469.9486745 ],
            [159295.70501427],
@@ -239,7 +239,7 @@ def wddtw_pairwise_distance(
         if X.ndim == 2:
             _X = X.reshape((X.shape[0], 1, X.shape[1]))
             return _wddtw_pairwise_distance(_X, window, g, itakura_max_slope)
-        raise ValueError("x and y must be 2D or 3D arrays")
+        raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     _x, _y = reshape_pairwise_to_multiple(X, y)
     return _wddtw_from_multiple_to_multiple_distance(
         _x, _y, window, g, itakura_max_slope
@@ -248,7 +248,7 @@ def wddtw_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _wddtw_pairwise_distance(
-    X: np.ndarray, window: float, g: float, itakura_max_slope: float
+    X: np.ndarray, window: Optional[float], g: float, itakura_max_slope: Optional[float]
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -274,9 +274,9 @@ def _wddtw_pairwise_distance(
 def _wddtw_from_multiple_to_multiple_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float,
+    window: Optional[float],
     g: float,
-    itakura_max_slope: float,
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
@@ -306,9 +306,9 @@ def _wddtw_from_multiple_to_multiple_distance(
 def wddtw_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     g: float = 0.05,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the WDDTW alignment path between two time series.
 
diff --git a/aeon/distances/_wdtw.py b/aeon/distances/_wdtw.py
index 6b053e5c..7bc559b7 100644
--- a/aeon/distances/_wdtw.py
+++ b/aeon/distances/_wdtw.py
@@ -2,7 +2,7 @@ r"""Weighted dynamic time warping (WDTW) distance between two time series."""
 
 __maintainer__ = []
 
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 
 import numpy as np
 from numba import njit
@@ -17,9 +17,9 @@ from aeon.distances._utils import reshape_pairwise_to_multiple
 def wdtw_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     g: float = 0.05,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> float:
     r"""Compute the WDTW distance between two time series.
 
@@ -111,9 +111,9 @@ def wdtw_distance(
 def wdtw_cost_matrix(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     g: float = 0.05,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the WDTW cost matrix between two time series.
 
@@ -236,10 +236,10 @@ def _wdtw_cost_matrix(
 @njit(cache=True, fastmath=True)
 def wdtw_pairwise_distance(
     X: np.ndarray,
-    y: np.ndarray = None,
-    window: float = None,
+    y: Optional[np.ndarray] = None,
+    window: Optional[float] = None,
     g: float = 0.05,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> np.ndarray:
     """Compute the WDTW pairwise distance between a set of time series.
 
@@ -292,7 +292,7 @@ def wdtw_pairwise_distance(
            [ 23.10042164,  70.74504127, 144.37763524]])
 
     >>> 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]])
+    >>> y_univariate = np.array([11, 12, 13])
     >>> wdtw_pairwise_distance(X, y_univariate)
     array([[144.37763524],
            [ 70.74504127],
@@ -305,7 +305,7 @@ def wdtw_pairwise_distance(
         if X.ndim == 2:
             _X = X.reshape((X.shape[0], 1, X.shape[1]))
             return _wdtw_pairwise_distance(_X, window, g, itakura_max_slope)
-        raise ValueError("x and y must be 2D or 3D arrays")
+        raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     _x, _y = reshape_pairwise_to_multiple(X, y)
     return _wdtw_from_multiple_to_multiple_distance(
         _x, _y, window, g, itakura_max_slope
@@ -314,7 +314,7 @@ def wdtw_pairwise_distance(
 
 @njit(cache=True, fastmath=True)
 def _wdtw_pairwise_distance(
-    X: np.ndarray, window: float, g: float, itakura_max_slope: float
+    X: np.ndarray, window: Optional[float], g: float, itakura_max_slope: Optional[float]
 ) -> np.ndarray:
     n_cases = X.shape[0]
     distances = np.zeros((n_cases, n_cases))
@@ -334,9 +334,9 @@ def _wdtw_pairwise_distance(
 def _wdtw_from_multiple_to_multiple_distance(
     x: np.ndarray,
     y: np.ndarray,
-    window: float,
+    window: Optional[float],
     g: float,
-    itakura_max_slope: float,
+    itakura_max_slope: Optional[float],
 ) -> np.ndarray:
     n_cases = x.shape[0]
     m_cases = y.shape[0]
@@ -355,9 +355,9 @@ def _wdtw_from_multiple_to_multiple_distance(
 def wdtw_alignment_path(
     x: np.ndarray,
     y: np.ndarray,
-    window: float = None,
+    window: Optional[float] = None,
     g: float = 0.05,
-    itakura_max_slope: float = None,
+    itakura_max_slope: Optional[float] = None,
 ) -> Tuple[List[Tuple[int, int]], float]:
     """Compute the WDTW alignment path between two time series.
 
diff --git a/aeon/distances/mpdist.py b/aeon/distances/mpdist.py
index 373792d3..bbc3851a 100644
--- a/aeon/distances/mpdist.py
+++ b/aeon/distances/mpdist.py
@@ -160,7 +160,7 @@ def _stomp_ab(ts1, ts2, m):
     return mp, ip
 
 
-def mpdist(ts1, ts2, m=0):
+def mpdist(ts1: np.ndarray, ts2: np.ndarray, m: int = 0):
     """
     Matrix profile distance.
 
@@ -190,10 +190,9 @@ def mpdist(ts1, ts2, m=0):
 
     if m == 0:
         if len1 > len2:
-            m = len1 / 4
+            m = int(len1 / 4)
         else:
-            m = len2 / 4
-    m = int(m)
+            m = int(len2 / 4)
     threshold = 0.05
     mp_ab, ip_ab = _stomp_ab(ts1, ts2, m)  # compute the AB matrix profile
     mp_ba, ip_ba = _stomp_ab(ts2, ts1, m)  # compute the BA matrix profile
diff --git a/aeon/regression/interval_based/_cif.py b/aeon/regression/interval_based/_cif.py
index cd5bc099..4d8e8ede 100644
--- a/aeon/regression/interval_based/_cif.py
+++ b/aeon/regression/interval_based/_cif.py
@@ -85,8 +85,6 @@ class CanonicalIntervalForestRegressor(BaseIntervalForest, BaseRegressor):
         Wraps the C based pycatch22 implementation for aeon.
         (https://github.com/DynamicsAndNeuralSystems/pycatch22). This requires the
         ``pycatch22`` package to be installed if True.
-    save_transformed_data : bool, default=False
-        Save the data transformed in fit for use in _get_train_preds.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -115,10 +113,6 @@ class CanonicalIntervalForestRegressor(BaseIntervalForest, BaseRegressor):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of TransformerMixin
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all regressors. Only saved when
-        save_transformed_data is true.
 
     See Also
     --------
@@ -165,7 +159,6 @@ class CanonicalIntervalForestRegressor(BaseIntervalForest, BaseRegressor):
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
         use_pycatch22=False,
-        save_transformed_data=False,
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -194,7 +187,6 @@ class CanonicalIntervalForestRegressor(BaseIntervalForest, BaseRegressor):
             replace_nan=0,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
@@ -216,9 +208,6 @@ class CanonicalIntervalForestRegressor(BaseIntervalForest, BaseRegressor):
                 "contracting" - used in classifiers that set the
                     "capability:contractable" tag to True to test contacting
                     functionality
-                "train_estimate" - used in some classifiers that set the
-                    "capability:train_estimate" tag to True to allow for more efficient
-                    testing when relevant parameters are available
 
         Returns
         -------
@@ -237,12 +226,5 @@ class CanonicalIntervalForestRegressor(BaseIntervalForest, BaseRegressor):
                 "n_intervals": 2,
                 "att_subsample_size": 2,
             }
-        elif parameter_set == "train_estimate":
-            return {
-                "n_estimators": 2,
-                "n_intervals": 2,
-                "att_subsample_size": 2,
-                "save_transformed_data": True,
-            }
         else:
             return {"n_estimators": 2, "n_intervals": 2, "att_subsample_size": 2}
diff --git a/aeon/regression/interval_based/_drcif.py b/aeon/regression/interval_based/_drcif.py
index 6d5d7622..8af43cdd 100644
--- a/aeon/regression/interval_based/_drcif.py
+++ b/aeon/regression/interval_based/_drcif.py
@@ -101,8 +101,6 @@ class DrCIFRegressor(BaseIntervalForest, BaseRegressor):
     use_pyfftw : bool, default=False
         Whether to use the pyfftw library for FFT calculations. Requires the pyfftw
         package to be installed.
-    save_transformed_data : bool, default=False
-        Save the data transformed in fit for use in _get_train_preds.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -131,10 +129,6 @@ class DrCIFRegressor(BaseIntervalForest, BaseRegressor):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of TransformerMixin
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all regressors. Only saved when
-        save_transformed_data is true.
 
     See Also
     --------
@@ -182,7 +176,6 @@ class DrCIFRegressor(BaseIntervalForest, BaseRegressor):
         contract_max_n_estimators=500,
         use_pycatch22=False,
         use_pyfftw=False,
-        save_transformed_data=False,
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -228,7 +221,6 @@ class DrCIFRegressor(BaseIntervalForest, BaseRegressor):
             replace_nan=0,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
@@ -250,9 +242,6 @@ class DrCIFRegressor(BaseIntervalForest, BaseRegressor):
                 "contracting" - used in classifiers that set the
                     "capability:contractable" tag to True to test contacting
                     functionality
-                "train_estimate" - used in some classifiers that set the
-                    "capability:train_estimate" tag to True to allow for more efficient
-                    testing when relevant parameters are available
 
         Returns
         -------
@@ -271,12 +260,5 @@ class DrCIFRegressor(BaseIntervalForest, BaseRegressor):
                 "n_intervals": 2,
                 "att_subsample_size": 2,
             }
-        elif parameter_set == "train_estimate":
-            return {
-                "n_estimators": 2,
-                "n_intervals": 2,
-                "att_subsample_size": 2,
-                "save_transformed_data": True,
-            }
         else:
             return {"n_estimators": 2, "n_intervals": 2, "att_subsample_size": 2}
diff --git a/aeon/regression/interval_based/_interval_forest.py b/aeon/regression/interval_based/_interval_forest.py
index 57dc46bc..ae121cd5 100644
--- a/aeon/regression/interval_based/_interval_forest.py
+++ b/aeon/regression/interval_based/_interval_forest.py
@@ -102,9 +102,6 @@ class IntervalForestRegressor(BaseIntervalForest, BaseRegressor):
         Default of 0 means n_estimators are used.
     contract_max_n_estimators : int, default=500
         Max number of estimators when time_limit_in_minutes is set.
-    save_transformed_data : bool, default=False
-        Save the data transformed in fit for use in _get_train_preds and
-        _get_train_probs.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -133,10 +130,6 @@ class IntervalForestRegressor(BaseIntervalForest, BaseRegressor):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of BaseTransformer
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all regressors. Only saved when
-        save_transformed_data is true.
 
     References
     ----------
@@ -185,7 +178,6 @@ class IntervalForestRegressor(BaseIntervalForest, BaseRegressor):
         replace_nan=None,
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
-        save_transformed_data=False,
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -203,7 +195,6 @@ class IntervalForestRegressor(BaseIntervalForest, BaseRegressor):
             replace_nan=replace_nan,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
@@ -225,9 +216,6 @@ class IntervalForestRegressor(BaseIntervalForest, BaseRegressor):
                 "contracting" - used in regressors that set the
                     "capability:contractable" tag to True to test contacting
                     functionality
-                "train_estimate" - used in some regressors that set the
-                    "capability:train_estimate" tag to True to allow for more efficient
-                    testing when relevant parameters are available
 
         Returns
         -------
@@ -245,11 +233,5 @@ class IntervalForestRegressor(BaseIntervalForest, BaseRegressor):
                 "contract_max_n_estimators": 2,
                 "n_intervals": 2,
             }
-        elif parameter_set == "train_estimate":
-            return {
-                "n_estimators": 2,
-                "n_intervals": 2,
-                "save_transformed_data": True,
-            }
         else:
             return {"n_estimators": 2, "n_intervals": 2}
diff --git a/aeon/regression/interval_based/_rise.py b/aeon/regression/interval_based/_rise.py
index bea2e8af..29aedbd6 100644
--- a/aeon/regression/interval_based/_rise.py
+++ b/aeon/regression/interval_based/_rise.py
@@ -59,8 +59,6 @@ class RandomIntervalSpectralEnsembleRegressor(BaseIntervalForest, BaseRegressor)
     use_pyfftw : bool, default=False
         Whether to use the pyfftw library for FFT calculations. Requires the pyfftw
         package to be installed.
-    save_transformed_data : bool, default=False
-        Save the data transformed in fit for use in _get_train_probs.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -89,10 +87,6 @@ class RandomIntervalSpectralEnsembleRegressor(BaseIntervalForest, BaseRegressor)
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of TransformerMixin
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all estimators. Only saved when
-        save_transformed_data is true.
 
     See Also
     --------
@@ -140,7 +134,6 @@ class RandomIntervalSpectralEnsembleRegressor(BaseIntervalForest, BaseRegressor)
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
         use_pyfftw=False,
-        save_transformed_data=False,
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -171,7 +164,6 @@ class RandomIntervalSpectralEnsembleRegressor(BaseIntervalForest, BaseRegressor)
             replace_nan=0,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
@@ -194,9 +186,6 @@ class RandomIntervalSpectralEnsembleRegressor(BaseIntervalForest, BaseRegressor)
                 "contracting" - used in classifiers that set the
                     "capability:contractable" tag to True to test contacting
                     functionality
-                "train_estimate" - used in some classifiers that set the
-                    "capability:train_estimate" tag to True to allow for more efficient
-                    testing when relevant parameters are available
 
         Returns
         -------
@@ -213,10 +202,5 @@ class RandomIntervalSpectralEnsembleRegressor(BaseIntervalForest, BaseRegressor)
                 "time_limit_in_minutes": 5,
                 "contract_max_n_estimators": 2,
             }
-        elif parameter_set == "train_estimate":
-            return {
-                "n_estimators": 2,
-                "save_transformed_data": True,
-            }
         else:
             return {"n_estimators": 2}
diff --git a/aeon/regression/interval_based/_tsf.py b/aeon/regression/interval_based/_tsf.py
index 38b26ace..b71d1ac0 100644
--- a/aeon/regression/interval_based/_tsf.py
+++ b/aeon/regression/interval_based/_tsf.py
@@ -73,8 +73,6 @@ class TimeSeriesForestRegressor(BaseIntervalForest, BaseRegressor):
         Default of 0 means n_estimators are used.
     contract_max_n_estimators : int, default=500
         Max number of estimators when time_limit_in_minutes is set.
-    save_transformed_data : bool, default=False
-        Save the data transformed in fit for use in _get_train_preds.
     random_state : int, RandomState instance or None, default=None
         If `int`, random_state is the seed used by the random number generator;
         If `RandomState` instance, random_state is the random number generator;
@@ -103,10 +101,6 @@ class TimeSeriesForestRegressor(BaseIntervalForest, BaseRegressor):
         The collections of estimators trained in fit.
     intervals_ : list of shape (n_estimators) of BaseTransformer
         Stores the interval extraction transformer for all estimators.
-    transformed_data_ : list of shape (n_estimators) of ndarray with shape
-    (n_cases_ ,total_intervals * att_subsample_size)
-        The transformed dataset for all regressors. Only saved when
-        save_transformed_data is true.
 
     References
     ----------
@@ -145,7 +139,6 @@ class TimeSeriesForestRegressor(BaseIntervalForest, BaseRegressor):
         max_interval_length=np.inf,
         time_limit_in_minutes=None,
         contract_max_n_estimators=500,
-        save_transformed_data=False,
         random_state=None,
         n_jobs=1,
         parallel_backend=None,
@@ -163,7 +156,6 @@ class TimeSeriesForestRegressor(BaseIntervalForest, BaseRegressor):
             replace_nan=0,
             time_limit_in_minutes=time_limit_in_minutes,
             contract_max_n_estimators=contract_max_n_estimators,
-            save_transformed_data=save_transformed_data,
             random_state=random_state,
             n_jobs=n_jobs,
             parallel_backend=parallel_backend,
diff --git a/extension_templates/distance.py b/extension_templates/distance.py
index c295bbbb..a5fe7be1 100644
--- a/extension_templates/distance.py
+++ b/extension_templates/distance.py
@@ -213,7 +213,7 @@ def foo_pairwise_distance(
         if X.ndim == 2:
             X = X.reshape((X.shape[0], 1, X.shape[1]))
         if X.ndim != 3:
-            raise ValueError("x and y must be 2D or 3D arrays")
+            raise ValueError("x and y must be 1D, 2D, or 3D arrays")
     else:
         X, y = reshape_pairwise_to_multiple(X, y)
     # NOW FIND AND RETURN PAIRWISE DISTANCE. If y is none, only calculate one


Test patch:
diff --git a/aeon/base/estimator/interval_based/tests/test_base_interval_forest.py b/aeon/base/estimator/interval_based/tests/test_base_interval_forest.py
index cea7a1f2..9db95aa9 100644
--- a/aeon/base/estimator/interval_based/tests/test_base_interval_forest.py
+++ b/aeon/base/estimator/interval_based/tests/test_base_interval_forest.py
@@ -98,14 +98,13 @@ def test_interval_forest_n_intervals(n_intervals, n_intervals_len):
         n_estimators=2,
         n_intervals=n_intervals,
         series_transformers=[None, FunctionTransformer(np.log1p)],
-        save_transformed_data=True,
         random_state=0,
     )
+    est._unit_test_flag = True
     est.fit(X, y)
     est.predict_proba(X)
 
-    data = est.transformed_data_
-    assert data[0].shape[1] == n_intervals_len
+    assert est._transformed_data[0].shape[1] == n_intervals_len
 
 
 att_subsample_c22 = Catch22(
@@ -143,14 +142,13 @@ def test_interval_forest_attribute_subsample(features, output_len):
         att_subsample_size=0.5,
         interval_features=features,
         replace_nan=0,
-        save_transformed_data=True,
         random_state=0,
     )
+    est._unit_test_flag = True
     est.fit(X, y)
     est.predict_proba(X)
 
-    data = est.transformed_data_
-    assert data[0].shape[1] == int(output_len * 0.5) * 2
+    assert est._transformed_data[0].shape[1] == int(output_len * 0.5) * 2
 
 
 def test_interval_forest_invalid_attribute_subsample():
@@ -184,17 +182,16 @@ def test_interval_forest_series_transformer(series_transformer):
         n_estimators=2,
         n_intervals=2,
         series_transformers=series_transformer,
-        save_transformed_data=True,
         random_state=0,
     )
+    est._unit_test_flag = True
     est.fit(X, y)
     est.predict_proba(X)
 
-    data = est.transformed_data_
     expected = (
         len(series_transformer) * 6 if isinstance(series_transformer, list) else 6
     )
-    assert data[0].shape[1] == expected
+    assert est._transformed_data[0].shape[1] == expected
 
 
 def test_min_interval_length():
diff --git a/aeon/distances/tests/test_distances.py b/aeon/distances/tests/test_distances.py
index e515fa1c..95966ddb 100644
--- a/aeon/distances/tests/test_distances.py
+++ b/aeon/distances/tests/test_distances.py
@@ -179,4 +179,4 @@ def test_incorrect_inputs():
 
     x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
     with pytest.raises(ValueError, match="x and y must be 1D, 2D, or 3D arrays"):
-        _custom_func_pairwise(x)
+        _custom_func_pairwise(x, dist_func=lambda x, y: 0)
diff --git a/aeon/distances/tests/test_numba_distance_parameters.py b/aeon/distances/tests/test_numba_distance_parameters.py
index 4a507afd..8ed070d3 100644
--- a/aeon/distances/tests/test_numba_distance_parameters.py
+++ b/aeon/distances/tests/test_numba_distance_parameters.py
@@ -55,9 +55,10 @@ def _test_distance_params(
                 param_dict["g_arr"] = np.std([_x, _y], axis=0).sum(axis=1)
                 if "g" in param_dict:
                     del param_dict["g"]
-            results = []
-            results.append(distance_func(x, y, **param_dict))
-            results.append(distance(x, y, metric=distance_str, **param_dict))
+            results = [
+                distance_func(x, y, **param_dict),
+                distance(x, y, metric=distance_str, **param_dict),
+            ]
 
             if distance_str in _expected_distance_results_params:
                 if _expected_distance_results_params[distance_str][i][j] is not None: