Skip to content

Make PyTorch optional, fix the seeding bug behind the flaky CI, and hold the advertised counts to the code - #16

Merged
DenisDrobyshev merged 4 commits into
mainfrom
claude/goofy-goldwasser-8e2b2c
Aug 23, 2026
Merged

Make PyTorch optional, fix the seeding bug behind the flaky CI, and hold the advertised counts to the code#16
DenisDrobyshev merged 4 commits into
mainfrom
claude/goofy-goldwasser-8e2b2c

Conversation

@DenisDrobyshev

Copy link
Copy Markdown
Member

Four related pieces of maintenance. The first two are what a consumer notices; the last two are what keeps the repository honest.

PyTorch is now optional

decisionrl.envs, decisionrl.baselines and decisionrl.core are useful to anything that only simulates or evaluates, but importing any of them pulled in the whole of PyTorch: the top-level package imported every subpackage eagerly, and decisionrl.utils re-exported torch_utils, which decisionrl.core reaches through core/agent.py's Logger import.

The public names now resolve through a PEP 562 module __getattr__. Nothing is imported when the package is; each name is resolved and cached on first access. __all__ is the same list, from decisionrl import PPO resolves as before, and a TYPE_CHECKING block keeps the static imports so type checkers and IDEs see what the runtime serves.

With that in place torch moves from dependencies to an extra:

  • pip install decisionrl — NumPy only: environments, classical baselines, solvers, core API.
  • pip install "decisionrl[torch]" — the half that trains.
  • [dev] carries torch, so contributor setup is unchanged.

import decisionrl.envs drops from ~1.9 s to ~0.2 s, the remainder being NumPy. Reaching a torch-backed name without torch raises a ModuleNotFoundError naming the attribute asked for and the command that fixes it, rather than a bare No module named 'torch' from somewhere inside the package. Only torch gets the rewritten message — any other missing module still surfaces as itself, and there is a test for that.

The seeding bug behind the flaky CI

NeuroevolutionAgent passed its seed to the optimizer's search and to BaseAgent's RNG but never to the environment. _fitness only ever called env.reset() unseeded, and an unseeded env draws start states from OS entropy — and those start states are the fitness signal. Every run was irreproducible regardless of the seed. Every other agent already seeds the env once at the top of its own learn; this one did not.

That is what failed test_neuroevolution_cem_solves_cartpole on unrelated pull requests, most recently the Dockerfile bump in #13, where assert 117.6 > 300.0 had nothing to do with the change under test.

Seeding the env makes runs reproducible and shows what the method actually does at this budget: across seeds 0–3 CEM returns roughly 283 / 105 / 241 / 97 against a 500-step ceiling, with a random policy at 23.5. It beats random by a wide margin but does not reliably solve CartPole, so assert mean_return > 300.0 was an assertion about luck. The test now takes the median of three seeds against the measured random-policy floor and is renamed to match the claim. A second, fast test pins the reproducibility this restores.

The advertised counts disagreed with the package, and with each other

CITATION.cff claimed twenty-two environments where twenty-four ship. The README, the packaging description and the citation file all said 31 algorithms where 32 concrete agents are exported — a number matching no consistent definition, since decisionrl.algorithms holds 30 classes of which two are abstract bases, and BC, DAgger, DPO and NeuroevolutionAgent are agents living elsewhere.

All three now read 32 algorithms and 24 environments, 9 of them applied, and tests/test_documented_counts.py checks them against the package rather than against each other. The applied subset is named in decisionrl.envs.APPLIED_ENVIRONMENTS instead of counted by hand. CITATION.cff gains the version, date-released and type fields it was missing, and its version is now checked against decisionrl.__version__.

This changes a public claim from 31 to 32. If 31 stood for a deliberate definition — excluding DPO, say, or neuroevolution — say so and I will restore it and write the exclusion into the test explicitly.

Python 3.13

The matrix stopped at 3.12 while the serving image was being bumped to 3.14, so the versions claimed and the versions tested had drifted apart. 3.13 joins the CI matrix and the classifiers.

Verification

ruff clean, mypy clean across 130 source files. Locally the fast suite's failure set is identical to main's — 19 against 19, empty difference both ways — so this introduces no regressions; those 19 are this machine's environment (matplotlib native aborts, a CUDA/CPU mismatch, relocated PettingZoo MPE envs) and do not occur on CI.

