Repository: uncscode/particula

Issue:
Add/check Re and St functions
### $Re_p$: Particle Reynolds Number

Characterizes droplet flow:

$$
Re_p = \frac{2 a v_p}{\nu}
$$

### $St$: Stokes Number

Non-dimensional inertia parameter:

$$
St = \frac{\tau_p}{\tau_k}
$$

Discussion:
sourcery-ai[bot]: <!-- Generated by sourcery-ai[bot]: start review_guide -->

🧙 Sourcery is reviewing your pull request!

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time. You can also use
  this command to specify where the summary should be inserted.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
github-actions[bot]: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.6.0
:---:
| <p></p> :rocket: View preview at <br> https://uncscode.github.io/particula/pr-preview/pr-586/ <br><br>
| <h6>Built to branch [`gh-pages`](https://github.com/uncscode/particula/tree/gh-pages) at 2025-01-19 15:44 UTC. <br> Preview will be ready when the [GitHub Pages deployment](https://github.com/uncscode/particula/deployments) is complete. <br><br> </h6>
<!-- Sticky Pull Request Commentpr-preview -->
Gorkowski: @sourcery-ai review

Patch:
diff --git a/particula/particles/properties/__init__.py b/particula/particles/properties/__init__.py
index 8a7a060a8..f2c9efc84 100644
--- a/particula/particles/properties/__init__.py
+++ b/particula/particles/properties/__init__.py
@@ -68,6 +68,12 @@ from particula.particles.properties.activity_module import (
     kappa_activity,
     calculate_partial_pressure,
 )
+from particula.particles.properties.reynolds_number import (
+    get_particle_reynolds_number,
+)
+from particula.particles.properties.stokes_number import (
+    get_stokes_number,
+)
 from particula.particles.properties.inertia_time import (
     get_particle_inertia_time,
 )
diff --git a/particula/particles/properties/reynolds_number.py b/particula/particles/properties/reynolds_number.py
new file mode 100644
index 000000000..55e12625f
--- /dev/null
+++ b/particula/particles/properties/reynolds_number.py
@@ -0,0 +1,65 @@
+"""
+Calculate the particle Reynolds number.
+"""
+
+from typing import Union
+from numpy.typing import NDArray
+import numpy as np
+
+from particula.util.validate_inputs import validate_inputs
+
+
+@validate_inputs(
+    {
+        "particle_radius": "positive",
+        "particle_velocity": "positive",
+        "kinematic_viscosity": "positive",
+    }
+)
+def get_particle_reynolds_number(
+    particle_radius: Union[float, NDArray[np.float64]],
+    particle_velocity: Union[float, NDArray[np.float64]],
+    kinematic_viscosity: Union[float, NDArray[np.float64]],
+) -> Union[float, NDArray[np.float64]]:
+    """
+    Calculate the particle Reynolds number.
+
+    The particle Reynolds number (Re_p) characterizes the flow behavior around
+    a particle in a fluid and is given by:
+
+        Re_p = (2 a v_p) / ν
+
+    - Re_p : Particle Reynolds number [-]
+    - a (particle_radius) : Particle radius [m]
+    - v_p (particle_velocity) : Particle velocity relative to the fluid
+        [m/s]
+    - ν (kinematic_viscosity) : Kinematic viscosity of the fluid [m²/s]
+
+
+
+    Arguments:
+    ----------
+        - particle_radius : Radius of the particle [m]
+        - particle_velocity : Particle velocity relative to the fluid [m/s]
+        - kinematic_viscosity : Kinematic viscosity of the surrounding fluid
+            [m²/s]
+
+    Returns:
+    --------
+        - Particle Reynolds number [-]
+
+    References:
+    -----------
+        - Wikipedia https://en.wikipedia.org/wiki/Reynolds_number
+        - **Stokes Flow (Viscous Dominated, Re_p < 1)**:
+            - Particles follow the fluid closely (e.g., aerosols).
+        - **Transitional Flow (1 < Re_p < 1000)**:
+            - Both **viscous and inertial forces** contribute to flow behavior.
+            - Intermediate drag corrections apply.
+        - **Turbulent Flow (Re_p > 1000)**:
+            - **Inertial forces dominate**, resulting in vortex shedding and
+                wake formation.
+            - Applies to **large, fast-moving particles**
+                (e.g., raindrops, large sediment).
+    """
+    return (2 * particle_radius * particle_velocity) / kinematic_viscosity
diff --git a/particula/particles/properties/stokes_number.py b/particula/particles/properties/stokes_number.py
new file mode 100644
index 000000000..f9f17a400
--- /dev/null
+++ b/particula/particles/properties/stokes_number.py
@@ -0,0 +1,49 @@
+"""
+Stokes number calculation.
+"""
+
+from typing import Union
+from numpy.typing import NDArray
+import numpy as np
+
+from particula.util.validate_inputs import validate_inputs
+
+
+@validate_inputs(
+    {
+        "particle_inertia_time": "positive",
+        "kolmogorov_time": "positive",
+    }
+)
+def get_stokes_number(
+    particle_inertia_time: Union[float, NDArray[np.float64]],
+    kolmogorov_time: Union[float, NDArray[np.float64]],
+) -> Union[float, NDArray[np.float64]]:
+    """
+    Calculate the Stokes number.
+
+    The Stokes number (St) is a dimensionless [-] parameter representing the
+    relative importance of particle inertia in a fluid flow. A high Stokes
+    number indicates that a particle resists following fluid motion, while a
+    low Stokes number means the particle closely follows the flow. Given by:
+
+        St = τ_p / τ_k
+
+    - St : Stokes number [-]
+    - τ_p (particle_inertia_time) : Particle inertia time [s]
+    - τ_k (kolmogorov_time) : Kolmogorov timescale of turbulence [s]
+
+    Arguments:
+    ----------
+        - particle_inertia_time : Particle inertia time [s]
+        - kolmogorov_time : Kolmogorov timescale [s]
+
+    Returns:
+    --------
+        - Stokes number [-]
+
+    References:
+    -----------
+        - Wikipedia https://en.wikipedia.org/wiki/Stokes_number
+    """
+    return particle_inertia_time / kolmogorov_time


Test patch:
diff --git a/particula/particles/properties/tests/reynolds_number_test.py b/particula/particles/properties/tests/reynolds_number_test.py
new file mode 100644
index 000000000..f7f0966ab
--- /dev/null
+++ b/particula/particles/properties/tests/reynolds_number_test.py
@@ -0,0 +1,81 @@
+"""
+Tests for the Reynolds number calculation.
+"""
+
+import pytest
+import numpy as np
+
+from particula.particles.properties.reynolds_number import (
+    get_particle_reynolds_number
+)
+
+
+def test_get_particle_reynolds_number_scalar():
+    """
+    Test get_particle_reynolds_number with scalar inputs.
+    """
+    particle_radius = 50e-6  # 50 microns
+    particle_velocity = 0.1  # m/s
+    kinematic_viscosity = 1.5e-5  # m²/s
+
+    expected = (2 * particle_radius * particle_velocity) / kinematic_viscosity
+    result = get_particle_reynolds_number(
+        particle_radius, particle_velocity, kinematic_viscosity
+    )
+
+    assert np.isclose(result, expected, atol=1e-10)
+
+
+def test_get_particle_reynolds_number_array():
+    """
+    Test get_particle_reynolds_number with NumPy array inputs.
+    """
+    particle_radius = np.array([50e-6, 100e-6])
+    particle_velocity = np.array([0.1, 0.2])
+    kinematic_viscosity = np.array([1.5e-5, 1.5e-5])
+
+    expected = (2 * particle_radius * particle_velocity) / kinematic_viscosity
+    result = get_particle_reynolds_number(
+        particle_radius, particle_velocity, kinematic_viscosity
+    )
+
+    assert np.allclose(result, expected, atol=1e-10)
+
+
+def test_get_particle_reynolds_number_invalid():
+    """
+    Test that get_particle_reynolds_number raises errors for invalid inputs.
+    """
+    with pytest.raises(ValueError):
+        get_particle_reynolds_number(-50e-6, 0.1, 1.5e-5)  # Negative radius
+
+    with pytest.raises(ValueError):
+        get_particle_reynolds_number(50e-6, -0.1, 1.5e-5)  # Negative velocity
+
+    with pytest.raises(ValueError):
+        get_particle_reynolds_number(50e-6, 0.1, -1.5e-5)  # Negative viscosity
+
+
+def test_get_particle_reynolds_number_regime_classification():
+    """
+    Test Reynolds number classification into flow regimes.
+    """
+    re_p_stokes = get_particle_reynolds_number(
+        10e-6, 0.001, 1.5e-5
+    )  # Should be < 1
+    re_p_transitional = get_particle_reynolds_number(
+        100e-6, 0.5, 1.5e-5
+    )  # Should be 1 < Re_p < 1000
+    re_p_turbulent = get_particle_reynolds_number(
+        500e-6, 50.0, 1.5e-5
+    )  # Should be > 1000
+
+    assert (
+        re_p_stokes < 1
+    )
+    assert (
+        1 <= re_p_transitional < 1000
+    )
+    assert (
+        re_p_turbulent > 1000
+    )
diff --git a/particula/particles/properties/tests/stokes_number_test.py b/particula/particles/properties/tests/stokes_number_test.py
new file mode 100644
index 000000000..be3f04180
--- /dev/null
+++ b/particula/particles/properties/tests/stokes_number_test.py
@@ -0,0 +1,48 @@
+"""
+Tests for the stokes_number module.
+"""
+
+import pytest
+import numpy as np
+
+from particula.particles.properties.stokes_number import get_stokes_number
+
+
+def test_get_stokes_number_scalar():
+    """
+    Test get_stokes_number with scalar inputs.
+    """
+    particle_inertia_time = 0.02  # s
+    kolmogorov_time = 0.005  # s
+
+    expected = particle_inertia_time / kolmogorov_time
+    result = get_stokes_number(particle_inertia_time, kolmogorov_time)
+
+    assert np.isclose(result, expected, atol=1e-10)
+
+
+def test_get_stokes_number_array():
+    """
+    Test get_stokes_number with NumPy array inputs.
+    """
+    particle_inertia_time = np.array([0.02, 0.03])
+    kolmogorov_time = np.array([0.005, 0.004])
+
+    expected = particle_inertia_time / kolmogorov_time
+    result = get_stokes_number(particle_inertia_time, kolmogorov_time)
+
+    assert np.allclose(result, expected, atol=1e-10)
+
+
+def test_get_stokes_number_invalid():
+    """
+    Test that get_stokes_number raises errors for invalid inputs.
+    """
+    with pytest.raises(ValueError):
+        get_stokes_number(-0.02, 0.005)  # Negative inertia time
+
+    with pytest.raises(ValueError):
+        get_stokes_number(0.02, -0.005)  # Negative Kolmogorov time
+
+    with pytest.raises(ValueError):
+        get_stokes_number(0.02, 0)  # Zero Kolmogorov time (division by zero)