// id_loop() is the main iterative deepening loop. It calls search() repeatedly // with increasing depth until the allocated thinking time has been consumed, // user stops the search, or the maximum search depth is reached. private static void id_loop(Position pos) { var stack = new Stack[_.MAX_PLY + 4]; for (var idx = 0; idx < stack.Length; idx++) { stack[idx] = new Stack(); } var ss = new StackArrayWrapper(stack, 2); // To allow referencing (ss-2) and (ss+2) ValueT alpha, delta; var easyMove = EasyMove.get(pos.key()); EasyMove.clear(); //TODO: need to memset? //Math.Memset(ss - 2, 0, 5 * sizeof(Stack)); var depth = Depth.DEPTH_ZERO; BestMoveChanges = 0; var bestValue = delta = alpha = -Value.VALUE_INFINITE; var beta = Value.VALUE_INFINITE; TranspositionTable.new_search(); var multiPV = uint.Parse(OptionMap.Instance["MultiPV"].v); var skill = new Skill(int.Parse(OptionMap.Instance["Skill Level"].v)); // When playing with strength handicap enable MultiPV search that we will // use behind the scenes to retrieve a set of possible moves. if (skill.enabled()) { multiPV = Math.Max(multiPV, 4); multiPV = Math.Max(multiPV, 4); multiPV = Math.Max(multiPV, 4); } multiPV = (uint) Math.Min(multiPV, RootMoves.Count); // Iterative deepening loop until requested to stop or target depth reached; while (++depth < _.MAX_PLY && !Signals.stop && (Limits.depth == 0 || depth <= Limits.depth)) { // Age out PV variability metric BestMoveChanges *= 0.5; // Save the last iteration's scores before first PV line is searched and // all the move scores except the (new) PV are set to -VALUE_INFINITE. foreach (var rm in RootMoves) { rm.previousScore = rm.score; } // MultiPV loop. We perform a full root search for each PV line for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx) { // Reset aspiration window starting size if (depth >= 5*Depth.ONE_PLY_C) { delta = Value.Create(16); alpha = Value.Create(Math.Max(RootMoves[(int) PVIdx].previousScore - delta, -Value.VALUE_INFINITE)); beta = Value.Create(Math.Min(RootMoves[(int) PVIdx].previousScore + delta, Value.VALUE_INFINITE)); } // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we're not failing // high/low anymore. while (true) { bestValue = search(NodeType.Root, false, pos, ss, alpha, beta, depth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the // first and eventually the new best one are set to -VALUE_INFINITE // and we want to keep the same order for all the moves except the // new PV that goes to the front. Note that in case of MultiPV // search the already searched PV lines are preserved. //TODO: Check for stable sort replacement Utils.stable_sort(RootMoves, (int) PVIdx, RootMoves.Count); //std::stable_sort(RootMoves.begin() + PVIdx, RootMoves.end()); // Write PV back to transposition table in case the relevant // entries have been overwritten during the search. for (var i = 0; i <= PVIdx; ++i) { RootMoves[i].insert_pv_in_tt(pos); } // If search has been stopped break immediately. Sorting and // writing PV back to TT is safe because RootMoves is still // valid, although it refers to previous iteration. if (Signals.stop) { break; } // When failing high/low give some update (without cluttering // the UI) before a re-search. if (multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && TimeManagement.elapsed() > 3000) { Output.WriteLine(UCI.pv(pos, depth, alpha, beta)); } // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. if (bestValue <= alpha) { beta = (alpha + beta)/2; alpha = Value.Create(Math.Max(bestValue - delta, -Value.VALUE_INFINITE)); Signals.failedLowAtRoot = true; Signals.stopOnPonderhit = false; } else if (bestValue >= beta) { alpha = (alpha + beta)/2; beta = Value.Create(Math.Min(bestValue + delta, Value.VALUE_INFINITE)); } else { break; } delta += delta/2; Debug.Assert(alpha >= -Value.VALUE_INFINITE && beta <= Value.VALUE_INFINITE); } // Sort the PV lines searched so far and update the GUI //TODO: Check for stable sort replacement Utils.stable_sort(RootMoves, 0, (int) PVIdx + 1); //std::stable_sort(RootMoves.begin(), RootMoves.begin() + PVIdx + 1); if (Signals.stop) { Output.WriteLine($"info nodes {RootPos.nodes_searched()} time {TimeManagement.elapsed()}"); } else if (PVIdx + 1 == multiPV || TimeManagement.elapsed() > 3000) { Output.WriteLine(UCI.pv(pos, depth, alpha, beta)); } } // If skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(depth)) { skill.pick_best(multiPV); } // Have we found a "mate in x"? if (Limits.mate != 0 && bestValue >= Value.VALUE_MATE_IN_MAX_PLY && Value.VALUE_MATE - bestValue <= 2*Limits.mate) { Signals.stop = true; } // Do we have time for the next iteration? Can we stop searching now? if (Limits.use_time_management()) { if (!Signals.stop && !Signals.stopOnPonderhit) { // Take some extra time if the best move has changed if (depth > 4*Depth.ONE_PLY && multiPV == 1) { TimeManagement.pv_instability(BestMoveChanges); } // Stop the search if only one legal move is available or all // of the available time has been used or we matched an easyMove // from the previous search and just did a fast verification. if (RootMoves.Count == 1 || TimeManagement.elapsed() > TimeManagement.available() || (RootMoves[0].pv[0] == easyMove && BestMoveChanges < 0.03 && TimeManagement.elapsed() > TimeManagement.available()/10)) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". if (Limits.ponder) { Signals.stopOnPonderhit = true; } else { Signals.stop = true; } } } if (RootMoves[0].pv.Count >= 3) { EasyMove.update(pos, RootMoves[0].pv); } else { EasyMove.clear(); } } } // Clear any candidate easy move that wasn't stable for the last search // iterations; the second condition prevents consecutive fast moves. if (EasyMove.stableCnt < 6 || TimeManagement.elapsed() < TimeManagement.available()) { EasyMove.clear(); } // If skill level is enabled, swap best PV line with the sub-optimal one if (skill.enabled()) { var foundIdx = RootMoves.FindIndex(rootMove1 => rootMove1.pv[0] == skill.best_move(multiPV)); Debug.Assert(foundIdx >= 0); var rootMove = RootMoves[0]; RootMoves[0] = RootMoves[foundIdx]; RootMoves[foundIdx] = rootMove; } }