Repository: simpeg/simpeg (python)

Issue:
ENH: Rename `seed` arguments to `random_seed` across simpeg
### Proposed new feature or change:

As discussed in #1394, would be nice if we standardize the name of the random seeds arguments that classes, functions and methods take. This rename would make it more clear that the argument will be used to define a random state in the class/method/function.

Implementing this rename requires raising `FutureWarning`s for the `seed` arguments and properties before we finally remove them.

Pull request:
Replace `seed` for `random_seed` in `model_builder`
#### Summary

Replace `seed` for `random_seed` in `model_builder.create_random_model.` Test the deprecation warnings and errors. Update examples and code with new argument.

#### 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
<!--Example: write "Closes #NNNN" to automatically close that issue on merge.-->

Closes #1461 

#### 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/examples/01-maps/plot_mesh2mesh.py b/examples/01-maps/plot_mesh2mesh.py
index b5621e5ae..1e76d511a 100644
--- a/examples/01-maps/plot_mesh2mesh.py
+++ b/examples/01-maps/plot_mesh2mesh.py
@@ -15,7 +15,7 @@ def run(plotIt=True):
     h1 = utils.unpack_widths([(6, 7, -1.5), (6, 10), (6, 7, 1.5)])
     h1 = h1 / h1.sum()
     M2 = discretize.TensorMesh([h1, h1])
-    V = utils.model_builder.create_random_model(M.vnC, seed=79, its=50)
+    V = utils.model_builder.create_random_model(M.vnC, random_seed=79, its=50)
     v = utils.mkvc(V)
     modh = maps.Mesh2Mesh([M, M2])
     modH = maps.Mesh2Mesh([M2, M])
diff --git a/simpeg/electromagnetics/static/utils/static_utils.py b/simpeg/electromagnetics/static/utils/static_utils.py
index 7d9d43146..23c5a01f5 100644
--- a/simpeg/electromagnetics/static/utils/static_utils.py
+++ b/simpeg/electromagnetics/static/utils/static_utils.py
@@ -1691,13 +1691,21 @@ def genTopography(mesh, zmin, zmax, seed=None, its=100, anisotropy=None):
             [mesh.h[0], mesh.h[1]], x0=[mesh.x0[0], mesh.x0[1]]
         )
         out = model_builder.create_random_model(
-            mesh.vnC[:2], bounds=[zmin, zmax], its=its, seed=seed, anisotropy=anisotropy
+            mesh.vnC[:2],
+            bounds=[zmin, zmax],
+            its=its,
+            random_seed=seed,
+            anisotropy=anisotropy,
         )
         return out, mesh2D
     elif mesh.dim == 2:
         mesh1D = discretize.TensorMesh([mesh.h[0]], x0=[mesh.x0[0]])
         out = model_builder.create_random_model(
-            mesh.vnC[:1], bounds=[zmin, zmax], its=its, seed=seed, anisotropy=anisotropy
+            mesh.vnC[:1],
+            bounds=[zmin, zmax],
+            its=its,
+            random_seed=seed,
+            anisotropy=anisotropy,
         )
         return out, mesh1D
     else:
diff --git a/simpeg/utils/model_builder.py b/simpeg/utils/model_builder.py
index 429874897..285a440a8 100644
--- a/simpeg/utils/model_builder.py
+++ b/simpeg/utils/model_builder.py
@@ -1,3 +1,4 @@
+import warnings
 import numpy as np
 import scipy.ndimage as ndi
 import scipy.sparse as sp
