fix(inference)!: add progress_bar to the permutation and bootstrap family - #473
Closed
ljchang wants to merge 1 commit into
Closed
fix(inference)!: add progress_bar to the permutation and bootstrap family#473ljchang wants to merge 1 commit into
ljchang wants to merge 1 commit into
Conversation
…mily
The inference functions printed a tqdm progress bar unconditionally, with no
way to turn it off. Calling them in a loop -- a calibration study running 100
permutation tests, a per-parcel sweep, anything scripted -- emitted one
progress bar per call, and the only workaround was wrapping every call in
contextlib.redirect_stderr.
Adds `progress_bar: bool = False` across the family, matching the canonical
kwarg table in CLAUDE.md and the existing convention in BrainCollection,
LocalAlignment, and the ridge solvers.
Two shared helpers in algorithms/inference/utils.py replace fourteen
hand-rolled tqdm sites:
maybe_tqdm(iterable, *, progress_bar, **kw) # iteration-driven bars
make_progress_bar(*, progress_bar, **kw) # manually-updated bars
`make_progress_bar` returns a `_NullProgressBar` when disabled, so call sites
that drive a bar via .update() need no branching. Both import tqdm lazily, so
it stays off the import path when unused.
Threaded through both layers, since nltools.stats re-exports thin wrappers that
are distinct function objects from the algorithm-layer functions:
algorithms/inference: one_sample, two_sample, correlation, matrix,
timeseries, bootstrap (5 private helpers), isc
nltools.stats: the five permutation wrappers
facades: Adjacency.similarity / .ttest, stats_label_distance
BREAKING: progress bars are now off by default. isc_permutation_test and
isc_group_permutation_test previously defaulted to progress_bar=True and now
default to False; every other function in the family previously had no way to
disable its bar. Pass progress_bar=True to restore the old output.
Tests cover both layers: that the kwarg exists, that it defaults to False, that
nothing reaches stderr by default, and that progress_bar=True still produces a
bar (so the knob is a real toggle, not a silent no-op).
This was referenced Jul 29, 2026
Collaborator
|
Collaborator
|
Superseded by #483 — the branch was merged into |
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.
Problem
The inference functions print a tqdm progress bar unconditionally, with no way to turn it off. Calling them in a loop — a calibration study running 100 permutation tests, a per-parcel sweep, anything scripted — emits one progress bar per call.
This came up while writing the dartbrains thresholding chapter, where a 100-iteration max-statistic calibration loop produced 100 progress bars. The only workaround was wrapping every call in
contextlib.redirect_stderr.It also contradicts our own canonical kwarg table in
CLAUDE.md:progress_bar: bool = Falseshow_progress,verboseBrainCollection,LocalAlignment, and the ridge solvers already follow it. The inference family was the gap.Change
Adds
progress_bar: bool = Falseacross the family, and replaces fourteen hand-rolled tqdm sites with two shared helpers inalgorithms/inference/utils.py:make_progress_barreturns a_NullProgressBarwhen disabled, so call sites that drive a bar via.update()need no branching. Both import tqdm lazily, keeping it off the import path when unused.Threaded through both layers, since
nltools.statsre-exports thin wrappers (they translateparallel=→device=) that are distinct function objects from the algorithm-layer functions — a wrapper accepting the kwarg is not the same as forwarding it:algorithms/inference: one_sample, two_sample, correlation, matrix, timeseries, bootstrap (5 private helpers), iscnltools.stats: the five permutation wrappersAdjacency.similarity/.ttest,stats_label_distanceProgress bars are now off by default.
isc_permutation_testandisc_group_permutation_testpreviously defaulted toprogress_bar=Trueand now default toFalse; every other function in the family previously had no way to disable its bar.Pass
progress_bar=Trueto restore the old output.The alternative — defaulting to
Trueto preserve behavior — was considered and rejected: it would contradict the convention table and leave the family internally inconsistent. A library shouldn't write to stderr unasked.Tests
New
nltools/tests/core/test_inference/test_progress_bar.py, parametrized over both layers, asserting that:False,progress_bar=Truestill produces a bar — so the knob is a real toggle, not a silent no-op.Point 3 caught a real bug during development: in
correlation.pyI initially insertedprogress_barbeforesingle_featurein a helper signature, and the positional dispatch call silently shiftedsingle_feature's value into it.tycaught a second one — an un-threadedprogress_barin the correlation GPU batching path, which no test could reach without CUDA.uv run poe lint— clean (ruff + ty)uv run poe test— 1702 passed🤖 Generated with Claude Code
https://claude.ai/code/session_01SSqvVqrZ7ZPXdQHH3Hivkr