509 max_work: Any =
None,
511 """Return what ``holdout`` recorded for the best vectors of ``selection``.
513 ``selection`` (source 0) and ``holdout`` (source 1) are each whatever
514 ``_rows.read_source`` takes: a path to a complete result JSON or to a rows JSONL, a
515 parsed result dict or a parsed list of rows. ``metric`` is ``"objective"`` or the
516 exact name of a recorded metric, ``direction`` is ``"maximize"`` or ``"minimize"``
517 (``None`` only for ``"objective"``, and then only when both sources record the same
518 direction) and ``k`` is a positive integer, clipped to the eligible selection
519 vector count. ``space`` is used only when no row and no result records one, and
520 ``max_work`` is a positive integer bound on the admission figure; both default to
523 Returns the ``pineforge-hpo-holdout/v1`` document (see the module text). Raises
524 ``HoldoutError`` for every refusal; nothing is returned for a refused call.
526 _check_args(metric, k, direction, space, max_work)
528 selection_source = read_source(selection, 0)
529 except RowsError
as error:
530 raise _from_rows_error(error, 0)
from None
532 holdout_source = read_source(holdout, 1)
533 except RowsError
as error:
534 raise _from_rows_error(error, 1)
from None
536 shared_space = common_space([selection_source, holdout_source], space)
537 except RowsError
as error:
538 raise _from_rows_error(error)
from None
539 used_direction = _direction_used(direction, selection_source, holdout_source)
540 for source
in (selection_source, holdout_source):
542 check_context(source.rows)
543 except RowsError
as error:
544 raise _from_rows_error(error)
from None
546 _ancestor_ids(selection, selection_source),
547 _ancestor_ids(holdout, holdout_source),
549 selection_ids = {row.trial_id
for row
in selection_source.rows}
550 repeated = sorted(t
for t
in ancestors[1]
if t
in selection_ids)
553 "holdout_ancestor_rows",
554 "the holdout source repeats a trial id of the selection source among its "
555 "ancestor rows, so it may be reporting the selection study's own rows "
556 f
"(selection ancestor rows {len(ancestors[0])}, holdout ancestor rows "
557 f
"{len(ancestors[1])})",
558 [(1, trial_id)
for trial_id
in repeated],
560 ordered = [(0, row)
for row
in selection_source.rows]
561 ordered += [(1, row)
for row
in holdout_source.rows]
562 ordered.sort(key=
lambda item: (item[0], item[1].trial_id))
564 for index, row
in ordered:
566 keys[(index, row.trial_id)] = vector_key(row.parameters, shared_space)
567 except RowsError
as error:
569 _CODES_BY_KIND[error.kind],
571 [(index, row.trial_id)],
574 if metric !=
"objective":
575 for source
in (selection_source, holdout_source):
578 holders = [row
for row
in source.rows
if metric
in (row.metrics
or {})]
581 "holdout_metric_unknown",
583 f
"{_SOURCE_WORDS[source.index]} source records the metric",
587 for index, row
in ordered:
588 cells[(index, row.trial_id)] = _metric_cell(row, metric)
590 for index, row
in ordered:
591 where = (index, row.trial_id)
592 entries.append((index, row.trial_id, row.status, cells[where][0], keys[where]))
593 digest = _input_digest(entries)
594 excluded: dict = {0: {}, 1: {}}
595 eligible: dict = {0: [], 1: []}
596 for index, row
in ordered:
597 where = (index, row.trial_id)
598 if row.status !=
"ok":
599 reason = f
"status_{row.status}"
600 elif metric !=
"objective" and cells[where][1]
is None:
601 metrics = row.metrics
602 if metrics
is None or metric
not in metrics:
603 reason =
"metric_not_recorded"
605 reason =
"metric_unavailable"
607 eligible[index].append((row.trial_id, cells[where][1], keys[where]))
609 excluded[index].setdefault(reason, []).append(row.trial_id)
612 f
"{reason} {len(excluded[0][reason])}" for reason
in sorted(excluded[0])
614 if selection_source.rows:
616 "no eligible row remains in the selection source "
617 f
"({len(selection_source.rows)} rows read: {counts})"
620 detail =
"no eligible row remains in the selection source (no rows read)"
622 vectors: dict = {0: {}, 1: {}}
625 for trial_id, value, key
in sorted(eligible[index]):
626 groups.setdefault(key, []).append((trial_id, value))
627 conflicting = [key
for key, rows
in groups.items()
if _differs(rows)]
631 for trial_id, _, key
in eligible[index]
632 if key
in conflicting
635 "holdout_duplicate_vector",
636 "two eligible rows of one source share a parameter vector and disagree "
637 f
"on the metric value ({min(conflicting)})",
640 for key, rows
in groups.items():
641 vectors[index][key] = rows[0]
642 rows_read = len(selection_source.rows) + len(holdout_source.rows)
643 n_sel = len(vectors[0])
644 n_hol = len(vectors[1])
645 selection_pairs = n_sel * (n_sel - 1) // 2
646 holdout_pairs = n_hol * (n_hol - 1) // 2
647 work = rows_read + selection_pairs + holdout_pairs
648 if max_work
is not None and work > max_work:
650 "holdout_resource_exceeded",
651 f
"the admission figure {work} (rows read {rows_read} + {selection_pairs} "
652 f
"pairs of {n_sel} selection vectors + {holdout_pairs} pairs of {n_hol} "
653 f
"holdout vectors) is above max_work {max_work}",
655 selection_order = _best_first(_vector_items(vectors[0]), used_direction)
657 key: position + 1
for position, (_, _, key)
in enumerate(selection_order)
659 k_followed = min(k, n_sel)
660 pool = {key: vectors[1][key]
for key
in vectors[1]
if key
in vectors[0]}
661 pool_order = _best_first(_vector_items(pool), used_direction)
662 pool_ranks = _mid_ranks([item[1]
for item
in pool_order], used_direction)
664 item[2]: pool_ranks[position]
for position, item
in enumerate(pool_order)
666 cut = Fraction(len(pool) + 1, 2)
670 holder_ids: dict = {}
671 for index, row
in ordered:
673 holder_ids.setdefault(keys[(1, row.trial_id)], []).append(row.trial_id)
674 for _, value, key
in selection_order[:k_followed]:
675 trial_id = vectors[0][key][0]
677 holdout_trial_id, holdout_value = pool[key]
678 difference = holdout_value - value
679 if not math.isfinite(difference):
681 "holdout_value_invalid",
682 "the difference of a matched followed vector is not finite",
683 [(0, trial_id), (1, holdout_trial_id)],
685 if used_direction ==
"maximize":
686 improved = holdout_value > value
688 improved = holdout_value < value
691 "selection_trial_id": trial_id,
693 "selection_value": value,
694 "selection_rank": selection_rank[key],
695 "holdout_trial_id": holdout_trial_id,
696 "holdout_value": holdout_value,
697 "holdout_rank": _pair(holdout_rank[key]),
698 "difference": difference,
699 "improved": improved,
702 matched.append((holdout_rank[key], value, holdout_value))
704 holds = holder_ids.get(key, [])
707 "selection_trial_id": trial_id,
709 "selection_value": value,
710 "selection_rank": selection_rank[key],
711 "reason":
"holdout_row_not_eligible" if holds
else "absent_in_holdout",
712 "holdout_ids": holds,
717 xs = _mid_ranks([entry[1]
for entry
in matched], used_direction)
718 ys = _mid_ranks([entry[2]
for entry
in matched], used_direction)
721 xy = count * sum(x * y
for x, y
in zip(xs, ys)) - sum_x * sum_y
722 xx = count * sum(x * x
for x
in xs) - sum_x * sum_x
723 yy = count * sum(y * y
for y
in ys) - sum_y * sum_y
724 terms = {
"xy": _pair(xy),
"xx": _pair(xx),
"yy": _pair(yy)}
725 ranks = {
"x": [_pair(x)
for x
in xs],
"y": [_pair(y)
for y
in ys]}
728 undefined =
"fewer_than_three_matched"
731 undefined =
"constant_selection_ranks"
734 undefined =
"constant_holdout_ranks"
736 root = math.sqrt(float(Fraction(xy * xy, xx * yy)))
737 spearman = root
if xy > 0
else (-root
if xy < 0
else 0.0)
740 terms = {
"xy":
None,
"xx":
None,
"yy":
None}
741 ranks = {
"x":
None,
"y":
None}
743 undefined =
"no_matched_vectors"
744 better = sum(1
for mid, _, _
in matched
if mid <= cut)
746 for key, (only_trial_id, _)
in vectors[1].items():
747 if key
not in vectors[0]:
748 holdout_only.append(only_trial_id)
767 "contract": CONTRACT,
770 "direction": used_direction,
772 "k_followed": k_followed,
775 "selection": documents[0],
776 "holdout": documents[1],
777 "followed": followed,
778 "unmatched": unmatched,
782 "median_rank_cut": _pair(cut),
783 "holdout_only": {
"count": len(holdout_only),
"ids": holdout_only},
786 "spearman": spearman,
787 "spearman_terms": terms,
788 "spearman_ranks": ranks,
789 "spearman_undefined": undefined,
792 "denominator": count,
793 "value": better / count
if count
else None,
796 "algorithm": ALGORITHM,
797 "input_sha256": digest,
799 "rows_read": rows_read,
800 "selection_pairs": selection_pairs,
801 "holdout_pairs": holdout_pairs,
803 "max_work": max_work,
805 "interpretation": INTERPRETATION,