@@ -418,10 +419,11 @@ def create_layers_model(cell_centers, layer_tops, layer_values):
 
 def create_random_model(
     shape,
-    seed: RandomSeed | None = 1000,
+    random_seed: RandomSeed | None = 1000,
     anisotropy=None,
     its=100,
     bounds=None,
+    **kwargs,
 ):
     """
     Create random model by convolving a kernel with a uniformly distributed random model.
@@ -429,8 +431,9 @@ def create_random_model(
     Parameters
     ----------
     shape : int or tuple of int
-        Shape of the model. Can define a vector of size (n_cells) or define the dimensions of a tensor
-    seed : None or :class:`~simpeg.typing.RandomSeed`, optional
+        Shape of the model. Can define a vector of size (n_cells) or define the
+        dimensions of a tensor.
+    random_seed : None or :class:`~simpeg.typing.RandomSeed`, optional
         Random seed for random uniform model that is convolved with the kernel.
         It can either be an int, a predefined Numpy random number generator, or
         any valid input to ``numpy.random.default_rng``.
@@ -440,6 +443,12 @@ def create_random_model(
         Number of smoothing iterations after convolutions
     bounds : list of float
         Lower and upper bound for the model values
+    seed : None or :class:`~simpeg.typing.RandomSeed`, optional
+
+        .. deprecated:: 0.23.0
+
+           Argument ``seed`` is deprecated in favor of ``random_seed`` and will
+           be removed in SimPEG v0.24.0.
 
     Returns
     -------
@@ -458,13 +467,33 @@ def create_random_model(
     >>> plt.show()
 
     """
+    # Deprecate seed argument
+    if "seed" in kwargs:
+        if random_seed != 1000:
+            raise TypeError(
+                "Cannot pass both 'random_seed' and 'seed'."
+                "'seed' has been deprecated and will be removed in "
+                " SimPEG v0.24.0, please use 'random_seed' instead.",
+            )
+        warnings.warn(
+            "'seed' has been deprecated and will be removed in "
+            " SimPEG v0.24.0, please use 'random_seed' instead.",
+            FutureWarning,
+            stacklevel=2,
+        )
+        random_seed = kwargs.pop("seed")
+    if kwargs:
+        args = ", ".join([f"'{key}'" for key in kwargs])
+        raise TypeError(f"Invalid arguments {args}.")
+    # ---
+
     if bounds is None:
         bounds = [0, 1]
 
     if isinstance(shape, int):
         shape = (shape,)  # make it a tuple for consistency
 
-    rng = np.random.default_rng(seed=seed)
+    rng = np.random.default_rng(seed=random_seed)
     mr = rng.random(size=shape)
     if anisotropy is None:
         if len(shape) == 1:


Test patch:
diff --git a/tests/utils/test_model_builder.py b/tests/utils/test_model_builder.py
new file mode 100644
index 000000000..d35d5901d
--- /dev/null
+++ b/tests/utils/test_model_builder.py
@@ -0,0 +1,60 @@
+"""
+Test functions in model_builder.
+"""
+
+import pytest
+import numpy as np
+from simpeg.utils.model_builder import create_random_model
+
+
+class TestDeprecateSeedProperty:
+    """
+    Test deprecation of seed property.
+    """
+
+    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
+
+    @pytest.fixture
+    def shape(self):
+        return (5, 5)
+
+    def test_warning_argument(self, shape):
+        """
+        Test if warning is raised after passing ``seed`` as argument.
+        """
+        msg = self.get_message_deprecated_warning("seed", "random_seed")
+        seed = 42135
+        with pytest.warns(FutureWarning, match=msg):
+            result = create_random_model(shape, seed=seed)
+        np.testing.assert_allclose(result, create_random_model(shape, random_seed=seed))
+
+    def test_error_duplicated_argument(self, shape):
+        """
+        Test error after passing ``seed`` and ``random_seed`` as arguments.
+        """
+        msg = self.get_message_duplicated_error("seed", "random_seed")
+        with pytest.raises(TypeError, match=msg):
+            create_random_model(shape, seed=42, random_seed=42)
+
+    def test_error_invalid_kwarg(self, shape):
+        """
+        Test error after passing invalid kwargs to the function.
+        """
+        kwargs = {"foo": 1, "bar": 2}
+        msg = "Invalid arguments 'foo', 'bar'."
+        with pytest.raises(TypeError, match=msg):
+            with pytest.warns(FutureWarning):
+                create_random_model(shape, seed=10, **kwargs)