Repository: pymc-devs/pymc

Issue:
Unused `compute_draw_speed`
This logic is redefined 

https://github.com/pymc-devs/pymc/blob/c8d83ca31a119ea3e65cb88b8ee63a60d5c427f3/pymc/util.py?plain=1#L860-L870

https://github.com/pymc-devs/pymc/blob/c8d83ca31a119ea3e65cb88b8ee63a60d5c427f3/pymc/util.py?plain=1#L939-L948

Discussion:
@ricardoV94: Wanna fix it @williambdean ?

Solution patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6148c1856..0d5dfec0b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -49,7 +49,7 @@ repos:
         - --exclude=versioneer.py
         - --last-year-present
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: v0.11.7
+  rev: v0.11.13
   hooks:
     - id: ruff
       args: [--fix, --show-fixes]
diff --git a/conda-envs/environment-alternative-backends.yml b/conda-envs/environment-alternative-backends.yml
index 1b575a199..5030e7bac 100644
--- a/conda-envs/environment-alternative-backends.yml
+++ b/conda-envs/environment-alternative-backends.yml
@@ -22,7 +22,7 @@ dependencies:
 - numpyro>=0.8.0
 - pandas>=0.24.0
 - pip
-- pytensor>=2.30.2,<2.31
+- pytensor>=2.31.2,<2.32
 - python-graphviz
 - networkx
 - rich>=13.7.1
diff --git a/conda-envs/environment-dev.yml b/conda-envs/environment-dev.yml
index 24da9d396..e161b66e1 100644
--- a/conda-envs/environment-dev.yml
+++ b/conda-envs/environment-dev.yml
@@ -12,7 +12,7 @@ dependencies:
 - numpy>=1.25.0
 - pandas>=0.24.0
 - pip
-- pytensor>=2.30.2,<2.31
+- pytensor>=2.31.2,<2.32
 - python-graphviz
 - networkx
 - scipy>=1.4.1
diff --git a/conda-envs/environment-docs.yml b/conda-envs/environment-docs.yml
index 34f648b0f..67e6673af 100644
--- a/conda-envs/environment-docs.yml
+++ b/conda-envs/environment-docs.yml
@@ -11,7 +11,7 @@ dependencies:
 - numpy>=1.25.0
 - pandas>=0.24.0
 - pip
-- pytensor>=2.30.2,<2.31
+- pytensor>=2.31.2,<2.32
 - python-graphviz
 - rich>=13.7.1
 - scipy>=1.4.1
diff --git a/conda-envs/environment-test.yml b/conda-envs/environment-test.yml
index f855163a6..2230d08e7 100644
--- a/conda-envs/environment-test.yml
+++ b/conda-envs/environment-test.yml
@@ -14,7 +14,7 @@ dependencies:
 - pandas>=0.24.0
 - pip
 - polyagamma
-- pytensor>=2.30.2,<2.31
+- pytensor>=2.31.2,<2.32
 - python-graphviz
 - networkx
 - rich>=13.7.1
diff --git a/conda-envs/windows-environment-dev.yml b/conda-envs/windows-environment-dev.yml
index d3df2b8a4..daf039814 100644
--- a/conda-envs/windows-environment-dev.yml
+++ b/conda-envs/windows-environment-dev.yml
@@ -12,7 +12,7 @@ dependencies:
 - numpy>=1.25.0
 - pandas>=0.24.0
 - pip
-- pytensor>=2.30.2,<2.31
+- pytensor>=2.31.2,<2.32
 - python-graphviz
 - networkx
 - rich>=13.7.1
diff --git a/conda-envs/windows-environment-test.yml b/conda-envs/windows-environment-test.yml
index 4d070f3f9..7c9c28b70 100644
--- a/conda-envs/windows-environment-test.yml
+++ b/conda-envs/windows-environment-test.yml
@@ -15,7 +15,7 @@ dependencies:
 - pandas>=0.24.0
 - pip
 - polyagamma
-- pytensor>=2.30.2,<2.31
+- pytensor>=2.31.2,<2.32
 - python-graphviz
 - networkx
 - rich>=13.7.1
