Repository: aeon-toolkit/aeon
Pull request #2115 (2024-09-28 13:40:36)

[REF,DEP] Refactor `BaseObject` and `BaseEstimator` into the same class
Moves methods from `BaseObject` into `BaseEstimator` and removes the `__eq__` overload. There is nothing inheriting from `BaseObject` anymore, and I don't see us returning to that format.

There is more to do here with the base class, but doing things one step at a time to reduce complexity.

aeon-actions-bot[bot]: 
## Thank you for contributing to `aeon`

I have added the following labels to this PR based on the title: **[ $\\color{#312315}{\\textsf{deprecation}}$, $\\color{#BDD9DB}{\\textsf{refactor}}$ ]**.
I have added the following labels to this PR based on the changes made: **[ $\\color{#2C2F20}{\\textsf{testing}}$ ]**. Feel free to change these if they do not properly represent the PR.

The [Checks](https://github.com/aeon-toolkit/aeon/pull/2115/checks) tab will show the status of our automated tests. You can click on individual test runs in the tab or "Details" in the panel below to see more information if there is a failure.

If our `pre-commit` code quality check fails, any trivial fixes will automatically be pushed to your PR unless it is a draft.

Don't hesitate to ask questions on the `aeon` [Slack](https://join.slack.com/t/aeon-toolkit/shared_invite/zt-22vwvut29-HDpCu~7VBUozyfL_8j3dLA) channel if you have any.

### PR CI actions

These checkboxes will add labels to enable/disable CI functionality for this PR. This may not take effect immediately, and a new commit may be required to run the new configuration.

- [ ] Run `pre-commit` checks for all files
- [ ] Run `mypy` typecheck tests
- [ ] Run all `pytest` tests and configurations
- [ ] Run all notebook example tests
- [ ] Run numba-disabled `codecov` tests
- [ ] Stop automatic `pre-commit` fixes (always disabled for drafts)
- [ ] Push an empty commit to re-run CI checks
    

Patch:
diff --git a/aeon/anomaly_detection/_pyodadapter.py b/aeon/anomaly_detection/_pyodadapter.py
index d7f560fd..cb0808e5 100644
--- a/aeon/anomaly_detection/_pyodadapter.py
+++ b/aeon/anomaly_detection/_pyodadapter.py
@@ -82,9 +82,6 @@ class PyODAdapter(BaseAnomalyDetector):
         "capability:univariate": True,
         "capability:missing_values": False,
         "fit_is_empty": False,
-        # Omit the version specification until PyOD has __version__
-        # (https://github.com/yzhao062/pyod/pull/584 in dev but not released yet)
-        # "python_dependencies": ["pyod>=1.1.3"]
         "python_dependencies": ["pyod"],
     }
 
@@ -178,7 +175,7 @@ class PyODAdapter(BaseAnomalyDetector):
             `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
             `create_test_instance` uses the first (or only) dictionary in `params`.
         """
-        _check_soft_dependencies("pyod")
+        _check_soft_dependencies(*cls._tags["python_dependencies"])
 
         from pyod.models.lof import LOF
 
diff --git a/aeon/anomaly_detection/_stomp.py b/aeon/anomaly_detection/_stomp.py
index 8cf441ff..0d7b624d 100644
--- a/aeon/anomaly_detection/_stomp.py
+++ b/aeon/anomaly_detection/_stomp.py
@@ -8,7 +8,6 @@ __all__ = ["STOMP"]
 import numpy as np
 
 from aeon.anomaly_detection.base import BaseAnomalyDetector
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 from aeon.utils.windowing import reverse_windowing
 
 
@@ -91,7 +90,6 @@ class STOMP(BaseAnomalyDetector):
         super().__init__(axis=0)
 
     def _predict(self, X: np.ndarray) -> np.ndarray:
-        _check_soft_dependencies("stumpy", severity="error")
         import stumpy
 
         self._check_params(X)
@@ -137,8 +135,6 @@ class STOMP(BaseAnomalyDetector):
             `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
             `create_test_instance` uses the first (or only) dictionary in `params`.
         """
-        _check_soft_dependencies(*cls._tags["python_dependencies"])
-
         return {
             "window_size": 10,
             "ignore_trivial": True,
diff --git a/aeon/base/__init__.py b/aeon/base/__init__.py
index 3b397b3b..1bcf0214 100644
--- a/aeon/base/__init__.py
+++ b/aeon/base/__init__.py
@@ -1,14 +1,13 @@
-"""Base classes for defining estimators and other objects in aeon."""
+"""Base classes for defining estimators in aeon."""
 
 __all__ = [
-    "BaseObject",
     "BaseEstimator",
     "BaseCollectionEstimator",
     "BaseSeriesEstimator",
     "_HeterogenousMetaEstimator",
 ]
 
-from aeon.base._base import BaseEstimator, BaseObject
+from aeon.base._base import BaseEstimator
 from aeon.base._base_collection import BaseCollectionEstimator
 from aeon.base._base_series import BaseSeriesEstimator
 from aeon.base._meta import _HeterogenousMetaEstimator
diff --git a/aeon/base/_base.py b/aeon/base/_base.py
index 436a6a9d..8b6c4327 100644
--- a/aeon/base/_base.py
+++ b/aeon/base/_base.py
@@ -1,22 +1,16 @@
 """
-Base class template for objects and fittable objects.
-
-templates in this module:
-
-    BaseObject - object with parameters and tags
-    BaseEstimator - BaseObject that can be fitted
+Base class template for estimators.
 
 Interface specifications below.
 
 ---
 
-    class name: BaseObject
-
 Parameter inspection and setter methods
     inspect parameter values      - get_params()
     setting parameter values      - set_params(**params)
     list of parameter names       - get_param_names()
     dict of parameter defaults    - get_param_defaults()
+    fitted parameter inspection - get_fitted_params()
 
 Tag inspection and setter methods
     inspect tags (all)            - get_tags()
@@ -34,14 +28,6 @@ Testing with default parameters methods
     getting default parameters (all sets)         - get_test_params()
     get one test instance with default parameters - create_test_instance()
     get list of all test instances plus name list - create_test_instances_and_names()
----
-
-    class name: BaseEstimator
-
-Provides all interface points of BaseObject, plus:
-
-Parameter inspection:
-    fitted parameter inspection - get_fitted_params()
 
 State:
     fitted model/strategy   - by convention, any attributes ending in "_"
@@ -49,8 +35,8 @@ State:
     fitted state check      - check_is_fitted (raises error if not is_fitted)
 """
 
-__maintainer__ = []
-__all__ = ["BaseEstimator", "BaseObject"]
+__maintainer__ = ["MatthewMiddlehurst", "TonyBagnall"]
+__all__ = ["BaseEstimator"]
 
 import inspect
 from collections import defaultdict
@@ -62,33 +48,14 @@ from sklearn.ensemble._base import _set_random_states
 from sklearn.exceptions import NotFittedError
 
 
-class BaseObject(_BaseEstimator):
-    """Base class for parametric objects with tags aeon.
-
-    Extends scikit-learn's BaseEstimator to include aeon interface for tags.
-    """
+class BaseEstimator(_BaseEstimator):
+    """Base class for defining estimators in aeon."""
 
     def __init__(self):
+        self._is_fitted = False
         self._tags_dynamic = dict()
-        super().__init__()
-
-    def __eq__(self, other):
-        """Equality dunder. Checks equal class and parameters.
 
-        Returns True iff result of get_params(deep=False)
-        results in equal parameter sets.
-
-        Nested BaseObject descendants from get_params are compared via __eq__ as well.
-        """
-        from aeon.testing.utils.deep_equals import deep_equals
-
-        if not isinstance(other, BaseObject):
-            return False
-
-        self_params = self.get_params(deep=False)
-        other_params = other.get_params(deep=False)
-
-        return deep_equals(self_params, other_params)
+        super().__init__()
 
     def reset(self):
         """Reset the object to a clean post-init state.
@@ -221,7 +188,7 @@ class BaseObject(_BaseEstimator):
         Parameters
         ----------
         **params : dict
-            BaseObject parameters
+            BaseEstimator parameters
 
         Returns
         -------
@@ -634,18 +601,18 @@ class BaseObject(_BaseEstimator):
         Returns
         -------
         composite: bool
-            Whether self contains a parameter which is BaseObject.
+            Whether self contains a parameter which is BaseEstimator.
         """
         # walk through method resolution order and inspect methods
         #   of classes and direct parents, "adjacent" classes in mro
         params = self.get_params(deep=False)
-        composite = any(isinstance(x, BaseObject) for x in params.values())
+        composite = any(isinstance(x, BaseEstimator) for x in params.values())
 
         return composite
 
     def _components(self, base_class=None):
         """
-        Return references to all state changing BaseObject type attributes.
+        Return references to all state changing BaseEstimator type attributes.
 
         This *excludes* the blue-print-like components passed in the __init__.
 
@@ -654,8 +621,8 @@ class BaseObject(_BaseEstimator):
 
         Parameters
         ----------
-        base_class : class, optional, default=None, must be subclass of BaseObject
-            if None, behaves the same as `base_class=BaseObject`
+        base_class : class, optional, default=None, must be subclass of BaseEstimator
+            if None, behaves the same as `base_class=BaseEstimator`
             if not None, return dict collects descendants of `base_class`.
 
         Returns
@@ -666,16 +633,16 @@ class BaseObject(_BaseEstimator):
             are not class attributes, and are not hyper-parameters (`__init__` args).
         """
         if base_class is None:
-            base_class = BaseObject
+            base_class = BaseEstimator
         if base_class is not None and not inspect.isclass(base_class):
             raise TypeError(f"base_class must be a class, but found {type(base_class)}")
-        # if base_class is not None and not issubclass(base_class, BaseObject):
-        #     raise TypeError("base_class must be a subclass of BaseObject")
+        # if base_class is not None and not issubclass(base_class, BaseEstimator):
+        #     raise TypeError("base_class must be a subclass of BaseEstimator")
 
         # retrieve parameter names to exclude them later
         param_names = self.get_params(deep=False).keys()
 
-        # retrieve all attributes that are BaseObject descendants
+        # retrieve all attributes that are BaseEstimator descendants
         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).difference(param_names)
@@ -772,17 +739,6 @@ class BaseObject(_BaseEstimator):
         with ZipFile(serial, "r") as file:
             return pickle.loads(file.open("_obj").read())
 
-
-class BaseEstimator(BaseObject):
-    """Base class for defining estimators in aeon.
-
-    Extends aeon's BaseObject to include basic functionality for fittable estimators.
-    """
-
-    def __init__(self):
-        self._is_fitted = False
-        super().__init__()
-
     @property
     def is_fitted(self):
         """Whether ``fit`` has been called."""
@@ -855,7 +811,7 @@ class BaseEstimator(BaseObject):
             else:
                 return x
 
-        # add all nested parameters from components that are aeon BaseObject
+        # add all nested parameters from components that are aeon BaseEstimator
         c_dict = self._components()
         for c, comp in c_dict.items():
             if isinstance(comp, BaseEstimator) and comp._is_fitted:
diff --git a/aeon/base/_meta.py b/aeon/base/_meta.py
index 0e5d0f45..1d081a38 100644
--- a/aeon/base/_meta.py
+++ b/aeon/base/_meta.py
@@ -87,7 +87,7 @@ class _HeterogenousMetaEstimator:
 
         Returns
         -------
-        composite: bool, whether self contains a parameter which is BaseObject
+        composite: bool, whether self contains a parameter which is BaseEstimator
         """
         # children of this class are always composite
         return True
diff --git a/aeon/clustering/_k_shape.py b/aeon/clustering/_k_shape.py
index 585e7dc8..9bcf4d16 100644
--- a/aeon/clustering/_k_shape.py
+++ b/aeon/clustering/_k_shape.py
@@ -6,7 +6,6 @@ import numpy as np
 from numpy.random import RandomState
 
 from aeon.clustering.base import BaseClusterer
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 class TimeSeriesKShape(BaseClusterer):
@@ -115,7 +114,6 @@ class TimeSeriesKShape(BaseClusterer):
         self:
             Fitted estimator.
         """
-        _check_soft_dependencies("tslearn", severity="error")
         from tslearn.clustering import KShape
 
         self._tslearn_k_shapes = KShape(
diff --git a/aeon/clustering/_k_shapes.py b/aeon/clustering/_k_shapes.py
index 6a3ae9eb..7d323c9a 100644
--- a/aeon/clustering/_k_shapes.py
+++ b/aeon/clustering/_k_shapes.py
@@ -7,7 +7,6 @@ from deprecated.sphinx import deprecated
 from numpy.random import RandomState
 
 from aeon.clustering.base import BaseClusterer
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 # TODO: remove in v1.0.0
@@ -116,7 +115,6 @@ class TimeSeriesKShapes(BaseClusterer):
         self:
             Fitted estimator.
         """
-        _check_soft_dependencies("tslearn", severity="error")
         from tslearn.clustering import KShape
 
         self._tslearn_k_shapes = KShape(
diff --git a/aeon/clustering/_kernel_k_means.py b/aeon/clustering/_kernel_k_means.py
index f39d1c50..23ab8430 100644
--- a/aeon/clustering/_kernel_k_means.py
+++ b/aeon/clustering/_kernel_k_means.py
@@ -6,7 +6,6 @@ import numpy as np
 from numpy.random import RandomState
 
 from aeon.clustering.base import BaseClusterer
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 class TimeSeriesKernelKMeans(BaseClusterer):
@@ -134,7 +133,6 @@ class TimeSeriesKernelKMeans(BaseClusterer):
         self:
             Fitted estimator.
         """
-        _check_soft_dependencies("tslearn", severity="error")
         from tslearn.clustering import KernelKMeans as TsLearnKernelKMeans
 
         verbose = 0
diff --git a/aeon/pipeline/_make_pipeline.py b/aeon/pipeline/_make_pipeline.py
index 501f5fef..de0c1996 100644
--- a/aeon/pipeline/_make_pipeline.py
+++ b/aeon/pipeline/_make_pipeline.py
@@ -29,7 +29,7 @@ def make_pipeline(*steps):
     Returns
     -------
     pipe : aeon pipeline containing steps, in order
-        always a descendant of BaseObject, precise object determined by
+        always a descendant of BaseEstimator, precise object determined by
         equivalent to result of step[0] * step[1] * ... * step[-1]
 
     Examples
diff --git a/aeon/registry/_base_classes.py b/aeon/registry/_base_classes.py
index 64b2e95e..4dd2a92d 100644
--- a/aeon/registry/_base_classes.py
+++ b/aeon/registry/_base_classes.py
@@ -33,12 +33,7 @@ __maintainer__ = []
 import pandas as pd
 
 from aeon.anomaly_detection.base import BaseAnomalyDetector
-from aeon.base import (
-    BaseCollectionEstimator,
-    BaseEstimator,
-    BaseObject,
-    BaseSeriesEstimator,
-)
+from aeon.base import BaseCollectionEstimator, BaseEstimator, BaseSeriesEstimator
 from aeon.classification.base import BaseClassifier
 from aeon.classification.early_classification import BaseEarlyClassifier
 from aeon.clustering.base import BaseClusterer
@@ -51,7 +46,6 @@ from aeon.transformations.series import BaseSeriesTransformer
 
 BASE_CLASS_REGISTER = [
     ("anomaly-detector", BaseAnomalyDetector, "anomaly detector"),
-    ("object", BaseObject, "object"),
     ("estimator", BaseEstimator, "estimator = object with fit"),
     ("classifier", BaseClassifier, "classifier"),
     ("collection-estimator", BaseCollectionEstimator, "collection estimator"),
diff --git a/aeon/registry/_identifier.py b/aeon/registry/_identifier.py
index 2f889565..53eba291 100644
--- a/aeon/registry/_identifier.py
+++ b/aeon/registry/_identifier.py
@@ -12,7 +12,7 @@ def get_identifiers(obj, force_single_identifier=True, coerce_to_list=False):
 
     Parameters
     ----------
-    obj : class or object inheriting from aeon BaseObject
+    obj : class or object inheriting from aeon BaseEstimator
     force_single_identifier : bool, optional, default = True
         whether only a single identifier is returned
         if True, only the *first* identifier found will be returned
diff --git a/aeon/registry/_tag_deprecation.py b/aeon/registry/_tag_deprecation.py
index d5995e18..d11289e5 100644
--- a/aeon/registry/_tag_deprecation.py
+++ b/aeon/registry/_tag_deprecation.py
@@ -8,7 +8,7 @@ class _TagAliaserMixin:
     """
     Mixin class for tag aliasing and deprecation of old tags.
 
-    To deprecate tags, add the TagAliaserMixin to BaseObject or BaseEstimator.
+    To deprecate tags, add the TagAliaserMixin to BaseEstimator.
     alias_dict contains the deprecated tags, and supports removal and renaming.
         For removal, add an entry "old_tag_name": ""
         For renaming, add an entry "old_tag_name": "new_tag_name"
@@ -22,7 +22,7 @@ class _TagAliaserMixin:
 
     When removing tags, ensure to remove the removed tags from this class.
     If no tags are deprecated anymore (e.g., all deprecated tags are removed/renamed),
-    ensure toremove this class as a parent of BaseObject or BaseEstimator.
+    ensure toremove this class as a parent of BaseEstimator.
     """
 
     def __init__(self):
diff --git a/aeon/segmentation/_binseg.py b/aeon/segmentation/_binseg.py
index 2ddbf5ca..ad54b047 100644
--- a/aeon/segmentation/_binseg.py
+++ b/aeon/segmentation/_binseg.py
@@ -7,7 +7,6 @@ import numpy as np
 import pandas as pd
 
 from aeon.segmentation.base import BaseSegmenter
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 class BinSegSegmenter(BaseSegmenter):
@@ -93,7 +92,6 @@ class BinSegSegmenter(BaseSegmenter):
         return {}
 
     def _run_binseg(self, X):
-        _check_soft_dependencies("ruptures", severity="error")
         import ruptures as rpt
 
         binseg = rpt.Binseg(
diff --git a/aeon/segmentation/_fluss.py b/aeon/segmentation/_fluss.py
index 98b74644..261044f9 100644
--- a/aeon/segmentation/_fluss.py
+++ b/aeon/segmentation/_fluss.py
@@ -7,7 +7,6 @@ import numpy as np
 import pandas as pd
 
 from aeon.segmentation.base import BaseSegmenter
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 class FLUSSSegmenter(BaseSegmenter):
@@ -103,7 +102,6 @@ class FLUSSSegmenter(BaseSegmenter):
         return {"profile": self.profile}
 
     def _run_fluss(self, X):
-        _check_soft_dependencies("stumpy", severity="error")
         import stumpy
 
         mp = stumpy.stump(X, m=self.period_length)
diff --git a/aeon/transformations/series/_acf.py b/aeon/transformations/series/_acf.py
index f36c95de..56d4dab1 100644
--- a/aeon/transformations/series/_acf.py
+++ b/aeon/transformations/series/_acf.py
@@ -7,7 +7,6 @@ import numpy as np
 from numba import njit
 
 from aeon.transformations.series.base import BaseSeriesTransformer
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 class AutoCorrelationSeriesTransformer(BaseSeriesTransformer):
@@ -219,7 +218,6 @@ class StatsModelsACF(BaseSeriesTransformer):
         -------
         transformed version of X
         """
-        _check_soft_dependencies("statsmodels", severity="error")
         X = X.squeeze()
         from statsmodels.tsa.stattools import acf
 
@@ -342,7 +340,6 @@ class StatsModelsPACF(BaseSeriesTransformer):
         -------
         transformed version of X
         """
-        _check_soft_dependencies("statsmodels", severity="error")
         X = X.squeeze()
 
         from statsmodels.tsa.stattools import pacf
diff --git a/aeon/transformations/series/_matrix_profile.py b/aeon/transformations/series/_matrix_profile.py
index 711cd70b..00832c4e 100644
--- a/aeon/transformations/series/_matrix_profile.py
+++ b/aeon/transformations/series/_matrix_profile.py
@@ -4,7 +4,6 @@ __maintainer__ = []
 __all__ = ["MatrixProfileSeriesTransformer"]
 
 from aeon.transformations.series.base import BaseSeriesTransformer
-from aeon.utils.validation._dependencies import _check_soft_dependencies
 
 
 class MatrixProfileSeriesTransformer(BaseSeriesTransformer):
@@ -65,26 +64,9 @@ class MatrixProfileSeriesTransformer(BaseSeriesTransformer):
             Matrix Profile of time series as output with length as
             (n_timepoints-window_length+1)
         """
-        _check_soft_dependencies("stumpy", severity="error")
         import stumpy
 
         X = X.squeeze()
 
         self.matrix_profile_ = stumpy.stump(X, self.window_length)
         return self.matrix_profile_[:, 0].astype("float")
-
-    @classmethod
-    def get_test_params(cls, parameter_set="default"):
-        """
-        Return testing parameter settings for the estimator.
-
-        Parameters
-        ----------
-        parameter_set : str, default="default"
-
-        Returns
-        -------
-        params : dict or list of dict, default = {}
-            Parameters to create testing instances of the class.
-        """
-        return {}
diff --git a/aeon/utils/sklearn.py b/aeon/utils/sklearn.py
index 90426ac2..f398a5d2 100644
--- a/aeon/utils/sklearn.py
+++ b/aeon/utils/sklearn.py
@@ -7,10 +7,10 @@ from sklearn.base import ClassifierMixin, ClusterMixin, RegressorMixin, Transfor
 from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
 from sklearn.pipeline import Pipeline
 
-from aeon.base import BaseObject
-
 __maintainer__ = []
 
+from aeon.base import BaseEstimator
+
 
 def is_sklearn_estimator(obj):
     """Check whether obj is an sklearn estimator.
@@ -27,7 +27,7 @@ def is_sklearn_estimator(obj):
         obj = type(obj)
 
     is_in_sklearn = issubclass(obj, SklearnBaseEstimator)
-    is_in_aeon = issubclass(obj, BaseObject)
+    is_in_aeon = issubclass(obj, BaseEstimator)
 
     is_sklearn_est = is_in_sklearn and not is_in_aeon
     return is_sklearn_est
diff --git a/aeon/utils/validation/_dependencies.py b/aeon/utils/validation/_dependencies.py
index ee31e710..30c34d48 100644
--- a/aeon/utils/validation/_dependencies.py
+++ b/aeon/utils/validation/_dependencies.py
@@ -234,7 +234,7 @@ def _check_python_version(obj, package=None, msg=None, severity="error"):
 
     Parameters
     ----------
-    obj : aeon estimator, BaseObject descendant
+    obj : aeon estimator, BaseEstimator descendant
         used to check python version
     package : str, default = None
         if given, will be used in error message as package name
@@ -312,12 +312,12 @@ def _check_estimator_deps(obj, msg=None, severity="error"):
     Convenience wrapper around `_check_python_version` and `_check_soft_dependencies`,
     checking against estimator tags `"python_version"`, `"python_dependencies"`.
 
-    Checks whether dependency requirements of `BaseObject`-s in `obj`
+    Checks whether dependency requirements of `BaseEstimator`-s in `obj`
     are satisfied by the current python environment.
 
     Parameters
     ----------
-    obj : `aeon` object, `BaseObject` descendant, or list/tuple thereof
+    obj : `aeon` object, `BaseEstimator` descendant, or list/tuple thereof
         object(s) that this function checks compatibility of, with the python env
     msg : str, optional, default = default message (msg below)
         error message to be returned in the `ModuleNotFoundError`, overrides default
@@ -335,7 +335,7 @@ def _check_estimator_deps(obj, msg=None, severity="error"):
         False is returned only if no exception is raised by the function
         checks for python version using the python_version tag of obj
         checks for soft dependencies present using the python_dependencies tag of obj
-        if `obj` contains multiple `BaseObject`-s, checks whether all are compatible
+        if `obj` contains multiple `BaseEstimator`-s, checks whether all are compatible
 
     Raises
     ------
diff --git a/docs/api_reference/base.rst b/docs/api_reference/base.rst
index ec15c749..c4329dc7 100644
--- a/docs/api_reference/base.rst
+++ b/docs/api_reference/base.rst
@@ -18,7 +18,6 @@ Base classes
     :toctree: auto_generated/
     :template: class.rst
 
-    BaseObject
     BaseEstimator
     BaseCollectionEstimator
     BaseSeriesEstimator


Test patch:
diff --git a/aeon/base/tests/test_base.py b/aeon/base/tests/test_base.py
index 258dca45..3b58b4d4 100644
--- a/aeon/base/tests/test_base.py
+++ b/aeon/base/tests/test_base.py
@@ -1,5 +1,5 @@
 """
-Tests for BaseObject universal base class.
+Tests for BaseEstimator universal base class.
 
 tests in this module:
 
@@ -14,8 +14,6 @@ tests in this module:
 
     test_components         - tests retrieval of list of components via _components
     test_get_fitted_params  - tests get_fitted_params logic, nested and non-nested
-
-    test_eq_dunder       - tests __eq__ dunder to compare parameter definition
 """
 
 __maintainer__ = []
@@ -30,18 +28,17 @@ __all__ = [
     "test_reset_composite",
     "test_components",
     "test_get_fitted_params",
-    "test_eq_dunder",
 ]
 
 from copy import deepcopy
 
 import pytest
 
-from aeon.base import BaseEstimator, BaseObject
+from aeon.base import BaseEstimator
 
 
 # Fixture class for testing tag system
-class FixtureClassParent(BaseObject):
+class FixtureClassParent(BaseEstimator):
     _tags = {"A": "1", "B": 2, "C": 1234, 3: "D"}
 
 
@@ -62,7 +59,7 @@ FIXTURE_OBJECT_TAGS = {"A": 42424241, "B": 3, "C": 1234, 3: "E"}
 
 
 def test_get_class_tags():
-    """Tests get_class_tags class method of BaseObject for correctness.
+    """Tests get_class_tags class method of BaseEstimator for correctness.
 
     Raises
     ------
@@ -70,13 +67,13 @@ def test_get_class_tags():
     """
     child_tags = FIXTURE_CLASSCHILD.get_class_tags()
 
-    msg = "Inheritance logic in BaseObject.get_class_tags is incorrect"
+    msg = "Inheritance logic in BaseEstimator.get_class_tags is incorrect"
 
     assert child_tags == FIXTURE_CLASSCHILD_TAGS, msg
 
 
 def test_get_class_tag():
-    """Tests get_class_tag class method of BaseObject for correctness.
+    """Tests get_class_tag class method of BaseEstimator for correctness.
 
     Raises
     ------
@@ -92,19 +89,19 @@ def test_get_class_tag():
     child_tag_default = FIXTURE_CLASSCHILD.get_class_tag("foo", "bar")
     child_tag_defaultNone = FIXTURE_CLASSCHILD.get_class_tag("bar")
 
-    msg = "Inheritance logic in BaseObject.get_class_tag is incorrect"
+    msg = "Inheritance logic in BaseEstimator.get_class_tag is incorrect"
 
     for key in child_tags_keys:
         assert child_tags[key] == FIXTURE_CLASSCHILD_TAGS[key], msg
 
-    msg = "Default override logic in BaseObject.get_class_tag is incorrect"
+    msg = "Default override logic in BaseEstimator.get_class_tag is incorrect"
 
     assert child_tag_default == "bar", msg
     assert child_tag_defaultNone is None, msg
 
 
 def test_get_tags():
-    """Tests get_tags method of BaseObject for correctness.
+    """Tests get_tags method of BaseEstimator for correctness.
 
     Raises
     ------
@@ -112,13 +109,13 @@ def test_get_tags():
     """
     object_tags = FIXTURE_OBJECT.get_tags()
 
-    msg = "Inheritance logic in BaseObject.get_tags is incorrect"
+    msg = "Inheritance logic in BaseEstimator.get_tags is incorrect"
 
     assert object_tags == FIXTURE_OBJECT_TAGS, msg
 
 
 def test_get_tag():
-    """Tests get_tag method of BaseObject for correctness.
+    """Tests get_tag method of BaseEstimator for correctness.
 
     Raises
     ------
@@ -134,12 +131,12 @@ def test_get_tag():
     object_tag_default = FIXTURE_OBJECT.get_tag("foo", "bar", raise_error=False)
     object_tag_defaultNone = FIXTURE_OBJECT.get_tag("bar", raise_error=False)
 
-    msg = "Inheritance logic in BaseObject.get_tag is incorrect"
+    msg = "Inheritance logic in BaseEstimator.get_tag is incorrect"
 
     for key in object_tags_keys:
         assert object_tags[key] == FIXTURE_OBJECT_TAGS[key], msg
 
-    msg = "Default override logic in BaseObject.get_tag is incorrect"
+    msg = "Default override logic in BaseEstimator.get_tag is incorrect"
 
     assert object_tag_default == "bar", msg
     assert object_tag_defaultNone is None, msg
@@ -163,19 +160,19 @@ FIXTURE_OBJECT_SET_DYN = {"A": 42424243, "B": 3, "E": 3}
 
 
 def test_set_tags():
-    """Tests set_tags method of BaseObject for correctness.
+    """Tests set_tags method of BaseEstimator for correctness.
 
     Raises
     ------
     AssertionError if override logic in set_tags is incorrect
     """
-    msg = "Setter/override logic in BaseObject.set_tags is incorrect"
+    msg = "Setter/override logic in BaseEstimator.set_tags is incorrect"
 
     assert FIXTURE_OBJECT_SET._tags_dynamic == FIXTURE_OBJECT_SET_DYN, msg
     assert FIXTURE_OBJECT_SET.get_tags() == FIXTURE_OBJECT_SET_TAGS, msg
 
 
-class CompositionDummy(BaseObject):
+class CompositionDummy(BaseEstimator):
     """Potentially composite object, for testing."""
 
     def __init__(self, foo, bar=84):
@@ -198,7 +195,7 @@ def test_is_composite():
     assert composite.is_composite()
 
 
-class ResetTester(BaseObject):
+class ResetTester(BaseEstimator):
     clsvar = 210
 
     def __init__(self, a, b=42):
@@ -262,8 +259,8 @@ def test_components():
     ------
     AssertionError if logic behind _components is incorrect, logic tested:
         calling _components on a non-composite returns an empty dict
-        calling _components on a composite returns name/BaseObject pair in dict,
-        and BaseObject returned is identical with attribute of the same name
+        calling _components on a composite returns name/BaseEstimator pair in dict,
+        and BaseEstimator returned is identical with attribute of the same name
     """
     non_composite = CompositionDummy(foo=42)
     composite = CompositionDummy(foo=non_composite)
@@ -323,46 +320,3 @@ def test_get_fitted_params():
     assert comp_f_params["foo"] is not composite.foo
     assert comp_f_params_shallow["foo"] is composite.foo_
     assert comp_f_params_shallow["foo"] is not composite.foo
-
-
-def test_eq_dunder():
-    """Tests equality dunder for BaseObject descendants.
-
-    Equality should be determined only by get_params results.
-
-    Raises
-    ------
-    AssertionError if logic behind __eq__ is incorrect, logic tested:
-        equality of non-composites depends only on params, not on identity
-        equality of composites depends only on params, not on identity
-        result is not affected by fitting the estimator
-    """
-    non_composite = FittableCompositionDummy(foo=42)
-    non_composite_2 = FittableCompositionDummy(foo=42)
-    non_composite_3 = FittableCompositionDummy(foo=84)
-
-    composite = FittableCompositionDummy(foo=non_composite)
-    composite_2 = FittableCompositionDummy(foo=non_composite_2)
-    composite_3 = FittableCompositionDummy(foo=non_composite_3)
-
-    assert non_composite == non_composite
-    assert composite == composite
-    assert non_composite == non_composite_2
-    assert non_composite != non_composite_3
-    assert non_composite_2 != non_composite_3
-    assert composite == composite_2
-    assert composite != composite_3
-    assert composite_2 != composite_3
-
-    # equality should not be affected by fitting
-    composite.fit()
-    non_composite_2.fit()
-
-    assert non_composite == non_composite
-    assert composite == composite
-    assert non_composite == non_composite_2
-    assert non_composite != non_composite_3
-    assert non_composite_2 != non_composite_3
-    assert composite == composite_2
-    assert composite != composite_3
-    assert composite_2 != composite_3
diff --git a/aeon/base/tests/test_base_aeon.py b/aeon/base/tests/test_base_aeon.py
index 07473912..f9d3b574 100644
--- a/aeon/base/tests/test_base_aeon.py
+++ b/aeon/base/tests/test_base_aeon.py
@@ -1,4 +1,4 @@
-"""Tests for BaseObject universal base class that require aeon or sklearn imports."""
+"""Tests for universal base class that require aeon or sklearn imports."""
 
 __maintainer__ = []
 
diff --git a/aeon/registry/tests/test_lookup.py b/aeon/registry/tests/test_lookup.py
index 966c003f..fd4f295f 100644
--- a/aeon/registry/tests/test_lookup.py
+++ b/aeon/registry/tests/test_lookup.py
@@ -4,7 +4,7 @@ __maintainer__ = []
 
 import pytest
 
-from aeon.base import BaseObject
+from aeon.base import BaseEstimator
 from aeon.registry import all_estimators, all_tags, get_identifiers
 from aeon.registry._base_classes import BASE_CLASS_IDENTIFIER_LIST, BASE_CLASS_LOOKUP
 from aeon.registry._lookup import _check_estimator_types
@@ -57,7 +57,7 @@ def _get_type_tuple(estimator_identifiers):
             BASE_CLASS_LOOKUP[id] for id in _to_list(estimator_identifiers)
         )
     else:
-        estimator_classes = (BaseObject,)
+        estimator_classes = (BaseEstimator,)
 
     return estimator_classes
 
diff --git a/aeon/testing/estimator_checking/_yield_estimator_checks.py b/aeon/testing/estimator_checking/_yield_estimator_checks.py
index c49d07f1..4097b659 100644
--- a/aeon/testing/estimator_checking/_yield_estimator_checks.py
+++ b/aeon/testing/estimator_checking/_yield_estimator_checks.py
@@ -14,7 +14,7 @@ from sklearn.exceptions import NotFittedError
 from sklearn.utils.estimator_checks import check_get_params_invariance
 
 from aeon.anomaly_detection.base import BaseAnomalyDetector
-from aeon.base import BaseEstimator, BaseObject
+from aeon.base import BaseEstimator
 from aeon.base._base import _clone_estimator
 from aeon.classification import BaseClassifier
 from aeon.classification.deep_learning.base import BaseDeepClassifier
@@ -329,13 +329,12 @@ def check_estimator_tags(estimator_class):
         )
 
 
-# todo consider removing the multiple base class allowance. Possibly deprecate
-#  BaseObject and roll it into BaseEstimator?
+# todo consider removing the multiple base class allowance.
 def check_inheritance(estimator_class):
-    """Check that estimator inherits from BaseObject and/or BaseEstimator."""
+    """Check that estimator inherits from BaseEstimator."""
     assert issubclass(
-        estimator_class, BaseObject
-    ), f"object {estimator_class} is not a sub-class of BaseObject."
+        estimator_class, BaseEstimator
+    ), f"object {estimator_class} is not a sub-class of BaseEstimator."
 
     if hasattr(estimator_class, "fit"):
         assert issubclass(estimator_class, BaseEstimator), (
diff --git a/aeon/testing/testing_config.py b/aeon/testing/testing_config.py
index bbeb5dc5..109fe95e 100644
--- a/aeon/testing/testing_config.py
+++ b/aeon/testing/testing_config.py
@@ -4,12 +4,7 @@ __maintainer__ = ["MatthewMiddlehurst"]
 __all__ = ["EXCLUDE_ESTIMATORS", "EXCLUDED_TESTS"]
 
 import aeon.testing.utils._cicd_numba_caching  # noqa: F401
-from aeon.base import (
-    BaseCollectionEstimator,
-    BaseEstimator,
-    BaseObject,
-    BaseSeriesEstimator,
-)
+from aeon.base import BaseCollectionEstimator, BaseEstimator, BaseSeriesEstimator
 from aeon.registry import BASE_CLASS_LIST, BASE_CLASS_LOOKUP, ESTIMATOR_TAG_LIST
 
 # whether to use smaller parameter matrices for test generation and subsample estimators
@@ -98,7 +93,6 @@ NON_STATE_CHANGING_METHODS = NON_STATE_CHANGING_METHODS_ARRAYLIKE + (
 # The following gives a list of valid estimator base classes.
 CORE_BASE_TYPES = (
     BaseEstimator,
-    BaseObject,
     BaseCollectionEstimator,
     BaseSeriesEstimator,
 )
diff --git a/aeon/testing/utils/estimator_checks.py b/aeon/testing/utils/estimator_checks.py
index 01f2358a..e6efa52e 100644
--- a/aeon/testing/utils/estimator_checks.py
+++ b/aeon/testing/utils/estimator_checks.py
@@ -9,7 +9,7 @@ import numpy as np
 import pandas as pd
 from pandas.testing import assert_frame_equal
 
-from aeon.base import BaseEstimator, BaseObject
+from aeon.base import BaseEstimator
 from aeon.clustering.base import BaseClusterer
 from aeon.regression.base import BaseRegressor
 from aeon.testing.testing_data import FULL_TEST_DATA_DICT
@@ -56,7 +56,7 @@ def _get_tag(estimator, tag_name, default=None, raise_error=False):
 
 def _list_required_methods(estimator):
     """Return list of required method names (beyond BaseEstimator ones)."""
-    # all BaseObject children must implement these
+    # all BaseEstimator children must implement these
     MUST_HAVE_FOR_OBJECTS = ["set_params", "get_params"]
 
     # all BaseEstimator children must implement these
@@ -75,7 +75,7 @@ def _list_required_methods(estimator):
 
     required_methods = []
 
-    if isinstance(estimator, BaseObject):
+    if isinstance(estimator, BaseEstimator):
         required_methods += MUST_HAVE_FOR_OBJECTS
 
     if isinstance(estimator, BaseEstimator):
diff --git a/docs/developer_guide/testing.md b/docs/developer_guide/testing.md
index 2875358c..9f0be7f5 100644
--- a/docs/developer_guide/testing.md
+++ b/docs/developer_guide/testing.md
@@ -9,8 +9,8 @@ tests, or how to extend the testing framework.
 `aeon` testing happens on three layers, roughly corresponding to the inheritance layers
 of estimators.
 
-* **package level**: testing interface compliance with the `BaseObject` and
-`BaseEstimator` specifications, in `tests/test_all_estimators.py`
+* **package level**: testing interface compliance with the `BaseEstimator`
+specifications, in `tests/test_all_estimators.py`
 * **module level**: testing interface compliance of concrete estimators with their base
 class, for instance `classification/tests/test_all_classifiers.py`
 * **low level**: testing individual functionality of estimators or other code, in