Skip to content

chore: switch to rngs - #1270

Open
selmanozleyen wants to merge 8 commits into
mainfrom
feat/rng-seeding
Open

chore: switch to rngs#1270
selmanozleyen wants to merge 8 commits into
mainfrom
feat/rng-seeding

Conversation

@selmanozleyen

Copy link
Copy Markdown
Member

This pull request refactors and standardizes the use of random seeds for reproducibility across several modules, replacing ambiguous or inconsistent random_state parameters with a uniform seed parameter. It also introduces utility functions for managing random number generators, and updates the documentation and validation logic accordingly.

These changes improve reproducibility, consistency, and clarity in the codebase's handling of random seeds and random number generation.

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.72%. Comparing base (76b6757) to head (727b389).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1270      +/-   ##
==========================================
+ Coverage   78.60%   78.72%   +0.11%     
==========================================
  Files          63       63              
  Lines        9348     9371      +23     
  Branches     1559     1561       +2     
==========================================
+ Hits         7348     7377      +29     
+ Misses       1426     1423       -3     
+ Partials      574      571       -3     
Files with missing lines Coverage Δ
src/squidpy/_docs.py 95.34% <100.00%> (+0.05%) ⬆️
src/squidpy/_utils.py 72.00% <100.00%> (+1.78%) ⬆️
src/squidpy/experimental/im/_detect_tissue.py 67.77% <100.00%> (ø)
...c/squidpy/experimental/im/_stain/_decomposition.py 91.93% <100.00%> (+0.06%) ⬆️
src/squidpy/gr/_ligrec.py 72.07% <100.00%> (+0.18%) ⬆️
src/squidpy/gr/_nhood.py 75.12% <100.00%> (+0.12%) ⬆️
src/squidpy/gr/_niche.py 77.72% <100.00%> (+1.57%) ⬆️
src/squidpy/gr/_ppatterns.py 80.08% <100.00%> (+0.08%) ⬆️
src/squidpy/gr/_ripley.py 96.52% <100.00%> (+0.03%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@flying-sheep flying-sheep left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything should be rng: SeedLike | RngLike | None = None, not seed: Something = 0, with the behavior and types specified here: https://scientific-python.org/specs/spec-0007/

Incompatible 3rd party APIs that only take a seed should be seeded via third_party_api(seed=rng.bytes(16)) (in case the 3rd party API takes a 128 bit seed sequence)

For backwards compatibility, you could do the same as I did for scanpy, i.e. if nothing is passed, add a decorator that passes a compatibility Generator which behaves exactly as a RandomState. New APIs (i.e. ones that haven’t been released yet or future ones) should have the standard None default, i.e. behave nondeterministically.

@selmanozleyen

selmanozleyen commented Aug 24, 2026

Copy link
Copy Markdown
Member Author

I agree but my intention isn't to convert to the SPEC-0007 conversion here. I just want to get rid of the unexposed random_state usages first

you know what, while I am at it I might as well do the full switch here

`random_state` is dropped outright rather than deprecated: it is simply gone
from the signatures, so passing it raises Python's own TypeError.

The released defaults are preserved -- `seed` still defaults to 42, so calls
that do not pass it stay reproducible exactly as before. Passing `seed=None`
opts out.

Internally `seed` now feeds `numpy.random.Generator`s: `spawn_generators`
derives an independent generator per library and per resolution, and
`rng_to_random_state` converts at the boundary of third-party APIs that take
an int but not a Generator (scikit-learn, spatialleiden). Adding a library or
a resolution therefore no longer shifts the others.

Derived from 777449e on feat/cluster-auto-k.
Completes the rename, so `random_state` no longer appears as a squidpy
parameter name anywhere.

`WekaParams.random_state`, `VahadaneParams.random_state` and
`_refine_with_background_classifier`'s parameter become `seed`, keeping their
existing default of 0. No conversion helper is needed: these are plain
`int | None` and go straight into scikit-learn, which accepts that. The
`random_state=` keywords that remain are scikit-learn's own, on
`RandomForestClassifier` and `NMF`.
`seed` defaults to `None` on everything that has not shipped yet:
`calculate_niche_cellcharter`, `calculate_niche_spatialleiden`,
`WekaParams`, `VahadaneParams` and `_refine_with_background_classifier`.

A new API defaulting to a fixed seed hides non-determinism behind an
arbitrary constant; `None` makes the choice explicit and matches the rest
of `squidpy.gr`. `calculate_niche` keeps its released default of 42.
`seed` and `random_state` become `rng`, accepting a seed, a
`numpy.random.Generator` or `None`, per SPEC 7. The old names still work
and emit a `FutureWarning` naming what happens to the value: it now seeds
a generator rather than reaching the underlying library as a legacy
`random_state`, so results for a given value can differ.

Public entry points normalise once with `numpy.random.default_rng`;
everything downstream takes a `Generator`. `spawn_generators` is gone --
after that split it was a one-line wrapper around `Generator.spawn`. The
one internal still seeing a raw `rng` is `_validate_niche_args`, which
reports on what the caller passed and needs `None` to stay `None`.

`_segment_weka` also stops handing the same seed to both the random
forest and the refinement classifier; they now draw from one generator.
`SeedLike` and `RNGLike` become plain unions instead of PEP-695 `type`
statements. A `type` statement builds a `TypeAliasType`, which sphinx
deliberately renders by name -- so `VahadaneParams.rng` documented itself as
`SeedLike | RNGLike | None`, two names that resolve to nothing because
`squidpy._utils` is private and undocumented.

Plain unions are evaluated, so autodoc expands them to
`int | integer | Sequence[int] | SeedSequence | Generator | BitGenerator | None`
on attributes, matching what sphinx-autodoc-typehints already produced for
function parameters. The aliases no longer appear as names anywhere, so the
build is nitpick-clean without ignore entries for them.
@selmanozleyen selmanozleyen changed the title Rename random_state to seed in niche and experimental.im chore: switch to rngs Aug 24, 2026
@flying-sheep

Copy link
Copy Markdown
Member

you know what, while I am at it I might as well do the full switch here

Yeah, why rename these twice? Once to seed and then to rng. Sounds like two opportunities for bugs to sneak in.

@flying-sheep flying-sheep left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! If you don’t have to maintain backwards compat, this is exactly how is should be done. I only have some nitpicks, mostly about naming.

Comment thread src/squidpy/gr/_niche.py Outdated
Comment thread src/squidpy/gr/_ripley.py

%(seed_versionchanged)s

%(rng_versionchanged)s

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there other places where this should go? Also, is %(seed_versionchanged)s above still relevant?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it is relevant because I fixed a bug which changed the rng behaviour: #1232. So it's not only about reproducibility but warning the users that the old way might be broken.

I am not sure if it should stay or not though. Seems like it belongs to the changelogs now.

Comment thread src/squidpy/_utils.py Outdated
Comment thread src/squidpy/_utils.py Outdated
Comment thread tests/conftest.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants