Dedup and completion#
Closed beta (nothing installed): the same completion over plain HTTP is
exhaustion_rest.py: the ask that
drains the space answers 409 space_exhausted with the proof in
details.dedup, and the script reads that answer as completion.
Dedup is the engine default#
Since 0.3.0 every ask refuses to suggest a point that is already observed, already open, or already in the current batch. Equality is exact and canonical: the active assignments, with integers normalized to integers, doubles to floats (negative zero folded), and categories to strings, serialized as compact JSON with sorted keys. Near-duplicates are not collapsed.
The default is on because re-evaluating a known point of a deterministic objective spends budget to learn nothing, and a strategy quietly returning the incumbent on a flat acquisition surface is the failure mode most likely to look like progress.
The universe#
The exclusion universe is observed points (failed ones included), open
suggestions, and whatever the current batch has already selected. Dismissed suggestions leave it, so a declined point
may come back later. Every receipt’s dedup record carries collision_count,
the repair counters, and per_draft_origin.
Repair belongs to the strategy that collided. The sequence rungs advance
their stream, examining at most four candidates per requested point; the
Gaussian-process (GP) rungs walk their already-evaluated restart pool, then try up to four bounded
re-optimizations per slot with the collision excluded (gp_nehvi walks its
pool only). A rung whose repair runs dry keeps what it found, and the
fallback ladder fills the rest with the exclusions carried down.
The completion ladder#
On a finite space the ask boundary (the layer between the strategy and the caller that enforces dedup and intents) can prove there is nothing left. When every axis is finite and the raw product of axis sizes is at most 1,024, it enumerates the feasible universe in a fixed order, fills remaining slots from it, and proves exhaustion if the walk ends with slots still empty. Beyond that bound, an unconditional, unconstrained all-finite space gets a cardinality certificate: distinct on-axis keys equal to the raw product prove exhaustion without enumeration.
Proven exhaustion is a completion state rather than a failure. Both drivers answer with a 409
of type space_exhausted, raised as SpaceExhausted – an
OptimizationComplete and an ApiError at once, so one clause catches the
terminal state while older handlers keep working. A shortfall without proof
(repair ran dry on a continuous or large space) is a plain 409
unique_candidate_unavailable and never completion: a same-state re-ask
returns the same answer, so observe or dismiss pending work first.
The completion ladder, for a one-point ask with no duplicate intents. (1) The unique search ran down every rung, repair included, and the slot is still empty. (2) When every axis is finite and the raw product of the axis sizes is at most 1,024, the boundary walks the feasible universe in a fixed order and an untaken point fills the slot; a walk that ends with the slot still empty is the enumeration proof. (3) Beyond that bound, an all-finite space with no conditions and no constraints gets the cardinality certificate: the distinct taken points on the axes are counted, and a count equal to the raw product is the proof. (4) Any other space, or a count that falls short, has no proof. (5) A proof is a 409 space_exhausted on both drivers, raised as SpaceExhausted, an OptimizationComplete and an ApiError at once: the optimization is complete, so read the best point or continue through an authorized duplicate intent. (6) No proof is a plain 409 unique_candidate_unavailable, never completion; a same-state re-ask returns the same answer, so observe or dismiss pending work before you ask again.#
from tauopt import Client, OptimizationComplete
client = Client(driver="local")
exp = client.create_experiment(
name="tiny",
parameters=[
{"name": "kernel", "type": "categorical", "categorical_values": ["rbf", "linear"]},
{"name": "degree", "type": "integer", "bounds": [1, 2]},
],
metric={"name": "score", "objective": "maximize"},
)
for i in range(4):
sug = client.suggest(exp.id)
client.observe(exp.id, suggestion_id=sug.id, value=float(i))
d = sug.provenance.dedup
print(sug.assignments, sug.provenance.strategy, d["per_draft_origin"], d["collision_count"])
try:
client.suggest(exp.id)
except OptimizationComplete as e:
d = e.details["dedup"]
print(type(e).__name__, e.status_code, e.error_type, d["exhaust_proof"], d["proof_method"], d["universe_size"])
{'kernel': 'linear', 'degree': 2} sobol ['sobol'] 0
{'kernel': 'rbf', 'degree': 1} sobol ['sobol'] 0
{'kernel': 'rbf', 'degree': 2} sobol ['sobol'] 0
{'kernel': 'linear', 'degree': 1} sobol ['sobol'] 0
SpaceExhausted 409 space_exhausted True enumeration 4
The proof receipt#
The proof rides details.dedup on the error, or the receipt when an ask
proved exhaustion while granting duplicates. exhaust_proof is the
verdict, proof_method names enumeration or cardinality,
universe_size counts the feasible points, and the cardinality method adds
matched, which must equal that size.
Turning dedup off#
dedup: false is the per-config escape hatch: the ask returns exactly the
requested number of drafts (candidate points) with no boundary, no proof,
and no dedup record,
as the last line above shows. It also refuses intents, since a grant
authorizes passage through a refusal that no longer exists. Reach for it
when you would rather manage repeats yourself; otherwise the intents path
keeps the accounting on the receipt. The field reference is in
strategies and config.
Do it: Use duplicate intents.