Repository: simpeg/simpeg
Pull request #1241 (2023-06-19 23:31:35)

Refactor `BaseObjectiveFunction` and `ComboObjectiveFunction`
#### Summary

Move class attributes of `BaseObjectiveFunction` and `ComboObjectiveFunction` to arguments of their constructors. Remove `kwargs` from constructors of `BaseObjectiveFunction`, `ComboObjectiveFunction` and `L2ObjectiveFunction`. Clean out some methods and replace assertion errors for a more suitable error type. As consequence of removing `kwargs`, changes to `BaseRegularization` and `WeightedLeastSquares` constructors had to be made: properly handle deprecated `indActive` and `active_cells` arguments and adjust initialization of parent class. Raise errors if the two arguments are being passed.

#### PR Checklist
* [ ] If this is a work in progress PR, set as a Draft PR
* [ ] Linted my code according to the [style guides](https://docs.simpeg.xyz/content/basic/practices.html#style).
* [ ] Added [tests](https://docs.simpeg.xyz/content/basic/practices.html#testing) to verify changes to the code.
* [ ] Added necessary documentation to any new functions/classes following the
      expect [style](https://docs.simpeg.xyz/content/basic/practices.html#documentation).
* [ ] Added relevant method tags (i.e. `GRAV`, `EM`, etc.)
* [ ] Marked as ready for review (ff this is was a draft PR), and converted 
      to a Pull Request
* [ ] Tagged ``@simpeg/simpeg-developers`` when ready for review.

#### Reference issue
<!--Example: write "Closes #NNNN" to automatically close that issue on merge.-->

#### What does this implement/fix?
<!--Please explain your changes.-->

#### Additional information
<!--Any additional information you think is important.-->


<!--
Once all tests pass and the code has been reviewed and approved, it will be merged into main
-->


Patch:
diff --git a/SimPEG/data_misfit.py b/SimPEG/data_misfit.py
index 12ca71306..c111e4255 100644
--- a/SimPEG/data_misfit.py
+++ b/SimPEG/data_misfit.py
@@ -17,13 +17,10 @@ class BaseDataMisfit(L2ObjectiveFunction):
     """
 
     def __init__(self, data, simulation, debug=False, counter=None, **kwargs):
-        super().__init__(**kwargs)
+        super().__init__(has_fields=True, debug=debug, counter=counter, **kwargs)
 
         self.data = data
         self.simulation = simulation
-        self.debug = debug
-        self.count = counter
-        self._has_fields = True
 
     @property
     def data(self):
diff --git a/SimPEG/objective_function.py b/SimPEG/objective_function.py
index fe2b4e8d9..400d94d2c 100644
--- a/SimPEG/objective_function.py
+++ b/SimPEG/objective_function.py
@@ -5,7 +5,7 @@ from discretize.tests import check_derivative
 
 from .maps import IdentityMap
 from .props import BaseSimPEG
-from .utils import set_kwargs, timeIt, Zero, Identity
+from .utils import timeIt, Zero, Identity
 
 __all__ = ["BaseObjectiveFunction", "ComboObjectiveFunction", "L2ObjectiveFunction"]
 
@@ -21,21 +21,29 @@ class BaseObjectiveFunction(BaseSimPEG):
     for building a data misfit, see :class:`SimPEG.DataMisfit.BaseDataMisfit`.
     """
 
-    counter = None
-    debug = False
-
-    mapPair = IdentityMap  #: Base class of expected maps
-    _mapping = None  #: An IdentityMap instance.
-    _has_fields = False  #: should we have the option to store fields
-    _parent = None  #: parent objective function
-    _nP = None  #: number of parameters
-
-    def __init__(self, nP=None, **kwargs):
-        if nP is not None:
-            self._nP = nP
-        set_kwargs(self, **kwargs)
+    map_class = IdentityMap  #: Base class of expected maps
+
+    def __init__(
+        self,
+        nP=None,
+        mapping=None,
+        has_fields=False,
+        counter=None,
+        debug=False,
+    ):
+        self._nP = nP
+        if mapping is None:
+            self._mapping = mapping
+        else:
+            self.mapping = mapping
+        self.counter = counter
+        self.debug = debug
+        self.has_fields = has_fields
 
     def __call__(self, x, f=None):
+        """
+        Evaluate the objective functions for a given model
+        """
         raise NotImplementedError(
             "__call__ has not been implemented for {} yet".format(
                 self.__class__.__name__
@@ -70,16 +78,18 @@ class BaseObjectiveFunction(BaseSimPEG):
         """
         if self._mapping is None:
             if self._nP is not None:
-                self._mapping = self.mapPair(nP=self.nP)
+                self._mapping = self.map_class(nP=self.nP)
             else:
-                self._mapping = self.mapPair()
+                self._mapping = self.map_class()
         return self._mapping
 
     @mapping.setter
     def mapping(self, value):
-        assert isinstance(value, self.mapPair), (
-            "mapping must be an instance of a {}, not a {}"
-        ).format(self.mapPair, value.__class__.__name__)
+        if not isinstance(value, self.map_class):
+            raise TypeError(
+                f"Invalid mapping of class '{value.__class__.__name__}'. "
+                f"It must be an instance of {self.map_class.__name__}"
+            )
         self._mapping = value
 
     @timeIt
@@ -135,7 +145,7 @@ class BaseObjectiveFunction(BaseSimPEG):
             **kwargs,
         )
 
-    def test(self, x=None, num=4, plotIt=False, **kwargs):
+    def test(self, x=None, num=4, **kwargs):
         """
         Run a convergence test on both the first and second derivatives - they
         should be second order!
@@ -167,11 +177,11 @@ class BaseObjectiveFunction(BaseSimPEG):
         )
         return combo
 
-    def __radd__(self, objfct2):
-        return self + objfct2
+    def __radd__(self, other):
+        return self + other
 
     def __mul__(self, multiplier):
-        return ComboObjectiveFunction([self], [multiplier])
+        return ComboObjectiveFunction(objfcts=[self], multipliers=[multiplier])
 
     def __rmul__(self, multiplier):
         return self * multiplier
@@ -255,64 +265,34 @@ class ComboObjectiveFunction(BaseObjectiveFunction):
     >>> combo_2 = combo_1 + objective_fun_c
     >>> print(len(combo_2))
     2
-
     """
 
-    _multiplier_types = (float, None, Zero, np.float64, int, np.integer)  # Directive
-    _multipliers = None
+    _multiplier_types = (float, None, Zero, np.float64, int, np.integer)
 
-    def __init__(self, objfcts=None, multipliers=None, unpack_on_add=True, **kwargs):
+    def __init__(self, objfcts=None, multipliers=None, unpack_on_add=True):
+        # Define default lists if None
         if objfcts is None:
             objfcts = []
         if multipliers is None:
             multipliers = len(objfcts) * [1]
 
-        self._nP = "*"
-
-        assert len(objfcts) == len(multipliers), (
-            "Must have the same number of Objective Functions and Multipliers "
-            "not {} and {}".format(len(objfcts), len(multipliers))
-        )
-
-        def validate_list(objfctlist, multipliers):
-            """
-            ensure that the number of parameters expected by each objective
-            function is the same, ensure that if multpliers are supplied, that
-            list matches the length of the objective function list
-            """
-            for fct, mult in zip(objfctlist, multipliers):
-                assert isinstance(fct, BaseObjectiveFunction), (
-                    "Unrecognized objective function type {} in objfcts. "
-                    "All entries in objfcts must inherit from "
-                    "ObjectiveFunction".format(fct.__class__.__name__)
-                )
-
-                assert type(mult) in self._multiplier_types, (
-                    "Objective Functions can only be multiplied by a "
-                    "float, or a properties.Float, not a {}, {}".format(
-                        type(mult), mult
-                    )
-                )
-
-                if fct.nP != "*":
-                    if self._nP != "*":
-                        assert self._nP == fct.nP, (
-                            "Objective Functions must all have the same "
-                            "nP={}, not {}".format(self.nP, [f.nP for f in objfcts])
-                        )
-                    else:
-                        self._nP = fct.nP
+        # Validate inputs
+        self._check_length_objective_funcs_multipliers(objfcts, multipliers)
+        self._validate_objective_functions(objfcts)
+        self._validate_multipliers(multipliers)
 
-                fct.parent = self
-
-        validate_list(objfcts, multipliers)
+        # Get number of parameters (nP) from objective functions
+        number_of_parameters = [f.nP for f in objfcts if f.nP != "*"]
+        if number_of_parameters:
+            nP = number_of_parameters[0]
+        else:
+            nP = None
 
+        super().__init__(nP=nP)
         self.objfcts = objfcts
         self._multipliers = multipliers
         self._unpack_on_add = unpack_on_add
 
-        super(ComboObjectiveFunction, self).__init__(**kwargs)
-
     def __len__(self):
         return len(self.multipliers)
 
@@ -321,35 +301,34 @@ class ComboObjectiveFunction(BaseObjectiveFunction):
 
     @property
     def multipliers(self):
+        """
+        Multipliers for each objective function
+        """
         return self._multipliers
 
     @multipliers.setter
     def multipliers(self, value):
-        for val in value:
-            assert (
-                type(val) in self._multiplier_types
-            ), "Multiplier must be in type {} not {}".format(
-                self._multiplier_types, type(val)
-            )
-
-        assert len(value) == len(self.objfcts), (
-            "the length of multipliers should be the same as the number of"
-            " objective functions ({}), not {}".format(len(self.objfcts), len(value))
-        )
-
+        """
+        Set multipliers attribute after checking if they are valid
+        """
+        self._validate_multipliers(value)
+        self._check_length_objective_funcs_multipliers(self.objfcts, value)
         self._multipliers = value
 
     def __call__(self, m, f=None):
+        """
+        Evaluate the objective functions for a given model
+        """
         fct = 0.0
         for i, phi in enumerate(self):
             multiplier, objfct = phi
             if multiplier == 0.0:  # don't evaluate the fct
                 continue
+            if f is not None and objfct.has_fields:
+                objective_func_value = objfct(m, f=f[i])
             else:
-                if f is not None and objfct._has_fields:
-                    fct += multiplier * objfct(m, f=f[i])
-                else:
-                    fct += multiplier * objfct(m)
+                objective_func_value = objfct(m)
+            fct += multiplier * objective_func_value
         return fct
 
     def deriv(self, m, f=None):
@@ -366,15 +345,12 @@ class ComboObjectiveFunction(BaseObjectiveFunction):
             multiplier, objfct = phi
             if multiplier == 0.0:  # don't evaluate the fct
                 continue
+            if f is not None and objfct.has_fields:
+                aux = objfct.deriv(m, f=f[i])
             else:
-                if f is not None and objfct._has_fields:
-                    aux = objfct.deriv(m, f=f[i])
-                    if not isinstance(aux, Zero):
-                        g += multiplier * aux
-                else:
-                    aux = objfct.deriv(m)
-                    if not isinstance(aux, Zero):
-                        g += multiplier * aux
+                aux = objfct.deriv(m)
+            if not isinstance(aux, Zero):
+                g += multiplier * aux
         return g
 
     def deriv2(self, m, v=None, f=None):
@@ -392,12 +368,11 @@ class ComboObjectiveFunction(BaseObjectiveFunction):
             multiplier, objfct = phi
             if multiplier == 0.0:  # don't evaluate the fct
                 continue
+            if f is not None and objfct.has_fields:
+                objfct_H = objfct.deriv2(m, v, f=f[i])
             else:
-                if f is not None and objfct._has_fields:
-                    objfct_H = objfct.deriv2(m, v, f=f[i])
-                else:
-                    objfct_H = objfct.deriv2(m, v)
-                H = H + multiplier * objfct_H
+                objfct_H = objfct.deriv2(m, v)
+            H = H + multiplier * objfct_H
         return H
 
     # This assumes all objective functions have a W.
@@ -431,6 +406,58 @@ class ComboObjectiveFunction(BaseObjectiveFunction):
 
         return [fun for fun in target if fun]
 
+    def _validate_objective_functions(self, objective_functions):
+        """
+        Validate objective functions
+
+        Check if the objective functions have the right types, and if
+        they all have the same number of parameters.
+        """
+        for function in objective_functions:
+            if not isinstance(function, BaseObjectiveFunction):
+                raise TypeError(
+                    "Unrecognized objective function type "
+                    f"{function.__class__.__name__} in 'objfcts'. "
+                    "All objective functions must inherit from BaseObjectiveFunction."
+                )
+        number_of_parameters = [f.nP for f in objective_functions if f.nP != "*"]
+        if number_of_parameters:
+            all_equal = all(np.equal(number_of_parameters, number_of_parameters[0]))
+            if not all_equal:
+                np_list = [f.nP for f in objective_functions]
+                raise ValueError(
+                    f"Invalid number of parameters '{np_list}' found in "
+                    "objective functions. Except for the ones with '*', they all "
+                    "must have the same number of parameters."
+                )
+
+    def _validate_multipliers(self, multipliers):
+        """
+        Validate multipliers
+
+        Check if the multipliers have the right types.
+        """
+        for multiplier in multipliers:
+            if type(multiplier) not in self._multiplier_types:
+                valid_types = ", ".join(str(t) for t in self._multiplier_types)
+                raise TypeError(
+                    f"Invalid multiplier '{multiplier}' of type '{type(multiplier)}'. "
+                    "Objective functions can only be multiplied by " + valid_types
+                )
+
+    def _check_length_objective_funcs_multipliers(
+        self, objective_functions, multipliers
+    ):
+        """
+        Check if objective functions and multipliers have the same length
+        """
+        if len(objective_functions) != len(multipliers):
+            raise ValueError(
+                "Inconsistent number of elements between objective functions "
+                f"('{len(objective_functions)}') and multipliers "
+                f"('{len(multipliers)}'). They must have the same number of parameters."
+            )
+
 
 class L2ObjectiveFunction(BaseObjectiveFunction):
     r"""
@@ -441,17 +468,36 @@ class L2ObjectiveFunction(BaseObjectiveFunction):
         \phi = \frac{1}{2}||\mathbf{W} \mathbf{m}||^2
     """
 
-    def __init__(self, W=None, **kwargs):
-        super(L2ObjectiveFunction, self).__init__(**kwargs)
-        if W is not None:
-            if self.nP == "*":
-                self._nP = W.shape[1]
+    def __init__(
+        self,
+        nP=None,
+        mapping=None,
+        W=None,
+        has_fields=False,
+        counter=None,
+        debug=False,
+    ):
+        # Check if nP and shape of W are consistent
+        if W is not None and nP is not None and nP != W.shape[1]:
+            raise ValueError(
+                f"Number of parameters nP ('{nP}') doesn't match the number of "
+                f"rows ('{W.shape[1]}') of the weights matrix W."
+            )
+        super().__init__(
+            nP=nP,
+            mapping=mapping,
+            has_fields=has_fields,
+            debug=debug,
+            counter=counter,
+        )
+        if W is not None and self.nP == "*":
+            self._nP = W.shape[1]
         self._W = W
 
     @property
     def W(self):
         """
-        Weighting matrix. The default if not sepcified is an identity.
+        Weighting matrix. The default if not specified is an identity.
         """
         if getattr(self, "_W", None) is None:
             if self._nC_residual != "*":
@@ -461,6 +507,9 @@ class L2ObjectiveFunction(BaseObjectiveFunction):
         return self._W
 
     def __call__(self, m):
+        """
+        Evaluate the objective functions for a given model
+        """
         r = self.W * (self.mapping * m)
         return 0.5 * r.dot(r)
 
diff --git a/SimPEG/regularization/base.py b/SimPEG/regularization/base.py
index cb6a5eade..62daf8546 100644
--- a/SimPEG/regularization/base.py
+++ b/SimPEG/regularization/base.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
+import warnings
 
 import numpy as np
 from discretize.base import BaseMesh
-import warnings
 from typing import TYPE_CHECKING
 from .. import maps
 from ..objective_function import BaseObjectiveFunction, ComboObjectiveFunction
@@ -54,19 +54,44 @@ class BaseRegularization(BaseObjectiveFunction):
                 f"Value of type {type(mesh)} provided."
             )
 
+        # Handle deprecated indActive argument
+        if (key := "indActive") in kwargs:
+            if active_cells is not None:
+                raise ValueError(
+                    f"Cannot simultanously pass 'active_cells' and '{key}'. "
+                    "Pass 'active_cells' only."
+                )
+            warnings.warn(
+                f"The '{key}' argument has been deprecated, please use 'active_cells'. "
+                "It will be removed in future versions of SimPEG.",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+            active_cells = kwargs.pop(key)
+
+        # Handle deprecated cell_weights argument
+        if (key := "cell_weights") in kwargs:
+            if weights is not None:
+                raise ValueError(
+                    f"Cannot simultanously pass 'weights' and '{key}'. "
+                    "Pass 'weights' only."
+                )
+            warnings.warn(
+                f"The '{key}' argument has been deprecated, please use 'weights'. "
+                "It will be removed in future versions of SimPEG.",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+            weights = kwargs.pop(key)
+
+        super().__init__(nP=None, mapping=None, **kwargs)
         self._regularization_mesh = mesh
         self._weights = {}
-
         if active_cells is not None:
             self.active_cells = active_cells
-
-        self.mapping = mapping
-
-        super().__init__(**kwargs)
-
+        self.mapping = mapping  # Set mapping using the setter
         self.reference_model = reference_model
         self.units = units
-
         if weights is not None:
             if not isinstance(weights, dict):
                 weights = {"user_weights": weights}
@@ -148,8 +173,12 @@ class BaseRegularization(BaseObjectiveFunction):
 
     @parent.setter
     def parent(self, parent):
-        if not isinstance(parent, ComboObjectiveFunction):
-            raise TypeError("Parent must be a ComboObjectiveFunction")
+        combo_class = ComboObjectiveFunction
+        if not isinstance(parent, combo_class):
+            raise TypeError(
+                f"Invalid parent of type '{parent.__class__.__name__}'. "
+                f"Parent must be a {combo_class.__name__}."
+            )
         self._parent = parent
 
     @property
@@ -731,8 +760,20 @@ class WeightedLeastSquares(ComboObjectiveFunction):
                 f"Value of type {type(mesh)} provided."
             )
         self._regularization_mesh = mesh
-        if active_cells is not None:
-            self._regularization_mesh.active_cells = active_cells
+
+        if (key := "indActive") in kwargs:
+            if active_cells is not None:
+                raise ValueError(
+                    f"Cannot simultanously pass 'active_cells' and '{key}'. "
+                    "Pass 'active_cells' only."
+                )
+            warnings.warn(
+                f"The '{key}' argument has been deprecated, please use 'active_cells'. "
+                "It will be removed in future versions of SimPEG.",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+            active_cells = kwargs.pop(key)
 
         self.alpha_s = alpha_s
         if alpha_x is not None:
@@ -798,7 +839,10 @@ class WeightedLeastSquares(ComboObjectiveFunction):
                 )
         else:
             objfcts = kwargs.pop("objfcts")
+
         super().__init__(objfcts=objfcts, unpack_on_add=False, **kwargs)
+        if active_cells is not None:
+            self.active_cells = active_cells
         self.mapping = mapping
         self.reference_model = reference_model
         self.reference_model_in_smooth = reference_model_in_smooth
@@ -1074,7 +1118,7 @@ class WeightedLeastSquares(ComboObjectiveFunction):
     def active_cells(self, values: np.ndarray):
         self.regularization_mesh.active_cells = values
         active_cells = self.regularization_mesh.active_cells
-        # notify the objtecive functions that the active_cells changed
+        # notify the objective functions that the active_cells changed
         for objfct in self.objfcts:
             objfct.active_cells = active_cells
 


Test patch:
diff --git a/tests/base/test_objective_function.py b/tests/base/test_objective_function.py
index cab7ea555..1280245b8 100644
--- a/tests/base/test_objective_function.py
+++ b/tests/base/test_objective_function.py
@@ -193,6 +193,18 @@ class TestBaseObjFct(unittest.TestCase):
 
         self.assertTrue(phi(m) == phi1(m))
 
+    def test_invalid_mapping(self):
+        """Test if setting mapping of wrong type raises errors."""
+
+        class Dummy:
+            pass
+
+        phi = objective_function.L2ObjectiveFunction()
+        invalid_mapping = Dummy()
+        msg = "Invalid mapping of class 'Dummy'."
+        with pytest.raises(TypeError, match=msg):
+            phi.mapping = invalid_mapping
+
     def test_early_exits(self):
         nP = 10
 
@@ -314,6 +326,15 @@ class TestBaseObjFct(unittest.TestCase):
         with self.assertRaises(Exception):
             phi3.multipliers = ["a", "b"]
 
+    def test_inconsistent_nparams_and_weights(self):
+        """
+        Test if L2ObjectiveFunction raises error after nP != columns in W
+        """
+        n_params = 9
+        weights = np.zeros((5, n_params + 1))
+        with pytest.raises(ValueError, match="Number of parameters nP"):
+            objective_function.L2ObjectiveFunction(nP=n_params, W=weights)
+
 
 class TestOperationsComboObjectiveFunctions:
     """Test arithmetic operations involving ComboObjectiveFunction"""
@@ -386,5 +407,42 @@ class TestOperationsComboObjectiveFunctions:
         assert combo_2.objfcts == [phi3, combo_1]
 
 
+@pytest.mark.parametrize(
+    "objfcts, multipliers",
+    (
+        (None, None),
+        ([objective_function.L2ObjectiveFunction()], None),
+        ([objective_function.L2ObjectiveFunction()], [2.5]),
+    ),
+)
+def test_empty_combo(objfcts, multipliers):
+    """Test defining an empty ComboObjectiveFunction."""
+    combo = objective_function.ComboObjectiveFunction(
+        objfcts=objfcts, multipliers=multipliers
+    )
+    if objfcts is None and multipliers is None:
+        assert combo.objfcts == []
+        assert combo.multipliers == []
+    if objfcts is not None:
+        assert combo.objfcts == objfcts
+        if multipliers is None:
+            assert combo.multipliers == [1]
+        else:
+            assert combo.multipliers == [2.5]
+
+
+def test_invalid_objfcts_in_combo():
+    """Test invalid objective function class in ComboObjectiveFunction."""
+
+    class Dummy:
+        pass
+
+    phi = objective_function.L2ObjectiveFunction()
+    invalid_phi = Dummy()
+    msg = "Unrecognized objective function type Dummy in 'objfcts'."
+    with pytest.raises(TypeError, match=msg):
+        objective_function.ComboObjectiveFunction(objfcts=[phi, invalid_phi])
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/base/test_regularization.py b/tests/base/test_regularization.py
index 574348288..b03376a0a 100644
--- a/tests/base/test_regularization.py
+++ b/tests/base/test_regularization.py
@@ -6,6 +6,8 @@ import inspect
 
 import discretize
 from SimPEG import maps, objective_function, regularization, utils
+from SimPEG.regularization import BaseRegularization, WeightedLeastSquares
+from SimPEG.objective_function import ComboObjectiveFunction
 
 
 TOL = 1e-7
@@ -627,5 +629,77 @@ def test_cross_reg_reg_errors():
         regularization.CrossReferenceRegularization(mesh, ref_dir)
 
 
+class TestParent:
+    """Test parent property of regularizations."""
+
+    @pytest.fixture
+    def regularization(self):
+        """Sample regularization instance."""
+        mesh = discretize.TensorMesh([3, 4, 5])
+        return BaseRegularization(mesh)
+
+    def test_parent(self, regularization):
+        """Test setting a parent class to a BaseRegularization."""
+        combo = ComboObjectiveFunction()
+        regularization.parent = combo
+        assert regularization.parent == combo
+
+    def test_invalid_parent(self, regularization):
+        """Test setting an invalid parent class to a BaseRegularization."""
+
+        class Dummy:
+            pass
+
+        invalid_parent = Dummy()
+        msg = "Invalid parent of type 'Dummy'."
+        with pytest.raises(TypeError, match=msg):
+            regularization.parent = invalid_parent
+
+
+class TestDeprecatedArguments:
+    """
+    Test errors after simultaneously passing new and deprecated arguments.
+
+    Within these arguments are:
+
+    * ``indActive`` (replaced by ``active_cells``)
+    * ``cell_weights`` (replaced by ``weights``)
+
+    """
+
+    @pytest.fixture(params=["1D", "2D", "3D"])
+    def mesh(self, request):
+        """Sample mesh."""
+        if request.param == "1D":
+            hx = np.random.rand(10)
+            h = [hx / hx.sum()]
+        elif request.param == "2D":
+            hx, hy = np.random.rand(10), np.random.rand(9)
+            h = [h_i / h_i.sum() for h_i in (hx, hy)]
+        elif request.param == "3D":
+            hx, hy, hz = np.random.rand(10), np.random.rand(9), np.random.rand(8)
+            h = [h_i / h_i.sum() for h_i in (hx, hy, hz)]
+        return discretize.TensorMesh(h)
+
+    @pytest.mark.parametrize(
+        "regularization_class", (BaseRegularization, WeightedLeastSquares)
+    )
+    def test_active_cells(self, mesh, regularization_class):
+        """Test indActive and active_cells arguments."""
+        active_cells = np.ones(len(mesh), dtype=bool)
+        msg = "Cannot simultanously pass 'active_cells' and 'indActive'."
+        with pytest.raises(ValueError, match=msg):
+            regularization_class(
+                mesh, active_cells=active_cells, indActive=active_cells
+            )
+
+    def test_weights(self, mesh):
+        """Test cell_weights and weights."""
+        weights = np.ones(len(mesh))
+        msg = "Cannot simultanously pass 'weights' and 'cell_weights'."
+        with pytest.raises(ValueError, match=msg):
+            BaseRegularization(mesh, weights=weights, cell_weights=weights)
+
+
 if __name__ == "__main__":
     unittest.main()