LittlePlyResearch notebook

curriculum_policy.py

python · 125 lines · Exact bytes

Line 1 ↗
Version & provenance · data-cm7

Recorded SHA-256 e0b59bec910793aa2187338c924ff130ed0d9195ccfcbac624d7386e26d38c16Rendered SHA-256 e0b59bec910793aa2187338c924ff130ed0d9195ccfcbac624d7386e26d38c16

Apache-2.0 original project code; dependencies retain their own licenses. License notes. Line links are stable within this snapshot. CM-7 completed implementation source; original and public rendered hashes recorded. Host-specific paths use /work placeholders; inspect reproduction instructions before running. Separate Rust helper retains its GPL license.

1"""Offline preflight and deterministic selection policy. No downloads or training.
2
3Candidates are independently validated metadata, not raw FENs. This module does
4not compute legal moves, exact labels, repetition, or clock-aware outcomes.
5"""
6from collections import Counter
7from hashlib import sha256
8import re
9
10TABLE_CAP = 1_200_000_000
11TASK_CAP = 2_000_000_000
12FREE_RESERVE = 50_000_000_000
13SERIALIZED_TOKENS = 123
14MODEL_INPUT_TOKENS = 122
15TABLE_NAME = re.compile(r"K[QRBNP]*vK[QRBNP]*\.rtb[wz]\Z")
16
17
18def preflight(files, *, host_free, native_free, task_bytes, reusable=None):
19    """Require exact sizes for entire plan BEFORE any table payload download.
20
21    reusable maps verified existing filenames to sizes. The caller must hash and
22    verify them first. A failed/partial file is NOT reusable and counts as scratch.
23    The 1.2 GB ceiling applies conservatively to the complete table set.
24    """
25    reusable = reusable or {}
26    if any(type(v) is not int or v < 0 for v in
27           (host_free, native_free, task_bytes)):
28        raise ValueError('both filesystem free-space measurements are required')
29    names = set()
30    table_bytes = 0
31    missing_bytes = 0
32    for item in files:
33        name, size = item['name'], item['bytes']
34        if not TABLE_NAME.fullmatch(name):
35            raise ValueError('unsupported table name')
36        pieces = len(name.split('.')[0].replace('v', ''))
37        if not 3 <= pieces <= 5:
38            raise ValueError('only 3-5 pieces allowed')
39        if name in names or type(size) is not int or size <= 0:
40            raise ValueError('duplicate file or unknown size')
41        names.add(name)
42        table_bytes += size
43        if name in reusable:
44            if reusable[name] != size:
45                raise ValueError('reuse size mismatch')
46        else:
47            missing_bytes += size
48    if not names:
49        raise ValueError('empty table manifest')
50    if table_bytes > TABLE_CAP:
51        raise ValueError('table payload cap exceeded')
52    if task_bytes + missing_bytes > TASK_CAP:
53        raise ValueError('total task cap exceeded')
54    # Even if ext4 already has space, conservatively reserve full VHD growth.
55    if min(host_free, native_free) - missing_bytes < FREE_RESERVE:
56        raise ValueError('50 GB checkpoint headroom reserve')
57    return dict(table_bytes=table_bytes, new_bytes=missing_bytes,
58                projected_task_bytes=task_bytes + missing_bytes,
59                host_free_after_worst_case=host_free-missing_bytes,
60                native_free_after=native_free-missing_bytes)
61
62
63def select(candidates, *, protected_keys, protected_games, limit,
64           family_cap, source_kind, protection_verified, seed='curriculum-v1'):
65    """Select only after verified historical protection and candidate validation.
66
67    position_key must be the existing codec key (board/turn/castling/legal EP,
68    clocks and history ignored). Terminal and castling positions cannot become
69    tablebase targets. Exact outcome/move sets require upstream rule-aware probes.
70    Unknown repetition remains explicit; never invent history from a FEN.
71    """
72    if not protection_verified:
73        raise ValueError('historical protection not verified')
74    if source_kind not in ('human_main', 'human_elite', 'opening_supported', 'tablebase_exact'):
75        raise ValueError('label provenance required')
76    if type(limit) is not int or limit < 0 or type(family_cap) is not int or family_cap < 1:
77        raise ValueError('invalid selection bound')
78    eligible = []
79    rejected = Counter()
80    for c in candidates:
81        reason = None
82        if c.get('source_kind') != source_kind:
83            reason = 'provenance'
84        elif not c.get('legal_verified') or c.get('terminal', True):
85            reason = 'illegal_or_terminal'
86        elif c['position_key'] in protected_keys or set(c['game_aliases']) & protected_games:
87            reason = 'heldout'
88        elif source_kind == 'tablebase_exact' and (
89            not 3 <= c.get('pieces', 0) <= 5 or c.get('castling', True)
90            or not c.get('clock_verified') or not c.get('all_children_probed')
91            or c.get('wdl') not in (-2, -1, 0, 1, 2)
92            or type(c.get('dtz')) is not int
93            or c.get('repetition_status') not in ('known_clear', 'unknown')
94            or not c.get('preserving_moves')
95        ):
96            reason = 'unverified_tablebase_semantics'
97        elif source_kind == 'opening_supported' and c.get('distinct_game_support', 0) < 3:
98            reason = 'insufficient_game_support'
99        if reason:
100            rejected[reason] += 1
101        else:
102            eligible.append(c)
103    # Prefer practical occurrence, then critical decisions. Seeded stable order
104    # prevents input scan order deciding ties; UCI ties remain in each record.
105    def priority(c):
106        critical = 0 < len(c.get('preserving_moves', [])) <= 2
107        special = bool(c.get('promotion') or c.get('stalemate_risk'))
108        support = min(c.get('distinct_game_support', 0), 100)
109        return (-int(c.get('from_real_game', False)), -int(critical),
110                -int(special), -support,
111                sha256((seed + ':' + c['position_key']).encode()).hexdigest(),
112                str(c.get('candidate_id', '')))
113    selected, seen, families = [], set(), Counter()
114    for c in sorted(eligible, key=priority):
115        if c['position_key'] in seen:
116            rejected['duplicate'] += 1
117            continue
118        if len(selected) >= limit or families[c['material_family']] >= family_cap:
119            rejected['selection_cap'] += 1
120            continue
121        seen.add(c['position_key'])
122        families[c['material_family']] += 1
123        selected.append(c)
124    return selected, dict(rejected)
125