@@ -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)
0 commit comments