polystep 1.0.0__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. {polystep-1.0.0 → polystep-1.1.0}/CHANGELOG.md +53 -2
  2. {polystep-1.0.0 → polystep-1.1.0}/CITATION.cff +2 -2
  3. polystep-1.1.0/CONTRIBUTING.md +65 -0
  4. {polystep-1.0.0 → polystep-1.1.0}/LIMITATIONS.md +70 -24
  5. {polystep-1.0.0 → polystep-1.1.0}/PKG-INFO +47 -24
  6. {polystep-1.0.0 → polystep-1.1.0}/README.md +46 -22
  7. polystep-1.1.0/docs/api_overview.md +527 -0
  8. {polystep-1.0.0 → polystep-1.1.0}/docs/determinism.md +13 -3
  9. polystep-1.1.0/docs/performance.md +439 -0
  10. {polystep-1.0.0 → polystep-1.1.0}/docs/reproducibility.md +34 -11
  11. {polystep-1.0.0 → polystep-1.1.0}/pyproject.toml +0 -6
  12. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/__init__.py +1 -19
  13. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_serialization.py +2 -1
  14. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_step_blockwise.py +8 -6
  15. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_step_core.py +27 -8
  16. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_step_momentum.py +0 -2
  17. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_step_monolithic.py +93 -60
  18. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/adaptive_subspace.py +7 -18
  19. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/api.py +77 -52
  20. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/ask_tell.py +11 -10
  21. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/baselines/core.py +7 -1
  22. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/baselines/methods.py +5 -5
  23. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/gym_evaluator.py +1 -2
  24. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/policies.py +1 -2
  25. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/threads.py +1 -2
  26. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/utils.py +29 -25
  27. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/cost_nn.py +861 -242
  28. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/costs.py +5 -0
  29. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/dynamics.py +1 -1
  30. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/epsilon.py +17 -12
  31. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/factored_subspace.py +7 -7
  32. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/geometry.py +12 -17
  33. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/hybrid_subspace.py +57 -38
  34. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/layers/attention.py +34 -8
  35. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/optimizer.py +186 -68
  36. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/quadratic_model.py +29 -21
  37. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solver.py +19 -8
  38. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/_shared.py +34 -0
  39. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/kl_softmax.py +44 -27
  40. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/sinkhorn.py +32 -11
  41. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/subspace.py +37 -6
  42. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/transform.py +117 -53
  43. {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/PKG-INFO +47 -24
  44. {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/requires.txt +0 -1
  45. {polystep-1.0.0 → polystep-1.1.0}/tests/conftest.py +2 -1
  46. {polystep-1.0.0 → polystep-1.1.0}/tests/test_ablation_solvers.py +6 -1
  47. {polystep-1.0.0 → polystep-1.1.0}/tests/test_adaptive_subspace.py +189 -12
  48. {polystep-1.0.0 → polystep-1.1.0}/tests/test_amortization.py +54 -0
  49. {polystep-1.0.0 → polystep-1.1.0}/tests/test_api.py +172 -10
  50. {polystep-1.0.0 → polystep-1.1.0}/tests/test_ask_tell.py +80 -1
  51. {polystep-1.0.0 → polystep-1.1.0}/tests/test_baseline_fairness.py +3 -27
  52. {polystep-1.0.0 → polystep-1.1.0}/tests/test_baselines.py +31 -19
  53. {polystep-1.0.0 → polystep-1.1.0}/tests/test_benchmarks.py +83 -1
  54. polystep-1.1.0/tests/test_biased_rotation.py +109 -0
  55. {polystep-1.0.0 → polystep-1.1.0}/tests/test_blockwise.py +10 -3
  56. {polystep-1.0.0 → polystep-1.1.0}/tests/test_checkpoint_resume.py +193 -8
  57. {polystep-1.0.0 → polystep-1.1.0}/tests/test_compiled.py +1 -2
  58. polystep-1.1.0/tests/test_cost_nn.py +1432 -0
  59. {polystep-1.0.0 → polystep-1.1.0}/tests/test_dfo.py +166 -10
  60. {polystep-1.0.0 → polystep-1.1.0}/tests/test_epsilon.py +46 -4
  61. {polystep-1.0.0 → polystep-1.1.0}/tests/test_evaluator_compile_forward.py +3 -2
  62. {polystep-1.0.0 → polystep-1.1.0}/tests/test_factored_subspace.py +54 -4
  63. {polystep-1.0.0 → polystep-1.1.0}/tests/test_fastpath_parity.py +31 -19
  64. {polystep-1.0.0 → polystep-1.1.0}/tests/test_fused_projection.py +32 -0
  65. {polystep-1.0.0 → polystep-1.1.0}/tests/test_hybrid_subspace.py +100 -7
  66. {polystep-1.0.0 → polystep-1.1.0}/tests/test_input_validation.py +39 -20
  67. {polystep-1.0.0 → polystep-1.1.0}/tests/test_integration.py +0 -4
  68. {polystep-1.0.0 → polystep-1.1.0}/tests/test_kl_softmax.py +117 -0
  69. {polystep-1.0.0 → polystep-1.1.0}/tests/test_layers.py +161 -12
  70. {polystep-1.0.0 → polystep-1.1.0}/tests/test_math_guards.py +21 -1
  71. {polystep-1.0.0 → polystep-1.1.0}/tests/test_mixed_precision.py +72 -7
  72. {polystep-1.0.0 → polystep-1.1.0}/tests/test_mnist.py +5 -6
  73. {polystep-1.0.0 → polystep-1.1.0}/tests/test_no_test_set_leakage.py +0 -6
  74. {polystep-1.0.0 → polystep-1.1.0}/tests/test_nondiff_data.py +20 -13
  75. {polystep-1.0.0 → polystep-1.1.0}/tests/test_nondiff_models.py +4 -7
  76. {polystep-1.0.0 → polystep-1.1.0}/tests/test_optimizer.py +158 -14
  77. {polystep-1.0.0 → polystep-1.1.0}/tests/test_per_sample.py +3 -2
  78. {polystep-1.0.0 → polystep-1.1.0}/tests/test_probe_jitter.py +1 -11
  79. {polystep-1.0.0 → polystep-1.1.0}/tests/test_quadratic_model.py +60 -13
  80. {polystep-1.0.0 → polystep-1.1.0}/tests/test_rl_benchmarks.py +0 -1
  81. {polystep-1.0.0 → polystep-1.1.0}/tests/test_search_experiments.py +2 -2
  82. {polystep-1.0.0 → polystep-1.1.0}/tests/test_sequential_fast_path.py +254 -61
  83. {polystep-1.0.0 → polystep-1.1.0}/tests/test_sinkhorn.py +108 -12
  84. {polystep-1.0.0 → polystep-1.1.0}/tests/test_sinkhorn_numerics.py +2 -2
  85. {polystep-1.0.0 → polystep-1.1.0}/tests/test_site_vmap.py +962 -66
  86. {polystep-1.0.0 → polystep-1.1.0}/tests/test_softmax.py +2 -2
  87. {polystep-1.0.0 → polystep-1.1.0}/tests/test_softmax_integration.py +0 -13
  88. {polystep-1.0.0 → polystep-1.1.0}/tests/test_sparse_delta_evaluator.py +182 -4
  89. {polystep-1.0.0 → polystep-1.1.0}/tests/test_sparse_projection.py +1 -2
  90. {polystep-1.0.0 → polystep-1.1.0}/tests/test_subspace_blockwise.py +1 -5
  91. {polystep-1.0.0 → polystep-1.1.0}/tests/test_subspace_contract.py +5 -0
  92. {polystep-1.0.0 → polystep-1.1.0}/tests/test_subspace_delta.py +34 -0
  93. {polystep-1.0.0 → polystep-1.1.0}/tests/test_transform.py +241 -8
  94. {polystep-1.0.0 → polystep-1.1.0}/tests/test_trust_region.py +31 -18
  95. {polystep-1.0.0 → polystep-1.1.0}/tests/test_tuning_sweep.py +0 -16
  96. polystep-1.0.0/CONTRIBUTING.md +0 -65
  97. polystep-1.0.0/docs/api_overview.md +0 -390
  98. polystep-1.0.0/docs/performance.md +0 -237
  99. polystep-1.0.0/tests/test_biased_rotation.py +0 -145
  100. polystep-1.0.0/tests/test_cost_nn.py +0 -609
  101. {polystep-1.0.0 → polystep-1.1.0}/LICENSE +0 -0
  102. {polystep-1.0.0 → polystep-1.1.0}/MANIFEST.in +0 -0
  103. {polystep-1.0.0 → polystep-1.1.0}/setup.cfg +0 -0
  104. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_compiled.py +0 -0
  105. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/baselines/__init__.py +0 -0
  106. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/__init__.py +0 -0
  107. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/__init__.py +0 -0
  108. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/cartpole.py +0 -0
  109. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/metrics.py +0 -0
  110. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/blockwise.py +0 -0
  111. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/layers/__init__.py +0 -0
  112. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/layers/rnn.py +0 -0
  113. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/objectives/__init__.py +0 -0
  114. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/objectives/base.py +0 -0
  115. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/objectives/synthetic.py +0 -0
  116. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/projection/__init__.py +0 -0
  117. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/projection/sparse.py +0 -0
  118. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/py.typed +0 -0
  119. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/__init__.py +0 -0
  120. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/base.py +0 -0
  121. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/greedy.py +0 -0
  122. {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/softmax.py +0 -0
  123. {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/SOURCES.txt +0 -0
  124. {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/dependency_links.txt +0 -0
  125. {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/top_level.txt +0 -0
  126. {polystep-1.0.0 → polystep-1.1.0}/tests/__init__.py +0 -0
  127. {polystep-1.0.0 → polystep-1.1.0}/tests/test_candidate_autocast.py +0 -0
  128. {polystep-1.0.0 → polystep-1.1.0}/tests/test_controlled.py +0 -0
  129. {polystep-1.0.0 → polystep-1.1.0}/tests/test_costs.py +0 -0
  130. {polystep-1.0.0 → polystep-1.1.0}/tests/test_determinism.py +0 -0
  131. {polystep-1.0.0 → polystep-1.1.0}/tests/test_diagnostics.py +0 -0
  132. {polystep-1.0.0 → polystep-1.1.0}/tests/test_dynamics.py +0 -0
  133. {polystep-1.0.0 → polystep-1.1.0}/tests/test_end_to_end_learning.py +0 -0
  134. {polystep-1.0.0 → polystep-1.1.0}/tests/test_geometry.py +0 -0
  135. {polystep-1.0.0 → polystep-1.1.0}/tests/test_numerical_stress.py +0 -0
  136. {polystep-1.0.0 → polystep-1.1.0}/tests/test_objectives.py +0 -0
  137. {polystep-1.0.0 → polystep-1.1.0}/tests/test_practical.py +0 -0
  138. {polystep-1.0.0 → polystep-1.1.0}/tests/test_sparse_integration.py +0 -0
  139. {polystep-1.0.0 → polystep-1.1.0}/tests/test_subspace_gain.py +0 -0
  140. {polystep-1.0.0 → polystep-1.1.0}/tests/test_timeseries_val_selection.py +0 -0
@@ -1,5 +1,56 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0 - 2026-10-24
4
+
5
+ ### Features and performance
6
+
7
+ - Add `step_batch(inputs, targets, loss_fn=...)` for supervised training. It shares
8
+ the fast evaluation paths with `train()`; both accept tuple and dict inputs.
9
+ `register_evaluator` now returns a closure for the registered batch.
10
+ - Add `is_causal` support to `VmapSafeMultiHeadAttention`.
11
+ - Extend fast MLP evaluation to functional losses and traceable custom forwards.
12
+ Reuse unchanged forward work, reduce CPU cross-entropy overhead, and replay
13
+ site-local CUDA forwards with CUDA graphs.
14
+ - Measured full-space MLP steps are 1.9–3.3x faster on one CPU thread and 2.6x on a
15
+ shared RTX 5090 than 1.0.0. Tiny tied-weight models can be slower on CUDA. See
16
+ [performance](docs/performance.md) for the evaluation paths and tradeoffs.
17
+ - Refresh the examples and their metrics, clarify simulated Loihi adaptation, and
18
+ add SNN training and CartPole GIFs. The MAX-SAT example reaches about 98% SAT with
19
+ 600 default steps instead of 1500.
20
+
21
+ ### Fixes
22
+
23
+ - Restore forward-written buffers between scoring calls and vmap chunks, even on
24
+ failure, and preserve scores that share those buffers. Protect frozen embedding
25
+ weights changed by `max_norm`, reject evaluators for another model, and follow
26
+ model dtype and device changes.
27
+ - Preserve finite-difference gradients and curvature at small probe radii. Correct
28
+ KL zero-support handling and marginal diagnostics, including tiny target masses.
29
+ - Fix CUDA rotation for large adaptive subspaces, carry FP16/BF16 absorb rounding
30
+ error forward, and correct biased rotation, screening, amortized schedules,
31
+ epsilon restarts, half-precision attention, and loss precision in ES and baselines.
32
+ - Fall back to sequential scoring when vmap cannot batch a model, including stock
33
+ `nn.LSTM`.
34
+
35
+ ### Upgrade notes
36
+
37
+ - Pass the model to `PolyStepOptimizer`, not `model.parameters()`. Rebuild the
38
+ optimizer if its trainable parameter set changes. Separate parameters or buffers
39
+ that share trainable storage now raise `ValueError`; tie weights by assigning
40
+ the same Parameter, or register a clone.
41
+ - `FactoredSubspace` redraws `B` every 5 OT steps by default; set
42
+ `rotation_interval=0` for the old behavior. Hybrid and factored seeded bases change.
43
+ - `SinkhornSolver` and `KLSoftmaxSolver` default to `threshold=3e-5`, using relative
44
+ marginal error and the dual step divided by epsilon, respectively. Stalled solves
45
+ above the threshold report `converged=False`.
46
+ - `train()` uses `step_batch`; `restore_best` compares weights on the first training
47
+ batch. Seeded results can differ from 1.0.
48
+ - For compiled hybrid/factored runs, rebuild the optimizer from saved model weights:
49
+ checkpoint parameter names no longer contain the `_orig_mod.` prefix. The
50
+ [upgrade guide](docs/api_overview.md#upgrading-from-10) covers all compatibility
51
+ changes; [reproducibility](docs/reproducibility.md#changes-since-the-published-tables)
52
+ lists the affected published results.
53
+
3
54
  ## 1.0.0 - 2026-10-13
4
55
 
5
56
  ### Upgrade notes
@@ -10,8 +61,8 @@
10
61
  - Checkpoints use format 6. Rebuild the optimizer from saved model weights when
11
62
  upgrading from 0.13; older optimizer checkpoints cannot be loaded.
12
63
  - Some low-level exports and experiment helpers moved or were removed. The
13
- [migration guide](docs/api_overview.md#upgrading-from-013) lists replacements and
14
- changes to evaluation, configuration, and callbacks.
64
+ [migration guide](docs/api_overview.md#upgrading-from-013) covers the main API
65
+ replacements and checkpoint requirements.
15
66
 
16
67
  ### Fixes
17
68
 
@@ -6,8 +6,8 @@ authors:
6
6
  - family-names: Le
7
7
  given-names: An T.
8
8
  license: Apache-2.0
9
- version: 1.0.0
10
- date-released: "2026-10-13"
9
+ version: 1.1.0
10
+ date-released: "2026-10-24"
11
11
  repository-code: "https://github.com/anindex/polystep"
12
12
  keywords:
13
13
  - gradient-free optimization
@@ -0,0 +1,65 @@
1
+ # Contributing
2
+
3
+ From the repository root, install the development dependencies and run the CI checks:
4
+
5
+ ```bash
6
+ pip install -e ".[dev]"
7
+ ruff check .
8
+ ruff format --check .
9
+ pytest tests/ -q -n auto --maxprocesses=4
10
+ ```
11
+
12
+ A bug fix should have a small regression test of observable behavior. Extend an
13
+ existing case when it already covers the same path; avoid separate tests for trivial
14
+ defaults, getters, or repeated shapes. Keep public APIs typed. Comments should
15
+ explain constraints, numerical choices, or non-obvious behavior.
16
+
17
+ ## Tests
18
+
19
+ The default suite excludes `slow` and `gpu` tests. To select them:
20
+
21
+ ```bash
22
+ pytest tests/ -m slow
23
+ pytest tests/ -m gpu
24
+ pytest tests/ -m ""
25
+ ```
26
+
27
+ Capping parallel test workers at four limits startup and memory costs. The fast
28
+ suite uses one CPU thread per worker; `POLYSTEP_TEST_THREADS` overrides it. Slow
29
+ tests use `ncpu - 8` threads, clamped to 2-16 and split across xdist workers, and may
30
+ download datasets. Keep downloads and training-quality checks in the slow suite;
31
+ use synthetic inputs for unit tests.
32
+
33
+ Tests importing `experiments/` must skip when that directory is absent, since it is
34
+ excluded from distributions. Install `pip install -e ".[dev,experiments]"` to run
35
+ tests that need optional experiment dependencies. Missing optional dependencies and
36
+ unavailable hardware are valid reasons to skip a test; unsupported parameter pairs
37
+ should not be collected. Tests run without automatic retries.
38
+
39
+ Pushes and pull requests test Python 3.11 with PyTorch 2.8 and Python 3.14 with
40
+ current PyTorch. Scheduled and manual runs add Python 3.12/3.13, coverage, optional
41
+ experiment dependencies, real-data learning checks, and the shipped tests against
42
+ the installed wheel. Every run builds both distributions and checks the wheel's
43
+ imports and type marker.
44
+
45
+ ## Releases
46
+
47
+ Update `polystep.__version__`. Set the same version and the release date in
48
+ `CITATION.cff`. Add a `CHANGELOG.md` entry with the heading `## X.Y.Z - YYYY-MM-DD`.
49
+ `pyproject.toml` reads the package version automatically.
50
+ If the project's maturity changes, update its `Development Status` classifier.
51
+
52
+ ```bash
53
+ uv build
54
+ uvx twine check --strict dist/*
55
+ ```
56
+
57
+ After the full manual CI workflow passes on the release commit, publish a GitHub
58
+ Release tagged `vX.Y.Z`. The release workflow checks versions, lint, metadata, and
59
+ the installed wheel before publishing. Only the publish job has OIDC permission;
60
+ it uses the `pypi` environment and PyPI Trusted Publishing.
61
+
62
+ ## Issues
63
+
64
+ Include a minimal reproduction, the traceback, Python and PyTorch versions, and
65
+ hardware details relevant to the failure.
@@ -10,41 +10,66 @@ directions; very small projection ratios can fail on large models.
10
10
  use sparse signed columns, which are not orthonormal. Claims requiring an orthogonal
11
11
  projection do not apply to those columns.
12
12
 
13
- `FactoredSubspace` restricts perturbations to the input directions in its fixed
14
- factor. Rotation changes those directions. Retune radii when switching subspaces.
13
+ `FactoredSubspace` restricts each step to the input directions spanned by its factor
14
+ `B`. It redraws `B` every 5 OT steps by default; with `rotation_interval=0` the whole
15
+ run stays in one set of directions. Retune radii when switching subspaces.
15
16
 
16
- Momentum, the `amortize_steps` coasting direction, and the `biased_rotation`
17
- direction are cleared when the basis changes. `AdaptiveSubspace` rotates every step
18
- by default. These options need a longer rotation interval to take effect.
19
- The optimizer warns when you combine them with rotation on every step.
20
- Heavy-ball momentum can amplify the barycentric displacement.
21
- `step_radius` bounds the displacement before momentum.
17
+ A basis change clears momentum, the `amortize_steps` coasting direction and the
18
+ `biased_rotation` direction, so these options need a rotation interval longer than one
19
+ step. `AdaptiveSubspace` rotates every step by default, and the optimizer warns when
20
+ any subspace combines these options with `rotation_interval=1`. `FactoredSubspace`
21
+ clears them every 5 OT steps without a warning. Under `amortize_steps`,
22
+ `rotation_interval` and `absorb_interval` count OT steps only.
23
+
24
+ Heavy-ball momentum can amplify the barycentric displacement; `step_radius` bounds the
25
+ displacement before momentum.
22
26
 
23
27
  ## Evaluation
24
28
 
25
29
  - `step()` expects `closure(batched_params) -> losses` with shape `(N,)`.
26
- The candidate tensors may share storage that the next chunk overwrites, so clone
27
- any you keep after the closure returns.
28
- - Manual loops must register the evaluator and current batch to enable fast paths.
29
- The evaluator and closure must compute the same objective.
30
- - `NNCostEvaluator` and `train()` score candidates in eval mode: dropout is off and
31
- batch-norm running statistics never update. A custom closure that runs the model in
32
- train mode compares candidates under different dropout masks.
30
+ The candidate tensors may be expanded views or share storage that the next chunk
31
+ overwrites, so treat them as read-only and clone any you keep after the closure
32
+ returns.
33
+ - `step_batch()` and `train()` register each batch. A manual `step(closure)` loop must
34
+ register the evaluator and current batch to use the fast paths. The evaluator's
35
+ loss and the closure must compute the same objective.
36
+ - Multi-input batches use vmap and the site-local path; the batched-matmul, delta and
37
+ factored paths and multifidelity screening need one input tensor.
38
+ - Tied weights must be the same Parameter object, and buffers may not share memory
39
+ with trainable parameters. Register a clone or compute the tensor in forward instead.
40
+ - `NNCostEvaluator`, `step_batch()` and `train()` score candidates in eval mode:
41
+ standard dropout is off and batch-norm layers do not update their running statistics.
42
+ A custom closure that runs the model in train mode compares candidates under
43
+ different dropout masks.
44
+ - The evaluator restores buffers written by the forward, such as a call counter,
45
+ after each batched chunk and each candidate in the sequential and in-place loops.
46
+ Embedding weights changed by `max_norm` are restored even when frozen. Update
47
+ persistent state outside the closure.
48
+ - Writes that leave the tensor version counter unchanged, such as
49
+ `F.batch_norm(training=True)` running statistics or writes through `.data`, are found
50
+ by comparing values after the first forward and the first candidate of each
51
+ sequential, in-place or chunked vmap sweep. A write first triggered after those
52
+ checks can go undetected and persist into later evaluations.
53
+ - Float inputs are cast to the dtype of the model's first floating weight only when
54
+ the forward raises on them as given. The evaluator checks this with one extra
55
+ forward per input dtype and puts back the buffers it changed. The delta and
56
+ factored paths, which accept plain Linear stacks only, always cast.
33
57
  - Mixed-dtype models require `HybridSubspace` or `FactoredSubspace`. Full-space and
34
58
  global-subspace coordinates use one dtype and reject mixed parameter dtypes.
35
59
  - `mixed_precision=True` uses BF16 geometry and forwards; solver costs use FP32.
36
60
  Models needing autocast internally must provide it. Half-precision candidate outputs
37
- reduce in FP32 only under the builtin cross-entropy, MSE and L1 losses at their
38
- defaults apart from `reduction`. Other losses may round nearby candidate scores
39
- to the same value. With FP16 or BF16 parameters, `FactoredSubspace` materializes
40
- every candidate and runs slower. See
61
+ reduce in FP32 only under the builtin cross-entropy, MSE and L1 losses; other losses
62
+ may round nearby candidate scores to the same value. With FP16 or BF16 parameters,
63
+ `FactoredSubspace` materializes every candidate, and the fast paths can differ from
64
+ the model's own Linear output by about one unit in the last place (ulp), enough to
65
+ flip a hard threshold. `use_inplace=True` scores with the model's forward. See
41
66
  [precision](docs/performance.md#compilation-and-precision).
42
- - `train()` rejects `trust_region`. See
67
+ - `step_batch()` and `train()` reject `trust_region`. See
43
68
  [radius control](docs/api_overview.md#quadratic-model-and-radius-control) for its
44
69
  manual-step use.
45
70
  - `trust_region` and `use_adaptive_radius` both scale the step. Choose one; the
46
71
  optimizer warns when both are on.
47
- - Multi-fidelity screening imputes dropped costs using an additive offset. This can
72
+ - Multifidelity screening imputes dropped costs using an additive offset. This can
48
73
  bias the full-fidelity cost estimate.
49
74
  - Full-space blockwise mode does not support every monolithic adaptation option;
50
75
  unsupported options warn at construction.
@@ -53,14 +78,21 @@ Heavy-ball momentum can amplify the barycentric displacement.
53
78
  forward neither draws random numbers nor reads the perturbed parameter before
54
79
  calling its layer. Attention scores and other temporary tensors inside a module's
55
80
  forward are not counted. Set `chunk_size` by hand if such a model runs out of memory.
81
+ Under vmap, CUDA attention materializes the scores of every candidate in the chunk.
82
+ `PolyStepOptimizer(chunk_size=...)` caps the candidates scored per step chunk, and
83
+ `NNCostEvaluator(chunk_size=...)` caps how many of them each vmap call batches.
84
+ - On CUDA the site-local path replays CUDA graphs, which freeze Python-side state the
85
+ forward reads, such as a Python counter or a flag. Pass `compile_forward=False` for
86
+ such a forward.
56
87
  - The library does not distribute optimizer state across multiple GPUs.
57
88
 
58
89
  ## Attention and recurrent layers
59
90
 
60
91
  [`VmapSafeMultiHeadAttention`](src/polystep/layers/attention.py) requires batch-first
61
92
  inputs and `kdim == vdim == embed_dim`. It does not support `add_bias_kv`,
62
- `add_zero_attn`, returned attention weights, or `is_causal=True`; supply an explicit
63
- causal mask. It returns an output tensor, not an `(output, weights)` tuple.
93
+ `add_zero_attn`, or returned attention weights. `is_causal` is a hint that needs
94
+ `attn_mask`, as in `nn.MultiheadAttention`. It returns an output tensor, not an
95
+ `(output, weights)` tuple.
64
96
 
65
97
  Boolean masks use `True` for masked positions; float masks are additive. Attention
66
98
  masks accept `(query, key)`, `(batch * heads, query, key)`, and broadcastable 4D shapes.
@@ -68,6 +100,19 @@ The wrapper also accepts `(batch, query, key)` masks.
68
100
 
69
101
  CPU vmap may emit a batching-fallback warning.
70
102
 
103
+ Stock `nn.MultiheadAttention` and `nn.TransformerEncoderLayer` work, but vmap has no
104
+ batching rule for their fused kernels, so it runs them once per candidate and PyTorch
105
+ prints a performance-drop warning. On CUDA this makes a step several times slower, and
106
+ the evaluator warns and points to `VmapSafeMultiHeadAttention`. On CPU stock
107
+ `nn.MultiheadAttention` measured faster than `VmapSafeMultiHeadAttention`, so the
108
+ evaluator stays silent there.
109
+
110
+ Stock `nn.LSTM`, `nn.GRU` and `nn.RNN` have no vmap batching rule, so the evaluator
111
+ scores them one candidate at a time in a sequential loop, with a warning. On a small
112
+ CPU model, `NNCostEvaluator(..., use_inplace=True)` ran 1.1 to 1.3 times faster than
113
+ the loop, and `VmapSafeLSTM` 3 to 5 times faster. `step_batch()` and `train()` build
114
+ their own evaluator, so they take the loop for these layers.
115
+
71
116
  [`VmapSafeLSTM`](src/polystep/layers/rnn.py) requires batch-first dense inputs. It does
72
117
  not support bidirectional recurrence, projection, or `PackedSequence`. Its explicit
73
118
  cell computations can be slower than fused cuDNN recurrence.
@@ -80,7 +125,7 @@ Independent particle rotations mean Sinkhorn's shared column index does not iden
80
125
  a common direction across particles.
81
126
 
82
127
  Sinkhorn requires `omega` in `[0.5, 1.95]`. Anderson acceleration and adaptive omega
83
- are inactive in fixed-iteration mode (`threshold <= 0`). Both solver families promote
128
+ are inactive in fixed-iteration mode (`threshold <= 0`). Sinkhorn and KLSoftmax promote
84
129
  FP16/BF16 costs to FP32. Cost differences and reductions must also fit the solver's
85
130
  dtype: finite FP32 inputs near its limits can still overflow when recentered or
86
131
  averaged. Rescale the objective before evaluation or return FP64 costs in that case.
@@ -92,6 +137,7 @@ finite magnitude plus one, clamped to the dtype limit. An infeasible candidate c
92
137
  therefore retain weight, depending on epsilon. With `scale_cost='mean'` or
93
138
  `'max_cost'`, the solvers compute the penalty after scaling. Otherwise, that weight
94
139
  also depends on the cost offset.
140
+
95
141
  Balanced Sinkhorn sends every column its prescribed mass, even when all of that
96
142
  column's candidates are infeasible. The monolithic step records a vertex's cost as
97
143
  the mean of its probes with each nonfinite probe already penalized, so with several
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polystep
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: PyTorch PolyStep Optimizer - gradient-free neural network training via entropic optimal transport
5
5
  Author: An T. Le
6
6
  License-Expression: Apache-2.0
@@ -35,7 +35,6 @@ Requires-Dist: numpy>=2.0; extra == "dev"
35
35
  Requires-Dist: setuptools>=77; extra == "dev"
36
36
  Requires-Dist: pytest>=8.0; extra == "dev"
37
37
  Requires-Dist: pytest-timeout>=2.2; extra == "dev"
38
- Requires-Dist: pytest-rerunfailures>=14.0; extra == "dev"
39
38
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
40
39
  Requires-Dist: pytest-xdist>=3.5; extra == "dev"
41
40
  Requires-Dist: ruff==0.15.13; extra == "dev"
@@ -75,10 +74,11 @@ backpropagation is usually faster.
75
74
  [API reference](https://github.com/anindex/polystep/blob/main/docs/api_overview.md) ·
76
75
  [Julia package](https://github.com/anindex/PolyStep.jl)
77
76
 
78
- <p align="center">
79
- <img src="https://raw.githubusercontent.com/anindex/polystep/main/docs/figures/method_diagram.png" width="840"
80
- alt="PolyStep: search coordinates, polytope probes, cost evaluation, soft assignment, and a barycentric update.">
81
- </p>
77
+ | One particle | Four particles, two minima |
78
+ |:---:|:---:|
79
+ | [<img src="docs/figures/polystep_particle.gif" width="440" alt="One particle probes an objective and moves toward its minimum through weighted barycentric steps.">](https://vietngth.github.io/polystep-visualization/) | [<img src="docs/figures/polystep_particles.gif" width="440" alt="Four particles probe a two-basin objective and move toward its minima through weighted barycentric steps.">](https://vietngth.github.io/polystep-visualization/) |
80
+
81
+ Animations from [Viet T. Nguyen's interactive visualization](https://vietngth.github.io/polystep-visualization/).
82
82
 
83
83
  ## Installation
84
84
 
@@ -89,7 +89,7 @@ pip install "polystep[examples]" # dependencies for the repository's examples
89
89
 
90
90
  Python 3.11+ and PyTorch 2.8+ are required. Choose a CUDA build compatible with your
91
91
  driver from the [PyTorch installation guide](https://pytorch.org/get-started/locally/).
92
- For development, install `pip install -e ".[dev]"` from a checkout.
92
+ For development, run `pip install -e ".[dev]"` from a checkout.
93
93
 
94
94
  ## Quickstart
95
95
 
@@ -99,6 +99,7 @@ from torch import nn
99
99
  from torch.utils.data import DataLoader, TensorDataset
100
100
  from polystep import HybridSubspace, ParamLayout, PolyStepOptimizer, TrainConfig, train
101
101
 
102
+ torch.set_num_threads(1)
102
103
  model = nn.Sequential(nn.Linear(16, 32), nn.ReLU(), nn.Linear(32, 3))
103
104
  loader = DataLoader(
104
105
  TensorDataset(torch.randn(256, 16), torch.randint(0, 3, (256,))),
@@ -111,9 +112,24 @@ optimizer = PolyStepOptimizer(model, subspace=subspace, solver="softmax")
111
112
  train(model, loader, nn.CrossEntropyLoss(), optimizer, TrainConfig(epochs=5))
112
113
  ```
113
114
 
114
- Replace the generated data with your dataset. `max_subspace_dim=128` caps the search
115
- at 128 coordinates, so each step scores 144 candidates. In manual loops, call
116
- `register_evaluator()` before each batch; `train()` does this automatically. See
115
+ `max_subspace_dim=128` caps the search at 128 coordinates, so each step scores
116
+ 144 candidates. Biases and normalization weights keep their full width. On a larger
117
+ model, the cap must cover those parameters plus at least one coordinate per weight
118
+ matrix. If the cap is too small, PolyStep
119
+ warns and reports the minimum dimension it can use.
120
+
121
+ `step_batch` uses the same candidate evaluation paths as `train()`:
122
+
123
+ ```python
124
+ loss_fn = nn.CrossEntropyLoss()
125
+ for x, y in loader:
126
+ optimizer.step_batch(x, y, loss_fn=loss_fn)
127
+ optimizer.release_evaluator()
128
+ ```
129
+
130
+ `torch.set_num_threads(1)` avoids thread overhead for a model this small. Larger
131
+ models may benefit from more threads; `python -m polystep.benchmarks.threads` compares
132
+ several counts on an MLP workload. See
117
133
  [performance](https://github.com/anindex/polystep/blob/main/docs/performance.md)
118
134
  for candidate paths, thread counts, compilation, and memory settings.
119
135
 
@@ -125,8 +141,8 @@ from polystep import PolyStepES
125
141
  es = PolyStepES(dim=20, epsilon=0.1, step_radius=0.3)
126
142
  for _ in range(300):
127
143
  candidates = es.ask()
128
- es.tell(candidates.square().sum(dim=1))
129
- print(es.best_fitness, es.best_solution)
144
+ es.tell((candidates - 1).square().sum(dim=1))
145
+ print(es.best_fitness, (es.mean - 1).square().sum().item())
130
146
  ```
131
147
 
132
148
  ### Synthetic objectives
@@ -152,6 +168,17 @@ depends on the radius, representation, and evaluation budget. PolyStep's optiona
152
168
  quadratic models and radius controllers are implementation heuristics; they do not
153
169
  inherit classical direct-search or trust-region guarantees.
154
170
 
171
+ | SNN training | CartPole policy |
172
+ |:---:|:---:|
173
+ | <img src="docs/figures/polystep_snn_progress.gif" width="440" alt="Training loss and batch accuracy over 60 PolyStep updates of a spiking network with hard LIF thresholds."> | <img src="docs/figures/rl_cartpole_policy.gif" width="440" alt="A trained CartPole policy uses hard left/right actions to keep the pole upright."> |
174
+
175
+ The [SNN example](examples/02_snn_starter.py) trains a network with hard LIF spikes
176
+ on four synthetic classes, without surrogate gradients.
177
+
178
+ The [CartPole example](examples/03_rl_cartpole.py) uses real-valued weights and a
179
+ hard left/right `argmax`. PolyStep optimizes episode return without gradients
180
+ through the actions or simulator.
181
+
155
182
  ## Baselines
156
183
 
157
184
  `polystep.baselines` provides `openai_es`, `spsa`, `mezo`, `random_search`, `eggroll`,
@@ -161,8 +188,8 @@ scored, even when candidates are batched:
161
188
  ```python
162
189
  from polystep.baselines import Objective, openai_es
163
190
 
164
- objective = Objective(lambda x: x.square().sum(1), dim=64, budget=10_000)
165
- result = openai_es(objective, sigma=0.05, lr=0.1, popsize=32)
191
+ objective = Objective(lambda x: (x - 1).square().sum(1), dim=64, budget=10_000)
192
+ result = openai_es(objective, sigma=0.05, popsize=32)
166
193
  ```
167
194
 
168
195
  The paper runners match search spaces, budgets and tuning as the
@@ -171,17 +198,12 @@ The paper runners match search spaces, budgets and tuning as the
171
198
  ## Benchmarks
172
199
 
173
200
  Five-seed mean ± sample standard deviation, with configurations and checkpoints
174
- selected on validation data; the probSAT and RC2 columns are single runs. In the
175
- architecture tables, OpenAI-ES, CMA-ES and EGGROLL get PolyStep's candidate-evaluation
176
- budget, and SPSA, MeZO and random search get its step count. On argmax attention, hard
177
- MoE and ETTh1, the population methods instead get 32 evaluations per PolyStep step,
178
- spent at their own population size, and Adam trains for PolyStep's 30 epochs, except
179
- 50 on ETTh1. On MAX-SAT, CMA-ES and OpenAI-ES get 50 evaluations per PolyStep step.
180
- OpenAI-ES scores 50 candidates at each of PolyStep's steps, while CMA-ES doubles its
181
- population at each restart and takes fewer generations. The tables were recorded
182
- before 1.0; the
201
+ selected on validation data; the probSAT and RC2 columns are single runs. Each
202
+ baseline gets a matched evaluation or step budget, as the
203
+ [protocol](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md#protocol)
204
+ describes. The tables were recorded before 1.0; the
183
205
  [reproducibility guide](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md#changes-since-the-published-tables)
184
- lists the rows that 1.0 changes. The
206
+ lists the rows that later releases change, and the
185
207
  [experiment index](https://github.com/anindex/polystep/blob/main/experiments/EXPERIMENT_INDEX.md)
186
208
  has the full tables.
187
209
 
@@ -225,6 +247,7 @@ for unsupported configurations and comparison limits.
225
247
  - [Performance](https://github.com/anindex/polystep/blob/main/docs/performance.md)
226
248
  - [Examples](https://github.com/anindex/polystep/blob/main/examples/README.md)
227
249
  - [Experiments](https://github.com/anindex/polystep/blob/main/experiments/README.md) and [reproduction](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md)
250
+ - [Limitations](https://github.com/anindex/polystep/blob/main/LIMITATIONS.md)
228
251
  - [Determinism](https://github.com/anindex/polystep/blob/main/docs/determinism.md)
229
252
  - [Contributing](https://github.com/anindex/polystep/blob/main/CONTRIBUTING.md) and [changelog](https://github.com/anindex/polystep/blob/main/CHANGELOG.md)
230
253
 
@@ -20,10 +20,11 @@ backpropagation is usually faster.
20
20
  [API reference](https://github.com/anindex/polystep/blob/main/docs/api_overview.md) ·
21
21
  [Julia package](https://github.com/anindex/PolyStep.jl)
22
22
 
23
- <p align="center">
24
- <img src="https://raw.githubusercontent.com/anindex/polystep/main/docs/figures/method_diagram.png" width="840"
25
- alt="PolyStep: search coordinates, polytope probes, cost evaluation, soft assignment, and a barycentric update.">
26
- </p>
23
+ | One particle | Four particles, two minima |
24
+ |:---:|:---:|
25
+ | [<img src="docs/figures/polystep_particle.gif" width="440" alt="One particle probes an objective and moves toward its minimum through weighted barycentric steps.">](https://vietngth.github.io/polystep-visualization/) | [<img src="docs/figures/polystep_particles.gif" width="440" alt="Four particles probe a two-basin objective and move toward its minima through weighted barycentric steps.">](https://vietngth.github.io/polystep-visualization/) |
26
+
27
+ Animations from [Viet T. Nguyen's interactive visualization](https://vietngth.github.io/polystep-visualization/).
27
28
 
28
29
  ## Installation
29
30
 
@@ -34,7 +35,7 @@ pip install "polystep[examples]" # dependencies for the repository's examples
34
35
 
35
36
  Python 3.11+ and PyTorch 2.8+ are required. Choose a CUDA build compatible with your
36
37
  driver from the [PyTorch installation guide](https://pytorch.org/get-started/locally/).
37
- For development, install `pip install -e ".[dev]"` from a checkout.
38
+ For development, run `pip install -e ".[dev]"` from a checkout.
38
39
 
39
40
  ## Quickstart
40
41
 
@@ -44,6 +45,7 @@ from torch import nn
44
45
  from torch.utils.data import DataLoader, TensorDataset
45
46
  from polystep import HybridSubspace, ParamLayout, PolyStepOptimizer, TrainConfig, train
46
47
 
48
+ torch.set_num_threads(1)
47
49
  model = nn.Sequential(nn.Linear(16, 32), nn.ReLU(), nn.Linear(32, 3))
48
50
  loader = DataLoader(
49
51
  TensorDataset(torch.randn(256, 16), torch.randint(0, 3, (256,))),
@@ -56,9 +58,24 @@ optimizer = PolyStepOptimizer(model, subspace=subspace, solver="softmax")
56
58
  train(model, loader, nn.CrossEntropyLoss(), optimizer, TrainConfig(epochs=5))
57
59
  ```
58
60
 
59
- Replace the generated data with your dataset. `max_subspace_dim=128` caps the search
60
- at 128 coordinates, so each step scores 144 candidates. In manual loops, call
61
- `register_evaluator()` before each batch; `train()` does this automatically. See
61
+ `max_subspace_dim=128` caps the search at 128 coordinates, so each step scores
62
+ 144 candidates. Biases and normalization weights keep their full width. On a larger
63
+ model, the cap must cover those parameters plus at least one coordinate per weight
64
+ matrix. If the cap is too small, PolyStep
65
+ warns and reports the minimum dimension it can use.
66
+
67
+ `step_batch` uses the same candidate evaluation paths as `train()`:
68
+
69
+ ```python
70
+ loss_fn = nn.CrossEntropyLoss()
71
+ for x, y in loader:
72
+ optimizer.step_batch(x, y, loss_fn=loss_fn)
73
+ optimizer.release_evaluator()
74
+ ```
75
+
76
+ `torch.set_num_threads(1)` avoids thread overhead for a model this small. Larger
77
+ models may benefit from more threads; `python -m polystep.benchmarks.threads` compares
78
+ several counts on an MLP workload. See
62
79
  [performance](https://github.com/anindex/polystep/blob/main/docs/performance.md)
63
80
  for candidate paths, thread counts, compilation, and memory settings.
64
81
 
@@ -70,8 +87,8 @@ from polystep import PolyStepES
70
87
  es = PolyStepES(dim=20, epsilon=0.1, step_radius=0.3)
71
88
  for _ in range(300):
72
89
  candidates = es.ask()
73
- es.tell(candidates.square().sum(dim=1))
74
- print(es.best_fitness, es.best_solution)
90
+ es.tell((candidates - 1).square().sum(dim=1))
91
+ print(es.best_fitness, (es.mean - 1).square().sum().item())
75
92
  ```
76
93
 
77
94
  ### Synthetic objectives
@@ -97,6 +114,17 @@ depends on the radius, representation, and evaluation budget. PolyStep's optiona
97
114
  quadratic models and radius controllers are implementation heuristics; they do not
98
115
  inherit classical direct-search or trust-region guarantees.
99
116
 
117
+ | SNN training | CartPole policy |
118
+ |:---:|:---:|
119
+ | <img src="docs/figures/polystep_snn_progress.gif" width="440" alt="Training loss and batch accuracy over 60 PolyStep updates of a spiking network with hard LIF thresholds."> | <img src="docs/figures/rl_cartpole_policy.gif" width="440" alt="A trained CartPole policy uses hard left/right actions to keep the pole upright."> |
120
+
121
+ The [SNN example](examples/02_snn_starter.py) trains a network with hard LIF spikes
122
+ on four synthetic classes, without surrogate gradients.
123
+
124
+ The [CartPole example](examples/03_rl_cartpole.py) uses real-valued weights and a
125
+ hard left/right `argmax`. PolyStep optimizes episode return without gradients
126
+ through the actions or simulator.
127
+
100
128
  ## Baselines
101
129
 
102
130
  `polystep.baselines` provides `openai_es`, `spsa`, `mezo`, `random_search`, `eggroll`,
@@ -106,8 +134,8 @@ scored, even when candidates are batched:
106
134
  ```python
107
135
  from polystep.baselines import Objective, openai_es
108
136
 
109
- objective = Objective(lambda x: x.square().sum(1), dim=64, budget=10_000)
110
- result = openai_es(objective, sigma=0.05, lr=0.1, popsize=32)
137
+ objective = Objective(lambda x: (x - 1).square().sum(1), dim=64, budget=10_000)
138
+ result = openai_es(objective, sigma=0.05, popsize=32)
111
139
  ```
112
140
 
113
141
  The paper runners match search spaces, budgets and tuning as the
@@ -116,17 +144,12 @@ The paper runners match search spaces, budgets and tuning as the
116
144
  ## Benchmarks
117
145
 
118
146
  Five-seed mean ± sample standard deviation, with configurations and checkpoints
119
- selected on validation data; the probSAT and RC2 columns are single runs. In the
120
- architecture tables, OpenAI-ES, CMA-ES and EGGROLL get PolyStep's candidate-evaluation
121
- budget, and SPSA, MeZO and random search get its step count. On argmax attention, hard
122
- MoE and ETTh1, the population methods instead get 32 evaluations per PolyStep step,
123
- spent at their own population size, and Adam trains for PolyStep's 30 epochs, except
124
- 50 on ETTh1. On MAX-SAT, CMA-ES and OpenAI-ES get 50 evaluations per PolyStep step.
125
- OpenAI-ES scores 50 candidates at each of PolyStep's steps, while CMA-ES doubles its
126
- population at each restart and takes fewer generations. The tables were recorded
127
- before 1.0; the
147
+ selected on validation data; the probSAT and RC2 columns are single runs. Each
148
+ baseline gets a matched evaluation or step budget, as the
149
+ [protocol](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md#protocol)
150
+ describes. The tables were recorded before 1.0; the
128
151
  [reproducibility guide](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md#changes-since-the-published-tables)
129
- lists the rows that 1.0 changes. The
152
+ lists the rows that later releases change, and the
130
153
  [experiment index](https://github.com/anindex/polystep/blob/main/experiments/EXPERIMENT_INDEX.md)
131
154
  has the full tables.
132
155
 
@@ -170,6 +193,7 @@ for unsupported configurations and comparison limits.
170
193
  - [Performance](https://github.com/anindex/polystep/blob/main/docs/performance.md)
171
194
  - [Examples](https://github.com/anindex/polystep/blob/main/examples/README.md)
172
195
  - [Experiments](https://github.com/anindex/polystep/blob/main/experiments/README.md) and [reproduction](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md)
196
+ - [Limitations](https://github.com/anindex/polystep/blob/main/LIMITATIONS.md)
173
197
  - [Determinism](https://github.com/anindex/polystep/blob/main/docs/determinism.md)
174
198
  - [Contributing](https://github.com/anindex/polystep/blob/main/CONTRIBUTING.md) and [changelog](https://github.com/anindex/polystep/blob/main/CHANGELOG.md)
175
199