diff --git a/pymc/model/core.py b/pymc/model/core.py
index d04011407..469001e80 100644
--- a/pymc/model/core.py
+++ b/pymc/model/core.py
@@ -964,6 +964,7 @@ class Model(WithMemoization, metaclass=ContextMeta):
         if name in self.coords:
             if not np.array_equal(values, self.coords[name]):
                 raise ValueError(f"Duplicate and incompatible coordinate: {name}.")
+            return
         if length is not None and not isinstance(length, int | Variable):
             raise ValueError(
                 f"The `length` passed for the '{name}' coord must be an int, PyTensor Variable or None."
diff --git a/pymc/stats/convergence.py b/pymc/stats/convergence.py
index d32831c8b..7dc880ba0 100644
--- a/pymc/stats/convergence.py
+++ b/pymc/stats/convergence.py
@@ -144,9 +144,15 @@ def warn_divergences(idata: arviz.InferenceData) -> list[SamplerWarning]:
     n_div = int(diverging.sum())
     if n_div == 0:
         return []
+
+    if n_div == 1:
+        verb, word = "was", "divergence"
+    else:
+        verb, word = "were", "divergences"
+
     warning = SamplerWarning(
         WarningType.DIVERGENCES,
-        f"There were {n_div} divergences after tuning. Increase `target_accept` or reparameterize.",
+        f"There {verb} {n_div} {word} after tuning. Increase `target_accept` or reparameterize.",
         "error",
     )
     return [warning]
diff --git a/pymc/util.py b/pymc/util.py
index 979b3beeb..224ce0fa9 100644
--- a/pymc/util.py
+++ b/pymc/util.py
@@ -857,18 +857,6 @@ class ProgressBarManager:
                 for chain_idx in range(self.chains)
             ]
 
-    def compute_draw_speed(self, chain_idx, draws):
-        elapsed = self._progress.tasks[chain_idx].elapsed
-        speed = draws / max(elapsed, 1e-6)
-
-        if speed > 1 or speed == 0:
-            unit = "draws/s"
-        else:
-            unit = "s/draws"
-            speed = 1 / speed
-
-        return speed, unit
-
     def update(self, chain_idx, is_last, draw, tuning, stats):
         if not self._show_progress:
             return
@@ -878,7 +866,8 @@ class ProgressBarManager:
             draw = self.completed_draws
             chain_idx = 0
 
-        speed, unit = self.compute_draw_speed(chain_idx, draw)
+        elapsed = self._progress.tasks[chain_idx].elapsed
+        speed, unit = compute_draw_speed(elapsed, draw)
 
         if not tuning and stats and stats[0].get("diverging"):
             self.divergences += 1
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 534503c24..840f3d806 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -16,7 +16,7 @@ numpydoc
 pandas>=0.24.0
 polyagamma
 pre-commit>=2.8.0
-pytensor>=2.30.2,<2.31
+pytensor>=2.31.2,<2.32
 pytest-cov>=2.5
 pytest>=3.0
 rich>=13.7.1
diff --git a/requirements.txt b/requirements.txt
index c278ad691..c1ca979dd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@ cachetools>=4.2.1
 cloudpickle
 numpy>=1.25.0
 pandas>=0.24.0
-pytensor>=2.30.2,<2.31
+pytensor>=2.31.2,<2.32
 rich>=13.7.1
 scipy>=1.4.1
 threadpoolctl>=3.1.0,<4.0.0
diff --git a/scripts/run_mypy.py b/scripts/run_mypy.py
index ccce06736..409e255d7 100755
--- a/scripts/run_mypy.py
+++ b/scripts/run_mypy.py
@@ -44,6 +44,7 @@ pymc/model/fgraph.py
 pymc/model/transform/conditioning.py
 pymc/pytensorf.py
 pymc/sampling/jax.py
