Stop when the estimate is precise enough

Replace a guessed trial budget with a stated precision target.

A fixed trial budget has to be set in advance, and set for the least precise participant you can imagine — so everyone else is over-tested. If inference costs a fraction of a millisecond you can instead refit between trials and stop each participant when their own estimate is precise enough.

In simulation this saves about 74% of trials at a target of 0.35 on the posterior SD, and the saving grows as the target tightens. Nobody is measured less precisely; the saving comes entirely from the participants who never needed the full budget.

You can try this without writing anything: the live task runs exactly this loop in your browser.

  1. State the target

    A target is a statement about precision, not about trial count. Either a maximum posterior SD on some parameters, or a minimum confidence in a construct.

    import grintools as gt
    
    # stop when the correlations are known to within an SD of 0.35
    decision = gt.stop_on_precision(fit, sd_max=0.35, params=["rho"])
    decision.stop           # True / False
    print(decision.summary())
    #   stop = True  (combine='all')
    #       sd:rho_0         0.171 vs 0.350  [met]
    
    decision.checks         # one dict per target: value, threshold, met, reachable
    decision.blocked_by     # the checks that are not met yet
    
  2. Put it in the trial loop

    Accrue in blocks rather than single trials. The posterior barely moves on one response, and every stimulus needs a few observations before the fit means anything.

    import numpy as np, grintools as gt
    
    counts = np.zeros((4, 4), dtype=int)
    BLOCK, MIN_TRIALS, MAX_TRIALS = 16, 40, 400
    
    for block in range(MAX_TRIALS // BLOCK):
        for _ in range(BLOCK):
            s = present_random_stimulus()      # your experiment
            r = collect_response()             # your experiment
            counts[s, r] += 1
    
        n = counts.sum()
        if n < MIN_TRIALS:
            continue                           # too early to ask
    
        fit, con = gt.infer(counts)
        if gt.stop_on_precision(fit, sd_max=0.35, params=["rho"]).stop:
            print(f"target reached after {n} trials")
            break
    
  3. Always set a ceiling

    Some participants will not reach any given target, because the design does not contain the information — particularly for the correlations, and particularly at high accuracy. Without a maximum you are running an experiment with no defined end.

    Report the ceiling and how many participants hit it. A saving computed over a cohort where the slowest were quietly dropped is not a saving.

Two caveats worth knowing

Targets on correlations inherit a calibration issue. The correlation intervals run narrower than nominal, so a target on rho is met slightly sooner than it should be. Either pass calibrated=True / calibrated = TRUE, or set the target a little tighter than you actually want.

Stopping on a construct probability is different from stopping on precision. A probability near one half can mean the data are uninformative, and more trials will fix that — or that the true answer sits near a boundary, and they will not. A precision target is the safer default.

Next: steer stimulus levels into the informative range, or go back to all tutorials.