Make PyTorch optional, fix the seeding bug behind the flaky CI, and hold the advertised counts to the code - #16
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.baselinesanddecisionrl.coreare 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, anddecisionrl.utilsre-exportedtorch_utils, whichdecisionrl.corereaches throughcore/agent.py'sLoggerimport.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 PPOresolves as before, and aTYPE_CHECKINGblock keeps the static imports so type checkers and IDEs see what the runtime serves.With that in place torch moves from
dependenciesto 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.envsdrops from ~1.9 s to ~0.2 s, the remainder being NumPy. Reaching a torch-backed name without torch raises aModuleNotFoundErrornaming the attribute asked for and the command that fixes it, rather than a bareNo 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
NeuroevolutionAgentpassed its seed to the optimizer's search and toBaseAgent's RNG but never to the environment._fitnessonly ever calledenv.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 ownlearn; this one did not.That is what failed
test_neuroevolution_cem_solves_cartpoleon unrelated pull requests, most recently the Dockerfile bump in #13, whereassert 117.6 > 300.0had 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.0was 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.cffclaimed 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, sincedecisionrl.algorithmsholds 30 classes of which two are abstract bases, andBC,DAgger,DPOandNeuroevolutionAgentare agents living elsewhere.All three now read 32 algorithms and 24 environments, 9 of them applied, and
tests/test_documented_counts.pychecks them against the package rather than against each other. The applied subset is named indecisionrl.envs.APPLIED_ENVIRONMENTSinstead of counted by hand.CITATION.cffgains theversion,date-releasedandtypefields it was missing, and its version is now checked againstdecisionrl.__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
ruffclean,mypyclean across 130 source files. Locally the fast suite's failure set is identical tomain'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