From a866e4e35dfa40bc98de90f320033258f66fb759 Mon Sep 17 00:00:00 2001 From: jpintar Date: Sun, 3 May 2026 12:33:54 -0400 Subject: [PATCH 1/5] Record leiden/louvain modularity in adata.uns --- src/rapids_singlecell/tools/_clustering.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/rapids_singlecell/tools/_clustering.py b/src/rapids_singlecell/tools/_clustering.py index f92261b8..9082441a 100644 --- a/src/rapids_singlecell/tools/_clustering.py +++ b/src/rapids_singlecell/tools/_clustering.py @@ -229,8 +229,9 @@ def leiden( resolutions = [resolution] else: resolutions = resolution + modularities = [] for resolution in resolutions: - leiden_parts, _ = culeiden( + leiden_parts, modularity = culeiden( g, resolution=resolution, random_state=random_state, @@ -241,6 +242,7 @@ def leiden( leiden_parts = leiden_parts.to_backend("pandas").compute() else: leiden_parts = leiden_parts.to_pandas() + modularities.append(modularity) # Format output groups = leiden_parts.sort_values("vertex")[["partition"]].to_numpy().ravel() @@ -270,10 +272,13 @@ def leiden( # store information on the clustering parameters adata.uns[key_added] = {} adata.uns[key_added]["params"] = { - "resolution": resolutions, + "resolution": resolutions if len(resolutions) > 1 else resolutions[0], "random_state": random_state, "n_iterations": n_iterations, } + adata.uns[key_added]["modularity"] = ( + modularities if len(modularities) > 1 else modularities[0] + ) return adata if copy else None @@ -383,8 +388,9 @@ def louvain( resolutions = [resolution] else: resolutions = resolution + modularities = [] for resolution in resolutions: - louvain_parts, _ = culouvain( + louvain_parts, modularity = culouvain( g, resolution=resolution, max_level=n_iterations, @@ -394,6 +400,7 @@ def louvain( louvain_parts = louvain_parts.to_backend("pandas").compute() else: louvain_parts = louvain_parts.to_pandas() + modularities.append(modularity) # Format output groups = louvain_parts.sort_values("vertex")[["partition"]].to_numpy().ravel() @@ -422,10 +429,13 @@ def louvain( Comms.destroy() adata.uns[key_added] = {} adata.uns[key_added]["params"] = { - "resolution": resolutions, + "resolution": resolutions if len(resolutions) > 1 else resolutions[0], "n_iterations": n_iterations, "threshold": threshold, } + adata.uns[key_added]["modularity"] = ( + modularities if len(modularities) > 1 else modularities[0] + ) return adata if copy else None From 8e6cd763d535d7bf37670a7ec5e29ec58eeeb4f4 Mon Sep 17 00:00:00 2001 From: jpintar Date: Mon, 4 May 2026 18:04:55 -0400 Subject: [PATCH 2/5] Add test and release note --- docs/release-notes/blank.md | 5 +++++ tests/test_clustering.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/blank.md b/docs/release-notes/blank.md index 9ce44aab..f6385d9c 100644 --- a/docs/release-notes/blank.md +++ b/docs/release-notes/blank.md @@ -2,6 +2,8 @@ ```{rubric} Features ``` +* ``tl.leiden`` and ``tl.louvain`` now record the final modularity value in ``adata.uns[key_added]["modularity"]`` +(scalar for a single resolution, list for multiple resolutions) {pr}`648` {smaller}`J Pintar` ```{rubric} Performance ``` @@ -13,6 +15,9 @@ ```{rubric} Misc ``` +* ``adata.uns[key_added]["params"]["resolution"]`` is now stored as a scalar ``float`` when a single resolution +is passed to ``tl.leiden`` and ``tl.louvain`` to match behaviour in Scanpy, and as a ``list`` when multiple +resolutions are passed. Previously it was always stored as a list. {pr}`648`. {smaller}`J Pintar` ```{rubric} Removals ``` diff --git a/tests/test_clustering.py b/tests/test_clustering.py index 9276cb37..6fcc4783 100644 --- a/tests/test_clustering.py +++ b/tests/test_clustering.py @@ -78,9 +78,13 @@ def test_clustering_resolution(adata_neighbors, clustering_function, resolution) if isinstance(resolution, list): for r in resolution: assert f"test_clustering_{r}" in adata.obs.columns + assert isinstance(adata.uns["test_clustering"]["modularity"], list) + assert len(adata.uns["test_clustering"]["modularity"]) == len(resolution) + assert adata.uns["test_clustering"]["params"]["resolution"] == resolution else: assert "test_clustering" in adata.obs.columns - + assert isinstance(adata.uns["test_clustering"]["modularity"], float) + assert adata.uns["test_clustering"]["params"]["resolution"] == resolution def test_kmeans_basic(adata_neighbors): rsc.tl.kmeans(adata_neighbors) From d5fc3ea3442a20f12db8e4966835c0bd495d0a25 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 22:05:13 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/release-notes/blank.md | 6 +++--- tests/test_clustering.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/blank.md b/docs/release-notes/blank.md index f6385d9c..b6b04f12 100644 --- a/docs/release-notes/blank.md +++ b/docs/release-notes/blank.md @@ -2,7 +2,7 @@ ```{rubric} Features ``` -* ``tl.leiden`` and ``tl.louvain`` now record the final modularity value in ``adata.uns[key_added]["modularity"]`` +* ``tl.leiden`` and ``tl.louvain`` now record the final modularity value in ``adata.uns[key_added]["modularity"]`` (scalar for a single resolution, list for multiple resolutions) {pr}`648` {smaller}`J Pintar` ```{rubric} Performance @@ -15,8 +15,8 @@ ```{rubric} Misc ``` -* ``adata.uns[key_added]["params"]["resolution"]`` is now stored as a scalar ``float`` when a single resolution -is passed to ``tl.leiden`` and ``tl.louvain`` to match behaviour in Scanpy, and as a ``list`` when multiple +* ``adata.uns[key_added]["params"]["resolution"]`` is now stored as a scalar ``float`` when a single resolution +is passed to ``tl.leiden`` and ``tl.louvain`` to match behaviour in Scanpy, and as a ``list`` when multiple resolutions are passed. Previously it was always stored as a list. {pr}`648`. {smaller}`J Pintar` ```{rubric} Removals diff --git a/tests/test_clustering.py b/tests/test_clustering.py index 6fcc4783..4209a4c0 100644 --- a/tests/test_clustering.py +++ b/tests/test_clustering.py @@ -86,6 +86,7 @@ def test_clustering_resolution(adata_neighbors, clustering_function, resolution) assert isinstance(adata.uns["test_clustering"]["modularity"], float) assert adata.uns["test_clustering"]["params"]["resolution"] == resolution + def test_kmeans_basic(adata_neighbors): rsc.tl.kmeans(adata_neighbors) assert adata_neighbors.obs["kmeans"].nunique() == 8 From 7bbbc5877beb95a46720e8f94c39c01b136bc357 Mon Sep 17 00:00:00 2001 From: jpintar Date: Tue, 12 May 2026 07:45:39 -0400 Subject: [PATCH 4/5] Add release note for 0.15.1 --- docs/release-notes/0.15.1.md | 11 +++++++++++ docs/release-notes/blank.md | 7 ------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/release-notes/0.15.1.md b/docs/release-notes/0.15.1.md index d5b484a2..8ffd8e32 100644 --- a/docs/release-notes/0.15.1.md +++ b/docs/release-notes/0.15.1.md @@ -1,5 +1,16 @@ ### 0.15.1 {small}`the-future` +```{rubric} Features +``` +* ``tl.leiden`` and ``tl.louvain`` now record the final modularity value in ``adata.uns[key_added]["modularity"]`` +(scalar for a single resolution, list for multiple resolutions) {pr}`648` {smaller}`J Pintar` + ```{rubric} Bug fixes ``` * Fixes `tl.rank_genes_groups` returning NaN/zero `logfoldchanges`/`pvals` with `groups=[subset]` and `reference='rest'` {pr}`651` {smaller}`S Dicks` + +```{rubric} Misc +``` +* ``adata.uns[key_added]["params"]["resolution"]`` is now stored as a scalar ``float`` when a single resolution +is passed to ``tl.leiden`` and ``tl.louvain`` to match behaviour in Scanpy, and as a ``list`` when multiple +resolutions are passed. Previously it was always stored as a list. {pr}`648`. {smaller}`J Pintar` \ No newline at end of file diff --git a/docs/release-notes/blank.md b/docs/release-notes/blank.md index b6b04f12..539df5ed 100644 --- a/docs/release-notes/blank.md +++ b/docs/release-notes/blank.md @@ -2,22 +2,15 @@ ```{rubric} Features ``` -* ``tl.leiden`` and ``tl.louvain`` now record the final modularity value in ``adata.uns[key_added]["modularity"]`` -(scalar for a single resolution, list for multiple resolutions) {pr}`648` {smaller}`J Pintar` ```{rubric} Performance ``` - ```{rubric} Bug fixes ``` - ```{rubric} Misc ``` -* ``adata.uns[key_added]["params"]["resolution"]`` is now stored as a scalar ``float`` when a single resolution -is passed to ``tl.leiden`` and ``tl.louvain`` to match behaviour in Scanpy, and as a ``list`` when multiple -resolutions are passed. Previously it was always stored as a list. {pr}`648`. {smaller}`J Pintar` ```{rubric} Removals ``` From d67804606678eafd8522555889c3f909d55d4c57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 11:45:57 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/release-notes/0.15.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/0.15.1.md b/docs/release-notes/0.15.1.md index 8ffd8e32..d44db4ae 100644 --- a/docs/release-notes/0.15.1.md +++ b/docs/release-notes/0.15.1.md @@ -13,4 +13,4 @@ ``` * ``adata.uns[key_added]["params"]["resolution"]`` is now stored as a scalar ``float`` when a single resolution is passed to ``tl.leiden`` and ``tl.louvain`` to match behaviour in Scanpy, and as a ``list`` when multiple -resolutions are passed. Previously it was always stored as a list. {pr}`648`. {smaller}`J Pintar` \ No newline at end of file +resolutions are passed. Previously it was always stored as a list. {pr}`648`. {smaller}`J Pintar`