Read a doctor report¶
Goal: turn atune doctor's output into a decision about your search space.
A tuning run can finish cleanly — every trial completes, the best trial looks
reasonable — while still wasting most of its budget on a range that was
guessed wrong. Nothing about a successful exit code tells you that. atune
doctor reads a study's completed trials and says, in plain language, which
parameters are probably costing you budget and what to do about each one. Run
it by hand after a study you are about to reuse, or on a schedule against a
study that keeps growing, so a bad range gets caught the morning after rather
than the week after.
The findings¶
| Finding | What it means | What to do |
|---|---|---|
| Boundary pressure | the good trials crowd one edge of a declared range | widen that side, then re-run — or declare the bound open so the next study widens it itself |
| Scale mismatch | the good trials fit the other scale (linear vs. logarithmic) far better than this one | switch scales by hand — never automated, because flipping a parameter's log flag is a different distribution as far as the study is concerned, and starts that parameter's history over (see When a space is wrong) |
| Inert | the parameter's PED-ANOVA importance is negligible on every objective | fix it to a good trial's value and spend the freed dimension elsewhere |
| Interior | the good trials cluster well inside the range, clear of both edges | optional: narrow to where they actually land |
| Growth stopped | a side this study declared open stopped growing, and the finding names which brake fired (the limit, the expansion cap, the failure veto, the sign barrier, or the type's edge) | judge the brake: a reached limit may deserve a wider one next study; a failure veto means the wider region genuinely lost |
| Sampler mismatch | the study's configured sampler no longer fits its realised shape and budget | switch samplers on the next run |
| Budget starved | fewer completed trials than the configured sampler wants before its model is trustworthy | run more trials, or tune fewer parameters at once |
GrowthStopped appears when an observer has the study's persisted open
declaration. On a braked side, BoundaryPressure stands down rather than
contradicting it, so you are never told both "widen this side" and "this side
stopped on purpose". The native doctor, HTML report, and native GUI read that
policy from the same hydrated snapshot. Raw StudyView callers and synthetic
fixtures without a persisted policy remain policy-free.
Below eight completed trials there is not enough evidence to say anything, and
atune doctor reports that plainly instead of guessing from noise. Inert
additionally needs at least thirty-two completed trials before it will flag a
parameter — an importance estimate over a handful of trials is itself noise,
and calling a parameter useless on that basis would be the worst kind of wrong
advice.
A worked example¶
The study below tunes one parameter whose true optimum sits at the edge of the range you would naturally guess:
$ atune run --study demo.atj --trials 24 --seed 3 --sampler tpe \
> --param 'x=uniform(0,100)' \
> -- python3 -c 'import sys; x=float(sys.argv[1]); print((100-x)**2)' {x}
atune run: study `demo` (new), 24 trials on 1 worker(s), sampler tpe
atune run: done — 24 trial(s): 24 complete, 0 pruned, 0 failed
best trial #18: f = 0.0017210245458255408
x = 99.95851476713545
$ atune doctor --study demo.atj --sampler tpe
24 completed trial(s), 1 finding(s) that likely cost you budget:
! x
x's upper bound (uniform(0, 100)) is crowded: 100% of the top 6 trial(s) sit in the outer band, including the best trial
Fix: widen x's upper bound and re-run — the suggested range roughly doubles the span on that side
Try: x=uniform(0, 200)
Every one of the top six trials, including the best one, recorded a value in
the outer band of x's declared range — a pressure of 1.0, the maximum. That
is the signature of a range that ends before the objective does, not of an
optimum that genuinely sits at a round number. The Try: line is paste-ready:
it doubles the span on the crowded side in the parameter's own coordinates (a
log range doubles its ratio, a stepped range grows by whole steps), and you
can hand it straight back to --param. When no safe replacement can be
computed — an overflowing integer range, say — the finding keeps the prose
advice and simply omits the line.
If pasting that line is a loop you would rather not run yourself, it has a
standing form: declare the bound open
(x=uniform(0, 100, open=up)) and the study widens it during the run, on
the same evidence this finding reads — the doctor then reports why a side
stopped growing rather than asking you to move it.
A clean study prints something shorter. Rerun the same idea with the optimum placed well inside the range instead of at its edge, and there is nothing to report:
$ atune doctor --study clean.atj --sampler tpe
20 completed trial(s) checked: nothing looks like it is costing you budget
Reading it by script¶
The exit status alone is enough for a nightly sweep: atune doctor exits 0
when there is nothing to report and 3 when at least one finding is
reported — deliberately distinct from the binary's usual 2 (usage error)
and 1 (fault) — so a CI job fails on a pressed bound with no parsing at all:
atune doctor --study nightly.atj --sampler tpe # non-zero exit fails the job
When you want the details too, --format json is a stable, jq-friendly
shape — the same fields either way:
$ atune doctor --study demo.atj --sampler tpe --format json | jq '.findings[] | {parameter, kind: .rule.kind}'
{
"parameter": "x",
"kind": "BoundaryPressure"
}
Each finding is {parameter, message, fix, rule}, and every measurement a
rule based its finding on lives inside rule, tagged by kind — for a
boundary finding that is .rule.side ("Low"/"High") and .rule.pressure
(0.0–1.0), exactly as the jq above reads kind — so a script can act on
the number rather than parsing the sentence. The Rust types
Finding, Fix and Rule are the shape's definition.
Naming the sampler¶
Sampler mismatch and budget starved only mean anything relative to the sampler
the study is actually using, and a journal file never records that: a sampler
is something your process chooses at runtime, not data the study carries. Pass
--sampler when you know it (--sampler tpe, --sampler grid, and so on);
leave it unset and those sampler-dependent findings are silently skipped rather
than guessed, while the other applicable findings are unaffected.
No declared space required¶
atune doctor reads only the study's recorded trials, so it works exactly the
same on a define-by-run study, a declared one, or a study with no atune-native
declaration at all — including one you have just imported from Optuna:
$ atune export --study demo.atj --optuna optuna.log
$ atune import optuna.log --storage imported.atj
imported 1 study and 24 trials from `optuna.log` into `imported.atj`
... # import also prints its timestamp/unfinished-trials note, elided here
$ atune doctor --study imported.atj
24 completed trial(s), 1 finding(s) that likely cost you budget:
! x
x's upper bound (uniform(0, 100)) is crowded: 100% of the top 6 trial(s) sit in the outer band, including the best trial
Fix: widen x's upper bound and re-run — the suggested range roughly doubles the span on that side
Try: x=uniform(0, 200)
No flags beyond --study, and no --sampler either, because an imported study
genuinely has no recorded answer to "which sampler made this" — sampler
mismatch and budget starved simply do not appear, and the boundary finding is
unaffected. Two commands after leaving Optuna, you have actionable output.
What doctor does not do¶
Every finding is advice, not an action: nothing about running atune doctor
changes what the study will do next, and nothing it reports is applied for
you. Widening a range and re-running starts a new study unless you seed it
from the old one — from Rust, atune::transfer::seed_study carries a prior
study's completed trials across exactly this kind of edit, so widening a
bound does not mean re-paying for trials you already ran.
Scale mismatch stays advice permanently, on purpose, not only until a future release: flipping a parameter between linear and logarithmic is a different distribution, and the study cannot carry the old history across the change automatically without silently discarding it.
Every transcript above really ran, against a study built for this page. Unlike
Tune any program, this page is not re-checked by the
test suite on every change, so treat the exact wording of a message as
illustrative — it may be rephrased between releases. The --format json
shape it renders is the part held to a stable-contract standard, not the
prose.
Where to go next¶
| To | Read |
|---|---|
Look up every atune doctor flag |
CLI reference |
| Understand what "crowds a bound" means underneath | Reading a boundary finding |
| Move a study in from Optuna in the first place | Interoperate with Optuna |
| Read the rest of a study's trials, not just the findings | Inspect a study |
| Add more trials to a study before or after acting on a finding | Resume and scale |