Skip to content

Commit ec2563c

Browse files
committed
sudoku details
1 parent 2ead8f1 commit ec2563c

2 files changed

Lines changed: 126 additions & 1 deletion

File tree

src/xlm/datamodule.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,10 +877,13 @@ def _preprocess(
877877
if self.columns_to_keep:
878878
columns_to_remove = [col for col in ds.column_names if col not in self.columns_to_keep]
879879
if self.columns_to_remove: columns_to_remove.extend(self.columns_to_remove)
880+
# HF datasets rejects num_proc=0; None means single-process map. Dump
881+
# runs set num_dataset_workers=0 for RAM; that must not propagate as 0 here.
882+
map_num_proc = num_proc if num_proc is not None and num_proc > 0 else None
880883
ds = ds.map(
881884
preprocess_fn,
882885
batched=False,
883-
num_proc=num_proc,
886+
num_proc=map_num_proc,
884887
fn_kwargs=fn_kwargs,
885888
remove_columns=columns_to_remove,
886889
)

src/xlm/tasks/sudoku_extreme.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,125 @@ def sudoku_extreme_preprocess_fn(
7474

7575
def sudoku_extreme_kaggle_filter_fn(example: Dict[str, Any]) -> bool:
7676
return example["source"] == "puzzles0_kaggle"
77+
78+
79+
# ---------------------------------------------------------------------------
80+
# Hard-tier filters for the Sudoku qualitative study.
81+
#
82+
# Used as ``datamodule.dataset_managers.val.infill_prediction.filter_fn=...`` to
83+
# bias the dump pool toward the long tail of difficulty without dumping the
84+
# whole 422k-puzzle test split. The filters live here (rather than in
85+
# ``doublebackprop``) so that they're discoverable from
86+
# ``xlm.utils.module_loading.get_function`` like the existing ``kaggle`` filter.
87+
# ---------------------------------------------------------------------------
88+
89+
# Strategies above the "single" / "candidate-line" baseline. Any of these
90+
# appearing in ``strategies_used`` means the timvink solver had to do real
91+
# heuristic work; if "Brute Force" is also present the puzzle additionally
92+
# required search/lookahead.
93+
_NON_TRIVIAL_STRATEGIES: frozenset[str] = frozenset({
94+
# Advanced
95+
"Naked Pair", "Naked Pairs",
96+
"Naked Triple", "Naked Triples",
97+
"Naked Quad", "Naked Quads",
98+
"Hidden Pair", "Hidden Pairs",
99+
"Hidden Triple", "Hidden Triples",
100+
"Hidden Quad", "Hidden Quads",
101+
# Master
102+
"X-Wing", "X-Wings",
103+
"Swordfish",
104+
"Jellyfish", "Jellyfishes",
105+
"Forcing Chain", "Forcing Chains",
106+
})
107+
108+
109+
def _strategies_set(example: Dict[str, Any]) -> set:
110+
s = example.get("strategies_used")
111+
if s is None:
112+
return set()
113+
try:
114+
return {str(x).strip() for x in s}
115+
except TypeError:
116+
return set()
117+
118+
119+
def sudoku_extreme_hard_filter_fn(example: Dict[str, Any]) -> bool:
120+
"""Default 'hard' filter: keep puzzles in the top decile by difficulty.
121+
122+
A puzzle qualifies as hard if any of:
123+
124+
- ``rating >= 50``: timvink solver's tiered difficulty score (p90 of the
125+
test split is ~51, p95 is ~64). Catches the heavy brute-force tail.
126+
- ``num_steps >= 8``: long heuristic chains (p90 of the test split is 7).
127+
- any strategy in ``_NON_TRIVIAL_STRATEGIES``: Advanced or Master tier in
128+
the corrected tier mapping (catches the ~6k Advanced + ~225 Master
129+
puzzles that were silently bucketed as BruteForce in the original sweep).
130+
131+
Together these cover ~13% of the test split (vs. ~1.5% for tier-only and
132+
~10% for rating-only); good for a dump pool that's small but not trivial.
133+
"""
134+
rating = example.get("rating")
135+
if rating is not None and rating >= 50:
136+
return True
137+
num_steps = example.get("num_steps")
138+
if num_steps is not None and num_steps >= 8:
139+
return True
140+
if _strategies_set(example) & _NON_TRIVIAL_STRATEGIES:
141+
return True
142+
return False
143+
144+
145+
def sudoku_extreme_extreme_filter_fn(example: Dict[str, Any]) -> bool:
146+
"""'Extreme' filter: top ~1% of the test split by rating *or* tier.
147+
148+
A puzzle qualifies as extreme if any of:
149+
150+
- ``rating >= 100``: ~1% of the split (p99 = 100).
151+
- ``num_steps >= 12``: ~1% (p99 = 12).
152+
- any Master-tier strategy (X-Wing / Swordfish / Jellyfish / Forcing Chain).
153+
154+
Designed for a small but pure hard slice when the goal is to localize
155+
where loopholing+BPTT actually starts to separate from StopGrad.
156+
"""
157+
_MASTER = frozenset({
158+
"X-Wing", "X-Wings",
159+
"Swordfish",
160+
"Jellyfish", "Jellyfishes",
161+
"Forcing Chain", "Forcing Chains",
162+
})
163+
rating = example.get("rating")
164+
if rating is not None and rating >= 100:
165+
return True
166+
num_steps = example.get("num_steps")
167+
if num_steps is not None and num_steps >= 12:
168+
return True
169+
if _strategies_set(example) & _MASTER:
170+
return True
171+
return False
172+
173+
174+
def sudoku_extreme_deduction_only_filter_fn(example: Dict[str, Any]) -> bool:
175+
"""'Deduction-only hard' filter: Advanced/Master strategies *and* no Brute Force.
176+
177+
A puzzle qualifies if it satisfies BOTH:
178+
179+
1. ``"Brute Force"`` is **not** in ``strategies_used`` (excludes the ~358k
180+
BruteForce-tier puzzles entirely; we don't expect a forward-only diffusion
181+
model to do recursive backtracking).
182+
2. ``strategies_used`` contains at least one Advanced or Master tier strategy
183+
(Naked/Hidden Pairs/Triples/Quads, X-Wing/Swordfish/Jellyfish/Forcing Chain).
184+
185+
This is the "real difficulty axis" cohort: ~6,190 puzzles in the test split
186+
(5,965 Advanced + 225 Master), so a 2,000-puzzle uniform-shuffled slice will
187+
contain ~1,930 Advanced + ~70 Master in expectation -- enough power to put a
188+
tight CI on the BPTT-vs-StopGrad paired delta separately for each tier.
189+
190+
Sister filters (`sudoku_extreme_hard_filter_fn`, `_extreme_filter_fn`) use
191+
OR over rating/num_steps/strategy, which pulls in lots of long-but-easy
192+
BruteForce-tail puzzles. This one uses AND so the cohort is *only* the
193+
deduction-needed puzzles.
194+
"""
195+
s = _strategies_set(example)
196+
if "Brute Force" in s:
197+
return False
198+
return bool(s & _NON_TRIVIAL_STRATEGIES)

0 commit comments

Comments
 (0)