summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/chess.h23
-rwxr-xr-xsrc/eval_tune/match_play.py29
-rwxr-xr-xsrc/eval_tune/run_ecm.sh41
-rwxr-xr-xsrc/eval_tune/test_vs_head.sh2
-rwxr-xr-xsrc/main.c4
-rwxr-xr-xsrc/root.c56
-rwxr-xr-xsrc/run_tests.sh53
-rwxr-xr-xsrc/search.c32
-rwxr-xr-xsrc/split.c13
-rw-r--r--src/unix.c4
10 files changed, 135 insertions, 122 deletions
diff --git a/src/chess.h b/src/chess.h
index 062e3dc..5e1d4a5 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -162,7 +162,7 @@ typedef struct _DLIST_ENTRY
#endif
#ifndef CONTAINING_STRUCT
#define CONTAINING_STRUCT(address, type, field) \
- ((type *)((BYTE *)(address) - (OFFSET_OF(field, type))))
+ ((type *)((BYTE *)(address) - (BYTE *)(OFFSET_OF(field, type))))
#endif
#define WHITE (1)
@@ -189,7 +189,6 @@ typedef struct _DLIST_ENTRY
#define THREE_PLY 192
#define FOUR_PLY 256
#define MAX_DEPTH_PER_SEARCH (MAX_PLY_PER_SEARCH * ONE_PLY)
-#define MAX_EXTEND_PER_LINE (MAX_PLY_PER_SEARCH * ONE_PLY / 2)
#define IS_VALID_DEPTH(x) (((x) >= 0) && \
((x) <= MAX_DEPTH_PER_SEARCH) && \
@@ -521,7 +520,7 @@ typedef union _MOVE
((mv).uMove & 0x0FF00000)
#define IS_ENPASSANT(mv) \
- (IS_SPECIAL_MOVE(mv) && (mv.pCaptured) && !IS_PROMOTION(mv))
+ (IS_SPECIAL_MOVE(mv) && ((mv).pCaptured) && !IS_PROMOTION(mv))
#define IS_DOUBLE_JUMP(mv) \
(IS_SPECIAL_MOVE(mv) && !IS_CAPTURE_OR_PROMOTION(mv))
@@ -1206,7 +1205,13 @@ _assert(CHAR *szFile, ULONG uLine);
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
-#ifdef _X86_
+#if defined(_X86_) || defined(_X64_)
+//
+// Note: these are 32-bit int bit tricks, not actually x86-specific --
+// they only require sizeof(int) == 4, which still holds under the LP64
+// data model used by 64-bit (_X64_) builds. Gated on both so a SIXTYFOUR
+// build (which defines _X64_, not _X86_) still gets the branchless
+// versions instead of falling through to the plain-C fallbacks below.
//
// Note: MAXU, MINU and ABS_DIFF require arguments with the high order
// bit CLEAR to work right.
@@ -1236,7 +1241,7 @@ _assert(CHAR *szFile, ULONG uLine);
#define ABS_DIFF(a, b) \
(((b)-(a)) - ((((b) - (a)) & (((int)((b) - (a))) >> 31) ) << 1))
-#endif // _X86_
+#endif // _X86_ || _X64_
#ifndef MINU
#define MINU(x, y) (MIN((x), (y)))
@@ -1255,7 +1260,7 @@ _assert(CHAR *szFile, ULONG uLine);
#endif
#ifndef ABS_DIFF
-#define ABS_DIFF(a, b) (abs((int)(a) - (int)(b)))
+#define ABS_DIFF(a, b) (abs((a) - (b)))
#endif
#define FILE_DISTANCE(a, b) (ABS_DIFF(FILE((a)), FILE((b))))
@@ -1746,14 +1751,8 @@ void
SlidePiece(POSITION *pos, COOR cFrom, COOR cTo);
void
-SlidePieceWithoutSigs(POSITION *pos, COOR cFrom, COOR cTo);
-
-void
SlidePawn(POSITION *pos, COOR cFrom, COOR cTo);
-void
-SlidePawnWithoutSigs(POSITION *pos, COOR cFrom, COOR cTo);
-
PIECE
LiftPiece(POSITION *pos, COOR cSquare);
diff --git a/src/eval_tune/match_play.py b/src/eval_tune/match_play.py
index 6891964..9591330 100755
--- a/src/eval_tune/match_play.py
+++ b/src/eval_tune/match_play.py
@@ -367,6 +367,18 @@ def elo_to_score(elo):
return 1.0 / (1.0 + 10.0 ** (-elo / 400.0))
+def sprt_bar(llr, la, lb, width=9):
+ """Render an ASCII gauge of where `llr` sits between the H0 (`la`) and
+ H1 (`lb`) SPRT bounds, e.g. '|---------|V---------|' with V marking
+ the rounded llr position."""
+ frac = 0.5 if lb == la else (llr - la) / (lb - la)
+ frac = min(max(frac, 0.0), 1.0)
+ slot = round(frac * (2 * width))
+ bar = "|" + "-" * width + "|" + "-" * width + "|"
+ idx = slot + 1
+ return bar[:idx] + "V" + bar[idx:]
+
+
class Sprt:
"""Sequential Probability Ratio Test for engine-vs-engine gating, same
formulation fishtest/cutechess-cli use for exactly this problem: two
@@ -602,17 +614,16 @@ def main():
avg_game_sec = sum(game_durations) / len(game_durations)
eta_sec = (avg_game_sec *
max(len(jobs) - done, 0)) / args.workers
+ score = candidate_points / done
sprt_note = ""
if sprt is not None:
- sprt_note = (f" llr={sprt.llr():+.2f} "
- f"(H0<={sprt.la:.2f} "
- f"H1>={sprt.lb:.2f})")
- print(f" {done} games played "
- f"(score, in submission order through game "
- f"{next_report_idx}: "
- f"{reported_points/max(reported_count,1):.3f}) "
- f"avg={avg_game_sec:.1f}s/game "
- f"ETA={eta_sec/60:.1f}min{sprt_note}",
+ sprt_note = (f": llr={sprt.llr():+.2f}, "
+ f"H0={sprt.la:+.2f}"
+ f"{sprt_bar(sprt.llr(), sprt.la, sprt.lb)}"
+ f"H1={sprt.lb:+.2f}")
+ print(f" {done} games: -{losses} ={draws} +{wins} "
+ f"({avg_game_sec:.1f}s avg, eta={eta_sec/60:.0f}min, "
+ f"score={score:.3f}){sprt_note}",
file=sys.stderr)
if sprt is not None and sprt_decision is None:
diff --git a/src/eval_tune/run_ecm.sh b/src/eval_tune/run_ecm.sh
deleted file mode 100755
index a6c1284..0000000
--- a/src/eval_tune/run_ecm.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/sh
-# Run the ECM tactical suite at a fixed search depth (not fixed time --
-# see CLAUDE.md: st introduces machine-load noise that sd avoids) and
-# print just the "correct solutions" tally so callers can parse it.
-#
-# Usage: run_ecm.sh <label> [depth]
-# Writes eval_tune/ecm_logs/<label>.log (full Trace() output) and
-# appends one line to eval_tune/ecm_history.log.
-
-set -e
-cd "$(dirname "$0")/.." # repo src/ root
-
-label="$1"
-depth="${2:-11}"
-if [ -z "$label" ]; then
- echo "Usage: $0 <label> [depth]" >&2
- exit 1
-fi
-
-mkdir -p eval_tune/ecm_logs
-logfile="eval_tune/ecm_logs/${label}.log"
-
-opts='--cpus 1 --hash 256m --egtbpath /zscratch/egtb'
-# --batch exits non-zero on normal EOF ("Exhausted input in batch
-# mode") -- that's expected, not a failure, so don't let set -e treat
-# it as one; the real success/failure check is the grep below.
-./typhoon ${opts} --logfile "$logfile" --batch \
- --command "sd ${depth}; script ../tests/ecm.ep_" || true
-
-correct=$(grep -o 'correct solutions : [0-9]*' "$logfile" | tail -1 | grep -o '[0-9]*$')
-total=$(grep -o 'total problems : [0-9]*' "$logfile" | tail -1 | grep -o '[0-9]*$')
-
-if [ -z "$correct" ] || [ -z "$total" ]; then
- echo "ERROR: couldn't parse solved count from $logfile" >&2
- exit 1
-fi
-
-echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) label=$label depth=$depth solved=${correct}/${total}" \
- >> eval_tune/ecm_history.log
-
-echo "${correct} ${total}"
diff --git a/src/eval_tune/test_vs_head.sh b/src/eval_tune/test_vs_head.sh
index c7c578e..d5d8006 100755
--- a/src/eval_tune/test_vs_head.sh
+++ b/src/eval_tune/test_vs_head.sh
@@ -3,7 +3,7 @@
python3 ./match_play.py ../../head_reference/typhoon ../typhoon \
--pgn ../../pgn/twic_filtered.pgn \
--games 20000 \
- --workers 20 \
+ --workers 12 \
--st 1 \
--sprt --elo0 0 --elo1 5 \
--scratch /usr/local/tmp/typhoon_match_overnight \
diff --git a/src/main.c b/src/main.c
index a705636..fb797ad 100755
--- a/src/main.c
+++ b/src/main.c
@@ -296,7 +296,7 @@ Return value:
strcpy(g_Options.szEGTBPath, "/zscratch/egtb");
strcpy(g_Options.szLogfile, "/usr/local/tmp/typhoon.log");
strcpy(g_Options.szBookName, "book.bin");
- g_Options.uNumHashTableEntries = 0x10000;
+ g_Options.uNumHashTableEntries = _ParseHashOption("256m", sizeof(HASH_ENTRY));
g_Options.uNumProcessors = 1;
g_Options.fStatusLine = TRUE;
g_Options.iResignThreshold = 0;
@@ -527,7 +527,7 @@ Return value:
**/
{
-#ifdef _X86_
+#if defined(_X86_) || defined(_X64_)
ULONG u, x, y;
Trace("Testing CPP macros...\n");
diff --git a/src/root.c b/src/root.c
index ed450b4..bfcf034 100755
--- a/src/root.c
+++ b/src/root.c
@@ -950,27 +950,45 @@ _IterateSetSearchGlobals(ULONG uDepth)
//
g_uSoftExtendLimit = g_uIterateDepth * 2;
g_uHardExtendLimit = g_uIterateDepth * 4;
- for (u = 0; u < MAX_PLY_PER_SEARCH; u++)
+
+ // g_uExtensionReduction[]'s own bands, below, must stay reachable
+ // within [0, MAX_PLY_PER_SEARCH) regardless of how deep g_uIterateDepth
+ // is -- naive g_uSoftExtendLimit/g_uHardExtendLimit (2x/4x iterate
+ // depth) can hugely exceed MAX_PLY_PER_SEARCH for a deep requested
+ // search (e.g. sd=30 -> hard limit 120 >> 64), which would leave every
+ // reachable u in the loop below permanently in the "0 penalty" band --
+ // no taper at all for the entire representable ply range. Deliberately
+ // a LOCAL, separately-capped depth for this table only --
+ // g_uSoftExtendLimit/g_uHardExtendLimit themselves stay uncapped (raw
+ // g_uIterateDepth * 2/4) since g_uHardExtendLimit is also read directly
+ // elsewhere (searchsup.c's QSearch runaway-depth cutoff) where capping
+ // it would be an unrelated, unintended behavior change.
{
- if (u < g_uSoftExtendLimit)
- {
- g_uExtensionReduction[u] = 0;
- }
- else if (u < (g_uSoftExtendLimit + g_uIterateDepth / 2))
- {
- g_uExtensionReduction[u] = QUARTER_PLY;
- }
- else if (u < (g_uSoftExtendLimit + g_uIterateDepth))
- {
- g_uExtensionReduction[u] = HALF_PLY;
- }
- else if (u < g_uHardExtendLimit)
- {
- g_uExtensionReduction[u] = THREE_QUARTERS_PLY;
- }
- else
+ ULONG uEffIterateDepth = MIN(g_uIterateDepth, (MAX_PLY_PER_SEARCH - 4) / 4);
+ ULONG uEffSoftLimit = uEffIterateDepth * 2;
+ ULONG uEffHardLimit = uEffIterateDepth * 4;
+ for (u = 0; u < MAX_PLY_PER_SEARCH; u++)
{
- g_uExtensionReduction[u] = 5 * ONE_PLY;
+ if (u < uEffSoftLimit)
+ {
+ g_uExtensionReduction[u] = 0;
+ }
+ else if (u < (uEffSoftLimit + uEffIterateDepth / 2))
+ {
+ g_uExtensionReduction[u] = QUARTER_PLY;
+ }
+ else if (u < (uEffSoftLimit + uEffIterateDepth))
+ {
+ g_uExtensionReduction[u] = HALF_PLY;
+ }
+ else if (u < uEffHardLimit)
+ {
+ g_uExtensionReduction[u] = THREE_QUARTERS_PLY;
+ }
+ else
+ {
+ g_uExtensionReduction[u] = 5 * ONE_PLY;
+ }
}
}
diff --git a/src/run_tests.sh b/src/run_tests.sh
index d5905e4..7e3a053 100755
--- a/src/run_tests.sh
+++ b/src/run_tests.sh
@@ -6,26 +6,41 @@ LOGDIR="/tmp/typhoon/manual"
HEAD_LOGDIR="../head_reference/logs"
TESTS="../tests"
-/bin/rm -rf "${LOGDIR}/*"
+# Suite names default to the three curated suites (see CLAUDE.md's
+# head_reference protocol). Override on the command line to run other
+# suites, e.g. ./run_tests.sh ecm_ringers ecm_lmr_delta
+SUITES=("$@")
+if [ ${#SUITES[@]} -eq 0 ]; then
+ SUITES=(ecm_ringers ecm_confident_quick ecm_hard_quick)
+fi
-clear
-echo -n "Launching all test suites @ sn=5M and sd=10 in parallel (expect ~5-6 min)... "
+# sd (fixed depth) is the right choice when judging whether a pruning/
+# ordering change makes the tree smaller or larger for the same search
+# effort; sn (fixed node budget) is the right choice when judging how many
+# positions solve within a fixed cost. Override via SD/SN env vars.
+SD="${SD:-10}"
+SN="${SN:-5000000}"
+
+/bin/rm -rf "${LOGDIR}"
+mkdir -p "${LOGDIR}"
+
+echo -n "Launching test suites (${SUITES[*]}) @ sn=${SN} and sd=${SD} in parallel... "
_start_time=$(date +%s)
-for suite in ecm_ringers ecm_confident_quick ecm_hard_quick; do
+for suite in "${SUITES[@]}"; do
./typhoon \
--cpus 1 \
--hash 256m \
- --logfile "$LOGDIR/sd10_${suite}.log" \
+ --logfile "$LOGDIR/sd${SD}_${suite}.log" \
--batch \
- --command "force; book name /nonexistent.book.bin; sd 10; script $TESTS/${suite}.ep_" \
- > "$LOGDIR/sd10_${suite}.out" 2>&1 &
+ --command "force; book name /nonexistent.book.bin; sd ${SD}; script $TESTS/${suite}.ep_" \
+ > "$LOGDIR/sd${SD}_${suite}.out" 2>&1 &
./typhoon \
--cpus 1 \
--hash 256m \
- --logfile "$LOGDIR/sn5m_${suite}.log" \
+ --logfile "$LOGDIR/sn${SN}_${suite}.log" \
--batch \
- --command "force; book name /nonexistent.book.bin; sn 5000000; script $TESTS/${suite}.ep_" \
- > "$LOGDIR/sn5m_${suite}.out" 2>&1 &
+ --command "force; book name /nonexistent.book.bin; sn ${SN}; script $TESTS/${suite}.ep_" \
+ > "$LOGDIR/sn${SN}_${suite}.out" 2>&1 &
done
wait
_elapsed=$(( $(date +%s) - _start_time ))
@@ -69,15 +84,19 @@ print_stats_table() {
' "$headfile" "$candfile"
}
-for suite in ecm_ringers ecm_confident_quick ecm_hard_quick; do
- echo "----[ $suite sd=10 ]----"
+for suite in "${SUITES[@]}"; do
+ echo "----[ $suite sd=${SD} ]------------------------------------------------------------------"
print_stats_table \
- <(sed -n '/correct solutions/,/script time/p' "$HEAD_LOGDIR/sd10_${suite}.out") \
- <(sed -n '/correct solutions/,/script time/p' "$LOGDIR/sd10_${suite}.out")
+ <(sed -n '/correct solutions/,/script time/p' "$HEAD_LOGDIR/sd${SD}_${suite}.out") \
+ <(sed -n '/correct solutions/,/script time/p' "$LOGDIR/sd${SD}_${suite}.out")
echo
- echo "----[ $suite sn=5M ]----"
+ echo "----[ $suite sn=${SN} ]------------------------------------------------------------------"
print_stats_table \
- <(sed -n '/correct solutions/,/script time/p' "$HEAD_LOGDIR/sn5m_${suite}.out") \
- <(sed -n '/correct solutions/,/script time/p' "$LOGDIR/sn5m_${suite}.out")
+ <(sed -n '/correct solutions/,/script time/p' "$HEAD_LOGDIR/sn${SN}_${suite}.out") \
+ <(sed -n '/correct solutions/,/script time/p' "$LOGDIR/sn${SN}_${suite}.out")
echo
done
+echo
+echo "NOTE: The logfiles for these runs are in /tmp/typhoon/manual and will be"
+echo " overwritten by the next invocation of this script; save now them if"
+echo " you want them!"
diff --git a/src/search.c b/src/search.c
index 31220e1..807f69d 100755
--- a/src/search.c
+++ b/src/search.c
@@ -486,7 +486,21 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
ctx->sSearchFlags.fAvoidNullmove = TRUE;
RescoreMovesViaSearch(ctx, uDepth, iAlpha, iBeta);
ctx->sSearchFlags.fAvoidNullmove = FALSE;
- ASSERT(TRUE == pi->fMovesRescoredByIID);
+ // NOT always TRUE here -- pre-existing bug, found
+ // via debug_smoke_test.sh (a deeper/larger-than-
+ // usual sample finally hit the rare path).
+ // RescoreMovesViaSearch's own fail-high branch
+ // (searchsup.c) deliberately leaves this FALSE by
+ // design -- a fail-high only proves uBest is good
+ // enough, not honest eval-axis scores for every
+ // move, so claiming fMovesRescoredByIID would be a
+ // lie. This assert demanded the opposite of that
+ // documented contract; DO_IID is unconditionally
+ // compiled in (chess.h) so this could fire on any
+ // DEBUG build given an unlucky enough rescore --
+ // ASSERT is a no-op in release, so this never
+ // crashed in production, but it made the DEBUG/
+ // TEST harness itself unreliable at random.
}
}
#endif
@@ -727,15 +741,12 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
iCheckSee,
&iExtend);
- // Cap how many extension plies this line may spend in total
- // (root to here) so that a chain of checks/threats/etc. can't
- // stall uDepth's descent indefinitely and burn the entire
- // MAX_PLY_PER_SEARCH ply budget on one forcing sequence.
- if (iExtend > 0)
- {
- iExtend = MIN(iExtend,
- MAX(MAX_EXTEND_PER_LINE - pf->iCumulativeExtend, 0));
- }
+ // Note: MAX_EXTEND_PER_LINE (a flat, non-depth-relative cap on
+ // total extension spent per line) used to be applied here.
+ // Removed -- g_uExtensionReduction[] (consumed inside
+ // ComputeMoveExtension, scaled off g_uIterateDepth) is the
+ // sole extension-runaway guard now; see root.c's construction
+ // of that table.
// Decide how much (if any) to reduce this move's depth --
// graded LMR.
@@ -799,7 +810,6 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
ULONG uFHAttempts = 0;
ULONG uFHPct = GetMoveFailHighPercentage(mv, &uFHAttempts);
fThisMoveEFPPruned =
- (mv.cFrom != FindEnprisePiece(ctx, pos->uToMove)) &&
((uFHAttempts < EFP_FH_MIN_SAMPLES) ||
(uFHPct <= EFP_FH_PRUNE_THRESHOLD)) &&
(!IS_SAME_MOVE(mv, ctx->mvKiller[ctx->uPly-1][0])) &&
diff --git a/src/split.c b/src/split.c
index 28d5ce0..a1f9d32 100755
--- a/src/split.c
+++ b/src/split.c
@@ -1128,15 +1128,12 @@ Return value:
&iExtend);
//
- // Cap total extension plies spent on this line, same as the
- // non-split move loop in search.c does.
+ // Note: MAX_EXTEND_PER_LINE (a flat, non-depth-relative cap on
+ // total extension spent per line) used to be applied here.
+ // Removed -- g_uExtensionReduction[] is the sole extension-
+ // runaway guard now, same as the non-split move loop in
+ // search.c.
//
- if (iExtend > 0)
- {
- iExtend = MIN(iExtend,
- MAX(MAX_EXTEND_PER_LINE -
- ctx->sSearchFlags.iCumulativeExtend, 0));
- }
//
// Decide how much (if any) to reduce this move's depth.
diff --git a/src/unix.c b/src/unix.c
index d50932f..564e0af 100644
--- a/src/unix.c
+++ b/src/unix.c
@@ -51,7 +51,7 @@ typedef struct _ALLOC_RECORD
} ALLOC_RECORD;
ALLOC_RECORD g_AllocHash[ALLOC_HASH_SIZE];
-#define PTR_TO_ALLOC_HASH(x) ((((ULONG)(size_t)(x)) >> 3) & (ALLOC_HASH_SIZE - 1))
+#define PTR_TO_ALLOC_HASH(x) ((((size_t)(x)) >> 3) & (ALLOC_HASH_SIZE - 1))
ULONG
GetHeapMemoryUsage(void)
@@ -838,7 +838,7 @@ Return value:
NULL,
"mprotect",
(void *)(size_t)errno,
- (void *)(PROT_READ | PROT_WRITE),
+ (void *)(size_t)(PROT_READ | PROT_WRITE),
__FILE__, __LINE__);
}
return(TRUE);