Repository: PlasmaPy/PlasmaPy

Issue:
Intermittent test failure in `TestSyntheticRadiograph.test_optical_density_histogram`
In #1456, I got a test failure for the Python 3.8 (Windows) check for `TestSyntheticRadiograph.test_optical_density_histogram`.  The test failure only showed up in one check, and did not show up in any of the other checks.  

I'm wondering if specifying a random seed for this test would enable us to resolve the problem, and also make the test deterministic.

Here's the test failure message:

```
==================================================================================== FAILURES =====================================================================================
_____________________________________________________________ TestSyntheticRadiograph.test_optical_density_histogram ______________________________________________________________
[gw0] win32 -- Python 3.8.10 D:\a\PlasmaPy\PlasmaPy\.tox\py38-all\Scripts\python.EXE

self = <plasmapy.diagnostics.tests.test_charged_particle_radiography.TestSyntheticRadiograph object at 0x0000023CE26F7AF0>

    def test_optical_density_histogram(self):
        """
        Test the optical density calculation is correct and stuffed
        with numpy.inf when the intensity is zero.
        """
        bins = (200, 60)
        size = np.array([[-1, 1], [-1, 1]]) * 30 * u.cm
    
        intensity_results = cpr.synthetic_radiograph(
            self.sim_results, size=size, bins=bins
        )
        od_results = cpr.synthetic_radiograph(
            self.sim_results, size=size, bins=bins, optical_density=True
        )
    
        assert np.allclose(intensity_results[0], od_results[0])
        assert np.allclose(intensity_results[1], od_results[1])
    
        intensity = intensity_results[2]
        zero_mask = intensity == 0
        i0 = np.mean(intensity[~zero_mask])
        od = -np.log10(intensity / i0)
    
>       assert np.allclose(od[~zero_mask], od_results[2][~zero_mask])
E       assert False
E        +  where False = <function allclose at 0x0000023CD57F8B80>(array([ 5.88380294e-01,  6.50528201e-01,  3.66531544e-01,  8.59669742e-03,\n       -9.88513070e-02,  4.42252258e-01,  1...7155e+00,  4.22048872e-01, -1.27623050e-01,\n       -8.67867956e-02,  3.66531544e-01,  7.64471553e-01,  8.10229044e-01]), array([ 5.85862638e-01,  6.48010545e-01,  3.64013888e-01,  6.07904120e-03,\n       -1.01368963e-01,  4.39734602e-01,  1...5390e+00,  4.19531216e-01, -1.30140706e-01,\n       -8.93044518e-02,  3.64013888e-01,  7.61953897e-01,  8.07711387e-01]))
E        +    where <function allclose at 0x0000023CD57F8B80> = np.allclose

plasmapy\diagnostics\tests\test_charged_particle_radiography.py:598: AssertionError
```


Discussion:
What do you mean by random seed?

I haven't seen this failure anywhere else yet.  I do recall us having issues in the past (I think with the radiography tests), where we'd have random failures on the CI but not local builds.  I believe it came down to CI tests running in parallel.  I think one test was essentially changing the value used by another test before the assertion could be made.  I think I'm recalling that correctly but I could be wrong.
I am looking into this because it occurred again on #1676 , but I really have no idea why it is occurring. This test should be deterministic. 

The only explanation I can imagine is that `self.sim_results` is getting overridden if the tests within the `TestSyntheticRadiograph` class get run in a bad order. But I think I am using a deep copy of that object anywhere I modify it, so I'm not sure where that is happening.
@pheuer Could it just be due to numerical instability? Every so often the result just barely slips outside the acceptable margin of error ?
This is indeed a strange one! 🤔 I did a web search for "how to fix intermittent software bugs" and one of the suggestions was to set up logging to provide more information about what is going on.  We could possibly add that to the test itself.

It seems the most common cause of an intermittent bug is a race condition, at least according to the World Wide Web™.  

I suppose the best advice to someone encountering this bug in an unrelated pull request would be to re-run the tests such as by pushing another commit.  Mentioning it here would also be really helpful too!
Re-opening this issue because this test just failed again on #1486. It always seems to fail on Python 3.8? 

<details>
_____________________________________________________________ TestSyntheticRadiograph.test_optical_density_histogram ______________________________________________________________
[gw1] win32 -- Python 3.8.10 D:\a\PlasmaPy\PlasmaPy\.tox\py38-all\Scripts\python.EXE

