Problem
scale.py / ADR-0010 claim the log10/10** boundary "lives in exactly one place" and is "not smeared across FreeParameter.add / _reflect / prior_logpdf / sample_value." That holds inside FreeParameter — but the algorithm layer bypasses the Scale object and inlines the transform directly as np.log10(x) if v.log_space else x / 10 ** u if v.log_space else u / exp10(...) at ~12 sites. So the θ↔u boundary is in fact replicated, not centralized — exactly the drift/bug surface ADR-0003 warns against, and the concrete blocker that made the recent LogBase(n) (second log base) question untenable: a base change would have to be chased through every one of these.
This is pure tech debt, and it compounds — cmaes/powell already copied the if v.log_space idiom when they were added.
Inventory (behavior-preserving refactor target)
algorithms/base.py:237 — _param_vec (the PSet→u bridge), forward transform
algorithms/base.py:1232 — exp10, the base-10 inverse helper
algorithms/optimizers/local_base.py:66,89,103 — start point / box widths / u→θ
algorithms/optimizers/powell.py:108,109 — box bounds in u
algorithms/optimizers/simplex.py:137,316,357,374,399,400,424,442 — start point + the centroid / reflection / contraction arithmetic in log space (the densest cluster)
algorithms/optimizers/particle_swarm.py:232 — velocity update in log space
algorithms/samplers/adaptive_mcmc.py:462,473,517 — pset→log10 (chain state + index-file inputs)
algorithms/samplers/base.py:358 — histogram data in log10
algorithms/samplers/dream.py:132 — u→θ
Direction (for an ADR / design note)
FreeParameter already owns _scale.forward/_scale.inverse (used by add / set_value / value_from_quantile / _reflect). Expose a public transform (e.g. to_sampling_space(θ) / from_sampling_space(u)) so the algorithms ask the parameter instead of inlining np.log10/10**.
_param_vec is already the single PSet→u forward chokepoint; add the inverse peer (_pset_from_u, today replicated in local_base / dream / simplex) so the u-vector↔PSet conversion lives in one place too.
- The
simplex internal arithmetic (centroid / reflection / contraction in u) should build u-vectors via the chokepoint and map back, rather than inlining per-operation log10.
- Contract: behavior-preserving / byte-green against the existing optimizer + sampler tests (mirrors the M2.3
Prior and M2.4 NoiseModel extractions). No new scale behavior — this is the cleanup that makes ADR-0010's "one place" claim actually true.
Why it matters
- Removes the replicated-transform drift/bug surface (ADR-0003).
- Makes the
Scale object the real single source of truth (ADR-0010's stated intent).
- Is the genuine prerequisite for any future scale change (a second log base, an alternate parameterization) — the refactor the
LogBase(n) discussion identified as the actual gating work.
Relevant ADRs: 0003 (prior/proposal share one scale), 0010 (scale lives in one place — the intent this restores).
Problem
scale.py/ ADR-0010 claim thelog10/10**boundary "lives in exactly one place" and is "not smeared acrossFreeParameter.add/_reflect/prior_logpdf/sample_value." That holds insideFreeParameter— but the algorithm layer bypasses theScaleobject and inlines the transform directly asnp.log10(x) if v.log_space else x/10 ** u if v.log_space else u/exp10(...)at ~12 sites. So the θ↔u boundary is in fact replicated, not centralized — exactly the drift/bug surface ADR-0003 warns against, and the concrete blocker that made the recentLogBase(n)(second log base) question untenable: a base change would have to be chased through every one of these.This is pure tech debt, and it compounds —
cmaes/powellalready copied theif v.log_spaceidiom when they were added.Inventory (behavior-preserving refactor target)
algorithms/base.py:237—_param_vec(the PSet→u bridge), forward transformalgorithms/base.py:1232—exp10, the base-10 inverse helperalgorithms/optimizers/local_base.py:66,89,103— start point / box widths / u→θalgorithms/optimizers/powell.py:108,109— box bounds in ualgorithms/optimizers/simplex.py:137,316,357,374,399,400,424,442— start point + the centroid / reflection / contraction arithmetic in log space (the densest cluster)algorithms/optimizers/particle_swarm.py:232— velocity update in log spacealgorithms/samplers/adaptive_mcmc.py:462,473,517— pset→log10 (chain state + index-file inputs)algorithms/samplers/base.py:358— histogram data in log10algorithms/samplers/dream.py:132— u→θDirection (for an ADR / design note)
FreeParameteralready owns_scale.forward/_scale.inverse(used byadd/set_value/value_from_quantile/_reflect). Expose a public transform (e.g.to_sampling_space(θ)/from_sampling_space(u)) so the algorithms ask the parameter instead of inliningnp.log10/10**._param_vecis already the single PSet→u forward chokepoint; add the inverse peer (_pset_from_u, today replicated inlocal_base/dream/simplex) so the u-vector↔PSet conversion lives in one place too.simplexinternal arithmetic (centroid / reflection / contraction in u) should build u-vectors via the chokepoint and map back, rather than inlining per-operationlog10.Priorand M2.4NoiseModelextractions). No new scale behavior — this is the cleanup that makes ADR-0010's "one place" claim actually true.Why it matters
Scaleobject the real single source of truth (ADR-0010's stated intent).LogBase(n)discussion identified as the actual gating work.Relevant ADRs: 0003 (prior/proposal share one scale), 0010 (scale lives in one place — the intent this restores).