+pymc/sampling/mcmc.py
 """
 
 


Test patch:
diff --git a/tests/model/test_core.py b/tests/model/test_core.py
index 4375a17ad..b26a9d96b 100644
--- a/tests/model/test_core.py
+++ b/tests/model/test_core.py
@@ -33,6 +33,7 @@ import scipy.sparse as sps
 import scipy.stats as st
 
 from pytensor.graph import graph_inputs
+from pytensor.graph.basic import get_var_by_name
 from pytensor.raise_op import Assert
 from pytensor.tensor.random.op import RandomVariable
 from pytensor.tensor.variable import TensorConstant
@@ -858,6 +859,19 @@ def test_nested_model_coords():
     assert set(m2.named_vars_to_dims) < set(m1.named_vars_to_dims)
 
 
+def test_multiple_add_coords_with_same_name():
+    coord = {"dim1": ["a", "b", "c"]}
+    with pm.Model(coords=coord) as m:
+        a = pm.Normal("a", dims="dim1")
+        with pm.Model(coords=coord) as nested_m:
+            b = pm.Normal("b", dims="dim1")
+        m.add_coords(coord)
+        c = pm.Normal("c", dims="dim1")
+        d = pm.Deterministic("d", a + b + c)
+    variables = get_var_by_name([d], "dim1")
+    assert len(variables) == 1 and variables[0] is m.dim_lengths["dim1"]
+
+
 class TestSetUpdateCoords:
     def test_shapeerror_from_set_data_dimensionality(self):
         with pm.Model() as pmodel:
diff --git a/tests/sampling/test_mcmc.py b/tests/sampling/test_mcmc.py
index 409ab6ff8..090b76130 100644
--- a/tests/sampling/test_mcmc.py
+++ b/tests/sampling/test_mcmc.py
@@ -526,7 +526,6 @@ def test_partial_trace_with_trace_unsupported():
             pm.sample(trace=[a])
 
 
-@pytest.mark.xfail(condition=(pytensor.config.floatX == "float32"), reason="Fails on float32")
 class TestNamedSampling:
     def test_shared_named(self):
         G_var = shared(value=np.atleast_2d(1.0), shape=(1, None), name="G")
@@ -537,7 +536,6 @@ class TestNamedSampling:
                 mu=np.atleast_2d(0),
                 tau=np.atleast_2d(1e20),
                 size=(1, 1),
-                initval=np.atleast_2d(0),
             )
             theta = pm.Normal(
                 "theta", mu=pt.dot(G_var, theta0), tau=np.atleast_2d(1e20), size=(1, 1)
@@ -553,7 +551,6 @@ class TestNamedSampling:
                 mu=np.atleast_2d(0),
                 tau=np.atleast_2d(1e20),
                 size=(1, 1),
-                initval=np.atleast_2d(0),
             )
             theta = pm.Normal(
                 "theta", mu=pt.dot(G_var, theta0), tau=np.atleast_2d(1e20), size=(1, 1)
@@ -569,7 +566,6 @@ class TestNamedSampling:
                 mu=np.atleast_2d(0),
                 tau=np.atleast_2d(1e20),
                 size=(1, 1),
-                initval=np.atleast_2d(0),
             )
             theta = pm.Normal(
                 "theta", mu=pt.dot(G_var, theta0), tau=np.atleast_2d(1e20), size=(1, 1)
diff --git a/tests/stats/test_convergence.py b/tests/stats/test_convergence.py
index 1f7ba4479..52d5c5048 100644
--- a/tests/stats/test_convergence.py
+++ b/tests/stats/test_convergence.py
@@ -16,19 +16,27 @@ import logging
 
 import arviz
 import numpy as np
+import pytest
 
 from pymc.stats import convergence
 
 
-def test_warn_divergences():
+@pytest.mark.parametrize(
+    "diverging, expected_phrase",
+    [
+        pytest.param([1, 0, 1, 0], "were 2 divergences after tuning", id="plural"),
+        pytest.param([1, 0, 0, 0], "was 1 divergence after tuning", id="singular"),
+    ],
+)
+def test_warn_divergences(diverging, expected_phrase):
     idata = arviz.from_dict(
         sample_stats={
-            "diverging": np.array([[1, 0, 1, 0], [0, 0, 0, 0]]).astype(bool),
+            "diverging": np.array([diverging, [0, 0, 0, 0]]).astype(bool),
         }
     )
     warns = convergence.warn_divergences(idata)
     assert len(warns) == 1
-    assert "2 divergences after tuning" in warns[0].message
+    assert expected_phrase in warns[0].message
 
 
 def test_warn_treedepth():