LCOV - code coverage report
Current view: top level - Source - searcherDriver.cpp (source / functions) Coverage Total Hit
Test: coverage Lines: 81.7 % 240 196
Test Date: 2026-03-02 16:42:41 Functions: 100.0 % 2 2

            Line data    Source code
       1              : #include "com.hpp"
       2              : #include "distributed.h"
       3              : #include "dynamicConfig.hpp"
       4              : #include "energyMonitor.hpp"
       5              : #include "logging.hpp"
       6              : #include "searcher.hpp"
       7              : #include "skill.hpp"
       8              : #include "timeMan.hpp"
       9              : #include "uci.hpp"
      10              : #include "xboard.hpp"
      11              : 
      12              : namespace {
      13              : // Sizes and phases of the skip-blocks, used for distributing search depths across the threads, from stockfish
      14              : constexpr unsigned int threadSkipSize = 20;
      15              : constexpr array1d<int,threadSkipSize> skipSize  = {1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4};
      16              : constexpr array1d<int,threadSkipSize> skipPhase = {0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7};
      17              : } // namespace
      18              : 
      19              : // Output following chosen protocol
      20         1363 : void Searcher::displayGUI(DepthType          depth,
      21              :                           DepthType          seldepth,
      22              :                           ScoreType          bestScore,
      23              :                           unsigned int       ply,
      24              :                           const PVList&      pv,
      25              :                           int                multipv,
      26              :                           const std::string& mark) {
      27         1363 :    const auto     now           = Clock::now();
      28              :    const TimeType ms            = getTimeDiff(startTime);
      29         1363 :    getSearchData().times[depth] = ms;
      30         1363 :    if (subSearch) return; // no need to display stuff for subsearch
      31          274 :    std::stringstream str;
      32          274 :    const Counter nodeCount = ThreadPool::instance().counter(Stats::sid_nodes) + ThreadPool::instance().counter(Stats::sid_qnodes);
      33          274 :    if (Logging::ct == Logging::CT_xboard) {
      34            0 :       str << static_cast<int>(depth) << " "
      35            0 :           << bestScore << " "
      36            0 :           << ms / 10 << " " // csec
      37            0 :           << nodeCount << " ";
      38            0 :       if (DynamicConfig::fullXboardOutput)
      39            0 :          str << static_cast<int>(seldepth) << " "
      40            0 :              << static_cast<Counter>(nodeCount / ms) << " "  // knps
      41            0 :              << ThreadPool::instance().counter(Stats::sid_tbHit1) + ThreadPool::instance().counter(Stats::sid_tbHit2);
      42            0 :       str << "\t" << ToString(pv);
      43            0 :       if (!mark.empty()) str << mark;
      44              :    }
      45          274 :    else if (Logging::ct == Logging::CT_uci) {
      46          306 :       const std::string multiPVstr = DynamicConfig::multiPV > 1 ? (" multipv " + std::to_string(multipv)) : "";
      47              :       str << "info" << multiPVstr
      48              :           << " depth " << static_cast<int>(depth)
      49          274 :           << " score " << UCI::uciScore(bestScore, ply)
      50          274 :           << " time " << ms // msec
      51              :           << " nodes " << nodeCount
      52          274 :           << " nps " << static_cast<Counter>(static_cast<double>(nodeCount) / msec2sec(ms)) // nps
      53          274 :           << " seldepth " << static_cast<int>(seldepth) << " tbhits "
      54          274 :           << ThreadPool::instance().counter(Stats::sid_tbHit1) + ThreadPool::instance().counter(Stats::sid_tbHit2);
      55          274 :       static auto lastHashFull = Clock::now(); ///@todo slow here because of guard variable
      56          307 :       if (getTimeDiff(now,lastHashFull) > 500 &&
      57           33 :           getTimeDiff(now,startTime)*2 < ThreadPool::instance().main().getCurrentMoveMs()) {
      58           33 :          lastHashFull = now;
      59           33 :          str << " hashfull " << TT::hashFull();
      60              :       }
      61          548 :       str << " pv " << ToString(pv); //only add pv at the end (c-chess-cli doesn't like to read something after pv...)
      62              :    }
      63              : #ifdef WITH_FMTLIB
      64              :    else if (Logging::ct == Logging::CT_pretty){
      65              :       str << fmt::format(fmt::fg(fmt::color::steel_blue) | fmt::emphasis::italic, "{:>3}/{:<3} ", depth, seldepth);
      66              :       if(bestScore > 100){
      67              :          str << fmt::format(fmt::fg(fmt::color::forest_green) | fmt::emphasis::bold, "{:>6} ", UCI::uciScore(bestScore, ply));
      68              :       }
      69              :       else if (bestScore < -100){
      70              :          str << fmt::format(fmt::fg(fmt::color::fuchsia) | fmt::emphasis::bold, "{:>6} ", UCI::uciScore(bestScore, ply));
      71              :       }
      72              :       else str << fmt::format(fmt::fg(fmt::color::light_gray) | fmt::emphasis::bold, "{:>6} ", UCI::uciScore(bestScore, ply));
      73              :       str << fmt::format(fmt::fg(fmt::color::steel_blue) , "{:>6}.{:0>3}s ", ms/1000, ms-(ms/1000)*1000);
      74              :       str << fmt::format(fmt::fg(fmt::color::steel_blue) | fmt::emphasis::faint, "{:>6} knps ", static_cast<Counter>(static_cast<double>(nodeCount)/ 1000 / msec2sec(ms)));
      75              :       str << fmt::format(fmt::fg(fmt::color::steel_blue) | fmt::emphasis::faint, "{:>12} nodes ", nodeCount);
      76              :       str << fmt::format(fmt::fg(fmt::color::floral_white) | fmt::emphasis::faint, ToString(pv));
      77              :    }
      78              : #endif   
      79          274 :    Logging::LogIt(Logging::logGUI) << str.str();
      80          274 : }
      81              : 
      82          223 : void Searcher::searchDriver(bool postMove) {
      83              :    //stopFlag = false; // shall be only done outside to avoid race condition
      84          223 :    height_ = 0;
      85              : 
      86          431 :    if (isMainThread()) Distributed::sync(Distributed::_commStat2, "start of search driver");
      87              : 
      88              :    // we start by a copy, because position object must be mutable here.
      89          223 :    Position p(_data.p);
      90              : #ifdef WITH_NNUE
      91              :    // Create an evaluator and reset it with the current position
      92          223 :    NNUEEvaluator nnueEvaluator;
      93              :    p.associateEvaluator(nnueEvaluator);
      94          223 :    p.resetNNUEEvaluator(nnueEvaluator);
      95              : #endif
      96              : 
      97              :    //if (isMainThread()) p.initCaslingPermHashTable(); // let's be sure ...
      98              : 
      99              :    // requested depth can be changed according to level or skill parameter
     100          223 :    DynamicConfig::level = DynamicConfig::limitStrength ? Skill::convertElo2Level() : DynamicConfig::level;
     101          223 :    DepthType maxDepth   = _data.depth; // _data.depth will be reset to zero later
     102              :    bool depthLimitedSearch = false;
     103              :    if (Distributed::isMainProcess()) {
     104          243 :       maxDepth = asLeastOne( (Skill::enabled() && !DynamicConfig::nodesBasedLevel) ? std::min(maxDepth, Skill::limitedDepth()) : maxDepth );
     105              :       depthLimitedSearch = maxDepth != _data.depth;
     106              :    }
     107              :    else {
     108              :       // other process performs infinite search
     109              :       maxDepth      = MAX_DEPTH;
     110              :       currentMoveMs = INFINITETIME; // overrides currentMoveMs
     111              :    }
     112          223 :    _data.depth = 0; // reset depth as it will be used to return searched depth later
     113              : 
     114              :    // when limiting skill by nodes only, only apply that to main process
     115              :    // this doesn't make much sense to apply limited nodes on a multi-process run anyway ...
     116          223 :    if (Distributed::isMainProcess() && Skill::enabled() && DynamicConfig::nodesBasedLevel) {
     117            0 :       TimeMan::maxNodes = Skill::limitedNodes();
     118            0 :       Logging::LogIt(Logging::logDebug) << "Limited nodes to fit level: " << TimeMan::maxNodes;
     119              :    }
     120              : 
     121              :    // initialize some search variable
     122          223 :    moveDifficulty = MoveDifficultyUtil::MD_std;
     123          223 :    positionEvolution = MoveDifficultyUtil::PE_std;
     124          223 :    startTime      = Clock::now();
     125              : 
     126              :    // check game history for potential situations (boom/moob)
     127          223 :    if (isMainThread()) {
     128          208 :       if (isBooming(p.halfmoves)) 
     129          277 :          positionEvolution = stack[p.halfmoves-2].eval > MoveDifficultyUtil::emergencyAttackThreshold ? MoveDifficultyUtil::PE_boomingAttack : 
     130              :                                                                                                         MoveDifficultyUtil::PE_boomingDefence;
     131           35 :       else if (isMoobing(p.halfmoves))
     132           32 :          positionEvolution = stack[p.halfmoves-2].eval > MoveDifficultyUtil::emergencyAttackThreshold ? MoveDifficultyUtil::PE_moobingAttack : 
     133              :                                                                                                         MoveDifficultyUtil::PE_moobingDefence;
     134              :    }
     135              : 
     136              :    // Main thread only will reset tables
     137          223 :    if (isMainThread()) {
     138          208 :       TT::age();
     139          208 :       MoveDifficultyUtil::variability = 1.f; // not usefull for co-searcher threads that won't depend on time
     140          208 :       ThreadPool::instance().clearSearch();  // reset tables for all threads !
     141              :    }
     142          223 :    if (isMainThread() || id() >= MAX_THREADS) {
     143          208 :       Logging::LogIt(Logging::logInfo) << "Search params :";
     144          416 :       Logging::LogIt(Logging::logInfo) << "requested time  " << getCurrentMoveMs() << " (" << currentMoveMs << ")"; // won't exceed TimeMan::maxTime
     145          416 :       Logging::LogIt(Logging::logInfo) << "requested depth " << static_cast<int>(maxDepth);
     146              :    }
     147              :    // other threads will wait here for start signal
     148              :    else {
     149           30 :       Logging::LogIt(Logging::logInfo) << "helper thread waiting ... " << id();
     150      1013116 :       while (startLock.load()) { ; }
     151           30 :       Logging::LogIt(Logging::logInfo) << "... go for id " << id();
     152              :    }
     153              : 
     154              :    // fill "root" position stack data
     155              :    {
     156          223 :       EvalData  eData;
     157          223 :       ScoreType e = eval(p, eData, *this, true);
     158          223 :       assert(p.halfmoves < MAX_PLY && p.halfmoves >= 0);
     159          446 :       stack[p.halfmoves] = {p, computeHash(p), /*eData,*/ e, INVALIDMINIMOVE};
     160              :    }
     161              : 
     162              :    // reset output search results
     163              :    _data.reset();
     164              : 
     165          223 :    const bool isInCheck = isPosInCheck(p);
     166              : 
     167              :    // initialize multiPV stuff
     168          223 :    DynamicConfig::multiPV = (Logging::ct == Logging::CT_uci ? DynamicConfig::multiPV : 1);
     169          224 :    if (Skill::enabled() && !DynamicConfig::nodesBasedLevel) { DynamicConfig::multiPV = std::max(DynamicConfig::multiPV, 4u); }
     170          223 :    Logging::LogIt(Logging::logInfo) << "MultiPV " << DynamicConfig::multiPV;
     171          223 :    std::vector<MultiPVScores> multiPVMoves(DynamicConfig::multiPV, {INVALIDMOVE, matedScore(0), {}, 0});
     172              :    // in multipv mode _data.score cannot be use a the aspiration loop score
     173          223 :    std::vector<ScoreType> currentScore(DynamicConfig::multiPV, 0);
     174              : 
     175              :    // handle "maxNodes" style search (will always complete depth 1 search)
     176          223 :    const auto maxNodes = TimeMan::maxNodes;
     177              :    // reset this for depth 1 to be sure to iterate at least once ...
     178              :    // on main process, requested value will be restored, but not on other process
     179          223 :    TimeMan::maxNodes = 0;
     180              : 
     181              :    double EBF = 1.7;
     182              : 
     183              :    // using MAX_DEPTH-6 so that draw can be found for sure ///@todo I don't understand this -6 anymore ..
     184          223 :    const DepthType targetMaxDepth = std::min(maxDepth, static_cast<DepthType>(MAX_DEPTH - 6));
     185              : 
     186          223 :    const bool isFiniteTimeSearch = maxNodes == 0 && !depthLimitedSearch && currentMoveMs < INFINITETIME && TimeMan::msecUntilNextTC > 0 && !getData().isPondering && !getData().isAnalysis;
     187              : 
     188              :    // forced bongcloud
     189          223 :    if (DynamicConfig::bongCloud && (p.castling & (p.c == Co_White ? C_w_all : C_b_all)) ){
     190            0 :       constexpr array1d<Move,5> wbc = { ToMove(Sq_e1,Sq_e2,T_std), ToMove(Sq_e1,Sq_d1,T_std), ToMove(Sq_e1,Sq_f1,T_std), ToMove(Sq_e1,Sq_d2,T_std), ToMove(Sq_e1,Sq_f2,T_std)};
     191            0 :       constexpr array1d<Move,5> bbc = { ToMove(Sq_e8,Sq_e7,T_std), ToMove(Sq_e8,Sq_d8,T_std), ToMove(Sq_e8,Sq_f8,T_std), ToMove(Sq_e8,Sq_d7,T_std), ToMove(Sq_e8,Sq_f7,T_std)};
     192            0 :       MoveList moves;
     193            0 :       MoveGen::generate(p,moves);
     194            0 :       for (int i = 0 ; i < 5; ++i){
     195            0 :          const Move m = p.c == Co_White ? wbc[i] : bbc[i];
     196            0 :          for (const auto & it : moves){
     197            0 :             if (sameMove(m,it)){
     198            0 :                _data.score = 0;
     199            0 :                _data.pv.emplace_back(m);
     200            0 :                goto pvsout;
     201              :             }
     202              :          }
     203              :       }
     204              :    }
     205              : 
     206              :    // random mover can be forced for the few first moves of a game or by setting level to 0
     207          223 :    if (DynamicConfig::level == 0 || p.halfmoves < DynamicConfig::randomPly || currentMoveMs <= TimeMan::msecMinimal) {
     208            0 :       if (p.halfmoves < DynamicConfig::randomPly) Logging::LogIt(Logging::logInfo) << "Randomized ply";
     209            0 :       _data.score = randomMover(p, _data.pv, isInCheck);
     210            0 :       goto pvsout;
     211              :    }
     212              : 
     213              :    // forced move detection
     214              :    // only main thread here (stopflag will be triggered anyway for other threads if needed)
     215          223 :    if (!Distributed::moreThanOneProcess() && isMainThread() && DynamicConfig::multiPV == 1 && isFiniteTimeSearch && currentMoveMs > 100) { ///@todo should work with nps here
     216            1 :       _data.score = pvs<true>(matedScore(0), matingScore(0), p, 1, 0, _data.pv, _data.seldepth, 0, isInCheck, false); // depth 1 search to get real valid moves
     217              :       // only one : check evasion or zugzwang
     218            1 :       if (rootScores.size() == 1) {
     219            0 :          moveDifficulty = MoveDifficultyUtil::MD_forced;
     220              :       }
     221              :    }
     222              : 
     223              :    // ID loop
     224         1301 :    for (DepthType depth = 1; depth <= targetMaxDepth && !stopFlag; ++depth) {
     225              : 
     226              :       // MultiPV loop
     227         1078 :       std::vector<MiniMove> skipMoves;
     228         2173 :       for (unsigned int multi = 0; multi < DynamicConfig::multiPV && !stopFlag; ++multi) {
     229              :          // No need to continue multiPV loop if a mate is found
     230         1095 :          if (!skipMoves.empty() && isMatedScore(currentScore[multi])) break;
     231              : 
     232         1095 :          if (isMainThread()) {
     233         1086 :             if (depth > 1) {
     234              :                // delayed other thread start (can use a depth condition...)
     235          869 :                if (startLock.load()) {
     236          201 :                   Logging::LogIt(Logging::logInfo) << "Unlocking other threads";
     237              :                   startLock.store(false);
     238              :                }
     239              :             }
     240              :          }
     241              :          // stockfish like thread management (not for co-searcher)
     242            9 :          else if (!subSearch) {
     243            9 :             const auto i = (id() - 1) % threadSkipSize;
     244            9 :             if (((depth + skipPhase[i]) / skipSize[i]) % 2){
     245            4 :                Logging::LogIt(Logging::logInfo) << "Thread " << id() << " skipping depth " << static_cast<int>(depth);
     246            2 :                continue; // next depth
     247              :             }
     248              :          }
     249              : 
     250         3279 :          Logging::LogIt(Logging::logInfo) << "Thread " << id() << " searching depth " << static_cast<int>(depth);
     251              : 
     252              : #ifndef WITH_EVAL_TUNING
     253         1093 :          contempt = {ScoreType((p.c == Co_White ? +1 : -1) * (DynamicConfig::contempt + DynamicConfig::contemptMG) * DynamicConfig::ratingFactor),
     254         1093 :                      ScoreType((p.c == Co_White ? +1 : -1) * DynamicConfig::contempt * DynamicConfig::ratingFactor)};
     255              : #else
     256              :          contempt = 0;
     257              : #endif
     258              :          // dynamic contempt ///@todo tune this
     259         1093 :          if (contempt[EG] >= 0){
     260          608 :             contempt += static_cast<ScoreType>(std::round(25 * std::tanh(currentScore[multi] / 400.f))); // slow but ok here
     261              :          }
     262         2186 :          Logging::LogIt(Logging::logInfo) << "Dynamic contempt " << contempt;
     263              : 
     264              :          // initialize aspiration window loop variables
     265         1093 :          PVList    pvLoc;
     266              :          ScoreType delta =
     267         1093 :              (SearchConfig::doWindow && depth > SearchConfig::aspirationMinDepth)
     268          266 :                  ? SearchConfig::aspirationInit + std::max(0, SearchConfig::aspirationDepthInit - SearchConfig::aspirationDepthCoef * depth)
     269         1359 :                  : matingScore(0); // MATE not INFSCORE in order to enter the loop below once
     270         1093 :          ScoreType alpha       = std::max(static_cast<ScoreType>(currentScore[multi] - delta), matedScore(0));
     271         1414 :          ScoreType beta        = std::min(static_cast<ScoreType>(currentScore[multi] + delta), matingScore(0));
     272              :          ScoreType score       = 0;
     273              :          DepthType windowDepth = depth;
     274         1093 :          Logging::LogIt(Logging::logInfo) << "Inital window: " << alpha << ".." << beta;
     275              : 
     276              :          // Aspiration loop
     277         1370 :          while (!stopFlag) {
     278              :             pvLoc.clear();
     279         1384 :             score = pvs<true>(alpha, beta, p, windowDepth, 0, pvLoc, _data.seldepth, 0, isInCheck, false, skipMoves.empty() ? nullptr : &skipMoves);
     280         1369 :             if (stopFlag) break;
     281         1368 :             ScoreType matW = 0;
     282         1368 :             ScoreType matB = 0;
     283         1368 :             delta += static_cast<ScoreType>((delta / 4) * std::exp(1.f - gamePhase(p.mat,matW,matB))); // in end-game, open window faster
     284         2736 :             if (delta > std::max(128, 1024*4/depth) ) delta = matingScore(0);
     285         1368 :             if (alpha > matedScore(0) && score <= alpha) {
     286              :                windowDepth = depth;
     287          120 :                beta  = std::min(matingScore(0), static_cast<ScoreType>((alpha + beta) / 2));
     288          120 :                alpha = std::max(static_cast<ScoreType>(score - delta), matedScore(0));
     289          120 :                Logging::LogIt(Logging::logInfo) << "Increase window alpha " << alpha << ".." << beta;
     290          120 :                if (isMainThread() && DynamicConfig::multiPV == 1) {
     291          120 :                   PVList pv2;
     292          120 :                   TT::getPV(p, *this, pv2);
     293          120 :                   displayGUI(depth, _data.seldepth, score, p.halfmoves, pv2, multi + 1, "?");
     294          120 :                }
     295              :             }
     296         1248 :             else if (beta < matingScore(0) && score >= beta) {
     297          157 :                --windowDepth; // from Ethereal
     298          160 :                beta = std::min(static_cast<ScoreType>(score + delta), matingScore(0));
     299          157 :                Logging::LogIt(Logging::logInfo) << "Increase window beta " << alpha << ".." << beta;
     300          157 :                if (isMainThread() && DynamicConfig::multiPV == 1) {
     301          157 :                   PVList pv2;
     302          157 :                   TT::getPV(p, *this, pv2);
     303          157 :                   displayGUI(depth, _data.seldepth, score, p.halfmoves, pv2, multi + 1, "!");
     304          157 :                }
     305              :             }
     306              :             else
     307              :                break;
     308              : 
     309              :          } // Aspiration loop end
     310              : 
     311         1093 :          if (stopFlag) {
     312            2 :             if (multi != 0 && isMainThread()) {
     313              :                // handle multiPV display only based on previous ID iteration data
     314            0 :                displayGUI(depth - 1, multiPVMoves[multi].seldepth, multiPVMoves[multi].s, p.halfmoves, multiPVMoves[multi].pv, multi + 1);
     315              :             }
     316              :          }
     317              :          else {
     318              :             // this aspiration multipv loop was fully done, let's update results
     319         1091 :             _data.depth         = depth;
     320         1091 :             currentScore[multi] = score;
     321              : 
     322              :             // In multiPV mode, fill skipmove for next multiPV iteration
     323         1091 :             if (!pvLoc.empty() && DynamicConfig::multiPV > 1) {
     324           21 :                skipMoves.emplace_back(Move2MiniMove(pvLoc[0]));
     325              :             }
     326              : 
     327              :             // backup multiPV info
     328         1091 :             if (!pvLoc.empty()){
     329         1085 :                multiPVMoves[multi].m = pvLoc[0];
     330              :             }
     331         1091 :             multiPVMoves[multi].s = score;
     332         1091 :             multiPVMoves[multi].pv = pvLoc;
     333         1091 :             multiPVMoves[multi].seldepth = _data.seldepth;
     334              : 
     335              :             // update the outputed pv only with the best move line
     336         1091 :             if (multi == 0) {
     337         1076 :                std::unique_lock lock(_mutexPV);
     338         1076 :                _data.pv    = pvLoc;
     339         1076 :                _data.depth = depth;
     340         1076 :                _data.score = score;
     341              :             }
     342              : 
     343         1091 :             if (isMainThread()) {
     344              :                // output to GUI
     345         2172 :                displayGUI(depth, _data.seldepth, multiPVMoves[multi].s, p.halfmoves, pvLoc, multi + 1);
     346              :             }
     347              : 
     348         1091 :             if (isMainThread() && multi == 0) {
     349              :                // store current depth info
     350         1074 :                getSearchData().scores[depth] = _data.score;
     351         1074 :                getSearchData().nodes[depth]  = ThreadPool::instance().counter(Stats::sid_nodes) + ThreadPool::instance().counter(Stats::sid_qnodes);
     352         1074 :                if (!pvLoc.empty()) getSearchData().moves[depth] = Move2MiniMove(pvLoc[0]);
     353              : 
     354              :                // check for an emergency : 
     355              :                // if IID reports decreasing score, we have to take action (like search for longer)
     356            0 :                if (TimeMan::isDynamic && 
     357            0 :                    depth > MoveDifficultyUtil::emergencyMinDepth && 
     358         1074 :                    moveDifficulty == MoveDifficultyUtil::MD_std &&
     359            0 :                    _data.score < getSearchData().scores[depth - 2] - MoveDifficultyUtil::emergencyMargin) {
     360            0 :                   moveDifficulty = _data.score > MoveDifficultyUtil::emergencyAttackThreshold ? MoveDifficultyUtil::MD_moobAttackIID
     361              :                                                                                               : MoveDifficultyUtil::MD_moobDefenceIID;
     362            0 :                   Logging::LogIt(Logging::logInfo) << "Emergency mode activated : " << _data.score << " < "
     363            0 :                                                    << getSearchData().scores[depth - 2] - MoveDifficultyUtil::emergencyMargin;
     364              :                }
     365              : 
     366              :                // update a "variability" measure to scale remaining time on it ///@todo tune this more
     367         1074 :                if (depth > 12 && !pvLoc.empty()) {
     368           26 :                   if (/*getSearchData().moves[depth] != getSearchData().moves[depth - 2] &&*/
     369           26 :                   std::fabs(getSearchData().scores[depth] - getSearchData().scores[depth - 2]) > MoveDifficultyUtil::emergencyMargin/4 )
     370           14 :                      MoveDifficultyUtil::variability *= (1.f + depth/100.f);
     371              :                   else
     372           12 :                      MoveDifficultyUtil::variability *= 0.98f;
     373           26 :                   Logging::LogIt(Logging::logInfo) << "Variability :" << MoveDifficultyUtil::variability;
     374           26 :                   Logging::LogIt(Logging::logInfo) << "Variability time factor :" << MoveDifficultyUtil::variabilityFactor();
     375              :                }
     376              : 
     377              :                // check for remaining time
     378         1074 :                if (TimeMan::isDynamic && static_cast<TimeType>(static_cast<double>(getTimeDiff(startTime))*1.2*EBF) > getCurrentMoveMs()) {
     379            0 :                   stopFlag = true;
     380            0 :                   Logging::LogIt(Logging::logInfo) << "stopflag triggered, not enough time for next depth";
     381              :                   break;
     382              :                }
     383              : 
     384              :                // compute EBF
     385         1074 :                if (depth > 12) {
     386           52 :                   EBF = getSearchData().nodes[depth] / static_cast<double>(asLeastOne(getSearchData().nodes[depth - 1]));
     387           26 :                   Logging::LogIt(Logging::logInfo) << "EBF  " << EBF;
     388           26 :                   Logging::LogIt(Logging::logInfo) << "EBF2 "
     389           26 :                                                    << ThreadPool::instance().counter(Stats::sid_qnodes) /
     390           52 :                                                       static_cast<double>(asLeastOne(ThreadPool::instance().counter(Stats::sid_nodes)));
     391              :                }
     392              : 
     393              :                // sync (pull) stopflag in other process
     394              :                if (!Distributed::isMainProcess()) {
     395              :                   bool masterStopFlag;
     396              :                   Distributed::get(&masterStopFlag, 1, Distributed::_winStopFromR0, 0);
     397              :                   ThreadPool::instance().main().stopFlag = masterStopFlag;
     398              :                }
     399              :             }
     400              :          }
     401         1093 :       } // multiPV loop end
     402              : 
     403              :       // check for a node count stop
     404         1078 :       if (isMainThread() || isStoppableCoSearcher) {
     405              :          // restore real value (only on main processus!), was discarded for depth 1 search
     406         1074 :          if (Distributed::isMainProcess()) TimeMan::maxNodes = maxNodes;
     407         2148 :          const Counter nodeCount = isStoppableCoSearcher ? stats.counters[Stats::sid_nodes] + stats.counters[Stats::sid_qnodes]
     408         1074 :                                  : ThreadPool::instance().counter(Stats::sid_nodes) + ThreadPool::instance().counter(Stats::sid_qnodes);
     409         1074 :          if (TimeMan::maxNodes > 0 && nodeCount > TimeMan::maxNodes) {
     410            0 :             stopFlag = true;
     411            0 :             Logging::LogIt(Logging::logInfo) << "stopFlag triggered in search driver (nodes limits) in thread " << id();
     412              :          }
     413              :       }
     414              : 
     415              :    } // iterative deepening loop end
     416              : 
     417          223 : pvsout:
     418              : 
     419              :    // for all thread, best move is set to first pv move (if there is one ...)
     420              :    // this will be changed later for main thread to take skill or best move into account
     421          223 :    if (_data.pv.empty()) _data.best = INVALIDMOVE;
     422          207 :    else                  _data.best = _data.pv[0];
     423              : 
     424          223 :    if (isMainThread()) {
     425              :       // in case of very very short depth or time, "others" threads may still be blocked
     426          208 :       Logging::LogIt(Logging::logInfo) << "Unlocking other threads (end of search)";
     427              :       startLock.store(false);
     428              : 
     429              :       // all threads are updating their output values but main one is looking for the longest pv
     430              :       // note that depth, score, seldepth and pv are already updated on-the-fly
     431          208 :       if (_data.pv.empty()) {
     432            5 :          if (!subSearch) Logging::LogIt(Logging::logWarn) << "Empty pv";
     433              :       }
     434              :       else {
     435              :          // !!! warning: when skill uses multiPV, returned move shall be used and not first move of pv in receiveMoves !!!
     436          205 :          if (Skill::enabled() && !DynamicConfig::nodesBasedLevel) { _data.best = Skill::pick(multiPVMoves); }
     437              :          else {
     438              :             // get pv from best (deepest) threads
     439          201 :             DepthType bestDepth    = _data.depth;
     440              :             size_t    bestThreadId = 0;
     441          402 :             for (const auto& s : ThreadPool::instance()) {
     442          201 :                std::unique_lock lock(_mutexPV);
     443          201 :                if (s->getData().depth > bestDepth) {
     444            0 :                   bestThreadId = s->id();
     445            0 :                   bestDepth    = s->getData().depth;
     446            0 :                   Logging::LogIt(Logging::logInfo) << "Better thread ! " << bestThreadId << ", depth " << static_cast<int>(bestDepth);
     447              :                }
     448              :             }
     449              :             // update data with best data available
     450          201 :             _data      = ThreadPool::instance()[bestThreadId]->getData();
     451          201 :             _data.best = _data.pv[0]; ///@todo this can lead to best move not being coherent with last reported PV
     452              :          }
     453              :          // update stack data on all searcher with "real" score
     454              :          // this way all stack[k (with k < p.halfmove)] will be "history" of the game accessible to searcher
     455          422 :          for (auto& s : ThreadPool::instance()) {
     456          217 :             s->stack[p.halfmoves].eval = _data.score;
     457              :          }
     458              :       }
     459              : 
     460              :       // wait for "ponderhit" or "stop" in case search returned too soon
     461          208 :       if (!stopFlag && (getData().isPondering || getData().isAnalysis)) {
     462            0 :          Logging::LogIt(Logging::logInfo) << "Waiting for ponderhit or stop ...";
     463            0 :          while (!stopFlag && (getData().isPondering || getData().isAnalysis)) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
     464            0 :          Logging::LogIt(Logging::logInfo) << "... ok";
     465              :       }
     466              : 
     467              :       // now send stopflag to all threads
     468          208 :       ThreadPool::instance().stop();
     469              :       // and wait for them
     470          208 :       ThreadPool::instance().wait(true);
     471              : 
     472              :       // gather various process stats
     473              :       Distributed::syncStat();
     474              :       // share our TT
     475              :       Distributed::syncTT();
     476              : 
     477              :       // display search statistics (only when all threads and process are done and sync)
     478              :       if (Distributed::moreThanOneProcess()) { Distributed::showStat(); }
     479              :       else {
     480          208 :          ThreadPool::instance().displayStats();
     481              :       }
     482              : 
     483              :       // report energy consumption for this search
     484          208 :       if (auto* monitor = getEnergyMonitor()) {
     485              :          const TimeType searchDuration = getTimeDiff(startTime);
     486          208 :          monitor->reportEnergy(searchDuration, Logging::logInfoPrio);
     487          208 :          monitor->reportCost(searchDuration, Logging::logInfoPrio);
     488              :       }
     489              : 
     490          208 :       if (postMove) {
     491              :          // send move and ponder move to GUI
     492          208 :          const bool success = COM::receiveMoves(_data.best, _data.pv.size() > 1 && !(Skill::enabled() && !DynamicConfig::nodesBasedLevel) ? _data.pv[1] : INVALIDMOVE);
     493          208 :          if ( COM::protocol == COM::p_xboard){
     494              :             // update position state and history (this is only a xboard need)
     495            2 :             XBoard::moveApplied(success, _data.best);
     496              :          }
     497              :       }
     498              : 
     499          416 :       Distributed::sync(Distributed::_commMove, "end of search driver");
     500              :    } // isMainThread()
     501              : 
     502          446 :    Logging::LogIt(Logging::logInfoPrio) << "End of search driver for thread " << id();
     503              : 
     504          223 :    _searching = false;
     505          669 : }
        

Generated by: LCOV version 2.0-1