🤖 Generated with Claude Code

DenisDrobyshev and others added 4 commits August 22, 2026 20:38
decisionrl.envs, decisionrl.baselines and decisionrl.core are useful to
consumers that only simulate or evaluate, but importing any of them pulled in
the whole of PyTorch: the top-level package imported every subpackage eagerly,
and decisionrl.utils re-exported torch_utils, which decisionrl.core reaches
through core/agent.py's Logger import.

Resolve the public names through a PEP 562 module __getattr__ instead. Nothing
is imported when the package is; each name is resolved and cached on first
access. The public API is unchanged - __all__ is the same list and
`from decisionrl import PPO` still resolves - and a TYPE_CHECKING block keeps
the static imports so type checkers and IDEs see what the runtime serves.

import decisionrl.envs drops from ~1.9s to ~0.2s, the remainder being NumPy,
and the three modules above now import with torch absent entirely. Touching
anything that trains still imports torch, on first use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeuroevolutionAgent passed its seed to the optimizer's search and to BaseAgent's
RNG, but never to the environment. `_fitness` only ever called `env.reset()`
unseeded, and an unseeded env draws its start states from OS entropy - so the
rollout returns that *are* the fitness signal were random every run, and
`seed=0` bought nothing. Every other agent already seeds the env once at the top
of its own `learn`; this one didn't.

That is what made test_neuroevolution_cem_solves_cartpole fail on unrelated pull
requests, most recently the Dockerfile bump in #13: with the run irreproducible,
`assert mean_return > 300.0` was an assertion about luck.

Seeding the env exposes what the method actually does at this budget. Across
seeds 0-3 CEM returns roughly 283 / 105 / 241 / 97 against a 500-step ceiling,
with a random policy at 23.5 - it beats random by a wide margin but does not
reliably solve CartPole. So the test now says that instead: the median of three
seeds against the measured random-policy floor, renamed to match the claim. A
second, fast test pins the reproducibility this commit restores.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three strands of maintenance that share the same files (pyproject.toml and the
README), so they land together.

PyTorch is now an extra rather than a hard requirement. The lazy imports added in
the previous commit made this possible; this makes it real. `pip install
decisionrl` now installs NumPy alone and gives you the environments, the
classical baselines, the solvers and the core API - which is what a consumer that
only simulates or evaluates needs, and it no longer pays a multi-gigabyte wheel
to get it. `decisionrl[torch]` installs the half that trains, and `[dev]` carries
torch so contributor setup is unchanged. Reaching a torch-backed name without it
now raises a ModuleNotFoundError naming the attribute asked for and the command
that fixes it, instead of a bare "No module named 'torch'" from somewhere inside
the package; decisionrl/_lazy.py holds that, and only torch gets the rewritten
message, so any other missing module still surfaces as itself.

The advertised counts disagreed with the package and with each other. CITATION.cff
claimed twenty-two environments where twenty-four ship; the README, the packaging
description and the citation file all said 31 algorithms where 32 concrete agents
are exported - a number matching no consistent definition, since the algorithms
subpackage holds 30 classes of which two are abstract bases. All three now read
32 algorithms and 24 environments, 9 of them applied, and tests check them against
the package rather than against each other. The applied subset is named in
decisionrl.envs.APPLIED_ENVIRONMENTS instead of counted by hand, and CITATION.cff
gains the version, date-released and type fields it was missing.

Python 3.13 joins the CI matrix and the classifiers: the matrix stopped at 3.12
while the serving image is being bumped to 3.14, so the version we claim to
support and the versions we test had drifted apart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The index still said "only NumPy + PyTorch in the core" and offered a single
install command, both of which stopped being true when torch became an extra.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@DenisDrobyshev
DenisDrobyshev enabled auto-merge (squash) August 22, 2026 19:28
@DenisDrobyshev
DenisDrobyshev merged commit e2ddcb1 into main Aug 23, 2026
17 of 19 checks passed
@DenisDrobyshev
DenisDrobyshev deleted the claude/goofy-goldwasser-8e2b2c branch August 23, 2026 01:22
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.

1 participant