summaryrefslogtreecommitdiff
path: root/src/script.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-25 08:44:25 -0700
committerScott Gasch <[email protected]>2026-09-25 08:44:25 -0700
commit9b28080f8b3dca4588cb2aaaf748451f45e3c626 (patch)
tree54e0aff5d6ee1520c590c7c04a5ae2f8299e7497 /src/script.c
parent66740dc89c7351c120c25788d0079055034f4942 (diff)
Two ideas:
1. Nullmove R=3 + depth/6 along with some ShouldWeDoNullmoveSearch tweaking and calibrating. 2. Probe EGTB everywhere in the search as long as the table/position is resident in memory.
Diffstat (limited to 'src/script.c')
-rw-r--r--src/script.c137
1 files changed, 137 insertions, 0 deletions
diff --git a/src/script.c b/src/script.c
index 532a0d5..f18ab7b 100644
--- a/src/script.c
+++ b/src/script.c
@@ -64,6 +64,24 @@ typedef struct _SUITE_COUNTERS
UINT64 u64TotalLeftoverFH;
UINT64 u64TotalCMEvidenceTries[CM_EVIDENCE_BUCKETS];
UINT64 u64TotalCMEvidenceFH[CM_EVIDENCE_BUCKETS];
+#ifdef TEST_NULL
+ // Null-move precision/recall calibration, summed across every
+ // position in the suite -- see _NullCalibBucketIndex's comment in
+ // search.c. Accumulated the same way as u64TotalCMEvidenceTries
+ // above (per-search counters are copied into g_Options at the end
+ // of each Think() call, then summed in here by _ProcessSuiteLine),
+ // and printed once by _PrintSuiteReport at true end-of-suite --
+ // deliberately *not* dumped after every position, which would
+ // balloon the logfile (confirmed: 191MB after ~28K of 200K
+ // positions the first time this was tried that way).
+ UINT64 u64TotalNCAttempts[NC_TOTAL_BUCKETS];
+ UINT64 u64TotalNCNullFH[NC_TOTAL_BUCKETS];
+ UINT64 u64TotalNCRealFH[NC_TOTAL_BUCKETS];
+ UINT64 u64TotalNCBothFH[NC_TOTAL_BUCKETS];
+ UINT64 u64TotalNCNullProbeNodeSum[NC_TOTAL_BUCKETS];
+ UINT64 u64TotalNCFirstMoveNodeSum[NC_TOTAL_BUCKETS];
+ UINT64 u64TotalNCRealLoopNodeSum[NC_TOTAL_BUCKETS];
+#endif
double dSigmaEBF; // sum of per-problem nodes^(1/depth)
ULONG uEBFCount; // number of problems with depth > 0
double dAverageTimeToSolution;
@@ -391,6 +409,28 @@ _ProcessSuiteLine(char *szLine, POSITION *pos)
g_Options.u64CMEvidenceFH[v];
}
}
+#ifdef TEST_NULL
+ {
+ ULONG v;
+ for (v = 0; v < NC_TOTAL_BUCKETS; v++)
+ {
+ g_SuiteCounters.u64TotalNCAttempts[v] +=
+ g_Options.u64NCAttempts[v];
+ g_SuiteCounters.u64TotalNCNullFH[v] +=
+ g_Options.u64NCNullFH[v];
+ g_SuiteCounters.u64TotalNCRealFH[v] +=
+ g_Options.u64NCRealFH[v];
+ g_SuiteCounters.u64TotalNCBothFH[v] +=
+ g_Options.u64NCBothFH[v];
+ g_SuiteCounters.u64TotalNCNullProbeNodeSum[v] +=
+ g_Options.u64NCNullProbeNodeSum[v];
+ g_SuiteCounters.u64TotalNCFirstMoveNodeSum[v] +=
+ g_Options.u64NCFirstMoveNodeSum[v];
+ g_SuiteCounters.u64TotalNCRealLoopNodeSum[v] +=
+ g_Options.u64NCRealLoopNodeSum[v];
+ }
+ }
+#endif
}
else
{
@@ -473,6 +513,103 @@ _PrintSuiteReport(double dSuiteStart)
}
}
+#ifdef TEST_NULL
+ // Null-move precision/recall calibration, summed across the whole
+ // suite (see _NullCalibBucketIndex's comment in search.c for the
+ // encoding and what each dimension means). Decode each bucket
+ // index back into its component bands for a readable line; skip
+ // empty cells so this stays legible even though the full table has
+ // NC_TOTAL_BUCKETS cells.
+ {
+ static const char *rgszDepthBand[NC_DEPTH_BUCKETS] =
+ { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "11", "12", "13", "14", "15", "16+" };
+ static const char *rgszDeficitBand[NC_DEFICIT_BUCKETS] =
+ { "<-300", "-300..-100", "-100..0", "0..100", "100..300", "300+" };
+ static const char *rgszMaterialBand[NC_MATERIAL_BUCKETS] =
+ { "0-1", "2", "3-5", "6+" };
+ ULONG uIdx, uDecode, uNullDepthB, uOuterDepthB, uDeficitB;
+ ULONG uMaxMaterialB, uMoverMaterialB;
+ UINT64 u64Attempts, u64NullFH, u64RealFH, u64BothFH;
+ UINT64 u64NullProbeNodeSum, u64FirstMoveNodeSum, u64RealLoopNodeSum;
+ double dAvgNullProbeNodes, dAvgFirstMoveNodes, dAvgRealLoopNodes;
+ double dNullSuccessRate, dEVFirstMoveOnly, dEV;
+
+ Trace("\nNull-move calibration (attempts / nullFH / realFH / both / "
+ "precision / recall / nullSuccessRate / avgNullProbeNodes / "
+ "avgFirstMoveNodes / avgRealLoopNodes / EVFirstMoveOnly / "
+ "EV-per-attempt-in-nodes):\n");
+ for (uIdx = 0; uIdx < NC_TOTAL_BUCKETS; uIdx++)
+ {
+ u64Attempts = g_SuiteCounters.u64TotalNCAttempts[uIdx];
+ if (0 == u64Attempts) continue;
+ u64NullFH = g_SuiteCounters.u64TotalNCNullFH[uIdx];
+ u64RealFH = g_SuiteCounters.u64TotalNCRealFH[uIdx];
+ u64BothFH = g_SuiteCounters.u64TotalNCBothFH[uIdx];
+ u64NullProbeNodeSum = g_SuiteCounters.u64TotalNCNullProbeNodeSum[uIdx];
+ u64FirstMoveNodeSum = g_SuiteCounters.u64TotalNCFirstMoveNodeSum[uIdx];
+ u64RealLoopNodeSum = g_SuiteCounters.u64TotalNCRealLoopNodeSum[uIdx];
+
+ // Decode uIdx (innermost/fastest-varying dimension first),
+ // matching _NullCalibBucketIndex's encoding in search.c.
+ // Use a scratch copy -- uIdx itself is still the array
+ // index for the lookups above/below.
+ uDecode = uIdx;
+ uMoverMaterialB = uDecode % NC_MATERIAL_BUCKETS;
+ uDecode /= NC_MATERIAL_BUCKETS;
+ uMaxMaterialB = uDecode % NC_MATERIAL_BUCKETS;
+ uDecode /= NC_MATERIAL_BUCKETS;
+ uDeficitB = uDecode % NC_DEFICIT_BUCKETS;
+ uDecode /= NC_DEFICIT_BUCKETS;
+ uOuterDepthB = uDecode % NC_DEPTH_BUCKETS;
+ uDecode /= NC_DEPTH_BUCKETS;
+ uNullDepthB = uDecode % NC_DEPTH_BUCKETS;
+
+ dAvgNullProbeNodes = (double)u64NullProbeNodeSum / (double)u64Attempts;
+ dAvgFirstMoveNodes = (double)u64FirstMoveNodeSum / (double)u64Attempts;
+ dAvgRealLoopNodes = (double)u64RealLoopNodeSum / (double)u64Attempts;
+ dNullSuccessRate = (double)u64NullFH / (double)u64Attempts;
+ // EV of TRYING null vs. going straight to the real move loop,
+ // in nodes saved per attempt (positive = trying pays off):
+ // on the dNullSuccessRate fraction of attempts we pay the
+ // (cheap) null probe instead of the (expensive) real-loop
+ // cost; on the rest we pay the null probe *in addition* to
+ // still needing the real loop. dEV uses avgRealLoopNodes --
+ // the true total cost of however the real search actually
+ // resolved the node (one move, several, or production's own
+ // separate null success) -- while dEVFirstMoveOnly keeps the
+ // cheaper move-1-only proxy alongside it for comparison.
+ // Both assume (per the user's simplifying assumption,
+ // matched by this dataset's ~99.96% overall precision) that
+ // a null-move fail-high is correct.
+ dEVFirstMoveOnly =
+ dNullSuccessRate * (dAvgFirstMoveNodes - dAvgNullProbeNodes)
+ - (1.0 - dNullSuccessRate) * dAvgNullProbeNodes;
+ dEV = dNullSuccessRate * (dAvgRealLoopNodes - dAvgNullProbeNodes)
+ - (1.0 - dNullSuccessRate) * dAvgNullProbeNodes;
+
+ Trace(" nullDepth=%s outerDepth=%s deficit=%s "
+ "maxMaterial=%s moverMaterial=%s : "
+ "%"COMPILER_LONGLONG_UNSIGNED_FORMAT" / "
+ "%"COMPILER_LONGLONG_UNSIGNED_FORMAT" / "
+ "%"COMPILER_LONGLONG_UNSIGNED_FORMAT" / "
+ "%"COMPILER_LONGLONG_UNSIGNED_FORMAT" / "
+ "%5.3f / %5.3f / %5.3f / %10.1f / %10.1f / %10.1f / %10.1f / %10.1f\n",
+ rgszDepthBand[uNullDepthB],
+ rgszDepthBand[uOuterDepthB],
+ rgszDeficitBand[uDeficitB],
+ rgszMaterialBand[uMaxMaterialB],
+ rgszMaterialBand[uMoverMaterialB],
+ u64Attempts, u64NullFH, u64RealFH, u64BothFH,
+ (u64NullFH != 0) ? (100.0 * (double)u64BothFH / (double)u64NullFH) : 0.0,
+ (u64RealFH != 0) ? (100.0 * (double)u64BothFH / (double)u64RealFH) : 0.0,
+ 100.0 * dNullSuccessRate,
+ dAvgNullProbeNodes, dAvgFirstMoveNodes, dAvgRealLoopNodes,
+ dEVFirstMoveOnly, dEV);
+ }
+ }
+#endif
+
// Histogram stuff
if (g_SuiteCounters.uTotal > 0) {
uMax = g_SuiteCounters.uHistogram[0];