self = <plasmapy.diagnostics.tests.test_charged_particle_radiography.TestSyntheticRadiograph object at 0x000001C2CD5C9FA0>

    def test_optical_density_histogram(self):
        """
        Test the optical density calculation is correct and stuffed
        with numpy.inf when the intensity is zero.
        """
        bins = (200, 60)
        size = np.array([[-1, 1], [-1, 1]]) * 30 * u.cm
    
        sim_results = copy.deepcopy(self.sim_results)
        intensity_results = cpr.synthetic_radiograph(sim_results, size=size, bins=bins)
    
        sim_results = copy.deepcopy(self.sim_results)
        od_results = cpr.synthetic_radiograph(
            sim_results, size=size, bins=bins, optical_density=True
        )
    
        assert np.allclose(intensity_results[0], od_results[0], rtol=1e-4, atol=1e-7)
        assert np.allclose(intensity_results[1], od_results[1], rtol=1e-4, atol=1e-7)
    
        # Manually calculate the OD and check that it agrees with the values
        # returned from the function
    
        zero_mask = intensity_results[2] == 0
        i0 = np.mean(intensity_results[2][~zero_mask])
        with np.errstate(divide="ignore"):
            od = -np.log10(intensity_results[2] / i0)
    
        # Assert that the calculated od is close to that returned by the function
>       assert np.allclose(
            od[~zero_mask], od_results[2][~zero_mask], rtol=1e-4, atol=1e-7
        )
E       assert False
E        +  where False = <function allclose at 0x000001C2BBDD2940>(array([ 0.76700[389](https://github.com/PlasmaPy/PlasmaPy/runs/8038784538?check_suite_focus=true#step:6:390),  0.56288[391](https://github.com/PlasmaPy/PlasmaPy/runs/8038784538?check_suite_focus=true#step:6:392),  0.31984586, -0.04590947, -0.17251536,\n        0.38679265,  1.46597389,  0.08576265, ...  0.01112903, -0.05907091,\n        0.53655497, -0.1360861 , -0.03233666,  0.51173138,  0.8639139 ,\n        0.59091263]), array([ 7.64471553e-01,  5.60351570e-01,  3.17313522e-01, -4.84418036e-02,\n       -1.75047700e-01,  3.84260311e-01,  1...2496e-02,  5.3[402](https://github.com/PlasmaPy/PlasmaPy/runs/8038784538?check_suite_focus=true#step:6:403)2632e-01, -1.38618434e-01, -3.48689964e-02,\n        5.09199048e-01,  8.61381566e-01,  5.88380294e-01]), rtol=0.0001, atol=1e-07)
E        +    where <function allclose at 0x000001C2BBDD2940> = np.allclose

plasmapy\diagnostics\tests\test_charged_particle_radiography.py:602: AssertionError
</details>

> It always seems to fail on Python 3.8?

That's an important observation!  We're likely to drop Python 3.8 support for our first release during or after April 2023 (following [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html)), so this issue might become obsolete then, assuming this isn't just coincidence.  Even so, it'd be preferable to understand why it's happening.  Thank you for continuing to look into this!
I'll try to replicate this locally. The last time this sort of thing happened, we had an issue with `pytest-xdist` and local state being different between subprocesses...
For future reference: the problematic test is, for example, in https://github.com/PlasmaPy/PlasmaPy/runs/8038784294?check_suite_focus=true
Just got this again for the Python 3.8 (MacOS X) tests on #1771.  Here's the full test failure message this time. This was indeed for Python 3.8.

```
______________________________________________________________ TestSyntheticRadiograph.test_optical_density_histogram ______________________________________________________________
[gw1] darwin -- Python 3.8.14 /Users/runner/work/PlasmaPy/PlasmaPy/.tox/py38-all/bin/python

self = <plasmapy.diagnostics.tests.test_charged_particle_radiography.TestSyntheticRadiograph object at 0x12e9d5b50>

    def test_optical_density_histogram(self):
        """
        Test the optical density calculation is correct and stuffed
        with numpy.inf when the intensity is zero.
        """
        bins = (200, 60)
        size = np.array([[-1, 1], [-1, 1]]) * 30 * u.cm
    
        sim_results = copy.deepcopy(self.sim_results)
        intensity_results = cpr.synthetic_radiograph(sim_results, size=size, bins=bins)
    
        sim_results = copy.deepcopy(self.sim_results)
        od_results = cpr.synthetic_radiograph(
            sim_results, size=size, bins=bins, optical_density=True
        )
    
        assert np.allclose(intensity_results[0], od_results[0], rtol=1e-4, atol=1e-7)
        assert np.allclose(intensity_results[1], od_results[1], rtol=1e-4, atol=1e-7)
    
        # Manually calculate the OD and check that it agrees with the values
        # returned from the function
    
        zero_mask = intensity_results[2] == 0
        i0 = np.mean(intensity_results[2][~zero_mask])
        with np.errstate(divide="ignore"):
            od = -np.log10(intensity_results[2] / i0)
    
        # Assert that the calculated od is close to that returned by the function
>       assert np.allclose(
            od[~zero_mask], od_results[2][~zero_mask], rtol=1e-4, atol=1e-7
        )
E       assert False
E        +  where False = <function allclose at 0x1101e2820>(array([ 6.50528201e-01,  4.63441557e-01,  4.42252258e-01, -1.36796973e-02,\n       -1.44013466e-01,  3.49498205e-01,  1...7155e+00,  4.42252258e-01, -6.80373596e-02,\n       -1.36796973e-02,  4.63441557e-01,  8.61381566e-01,  6.18343517e-01]), array([ 6.48010545e-01,  4.60923901e-01,  4.39734602e-01, -1.61973535e-02,\n       -1.46531122e-01,  3.46980549e-01,  1...5390e+00,  4.39734602e-01, -7.05550158e-02,\n       -1.61973535e-02,  4.60923901e-01,  8.58863910e-01,  6.15825861e-01]), rtol=0.0001, atol=1e-07)
E        +    where <function allclose at 0x1101e2820> = np.allclose

bins       = (200, 60)
i0         = 58.13953488372093
intensity_results = (<Quantity [-0.2985, -0.2955, -0.2925, -0.2895, -0.2865, -0.2835, -0.2805,
           -0.2775, -0.2745, -0.2715, -0.26.....,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]]))
od         = array([[inf, inf, inf, ..., inf, inf, inf],
       [inf, inf, inf, ..., inf, inf, inf],
       [inf, inf, inf, ..., in...inf, inf, ..., inf, inf, inf],
       [inf, inf, inf, ..., inf, inf, inf],
       [inf, inf, inf, ..., inf, inf, inf]])
od_results = (<Quantity [-0.2985, -0.2955, -0.2925, -0.2895, -0.2865, -0.2835, -0.2805,
           -0.2775, -0.2745, -0.2715, -0.26...nf, inf, ..., inf, inf, inf],
       [inf, inf, inf, ..., inf, inf, inf],
       [inf, inf, inf, ..., inf, inf, inf]]))
self       = <plasmapy.diagnostics.tests.test_charged_particle_radiography.TestSyntheticRadiograph object at 0x12e9d5b50>
sim_results = {'detector': array([0. , 0.2, 0. ]), 'mag': 21.0, 'max_deflection': 0.03326604501361153, 'nparticles': 10000, ...}
size       = <Quantity [[-30.,  30.],
           [-30.,  30.]] cm>
zero_mask  = array([[ True,  True,  True, ...,  True,  True,  True],
       [ True,  True,  True, ...,  True,  True,  True],
      ...True],
       [ True,  True,  True, ...,  True,  True,  True],
       [ True,  True,  True, ...,  True,  True,  True]])

plasmapy/diagnostics/tests/test_charged_particle_radiography.py:602: AssertionError
```
Alas, this test just failed again on Python 3.9 in #1954.
Just noticed this failure for the Python 3.10 tests in #2482.  Grumblemuffins, I say. Grumblemuffins.
```
______________________________________________________________ TestSyntheticRadiograph.test_optical_density_histogram ______________________________________________________________
[gw3] darwin -- Python 3.10.13 /Users/runner/work/PlasmaPy/PlasmaPy/.tox/py310-cov-all/bin/python

self = <plasmapy.diagnostics.charged_particle_radiography.tests.test_synthetic_radiography.TestSyntheticRadiograph object at 0x12b3aae00>

    @pytest.mark.filterwarnings("ignore:divide by zero:RuntimeWarning")
    def test_optical_density_histogram(self) -> None:
        """
        Test the optical density calculation is correct and stuffed
        with numpy.inf when the intensity is zero.
        """
        bins = (200, 60)
        size = np.array([[-1, 1], [-1, 1]]) * 30 * u.cm
    
        intensity_results = cpr.synthetic_radiograph(
            self.sim_results, size=size, bins=bins
        )
        od_results = cpr.synthetic_radiograph(
            self.sim_results, size=size, bins=bins, optical_density=True
        )
    
        assert np.allclose(intensity_results[0], od_results[0])
        assert np.allclose(intensity_results[1], od_results[1])
    
        intensity = intensity_results[2]
        zero_mask = intensity == 0
        initial_intensity = np.mean(intensity[~zero_mask])
        optical_density = -np.log10(intensity / initial_intensity)
    
>       assert np.allclose(optical_density[~zero_mask], od_results[2][~zero_mask])
E       assert False
E        +  where False = <function allclose at 0x103a9cbf0>(array([ 8.61381566e-01,  6.85290307e-01,  4.85717952e-01,  6.55015488e-02,\n        1.04355953e-03,  4.02743717e-01, -1...5030e+00,  4.63441557e-01, -1.36796973e-02,\n       -6.16032496e-02,  5.88380294e-01,  8.61381566e-01,  5.09199048e-01]), array([ 8.58863910e-01,  6.82772651e-01,  4.83200296e-01,  6.29838925e-02,\n       -1.47409669e-03,  4.00226061e-01, -1...3264e+00,  4.60923901e-01, -1.61973535e-02,\n       -6.41209058e-02,  5.85862638e-01,  8.58863910e-01,  5.06681392e-01]))
E        +    where <function allclose at 0x103a9cbf0> = np.allclose

plasmapy/diagnostics/charged_particle_radiography/tests/test_synthetic_radiography.py:636: AssertionError
```
@namurphy I can't think why this keeps happening, and it's very hard to debug since it's seemingly random? The only random aspect of the year is the create_particles method, which uses numpy.random. The only solution I can see, which @StanczakDominik suggested a while back, is to add a random seed keyword to that method so we can make the tests at least be deterministic.

I don't think this is a real issue for users, it's probably pathological partially because these tests try to use a minimal number of particles for speed.
Agreed that this probably isn't a real issue for users.  

I'm going to try `pytest-rerunfailures` which I learned about from the Python Test podcast a few weeks ago.  We can use a decorator to say that when this particular test fails, to rerun it another couple of times.  That will only hide the intermittent failure, so we should still keep this issue open unless we implement the random seed keyword.

There's also pytest-repeat, which I think is the pytest extension that can repeat a test an arbitrary number of times, and is useful for flaky tests.  This one seems to fail only once every few hundred times...or maybe less frequently than that.

Resolving patch:
diff --git a/plasmapy/diagnostics/charged_particle_radiography/synthetic_radiography.py b/plasmapy/diagnostics/charged_particle_radiography/synthetic_radiography.py
--- a/plasmapy/diagnostics/charged_particle_radiography/synthetic_radiography.py
+++ b/plasmapy/diagnostics/charged_particle_radiography/synthetic_radiography.py
@@ -522,7 +522,7 @@ def _apply_wire_mesh(
     # Particle creation methods
     # *************************************************************************
 
-    def _angles_monte_carlo(self):
+    def _angles_monte_carlo(self, random_seed=None):
         """
         Generates angles for each particle randomly such that the flux
         per solid angle is uniform.
@@ -534,13 +534,14 @@ def _angles_monte_carlo(self):
         prob = np.sin(arg)
         prob *= 1 / np.sum(prob)
 
+        # Create a numpy random number generator instance
+        rng = np.random.default_rng(seed=random_seed)
+
         # Randomly choose theta's weighted with the sine probabilities
-        theta = np.random.choice(  # noqa: NPY002
-            arg, size=self.nparticles, replace=True, p=prob
-        )
+        theta = rng.choice(arg, size=self.nparticles, replace=True, p=prob)
 
         # Also generate a uniform phi distribution
-        phi = np.random.uniform(high=2 * np.pi, size=self.nparticles)  # noqa: NPY002
+        phi = rng.uniform(high=2 * np.pi, size=self.nparticles)
 
         return theta, phi
 
@@ -579,6 +580,7 @@ def create_particles(
         max_theta=None,
         particle: Particle = Particle("p+"),  # noqa: B008
         distribution="monte-carlo",
+        random_seed=None,
     ) -> None:
         r"""
         Generates the angular distributions about the Z-axis, then
@@ -630,6 +632,10 @@ def create_particles(
             on the image, but will well-sample the field grid with a
             smaller number of particles. The default is ``'monte-carlo'``.
 
+        random_seed : int, optional
+            A random seed to be used when generating random particle
+            distributions, e.g. with the ``monte-carlo`` distribution.
+
 
         """
         self._log("Creating Particles")
@@ -656,7 +662,7 @@ def create_particles(
         v0 = self._c * np.sqrt(1 - 1 / (ER + 1) ** 2)
 
         if distribution == "monte-carlo":
-            theta, phi = self._angles_monte_carlo()
+            theta, phi = self._angles_monte_carlo(random_seed=random_seed)
         elif distribution == "uniform":
             theta, phi = self._angles_uniform()
 


Test patch:
diff --git a/plasmapy/diagnostics/charged_particle_radiography/tests/test_synthetic_radiography.py b/plasmapy/diagnostics/charged_particle_radiography/tests/test_synthetic_radiography.py
--- a/plasmapy/diagnostics/charged_particle_radiography/tests/test_synthetic_radiography.py
+++ b/plasmapy/diagnostics/charged_particle_radiography/tests/test_synthetic_radiography.py
@@ -153,7 +153,7 @@ def test_multiple_grids() -> None:
 
     sim = cpr.Tracker(grids, source, detector, verbose=True)
 
-    sim.create_particles(1e5, 15 * u.MeV, max_theta=8 * u.deg)
+    sim.create_particles(1e5, 15 * u.MeV, max_theta=8 * u.deg, random_seed=42)
 
     sim.run(field_weighting="nearest neighbor")
 
@@ -187,7 +187,7 @@ def run_1D_example(name: str):
         RuntimeWarning, match="Fields should go to zero at edges of grid to avoid "
     ):
         sim = cpr.Tracker(grid, source, detector, verbose=False)
-    sim.create_particles(1e4, 3 * u.MeV, max_theta=0.1 * u.deg)
+    sim.create_particles(1e4, 3 * u.MeV, max_theta=0.1 * u.deg, random_seed=42)
     sim.run()
 
     size = np.array([[-1, 1], [-1, 1]]) * 10 * u.cm
@@ -231,7 +231,7 @@ def run_mesh_example(
         mesh_vdir=mesh_vdir,
     )
 
-    sim.create_particles(nparticles, 3 * u.MeV, max_theta=10 * u.deg)
+    sim.create_particles(nparticles, 3 * u.MeV, max_theta=10 * u.deg, random_seed=42)
     sim.run(field_weighting="nearest neighbor")
 
     return sim
@@ -333,14 +333,16 @@ def test_input_validation() -> None:
     # During create_particles
     # ************************************************************************
     sim = cpr.Tracker(grid, source, detector, verbose=False)
-    sim.create_particles(1e3, 15 * u.MeV, max_theta=0.99 * np.pi / 2 * u.rad)
+    sim.create_particles(
+        1e3, 15 * u.MeV, max_theta=0.99 * np.pi / 2 * u.rad, random_seed=42
+    )
 
     # ************************************************************************
     # During runtime
     # ************************************************************************
 
     sim = cpr.Tracker(grid, source, detector, verbose=False)
-    sim.create_particles(1e3, 15 * u.MeV)
+    sim.create_particles(1e3, 15 * u.MeV, random_seed=42)
 
     # Test an invalid field weighting keyword
     with pytest.raises(ValueError):
@@ -399,13 +401,19 @@ def test_create_particles() -> None:
     sim = cpr.Tracker(grid, source, detector, verbose=False)
 
     sim.create_particles(
-        1e3, 15 * u.MeV, max_theta=0.1 * u.rad, distribution="monte-carlo"
+        1e3,
+        15 * u.MeV,
+        max_theta=0.1 * u.rad,
+        distribution="monte-carlo",
+        random_seed=42,
     )
 
-    sim.create_particles(1e3, 15 * u.MeV, max_theta=0.1 * u.rad, distribution="uniform")
+    sim.create_particles(
+        1e3, 15 * u.MeV, max_theta=0.1 * u.rad, distribution="uniform", random_seed=42
+    )
 
     # Test specifying particle
-    sim.create_particles(1e3, 15 * u.MeV, particle="e")
+    sim.create_particles(1e3, 15 * u.MeV, particle="e", random_seed=42)
 
 
 @pytest.mark.slow()
@@ -417,7 +425,9 @@ def test_load_particles() -> None:
     detector = (0 * u.mm, 200 * u.mm, 0 * u.mm)
 
     sim = cpr.Tracker(grid, source, detector, verbose=False)
-    sim.create_particles(1e3, 15 * u.MeV, max_theta=0.1 * u.rad, distribution="uniform")
+    sim.create_particles(
+        1e3, 15 * u.MeV, max_theta=0.1 * u.rad, distribution="uniform", random_seed=42
+    )
 
     # Test adding unequal numbers of particles
     x = np.zeros([100, 3]) * u.m
@@ -455,7 +465,7 @@ def test_run_options() -> None:
         sim.run()
 
     sim = cpr.Tracker(grid, source, detector, verbose=True)
-    sim.create_particles(1e4, 3 * u.MeV, max_theta=10 * u.deg)
+    sim.create_particles(1e4, 3 * u.MeV, max_theta=10 * u.deg, random_seed=42)
 
     # Try running with nearest neighbor interpolator
     # Test manually setting a timestep
@@ -466,7 +476,7 @@ def test_run_options() -> None:
 
     # Test way too big of a max_theta
     sim = cpr.Tracker(grid, source, detector, verbose=True)
-    sim.create_particles(1e4, 3 * u.MeV, max_theta=89 * u.deg)
+    sim.create_particles(1e4, 3 * u.MeV, max_theta=89 * u.deg, random_seed=42)
     with pytest.warns(RuntimeWarning, match="of particles entered the field grid"):
         sim.run(field_weighting="nearest neighbor", dt=1e-12 * u.s)
 
@@ -482,7 +492,7 @@ def test_run_options() -> None:
         RuntimeWarning, match="Fields should go to zero at edges of grid to avoid "
     ):
         sim = cpr.Tracker(grid, source, detector, verbose=False)
-    sim.create_particles(1e4, 3 * u.MeV, max_theta=0.1 * u.deg)
+    sim.create_particles(1e4, 3 * u.MeV, max_theta=0.1 * u.deg, random_seed=42)
     with pytest.warns(
         RuntimeWarning,
         match="particles have been deflected away from the detector plane",
@@ -502,7 +512,7 @@ def create_tracker_obj() -> cpr.Tracker:
     detector = (0 * u.mm, 200 * u.mm, 0 * u.mm)
 
     sim = cpr.Tracker(grid, source, detector, verbose=False)
-    sim.create_particles(int(1e4), 3 * u.MeV, max_theta=10 * u.deg)
+    sim.create_particles(int(1e4), 3 * u.MeV, max_theta=10 * u.deg, random_seed=42)
     return sim
 
 
@@ -680,7 +690,7 @@ def test_cannot_modify_simulation_after_running(case) -> None:
     # Error from creating particles
     with pytest.raises(RuntimeError):
         if case == "creating particles":
-            sim.create_particles(1e4, 3 * u.MeV, max_theta=10 * u.deg)
+            sim.create_particles(1e4, 3 * u.MeV, max_theta=10 * u.deg, random_seed=42)
         elif case == "loading particles":
             sim.load_particles(sim.x, sim.v)
         elif case == "adding a wire mesh":
@@ -735,7 +745,7 @@ def test_gaussian_sphere_analytical_comparison() -> None:
     ):
         sim = cpr.Tracker(grid, source, detector, verbose=False)
 
-    sim.create_particles(1e3, W * u.eV, max_theta=12 * u.deg)
+    sim.create_particles(1e3, W * u.eV, max_theta=12 * u.deg, random_seed=42)
     sim.run()
 
     size = np.array([[-1, 1], [-1, 1]]) * 4 * u.cm
@@ -920,7 +930,7 @@ def test_multiple_grids2() -> None:
 
     sim = cpr.Tracker(grids, source, detector, verbose=True)
 
-    sim.create_particles(1e5, 15 * u.MeV, max_theta=8 * u.deg)
+    sim.create_particles(1e5, 15 * u.MeV, max_theta=8 * u.deg, random_seed=42)
 
     sim.run(field_weighting="nearest neighbor")