Source code for mixle.task.collapse
"""``collapse_monitor`` -- the shared collapse-detection utility for self-improvement loops.
Every self-improvement round claims to be getting better. This shared check evaluates that claim
without each loop reimplementing collapse detection: across rounds, the held-out verified score must
be non-decreasing, and the proposal diversity must not be shrinking. A loop that improves its score by
collapsing onto a few candidates is overfitting to the verifier, not genuinely improving.
verdict = collapse_monitor(history)
verdict.ok # True iff both checks hold across every round
verdict.reason # None, or "score_decreased" / "diversity_shrunk" (which check failed)
``history`` is one entry per round: a dict with the round's held-out verified score and its pool of
candidates, or a precomputed diversity number.
"""
from __future__ import annotations
import math
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass, field
from typing import Any
[docs]
def distinct_count_diversity(candidates: Sequence[Any]) -> float:
"""Diversity proxy: the number of distinct candidates (by ``str`` identity) in the round's pool."""
return float(len({str(c) for c in candidates}))
[docs]
def entropy_diversity(candidates: Sequence[Any]) -> float:
"""Diversity proxy: Shannon entropy (nats) of the candidate-frequency distribution in the round's pool."""
counts: dict[str, int] = {}
for c in candidates:
key = str(c)
counts[key] = counts.get(key, 0) + 1
n = sum(counts.values())
if n == 0:
return 0.0
return float(-sum((k / n) * math.log(k / n) for k in counts.values()))
[docs]
@dataclass
class CollapseVerdict:
"""The result of :func:`collapse_monitor`: ``ok`` plus which check failed, and the raw series."""
ok: bool
reason: str | None # None if ok; else "score_decreased" or "diversity_shrunk"
scores: list[float] = field(default_factory=list)
diversities: list[float] = field(default_factory=list)
failed_round: int | None = None # index of the first round where the failing check tripped
[docs]
def collapse_monitor(
history: Sequence[Mapping[str, Any]],
*,
score_key: str = "score",
candidates_key: str = "candidates",
diversity_fn: Callable[[Sequence[Any]], float] = distinct_count_diversity,
score_tol: float = 0.0,
diversity_tol: float = 0.0,
) -> CollapseVerdict:
"""Check a self-improvement round history for collapse: score non-decreasing and diversity not shrinking.
Each entry of ``history`` supplies the round's held-out verified score under ``score_key`` and either
its candidate pool under ``candidates_key`` (diversity computed via ``diversity_fn``) or, when
``candidates_key`` is absent, a precomputed diversity number directly under ``"diversity"``.
``score_tol``/``diversity_tol`` allow a small, explicitly-named amount of round-to-round noise before
a decrease/shrink counts as a real regression (0.0 = strict non-decreasing). The first round to violate
either check ends the scan -- ``reason`` names which check failed, ``failed_round`` where.
"""
scores: list[float] = []
diversities: list[float] = []
reason: str | None = None
failed_round: int | None = None
for i, round_ in enumerate(history):
score = float(round_[score_key])
if candidates_key in round_:
diversity = float(diversity_fn(list(round_[candidates_key])))
else:
diversity = float(round_["diversity"])
scores.append(score)
diversities.append(diversity)
if reason is None and i > 0:
if scores[i] < scores[i - 1] - score_tol:
reason, failed_round = "score_decreased", i
elif diversities[i] < diversities[i - 1] - diversity_tol:
reason, failed_round = "diversity_shrunk", i
return CollapseVerdict(
ok=reason is None, reason=reason, scores=scores, diversities=diversities, failed_round=failed_round
)
__all__ = ["CollapseVerdict", "collapse_monitor", "distinct_count_diversity", "entropy_diversity"]