Skip to content

Commit 8addaa6

Browse files
Final fix for MultiPV mate PV corner cases
1 parent 86f1df7 commit 8addaa6

2 files changed

Lines changed: 69 additions & 59 deletions

File tree

src/search.cpp

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -268,13 +268,13 @@ bool Search::Worker::iterative_deepening() {
268268

269269
PVMoves pv;
270270

271-
Move lastBestMove = Move::none();
272-
Depth lastBestMoveDepth = 0;
271+
PVMoves lastBestMovePV;
272+
Depth lastBestMoveDepth = 0;
273+
Value lastBestMoveScore = -VALUE_INFINITE;
273274

274275
Value alpha, beta;
275-
Value bestValue = -VALUE_INFINITE;
276-
Value lastIterationScore = -VALUE_INFINITE;
277-
Color us = rootPos.side_to_move();
276+
Value bestValue = -VALUE_INFINITE;
277+
Color us = rootPos.side_to_move();
278278
double timeReduction = 1, totBestMoveChanges = 0;
279279
int delta, iterIdx = 0;
280280

@@ -339,10 +339,11 @@ bool Search::Worker::iterative_deepening() {
339339

340340
// Save the last iteration's scores before the first PV line is searched and
341341
// all the move scores except the (new) PV are set to -VALUE_INFINITE.
342-
for (RootMove& rm : rootMoves)
342+
for (usize i = 0; i < rootMoves.size(); ++i)
343343
{
344-
rm.previousScore = rm.score;
345-
rm.previousPV = rm.pv;
344+
rootMoves[i].previousScore = rootMoves[i].score;
345+
rootMoves[i].previousPV = rootMoves[i].pv;
346+
rootMoves[i].previousScoreExact = i < multiPV;
346347
}
347348

348349
usize pvFirst = 0;
@@ -436,44 +437,52 @@ bool Search::Worker::iterative_deepening() {
436437
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
437438
}
438439

439-
// In multiPV analysis we do not let aborted searches spoil mated-in/
440-
// TB loss scores from a completed search in an earlier PV line.
441-
// Hence we guard against an aborted pvIdx line overtaking pvIdx - 1
442-
// when pvIdx - 1 is a proven loss.
443-
// Moreover, we do not trust an exact loss score from an aborted search.
444-
if (threads.stop && pvIdx
445-
&& ((is_loss(rootMoves[pvIdx - 1].score) && rootMoves[pvIdx] < rootMoves[pvIdx - 1])
446-
|| rootMoves[pvIdx].score_is_exact_loss()))
440+
if (threads.stop && pvIdx)
447441
{
448-
// If the previous score is worse than pvIdx - 1, we can safely use it.
449-
// If it is equal, we make sure it cannot overtake pvIdx - 1.
450-
if (rootMoves[pvIdx].previousScore != -VALUE_INFINITE
451-
&& rootMoves[pvIdx].previousScore <= rootMoves[pvIdx - 1].score)
442+
// In multiPV analysis we do not let aborted searches spoil mated-in/
443+
// TB loss scores from a completed search in an earlier PV line.
444+
// Hence we guard against an aborted pvIdx line overtaking pvIdx - 1
445+
// when pvIdx - 1 is a proven loss.
446+
// Moreover, we do not trust an exact loss score from an aborted search.
447+
if ((is_loss(rootMoves[pvIdx - 1].score) && rootMoves[pvIdx] < rootMoves[pvIdx - 1])
448+
|| rootMoves[pvIdx].score_is_exact_loss())
452449
{
453-
rootMoves[pvIdx].score = rootMoves[pvIdx].uciScore =
454-
rootMoves[pvIdx].previousScore;
455-
rootMoves[pvIdx].previousScore = -VALUE_INFINITE;
456-
rootMoves[pvIdx].pv = rootMoves[pvIdx].previousPV;
457-
rootMoves[pvIdx].unset_bound_flags();
458-
}
459-
460-
// Otherwise, if we can, we cap the score to the best possible, and mark
461-
// the score as a bound (also a valid excuse for the incomplete PV.)
462-
else
463-
{
464-
if (is_loss(rootMoves[pvIdx - 1].score))
450+
// If previousScore is exact and worse than pvIdx - 1, we can safely use it.
451+
// If it is equal, we make sure it cannot overtake pvIdx - 1.
452+
if (rootMoves[pvIdx].previousScore != -VALUE_INFINITE
453+
&& rootMoves[pvIdx].previousScoreExact
454+
&& rootMoves[pvIdx].previousScore <= rootMoves[pvIdx - 1].score)
465455
{
466456
rootMoves[pvIdx].score = rootMoves[pvIdx].uciScore =
467-
rootMoves[pvIdx - 1].score;
457+
rootMoves[pvIdx].previousScore;
468458
rootMoves[pvIdx].previousScore = -VALUE_INFINITE;
469-
rootMoves[pvIdx].pv.resize(1);
470-
rootMoves[pvIdx].scoreUpperbound = true;
459+
rootMoves[pvIdx].pv = rootMoves[pvIdx].previousPV;
460+
rootMoves[pvIdx].unset_bound_flags();
471461
}
472-
else
473-
rootMoves[pvIdx].scoreUpperbound = false;
474462

475-
rootMoves[pvIdx].scoreLowerbound = !rootMoves[pvIdx].scoreUpperbound;
463+
// Otherwise, if we can, we cap the score to the best possible, and mark
464+
// the score as a bound (also a valid excuse for the incomplete PV.)
465+
else
466+
{
467+
if (is_loss(rootMoves[pvIdx - 1].score))
468+
{
469+
rootMoves[pvIdx].score = rootMoves[pvIdx].uciScore =
470+
rootMoves[pvIdx - 1].score;
471+
rootMoves[pvIdx].previousScore = -VALUE_INFINITE;
472+
rootMoves[pvIdx].pv.resize(1);
473+
rootMoves[pvIdx].scoreUpperbound = true;
474+
}
475+
else
476+
rootMoves[pvIdx].scoreUpperbound = false;
477+
478+
rootMoves[pvIdx].scoreLowerbound = !rootMoves[pvIdx].scoreUpperbound;
479+
}
476480
}
481+
482+
// Finally, we mark all loss scores from partially searched moves as a bound.
483+
for (usize i = pvIdx + 1; i < multiPV; ++i)
484+
if (rootMoves[i].score_is_exact_loss())
485+
rootMoves[i].scoreLowerbound = true;
477486
}
478487

479488
// Sort the PV lines searched so far and update the GUI
@@ -489,21 +498,21 @@ bool Search::Worker::iterative_deepening() {
489498
break;
490499
}
491500

492-
const bool forgottenMate = lastIterationScore != -VALUE_INFINITE
493-
&& is_mate_or_mated(lastIterationScore)
494-
&& (std::abs(rootMoves[0].score) < std::abs(lastIterationScore)
501+
const bool forgottenMate = lastBestMoveScore != -VALUE_INFINITE
502+
&& is_mate_or_mated(lastBestMoveScore)
503+
&& (std::abs(rootMoves[0].score) < std::abs(lastBestMoveScore)
495504
|| rootMoves[0].score_is_bound());
496505

497506
if (!threads.stop)
498507
{
499-
if (lastBestMove != rootMoves[0].pv[0])
508+
if (lastBestMovePV.empty() || lastBestMovePV[0] != rootMoves[0].pv[0])
500509
lastBestMoveDepth = rootDepth;
501510

502511
// Do not replace (shorter) mate scores from a previous iteration.
503512
if (!forgottenMate)
504513
{
505-
lastBestMove = rootMoves[0].pv[0];
506-
lastIterationScore = rootMoves[0].score;
514+
lastBestMovePV = rootMoves[0].pv;
515+
lastBestMoveScore = rootMoves[0].score;
507516
}
508517
}
509518

@@ -517,12 +526,12 @@ bool Search::Worker::iterative_deepening() {
517526
if (abortedLossSearch || (rootMoves[0].score != -VALUE_INFINITE && forgottenMate))
518527
{
519528
// Bring the last best move to the front for best thread selection.
520-
if (lastBestMove != Move::none())
529+
if (!lastBestMovePV.empty())
521530
{
522-
Utility::move_to_front(
523-
rootMoves, [lastBestMove](const auto& rm) { return rm == lastBestMove; });
524-
rootMoves[0].score = rootMoves[0].uciScore = rootMoves[0].previousScore;
525-
rootMoves[0].pv = rootMoves[0].previousPV;
531+
Utility::move_to_front(rootMoves, [&lastPV = std::as_const(lastBestMovePV)](
532+
const auto& rm) { return rm == lastPV[0]; });
533+
rootMoves[0].score = rootMoves[0].uciScore = lastBestMoveScore;
534+
rootMoves[0].pv = lastBestMovePV;
526535
rootMoves[0].unset_bound_flags();
527536

528537
if (mainThread)

src/search.h

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,17 @@ struct RootMove {
138138
return m.score != score ? m.score < score : m.previousScore < previousScore;
139139
}
140140

141-
u64 effort = 0;
142-
Value score = -VALUE_INFINITE;
143-
Value previousScore = -VALUE_INFINITE;
144-
Value averageScore = -VALUE_INFINITE;
145-
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
146-
Value uciScore = -VALUE_INFINITE;
147-
bool scoreLowerbound = false;
148-
bool scoreUpperbound = false;
149-
int selDepth = 0;
150-
int tbRank = 0;
141+
u64 effort = 0;
142+
Value score = -VALUE_INFINITE;
143+
Value previousScore = -VALUE_INFINITE;
144+
Value averageScore = -VALUE_INFINITE;
145+
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
146+
Value uciScore = -VALUE_INFINITE;
147+
bool scoreLowerbound = false;
148+
bool scoreUpperbound = false;
149+
bool previousScoreExact = false;
150+
int selDepth = 0;
151+
int tbRank = 0;
151152
Value tbScore;
152153
PVMoves pv, previousPV;
153154
};

0 commit comments

Comments
 (0)