diff --git a/cpp/command/runtests.cpp b/cpp/command/runtests.cpp index e7dfa0c90..c9532d4a1 100644 --- a/cpp/command/runtests.cpp +++ b/cpp/command/runtests.cpp @@ -52,6 +52,7 @@ int MainCmds::runtests(const vector& args) { Tests::runBoardStressTest(); Tests::runSgfTests(); + Tests::runTrainingDataEndTests(); Tests::runBasicSymmetryTests(); Tests::runBoardSymmetryTests(); Tests::runSymmetryDifferenceTests(); diff --git a/cpp/command/writetrainingdata.cpp b/cpp/command/writetrainingdata.cpp index 70119884a..242c73f51 100644 --- a/cpp/command/writetrainingdata.cpp +++ b/cpp/command/writetrainingdata.cpp @@ -13,6 +13,7 @@ #include "../program/play.h" #include "../command/commandline.h" #include "../core/test.h" +#include "../tests/tests.h" #include "../main.h" #include @@ -20,6 +21,46 @@ using namespace std; +// Keep this ownership gate independent of NN evaluation so its thresholds can be tested directly. +static bool isNearEndByOwnership( + int boardArea, int blackWhiteVeryDifferent, int blackCountUnsettled, int whiteCountUnsettled +) { + // count / boardArea < 0.10 + 6.0 / boardArea, compared exactly for positive board areas. + // Widen before scaling to avoid both integer truncation and floating-point boundary drift. + const int64_t maxScaledUnsettled = (int64_t)boardArea + 60; + return + 10LL * blackWhiteVeryDifferent < maxScaledUnsettled + && 10LL * blackCountUnsettled < maxScaledUnsettled + && 10LL * whiteCountUnsettled < maxScaledUnsettled; +} + +void Tests::runTrainingDataEndTests() { + cout << "Running scored-game near-end ownership tests" << endl; + struct TestCase { + int xSize; + int ySize; + int lastAllowedCount; + }; + // Rectangles exercise exact thresholds that the fractional comparison can round upward. + // 10x10 also checks the strict comparison at the exact 16-point threshold. + const TestCase cases[] = { + {5,6,8}, {9,9,14}, {9,10,14}, {10,10,15}, {10,12,17}, + {10,19,24}, {13,13,22}, {14,15,26}, {15,18,32}, {19,19,42} + }; + for(const TestCase& testCase: cases) { + int boardArea = testCase.xSize * testCase.ySize; + // Sweep zero, both sides of the threshold, half the board, and the full board. + // Each counter must reject independently, even when the other two are zero. + for(int count = 0; count <= boardArea; count++) { + bool expected = count <= testCase.lastAllowedCount; + testAssert(isNearEndByOwnership(boardArea,count,0,0) == expected); + testAssert(isNearEndByOwnership(boardArea,0,count,0) == expected); + testAssert(isNearEndByOwnership(boardArea,0,0,count) == expected); + testAssert(isNearEndByOwnership(boardArea,count,count,count) == expected); + } + } +} + static ValueTargets makeForcedWinnerValueTarget(Player winner) { ValueTargets targets; if(winner == C_EMPTY) { @@ -2028,7 +2069,6 @@ int MainCmds::writetrainingdata(const vector& args) { double winlossDiff = abs(valuesIfBlackFirst.winLossValue - valuesIfWhiteFirst.winLossValue); // Remaining difference between side to move is less than 4 pointsish, and the winrate is close if(leadDiff < 3.5 + 0.05 * absAvgLead && winlossDiff < 0.2) { - const double maxFractionOfBoardUnsettled = 0.10 + 6.0 / boardArea; int blackCountUnsettled = 0; int whiteCountUnsettled = 0; int blackWhiteVeryDifferent = 0; @@ -2043,13 +2083,9 @@ int MainCmds::writetrainingdata(const vector& args) { blackWhiteVeryDifferent += 1; } } - if( - blackWhiteVeryDifferent / boardArea < maxFractionOfBoardUnsettled - && blackCountUnsettled / boardArea < maxFractionOfBoardUnsettled - && whiteCountUnsettled / boardArea < maxFractionOfBoardUnsettled - ) { - gameIsNearEnd = true; - } + gameIsNearEnd = isNearEndByOwnership( + boardArea,blackWhiteVeryDifferent,blackCountUnsettled,whiteCountUnsettled + ); } } diff --git a/cpp/tests/tests.h b/cpp/tests/tests.h index 90c28f663..cea1ad5a6 100644 --- a/cpp/tests/tests.h +++ b/cpp/tests/tests.h @@ -69,6 +69,9 @@ namespace Tests { //testtime.cpp void runTimeControlsTests(); + //command/writetrainingdata.cpp + void runTrainingDataEndTests(); + //testtrainingwrite.cpp void runTrainingWriteTests(); void runPassAliveSuicideGameTests();