summaryrefslogtreecommitdiff
path: root/annotate_pgns/quiet_common.py
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-15 13:11:22 -0700
committerScott Gasch <[email protected]>2026-10-15 13:11:22 -0700
commit993b6eeb2a9ac7b659c473b6883fa53af1ad3a0b (patch)
treec5388824dec15a7691ce9952a6eb4af66e837127 /annotate_pgns/quiet_common.py
parent53b380c658f828c4c0e096be2763e6b012c23abd (diff)
Phase A NNUE: feature-indexing spec, annotation/training pipeline, dump nnuefeatures
Builds out the NNUE Phase A prototype per docs/NNUE.md: - src/nnuefeatures.c: standalone HalfKAv2_hm feature-index reference implementation (new `dump nnuefeatures` command), sourced from Stockfish's half_ka_v2_hm.{h,cpp} (sf_16 tag), including the 0x88->64 square conversion typhoon's own board representation needs. - annotate_pgns/: sharded TWIC/self-play annotation harness (annotate.py), position extraction, quiet-position filtering (flat and game-shaped, plus an upstream --prefilter-quiet option), train/holdout game-level stratified split, WDL-sigmoid K fitting, the Python HalfKAv2_hm reference encoder (nnue_feature_spec.py) and its bit-for-bit cross-checker against the C side (cross_check_features.py), and a first PyTorch training pipeline (nnue_model.py, nnue_train.py, build_training_tensors.py) -- a 256x2-32-32-1-ish net trained locally on CPU against ~1.85M quiet TWIC positions as a pipeline/framework validation pass. - Finish an in-progress InitMagic/InitLMRTable/InitEval -> InitializeBitboardMagic/InitializeLMRTable/InitializeEval rename: chess.h's prototypes hadn't been updated to match the already-renamed definitions/call sites, leaving 4 implicit-function-declaration warnings. - tests/arasan.epd: new curated EPD suite. - docs/: add the project's design docs (previously untracked). Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'annotate_pgns/quiet_common.py')
-rw-r--r--annotate_pgns/quiet_common.py68
1 files changed, 68 insertions, 0 deletions
diff --git a/annotate_pgns/quiet_common.py b/annotate_pgns/quiet_common.py
new file mode 100644
index 0000000..77544ef
--- /dev/null
+++ b/annotate_pgns/quiet_common.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+"""Shared quiet-position predicate, per NNUE.md: exclude positions
+where the side to move is in check, or where a given move is an
+SEE-positive (>=50cp) capture.
+
+Used two ways by this pipeline, with a deliberately different choice
+of *which* move gets SEE-checked in each case:
+
+- Pre-search filtering (annotate.py): checked against the move actually
+ played in the source human game, since that's the only move known
+ before the engine has searched anything -- the whole point is to
+ skip the sd-N search entirely for positions that would be discarded
+ anyway.
+- Post-hoc filtering (quiet_filter.py, quiet_filter_games.py): checked
+ against the first move of the already-recorded sd-N search PV (the
+ engine's own considered-best reply), per NNUE.md's original filter
+ design. This remains the definition used for data already annotated
+ before pre-filtering existed, and is NOT the same training
+ distribution as the game-move variant above -- do not mix rows
+ produced under the two definitions into one training run without
+ accounting for that.
+
+Both call the same `position_is_quiet` predicate once a concrete move
+is in hand; only how that move is obtained differs.
+
+Reuses eval_tune/tune_eval_dna.py's existing static_exchange_eval() --
+a classic swap-list SEE (ignores pins/x-rays/discoveries, a
+documented, accepted simplification; see check_see_against_engine.py
+for a cross-check against the engine's real SEE()) -- rather than
+reimplementing it.
+"""
+import os
+import sys
+from pathlib import Path
+
+_EVAL_TUNE_DIR = Path(__file__).resolve().parent.parent / "src" / "eval_tune"
+sys.path.insert(0, str(_EVAL_TUNE_DIR))
+import chess # noqa: E402
+
+# tune_eval_dna imports dna_diff, which reads "../eval.c" relative to
+# the *current working directory* at import time, not relative to its
+# own file -- chdir there just for the import, then restore, so this
+# module can be imported from any directory.
+_cwd = os.getcwd()
+os.chdir(_EVAL_TUNE_DIR)
+try:
+ from tune_eval_dna import static_exchange_eval, has_hanging_material # noqa: E402,F401
+finally:
+ os.chdir(_cwd)
+
+SEE_THRESHOLD = 50 # matches has_hanging_material's own default
+
+
+def is_see_positive_capture(board, move, threshold=SEE_THRESHOLD):
+ """board is the position *before* move. Does not mutate board."""
+ if not board.is_capture(move):
+ return False
+ return static_exchange_eval(board, move) >= threshold
+
+
+def position_is_quiet(board, move, threshold=SEE_THRESHOLD):
+ """board is the position *before* move; move is whichever move the
+ caller has decided to SEE-check (played move or PV's first move --
+ see module docstring). True means: keep this position in the
+ training set."""
+ if board.is_check():
+ return False
+ return not is_see_positive_capture(board, move, threshold)