D2: offline coordinate-ascent over per-arm fusion weights, scored as mean nDCG@k against graded relevance labels.
This is the exceed move. Hindsight applies one global fusion scheme to every bank; this learns a weighting per bank from that bank's own labelled data.
Why a retrieval metric rather than end-to-end answer accuracy: the LongMemEval n=30 answer-judge lane was measured to have hit its sensitivity ceiling (calibration Cluster B — a +52% observation gain came out eval-neutral). nDCG over graded labels removes the reader and the judge from the measurement entirely, so a fusion change registers as a fusion change instead of disappearing into reflect noise.
Everything here is pure and deterministic: no RNG, no DB, no network. Ascent visits arms in a fixed order and takes the first strictly-better value at each step, so the same inputs always yield the same weights.
DISCIPLINE. Ascent must be run on the TRAIN split only, and the resulting weights scored
once on the held-out split. ascend/3 has no access to a test set by construction — the
caller supplies whatever pools it is allowed to fit on.
Summary
Functions
Arm names present anywhere in the pools, sorted for determinism.
Coordinate-ascent over per-arm weights, maximising mean nDCG@k.
Mean nDCG@k over every query pool, ranked under fusion_opts.
Fold learned weights into a fusion opts keyword list.
Types
@type labels() :: %{required({String.t(), String.t()}) => non_neg_integer()}
Functions
Arm names present anywhere in the pools, sorted for determinism.
Coordinate-ascent over per-arm weights, maximising mean nDCG@k.
Returns the learned weights, the starting and final scores, and the number of rounds
actually taken. fusion_opts supplies the mode: weights only bite under :normalized
or :hybrid, so ascending with fusion: :rrf is a no-op by construction and reports a
flat score rather than pretending to have learned something.
@spec score([map()], labels(), keyword(), keyword()) :: %{ ndcg: float(), mrr: float(), n: non_neg_integer() }
Mean nDCG@k over every query pool, ranked under fusion_opts.
Queries with no labelled candidate are skipped rather than scored as 0.0 — an unlabelled query carries no information about a weighting, and counting it as a miss would let the metric be moved by label coverage instead of ranking quality.
Fold learned weights into a fusion opts keyword list.