Repository: simpeg/simpeg (python)

Issue:
Active Cells names
We need to standardize the name we use to refer to active cell index arrays throughout SimPEG. In some areas it is referred to as `ind_active`, in other `actInd`, and some newer ones as `active_cells`.

I would favor switching all of them to `active_cells`.

Pull request description:
Replace old args for `active_cells` in EM static functions
#### Summary

Replace `ind_active` for `active_cells` in `drapeTopotoLoc`, `drape_electrodes_on_topography` and `spectral_ip_mappings`. Test warnings and errors for the deprecations. Update usage of `active_cells`.

#### 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/getting_started/contributing/code-style.html).
* [ ] Added [tests](https://docs.simpeg.xyz/content/getting_started/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/getting_started/practices.html#documentation).
* [ ] Marked as ready for review (if this is was a draft PR), and converted
      to a Pull Request
* [ ] Tagged ``@simpeg/simpeg-developers`` when ready for review.

#### Reference issue

Closes #1121 

#### 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/electromagnetics/static/resistivity/survey.py b/simpeg/electromagnetics/static/resistivity/survey.py
index 411f9ff7a..1da25060d 100644
--- a/simpeg/electromagnetics/static/resistivity/survey.py
+++ b/simpeg/electromagnetics/static/resistivity/survey.py
@@ -285,7 +285,13 @@ class Survey(BaseSurvey):
         )
 
     def drape_electrodes_on_topography(
-        self, mesh, ind_active, option="top", topography=None, force=False
+        self,
+        mesh,
+        active_cells,
+        option="top",
+        topography=None,
+        force=False,
+        ind_active=None,
     ):
         """Shift electrode locations to discrete surface topography.
 
@@ -293,7 +299,7 @@ class Survey(BaseSurvey):
         ----------
         mesh : discretize.TensorMesh or discretize.TreeMesh
             The mesh on which the discretized fields are computed
-        ind_active : numpy.ndarray of int or bool
+        active_cells : numpy.ndarray of int or bool
             Active topography cells
         option :{"top", "center"}
             Define topography at tops of cells or cell centers.
@@ -301,8 +307,21 @@ class Survey(BaseSurvey):
             Surface topography
         force : bool, default = ``False``
             If ``True`` force electrodes to surface even if borehole
+        ind_active : numpy.ndarray of int or bool, optional
+
+            .. deprecated:: 0.23.0
+
+               Argument ``ind_active`` is deprecated in favor of ``active_cells``
+               and will be removed in SimPEG v0.24.0.
 
         """
+        # Deprecate ind_active argument
+        if ind_active is not None:
+            raise TypeError(
+                "'ind_active' has been deprecated and will be removed in "
+                " SimPEG v0.24.0, please use 'active_cells' instead."
+            )
+
         if self.survey_geometry == "surface":
             loc_a = self.locations_a[:, :2]
             loc_b = self.locations_b[:, :2]
@@ -316,7 +335,7 @@ class Survey(BaseSurvey):
             inv_m, inv_n = inv[: len(loc_m)], inv[len(loc_m) :]
 
             electrodes_shifted = drapeTopotoLoc(
-                mesh, unique_electrodes, ind_active=ind_active, option=option
+                mesh, unique_electrodes, active_cells=active_cells, option=option
             )
             a_shifted = electrodes_shifted[inv_a]
             b_shifted = electrodes_shifted[inv_b]
diff --git a/simpeg/electromagnetics/static/spectral_induced_polarization/run.py b/simpeg/electromagnetics/static/spectral_induced_polarization/run.py
index 36bedcbb4..8a13cd0b8 100644
--- a/simpeg/electromagnetics/static/spectral_induced_polarization/run.py
+++ b/simpeg/electromagnetics/static/spectral_induced_polarization/run.py
@@ -1,3 +1,4 @@
+import warnings
 import numpy as np
 from simpeg import (
     maps,
@@ -12,13 +13,14 @@ from simpeg import (
 
 def spectral_ip_mappings(
     mesh,
-    indActive=None,
+    active_cells=None,
     inactive_eta=1e-4,
     inactive_tau=1e-4,
     inactive_c=1e-4,
     is_log_eta=True,
     is_log_tau=True,
     is_log_c=True,
+    indActive=None,
 ):
     """
     Generates Mappings for Spectral Induced Polarization Simulation.
@@ -36,22 +38,39 @@ def spectral_ip_mappings(
 
     TODO: Illustrate input and output variables
     """
-
-    if indActive is None:
-        indActive = np.ones(mesh.nC, dtype=bool)
+    # Deprecate indActive argument
+    if indActive is not None:
+        if active_cells is not None:
+            raise TypeError(
+                "Cannot pass both 'active_cells' and 'indActive'."
+                "'indActive' has been deprecated and will be removed in "
+                " SimPEG v0.24.0, please use 'active_cells' instead.",
+            )
+        warnings.warn(
+            "'indActive' has been deprecated and will be removed in "
+            " SimPEG v0.24.0, please use 'active_cells' instead.",
+            FutureWarning,
+            stacklevel=2,
+        )
+        active_cells = indActive
+
+    if active_cells is None:
+        active_cells = np.ones(mesh.nC, dtype=bool)
 
     actmap_eta = maps.InjectActiveCells(
-        mesh, active_cells=indActive, value_inactive=inactive_eta
+        mesh, active_cells=active_cells, value_inactive=inactive_eta
     )
     actmap_tau = maps.InjectActiveCells(
-        mesh, active_cells=indActive, value_inactive=inactive_tau
+        mesh, active_cells=active_cells, value_inactive=inactive_tau
     )
     actmap_c = maps.InjectActiveCells(
-        mesh, active_cells=indActive, value_inactive=inactive_c
+        mesh, active_cells=active_cells, value_inactive=inactive_c
     )
 
     wires = maps.Wires(
-        ("eta", indActive.sum()), ("tau", indActive.sum()), ("c", indActive.sum())
+        ("eta", active_cells.sum()),
+        ("tau", active_cells.sum()),
+        ("c", active_cells.sum()),
     )
 
     if is_log_eta:
diff --git a/simpeg/electromagnetics/static/utils/static_utils.py b/simpeg/electromagnetics/static/utils/static_utils.py
index 23c5a01f5..7a552a03b 100644
--- a/simpeg/electromagnetics/static/utils/static_utils.py
+++ b/simpeg/electromagnetics/static/utils/static_utils.py
@@ -1597,7 +1597,9 @@ def gettopoCC(mesh, ind_active, option="top"):
         raise NotImplementedError(f"{type(mesh)} mesh is not supported.")
 
 
-def drapeTopotoLoc(mesh, pts, ind_active=None, option="top", topo=None, **kwargs):
+def drapeTopotoLoc(
+    mesh, pts, active_cells=None, option="top", topo=None, ind_active=None
+):
     """Drape locations right below discretized surface topography
 
     This function projects the set of locations provided to the discrete
@@ -1609,7 +1611,7 @@ def drapeTopotoLoc(mesh, pts, ind_active=None, option="top", topo=None, **kwargs
         A 2D tensor or tree mesh
     pts : (n, dim) numpy.ndarray
         The set of points being projected to the discretize surface topography
-    ind_active : numpy.ndarray of int or bool, optional
+    active_cells : numpy.ndarray of int or bool, optional
         Index array for all cells lying below the surface topography. Surface topography
         can be specified using the 'ind_active' or 'topo' input parameters.
     option : {"top", "center"}
@@ -1618,10 +1620,28 @@ def drapeTopotoLoc(mesh, pts, ind_active=None, option="top", topo=None, **kwargs
     topo : (n, dim) numpy.ndarray
         Surface topography. Can be used if an active indices array cannot be provided
         for the input parameter 'ind_active'
-    """
+    ind_active : numpy.ndarray of int or bool, optional
+
+        .. deprecated:: 0.23.0
 
-    if "actind" in kwargs:
-        ind_active = kwargs.pop("actind")
+           Argument ``ind_active`` is deprecated in favor of ``active_cells``
+           and will be removed in SimPEG v0.24.0.
+    """
+    # Deprecate ind_active argument
+    if ind_active is not None:
+        if active_cells is not None:
+            raise TypeError(
+                "Cannot pass both 'active_cells' and 'ind_active'."
+                "'ind_active' has been deprecated and will be removed in "
+                " SimPEG v0.24.0, please use 'active_cells' instead.",
+            )
+        warnings.warn(
+            "'ind_active' has been deprecated and will be removed in "
+            " SimPEG v0.24.0, please use 'active_cells' instead.",
+            FutureWarning,
+            stacklevel=2,
+        )
+        active_cells = ind_active
 
     if isinstance(mesh, discretize.CurvilinearMesh):
         raise ValueError("Curvilinear mesh is not supported.")
@@ -1640,22 +1660,22 @@ def drapeTopotoLoc(mesh, pts, ind_active=None, option="top", topo=None, **kwargs
     else:
         raise ValueError("Unsupported mesh dimension")
 
-    if ind_active is None:
-        ind_active = discretize.utils.active_from_xyz(mesh, topo)
+    if active_cells is None:
+        active_cells = discretize.utils.active_from_xyz(mesh, topo)
 
     if mesh._meshType == "TENSOR":
-        meshtemp, topoCC = gettopoCC(mesh, ind_active, option=option)
+        meshtemp, topoCC = gettopoCC(mesh, active_cells, option=option)
         inds = meshtemp.closest_points_index(pts)
         topo = topoCC[inds]
         out = np.c_[pts, topo]
 
     elif mesh._meshType == "TREE":
         if mesh.dim == 3:
-            uniqXYlocs, topoCC = gettopoCC(mesh, ind_active, option=option)
+            uniqXYlocs, topoCC = gettopoCC(mesh, active_cells, option=option)
             inds = closestPointsGrid(uniqXYlocs, pts)
             out = np.c_[uniqXYlocs[inds, :], topoCC[inds]]
         else:
-            uniqXlocs, topoCC = gettopoCC(mesh, ind_active, option=option)
+            uniqXlocs, topoCC = gettopoCC(mesh, active_cells, option=option)
             inds = closestPointsGrid(uniqXlocs, pts, dim=1)
             out = np.c_[uniqXlocs[inds], topoCC[inds]]
     else:


Test patch:
diff --git a/tests/em/static/test_dc_survey.py b/tests/em/static/test_dc_survey.py
index 20d7f8619..f7b88754d 100644
--- a/tests/em/static/test_dc_survey.py
+++ b/tests/em/static/test_dc_survey.py
@@ -3,7 +3,9 @@ Tests for resistivity (DC) survey objects.
 """
 
 import pytest
+import numpy as np
 
+from discretize import TensorMesh
 from simpeg.electromagnetics.static.resistivity import Survey
 from simpeg.electromagnetics.static.resistivity import sources
 from simpeg.electromagnetics.static.resistivity import receivers
@@ -36,6 +38,28 @@ class TestRemovedSourceType:
             survey.survey_type = "dipole-dipole"
 
 
+class TestDeprecatedIndActive:
+    """
+    Test the deprecated ``ind_active`` argument in ``drape_electrodes_on_topography``.
+    """
+
+    @pytest.fixture
+    def mesh(self):
+        return TensorMesh((5, 5, 5))
+
+    def test_error(self, mesh):
+        """
+        Test if error is raised after passing ``ind_active`` as argument.
+        """
+        survey = Survey(source_list=[])
+        msg = "'ind_active' has been deprecated and will be removed in "
+        active_cells = np.ones(mesh.n_cells, dtype=bool)
+        with pytest.raises(TypeError, match=msg):
+            survey.drape_electrodes_on_topography(
+                mesh, active_cells, ind_active=active_cells
+            )
+
+
 def test_repr():
     """
     Test the __repr__ method of the survey.
diff --git a/tests/em/static/test_spectral_ip_mappings.py b/tests/em/static/test_spectral_ip_mappings.py
new file mode 100644
index 000000000..99f244a2c
--- /dev/null
+++ b/tests/em/static/test_spectral_ip_mappings.py
@@ -0,0 +1,63 @@
+"""
+Test ``spectral_ip_mappings`` function.
+"""
+
+import pytest
+import numpy as np
+
+import discretize
+from simpeg.electromagnetics.static.spectral_induced_polarization import (
+    spectral_ip_mappings,
+)
+
+
+class TestDeprecatedIndActive:
+    """Test deprecated ``indActive`` argument ``spectral_ip_mappings``."""
+
+    OLD_NAME = "indActive"
+    NEW_NAME = "active_cells"
+
+    @pytest.fixture
+    def mesh(self):
+        """Sample mesh."""
+        return discretize.TensorMesh([10, 10, 10], "CCN")
+
+    @pytest.fixture
+    def active_cells(self, mesh):
+        """Sample active cells for the mesh."""
+        active_cells = np.ones(mesh.n_cells, dtype=bool)
+        active_cells[0] = False
+        return active_cells
+
+    def get_message_duplicated_error(self, old_name, new_name, version="v0.24.0"):
+        msg = (
+            f"Cannot pass both '{new_name}' and '{old_name}'."
+            f"'{old_name}' has been deprecated and will be removed in "
+            f" SimPEG {version}, please use '{new_name}' instead."
+        )
+        return msg
+
+    def get_message_deprecated_warning(self, old_name, new_name, version="v0.24.0"):
+        msg = (
+            f"'{old_name}' has been deprecated and will be removed in "
+            f" SimPEG {version}, please use '{new_name}' instead."
+        )
+        return msg
+
+    def test_warning_argument(self, mesh, active_cells):
+        """
+        Test if warning is raised after passing ``indActive`` as argument.
+        """
+        msg = self.get_message_deprecated_warning(self.OLD_NAME, self.NEW_NAME)
+        with pytest.warns(FutureWarning, match=msg):
+            spectral_ip_mappings(mesh, indActive=active_cells)
+
+    def test_error_duplicated_argument(self, mesh, active_cells):
+        """
+        Test error after passing ``indActive`` and ``active_cells`` as arguments.
+        """
+        msg = self.get_message_duplicated_error(self.OLD_NAME, self.NEW_NAME)
+        with pytest.raises(TypeError, match=msg):
+            spectral_ip_mappings(
+                mesh, active_cells=active_cells, indActive=active_cells
+            )
diff --git a/tests/em/static/test_static_utils.py b/tests/em/static/test_static_utils.py
new file mode 100644
index 000000000..b02489fac
--- /dev/null
+++ b/tests/em/static/test_static_utils.py
@@ -0,0 +1,66 @@
+"""
+Test functions in ``static_utils``.
+"""
+
+import pytest
+import numpy as np
+
+import discretize
+from simpeg.electromagnetics.static.utils.static_utils import drapeTopotoLoc
+
+
+class TestDeprecatedIndActive:
+    """Test deprecated ``ind_active`` argument ``drapeTopotoLoc``."""
+
+    OLD_NAME = "ind_active"
+    NEW_NAME = "active_cells"
+
+    @pytest.fixture
+    def mesh(self):
+        """Sample mesh."""
+        return discretize.TensorMesh([10, 10, 10], "CCN")
+
+    @pytest.fixture
+    def points(self):
+        """Sample points."""
+        return np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
+
+    @pytest.fixture
+    def active_cells(self, mesh):
+        """Sample active cells for the mesh."""
+        active_cells = np.ones(mesh.n_cells, dtype=bool)
+        active_cells[0] = False
+        return active_cells
+
+    def get_message_duplicated_error(self, old_name, new_name, version="v0.24.0"):
+        msg = (
+            f"Cannot pass both '{new_name}' and '{old_name}'."
+            f"'{old_name}' has been deprecated and will be removed in "
+            f" SimPEG {version}, please use '{new_name}' instead."
+        )
+        return msg
+
+    def get_message_deprecated_warning(self, old_name, new_name, version="v0.24.0"):
+        msg = (
+            f"'{old_name}' has been deprecated and will be removed in "
+            f" SimPEG {version}, please use '{new_name}' instead."
+        )
+        return msg
+
+    def test_warning_argument(self, mesh, points, active_cells):
+        """
+        Test if warning is raised after passing ``ind_active`` as argument.
+        """
+        msg = self.get_message_deprecated_warning(self.OLD_NAME, self.NEW_NAME)
+        with pytest.warns(FutureWarning, match=msg):
+            drapeTopotoLoc(mesh, points, ind_active=active_cells)
+
+    def test_error_duplicated_argument(self, mesh, points, active_cells):
+        """
+        Test error after passing ``ind_active`` and ``active_cells`` as arguments.
+        """
+        msg = self.get_message_duplicated_error(self.OLD_NAME, self.NEW_NAME)
+        with pytest.raises(TypeError, match=msg):
+            drapeTopotoLoc(
+                mesh, points, active_cells=active_cells, ind_active=active_cells
+            )