diff --git a/source/engine/yaneuraou-engine/yaneuraou-search.cpp b/source/engine/yaneuraou-engine/yaneuraou-search.cpp index b180e09ac..0fc079013 100644 --- a/source/engine/yaneuraou-engine/yaneuraou-search.cpp +++ b/source/engine/yaneuraou-engine/yaneuraou-search.cpp @@ -332,7 +332,7 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply /*,int r50c */); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); +void update_quiet_histories(const Position& pos, Stack* ss, Move move, int bonus); void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth); @@ -950,7 +950,10 @@ void Thread::search() // ※ あまり同じ深さでつっかえている時は、aspiration windowの幅を大きくしてやるなどして回避する必要がある。 int searchAgainCounter = 0; + lowPlyHistory.fill(0); + // Iterative deepening loop until requested to stop or the target depth is reached + // 要求があるか、または目標深度に達するまで反復深化ループを実行します // 1つ目のrootDepthはこのthreadの反復深化での探索中の深さ。 // 2つ目のrootDepth (Threads.main()->rootDepth)は深さで探索量を制限するためのもの。 @@ -1571,9 +1574,6 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ASSERT_LV3(0 <= ss->ply && ss->ply < MAX_PLY); (ss + 1)->excludedMove = bestMove = MOVE_NONE; - - // 2手先のkillerの初期化。 - (ss + 2)->killers[0] = (ss + 2)->killers[1] = MOVE_NONE; (ss + 2)->cutoffCnt = 0; // 前の指し手で移動させた先の升目 @@ -1740,7 +1740,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // fail highしたquietなquietな(駒を取らない)ttMove(置換表の指し手)に対するボーナス if (!ttCapture) - update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); + update_quiet_histories(pos, ss, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) // 1手前の早い時点のquietの指し手に対する追加のペナルティ @@ -2337,19 +2337,13 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo (ss - 3)->continuationHistory , (ss - 4)->continuationHistory , nullptr , (ss - 6)->continuationHistory }; - // 1手前の指し手(1手前のtoとPiece)に対応するよさげな応手を統計情報から取得。 - // 1手前がnull moveの時prevSq == SQ_NONEになるのでこのケースは除外する。 - Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves(pos.piece_on(prevSq), prevSq) : MOVE_NONE; - - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, - &captureHistory, - contHist, + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->lowPlyHistory, + &thisThread->captureHistory, contHist, #if defined(ENABLE_PAWN_HISTORY) &thisThread->pawnHistory, #endif - countermove, - ss->killers); - + ss->ply + ); value = bestValue; @@ -2782,8 +2776,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // 【計測資料 18.】cut nodeのときにreductionを増やすかどうか。 if (cutNode) - r += 2 - (tte->depth() >= depth && ss->ttPv) - + (!ss->ttPv && move != ttMove && move != ss->killers[0]); + r += 2 - (tte->depth() >= depth && ss->ttPv); // Increase reduction if ttMove is a capture (~3 Elo) // 【計測資料 3.】置換表の指し手がcaptureのときにreduction量を増やす。 @@ -3374,8 +3367,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) // 置換表に登録するdepthはあまりマイナスの値だとおかしいので、 // 王手がかかっているときは、DEPTH_QS_CHECKS(=0)、 // 王手がかかっていないときはDEPTH_QS_NORMAL(-1)とみなす。 - ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS - : DEPTH_QS_NORMAL; + //ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS + // : DEPTH_QS_NORMAL; // ----------------------- // 置換表のprobe @@ -3563,21 +3556,23 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) const PieceToHistory* contHist[] = { (ss - 1)->continuationHistory, (ss - 2)->continuationHistory }; - // Initialize a MovePicker object for the current position, and prepare - // to search the moves. Because the depth is <= 0 here, only captures, - // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) - // will be generated. - // 取り合いの指し手だけ生成する // searchから呼び出された場合、直前の指し手がMOVE_NULLであることがありうる。この場合、SQ_NONEを設定する。 Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, - &thisThread->captureHistory, - contHist + + // Initialize a MovePicker object for the current position, and prepare to search + // the moves. We presently use two stages of move generator in quiescence search: + // captures, or evasions only when in check. + + // 現在の局面に対してMovePickerオブジェクトを初期化し、手を探索する準備を行います。 + // 現在、静止探索では2段階の手生成器を使用しています:キャプチャ、またはチェックされた場合の回避のみです。 + + MovePicker mp(pos, ttMove, DEPTH_QS, &thisThread->mainHistory, &thisThread->lowPlyHistory, + &thisThread->captureHistory, contHist #if defined(ENABLE_PAWN_HISTORY) ,&thisThread->pawnHistory #endif - ,prevSq); + ,ss->ply); // 王手回避の指し手のうちquiet(駒を捕獲しない)な指し手の数 int quietCheckEvasions = 0; @@ -3955,7 +3950,7 @@ void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestV : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move - update_quiet_stats(pos, ss, bestMove, bestMoveBonus); + update_quiet_histories(pos, ss, bestMove, bestMoveBonus); #if defined(ENABLE_PAWN_HISTORY) thisThread->pawnHistory(pawn_structure(pos), moved_piece, to_sq(bestMove)) @@ -3982,16 +3977,19 @@ void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestV captureHistory(moved_piece, to_sq(bestMove), captured) << quietMoveBonus; } - // Extra penalty for a quiet early move that was not a TT move or - // main killer move in previous ply when it gets refuted. + // Extra penalty for a quiet early move that was not a TT move in + // previous ply when it gets refuted. + + // quietな初期の手が、前の手でトランスポジションテーブル(TT)の手ではなく、 + // かつ反証された場合に追加のペナルティを与えます。 - // (ss-1)->ttHit : 一つ前のnodeで置換表にhitしたか + // ※ (ss-1)->ttHit : 一つ前のnodeで置換表にhitしたか // MOVE_NULLの場合、Stockfishでは65(移動後の升がSQ_NONEであることを保証している。やねうら王もそう変更した。) - if ( prevSq != SQ_NONE - && ( (ss - 1)->moveCount == 1 + (ss - 1)->ttHit - || ((ss - 1)->currentMove == (ss - 1)->killers[0])) - && !pos.captured_piece()) + if ( prevSq != SQ_NONE + && ( (ss - 1)->moveCount == 1 + (ss - 1)->ttHit ) + && !pos.captured_piece() + ) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus); // Decrease stats for all non-best capture moves @@ -4033,37 +4031,29 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) } } -// update_quiet_stats() updates move sorting heuristics +// Updates move sorting heuristics +// 手のソートのヒューリスティックを更新します -// update_quiet_stats()は、新しいbest moveが見つかったときに指し手の並べ替えheuristicsを更新する。 -// 具体的には駒を取らない指し手のstat tables、killer等を更新する。 +// ⇨ 新しいbest moveが見つかったときに指し手の並べ替えheuristicsを更新する。 +// 具体的には駒を取らない指し手のstat tables等を更新する。 // move = これが良かった指し手 -void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) -{ - // Update killers - // killerの指し手のupdate - // killer 2本しかないので[0]と違うならいまの[0]を[1]に降格させて[0]と差し替え - if (ss->killers[0] != move) - { - ss->killers[1] = ss->killers[0]; - ss->killers[0] = move; - } +void update_quiet_histories( + const Position& pos, Stack* ss, /*Search::Worker& workerThread, */ Move move, int bonus) { + + Color us = pos.side_to_move(); + Thread* workerThread = pos.this_thread(); + workerThread->mainHistory(us, from_to(move)) << bonus; + if (ss->ply < 4) + workerThread->lowPlyHistory(ss->ply, from_to(move)) << bonus; - // historyのupdate - Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); - thisThread->mainHistory(us, from_to(move)) << bonus; update_continuation_histories(ss, pos.moved_piece_after(move), to_sq(move), bonus); - // Update countermove history - if (is_ok((ss - 1)->currentMove)) - { - // 直前に移動させた升(その升に移動させた駒がある。今回の指し手はcaptureではないはずなので) - Square prevSq = to_sq((ss - 1)->currentMove); - thisThread->counterMoves(pos.piece_on(prevSq), prevSq) = move; - } +#if defined(ENABLE_PAWN_HISTORY) + int pIndex = pawn_structure_index(pos); + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus / 2; +#endif } #if 0 @@ -4573,6 +4563,8 @@ ValueAndPV search(Position& pos, int depth_, size_t multiPV /* = 1 */, u64 nodes Value delta = -VALUE_INFINITE; Value bestValue = -VALUE_INFINITE; + lowPlyHistory.fill(0); + while (++rootDepth <= depth // node制限を超えた場合もこのループを抜ける // 探索ノード数は、この関数の引数で渡されている。 diff --git a/source/movepick.cpp b/source/movepick.cpp index 1bd6994ca..217b45066 100644 --- a/source/movepick.cpp +++ b/source/movepick.cpp @@ -60,10 +60,10 @@ enum Stages: int { MAIN_TT, // 置換表の指し手を返すフェーズ CAPTURE_INIT, // (CAPTURESの指し手生成) GOOD_CAPTURE, // 捕獲する指し手(CAPTURES_PRO_PLUS)を生成して指し手を一つずつ返す - REFUTATION, // killer move,counter move QUIET_INIT, // (QUIETの指し手生成) - QUIET, // CAPTURES_PRO_PLUSで生成しなかった指し手を生成して、一つずつ返す。SEE値の悪い手は後回し。 + GOOD_QUIET, // CAPTURES_PRO_PLUSで生成しなかった指し手を生成して、一つずつ返す。SEE値の悪い手は後回し。 BAD_CAPTURE, // 捕獲する悪い指し手(SEE < 0 の指し手だが、将棋においてそこまで悪い手とは限らないが…) + BAD_QUIET, // 捕獲しない悪い指し手(SSE < 0) // 将棋ではBAD_CAPTUREをQUIET_の前にやったほうが良いという従来説は以下の実験データにより覆った。 // r300, 2585 - 62 - 2993(46.34% R - 25.46)[2016/08/19] @@ -92,16 +92,14 @@ enum Stages: int { QSEARCH_TT, // 置換表の指し手を返すフェーズ QCAPTURE_INIT, // (QCAPTUREの指し手生成) QCAPTURE, // 捕獲する指し手 + 歩を成る指し手を一手ずつ返す - QCHECK_INIT, // 王手となる指し手を生成 - QCHECK // 王手となる指し手(- 歩を成る指し手)を返すフェーズ }; /* 状態遷移の順番は、 王手がかかっていない時。 - 通常探索時 : MAIN_TT → CAPTURE_INIT → GOOD_CAPTURE → REFUTATION → QUIET_INIT → QUIET → BAD_CAPTURE + 通常探索時 : MAIN_TT → CAPTURE_INIT → GOOD_CAPTURE → QUIET_INIT → GOOD_QUIET → BAD_CAPTURE → BAD_QUIET ProbCut時 : PROBCUT_TT → PROBCUT_INIT → PROBCUT - 静止探索時 : QSEARCH_TT → QCAPTURE_INIT → QCAPTURE → (王手を生成するなら) QCHECK_INIT → QCHECK + 静止探索時 : QSEARCH_TT → QCAPTURE_INIT → QCAPTURE ※ 通常探索時にしか、REFUTATIONを呼び出していないので、すなわちProbCut時と静止探索時には killerとかcountermoveの生成はしない。 @@ -142,101 +140,80 @@ void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { // 指し手オーダリング器 // Constructors of the MovePicker class. As arguments, we pass information -// to help it return the (presumably) good moves first, to decide which -// moves to return (in the quiescence search, for instance, we only want to -// search captures, promotions, and some checks) and how important a good -// move ordering is at the current node. - -// 引数として、我々は情報を渡します。 -// それが最初に(おそらく)良い手を返す手助けとなるため、どの手を返すかを決定するために -// (例えば、静止探索(quiescence search)では、我々は駒を取る手、 -// 成る手、およびいくつかの王手だけを探索したい)そして、 -// 現在のノードにおいて良い手の順序付けがどれほど重要であるかについてです。 - -// MovePicker constructor for the main search -// 通常探索(main search)から呼び出されるとき用のコンストラクタ。 -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph , - const PieceToHistory** ch, +// to decide which class of moves to emit, to help sorting the (presumably) +// good moves first, and how important move ordering is at the current node. + +// MovePicker constructor for the main search and for the quiescence search + +// MovePickerクラスのコンストラクタ。引数として、どの種類の手を生成するかを決定するための情報、 +// どの手を優先的に(おそらく良い手を)ソートするか、そして現在のノードで手順の順序がどれほど重要かを渡します。 + +MovePicker::MovePicker( + const Position& p, + Move ttm, + Depth d, + const ButterflyHistory* mh, + const LowPlyHistory* lph, + const CapturePieceToHistory* cph, + const PieceToHistory** ch, #if defined(ENABLE_PAWN_HISTORY) - const PawnHistory* ph, + const PawnHistory* ph, #endif - Move cm, - const Move* killers) - : pos(p), mainHistory(mh), captureHistory(cph) , continuationHistory(ch), + int pl) : + pos(p), + mainHistory(mh), + lowPlyHistory(lph), + captureHistory(cph), + continuationHistory(ch), #if defined(ENABLE_PAWN_HISTORY) pawnHistory(ph), #endif - ttMove(ttm), refutations{ { killers[0], 0 },{ killers[1], 0 },{ cm, 0 } }, depth(d) + ttMove(ttm), + depth(d), + ply(pl) { // 通常探索から呼び出されているので残り深さはゼロより大きい。 ASSERT_LV3(d > 0); // 次の指し手生成の段階 - // 王手がかかっているなら回避手、かかっていないなら通常探索用の指し手生成 - stage = (pos.in_check() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); + if (pos.in_check()) + // 王手がかかっているなら回避手 + stage = EVASION_TT + !(ttm && pos.pseudo_legal(ttm)); + + else + // 王手がかかっていないなら通常探索用/静止探索の指し手生成 + // ⇨ 通常探索から呼び出されたのか、静止探索から呼び出されたのかについてはdepth > 0 によって判定できる。 + stage = (depth > 0 ? MAIN_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); // 置換表の指し手があるならそれを最初に試す。ただしpseudo_legalでなければならない。 // 置換表の指し手がないなら、次のstageから開始する。 } -// Constructor for quiescence search -// -// qsearch(静止探索)から呼び出される時用。 -// rs : recapture square(直前の駒の移動先。この駒を取り返す指し手をいまから生成する) -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, - const PieceToHistory** ch -#if defined(ENABLE_PAWN_HISTORY) - , const PawnHistory* ph -#endif - , Square rs) - : pos(p), mainHistory(mh), captureHistory(cph) , continuationHistory(ch) -#if defined(ENABLE_PAWN_HISTORY) - , pawnHistory(ph) -#endif - , ttMove(ttm), recaptureSquare(rs), depth(d) -{ - - // 静止探索から呼び出されているので残り深さはゼロ以下。 - ASSERT_LV3(d <= 0); - - // 王手がかかっているなら王手回避のフェーズへ。さもなくばQSEARCHのフェーズへ。 -// stage = (pos.in_check() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); - - // ⇨ Stockfish 16のコード、ttm(置換表の指し手)は無条件でこのMovePickerが返す1番目の指し手としているが、これだと - // TTの指し手だけで千日手になってしまうことがある。これは、将棋ではわりと起こりうる。 - // 対策としては、qsearchで千日手チェックをしたり、SEEが悪いならskipするなど。 - // ※ ここでStockfish 14のころのように置換表の指し手に条件をつけるのは良くなさげ。(V7.74l3 と V7.74mとの比較) - // → ただし、その場合、qsearch()で千日手チェックが必要になる。 - // qsearchでの千日手チェックのコストが馬鹿にならないので、 - // ⇓このコードを有効にして、qsearch()での千日手チェックをやめた方が得。 - - // Stockfish 14のコード - - stage = (pos.in_check() ? EVASION_TT : QSEARCH_TT) + - !(ttm - && (pos.in_check() || depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare) - && pos.pseudo_legal(ttm)); - -} - -// Constructor for ProbCut: we generate captures with SEE greater -// than or equal to the given threshold. +// MovePicker constructor for ProbCut: we generate captures with Static Exchange +// Evaluation (SEE) greater than or equal to the given threshold. -// 通常探索時にProbCutの処理から呼び出されるのコンストラクタ。 +// 通常探索のProbCutのためのMovePickerコンストラクタ +// : 与えられた閾値以上の静的交換評価(SEE)を持つキャプチャを生成します。 // th = 枝刈りのしきい値 -// SEEの値がth以上となるcaptureの指し手(歩の成りは含む)だけを生成する。 -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) - : pos(p), captureHistory(cph) , ttMove(ttm), threshold(th) +// ⇨ SEEの値がth以上となるcaptureの指し手(歩の成りは含む)だけを生成する。 + +MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) : + pos(p), + captureHistory(cph), + ttMove(ttm), + threshold(Value(th)) { + // ProbCutから呼び出されているので王手はかかっていないはず。 ASSERT_LV3(!pos.in_check()); // ProbCutにおいて、SEEが与えられたthresholdの値以上の指し手のみ生成する。 // (置換表の指し手も、この条件を満たさなければならない) // 置換表の指し手がないなら、次のstageから開始する。 - stage = PROBCUT_TT + !(ttm + + stage = PROBCUT_TT + + !(ttm + // && pos.capture_stage(ttm) #if defined(GENERATE_PRO_PLUS) && pos.capture_or_pawn_promotion(ttm) #else @@ -252,10 +229,14 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece } +// Assigns a numerical value to each move in a list, used for sorting. +// Captures are ordered by Most Valuable Victim (MVV), preferring captures +// with a good history. Quiets moves are ordered using the history tables. -// MovePicker::score() assigns a numerical value to each move in a list, used -// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring -// captures with a good history. Quiets moves are ordered using the history tables. +// 各手に数値を割り当ててリストをソートします。 +// キャプチャは最も価値のある駒(MVV)に基づいて順序付けられ、 +// 良好な履歴を持つキャプチャが優先されます。 +// 静かな手は履歴テーブルを使用して順序付けられます。 // QUIETS、EVASIONS、CAPTURESの指し手のオーダリングのためのスコアリング。似た処理なので一本化。 template @@ -378,6 +359,11 @@ void MovePicker::score() #endif // → 強くならなかったのでコメントアウト。 ; + + // lowPlyHistoryも加算 + if (ply < 4) + m.value += 8 * (*lowPlyHistory)(ply , from_to(m)) / (1 + 2 * ply); + } else // Type == EVASIONS { @@ -424,12 +410,14 @@ void MovePicker::score() } // Returns the next move satisfying a predicate function. -// It never returns the TT move. -// -// MovePicker::select()は、Pred(predicate function:述語関数)を満たす次の指し手を返す。 -// 置換表の指し手は決して返さない。 +// This never returns the TT move, as it was emitted before. + +// 条件を満たす次の手を返します。 +// この関数は、トランスポジションテーブル(TT)の手は既に出力されているため、決して返しません。 + // ※ この関数の返し値は同時にthis->moveにも格納されるので活用すると良い。filterのなかでも // この変数にアクセスできるので、指し手によってfilterするかどうかを選べる。 + template Move MovePicker::select(Pred filter) { @@ -449,13 +437,14 @@ Move MovePicker::select(Pred filter) { return MOVE_NONE; } -// Most important method of the MovePicker class. It -// returns a new pseudo-legal move every time it is called until there are no more -// moves left, picking the move with the highest score from a list of generated moves. +// This is the most important method of the MovePicker class. We emit one +// new pseudo-legal move on every call until there are no more moves left, +// picking the move with the highest score from a list of generated moves. -// 呼び出されるごとに新しいpseudo legalな指し手をひとつ返す。 -// 指し手が尽きればMOVE_NONEが返る。 -// 置換表の指し手(ttMove)を返したあとは、それを取り除いた指し手を返す。 +// これはMovePickerクラスで最も重要なメソッドです。呼び出すたびに、新しい擬似合法手を1つ生成し、 +// すべての手が尽きるまで続けます。生成された手のリストから、最も高いスコアを持つ手を選びます。 + +// ※ 置換表の指し手(ttMove)を返したあとは、それを取り除いた指し手を返す。 // skipQuiets : これがtrueだとQUIETな指し手は返さない。 Move MovePicker::next_move(bool skipQuiets) { @@ -520,42 +509,6 @@ Move MovePicker::next_move(bool skipQuiets) { })) return *(cur -1); - // Prepare the pointers to loop over the refutations array - // refutations配列に対して繰り返すためにポインターを準備する。 - cur = std::begin(refutations); - endMoves = std::end(refutations); - - // If the countermove is the same as a killer, skip it - // countermoveがkillerと同じならばそれをskipする。 - - // ※ refutations[0]と[1]はkiller、refutations[2]はcounter move - // ※ refutations[]はすべてMove16ではなくMove(上位に移動後の駒が格納されている)と仮定している。 - // (ゆえにこれが一致すれば同じ駒を移動させる同一の指し手) - if ( refutations[0].move == refutations[2].move - || refutations[1].move == refutations[2].move) - --endMoves; - - ++stage; - [[fallthrough]]; - - // killer move , counter moveを返すフェーズ - case REFUTATION: - - // 直前に置換表の指し手を返しているし、CAPTURES_PRO_PLUSでの指し手も返しているので - // それらの指し手は除外する。 - // 直前にCAPTURES_PRO_PLUSで生成している指し手を除外 - // pseudo_legalでない指し手以外に歩や大駒の不成なども除外 - if (select([&]() { return *cur != MOVE_NONE -#if defined(GENERATE_PRO_PLUS) - && !pos.capture_or_pawn_promotion(*cur) -#else - && !pos.capture(*cur) -#endif - // 注意 : ここ⇑、CAPTURE_INITで生成した指し手に歩の成りが混じっているなら、 - // capture_or_pawn_promotion()の方を用いなければならないので注意。 - && pos.pseudo_legal(*cur); })) - return *(cur - 1); - ++stage; [[fallthrough]]; @@ -564,7 +517,7 @@ Move MovePicker::next_move(bool skipQuiets) { if (!skipQuiets) { - cur = endBadCaptures; + cur = endBadCaptures; /* moves : バッファの先頭 @@ -576,7 +529,7 @@ Move MovePicker::next_move(bool skipQuiets) { |--------------------------------------------------------------| | badCaptures | quiet | 未使用のバッファ | quiet生成時 (NON_CAPTURES_PRO_MINUS) |--------------------------------------------------------------| - ↑ ↑ + ↑ ↑ moves endBadCaptures */ @@ -589,9 +542,9 @@ Move MovePicker::next_move(bool skipQuiets) { #endif #if defined(GENERATE_PRO_PLUS) - endMoves = Search::Limits.generate_all_legal_moves ? generateMoves(pos, cur) : generateMoves(pos, cur); + endMoves = beginBadQuiets = endBadQuiets = Search::Limits.generate_all_legal_moves ? generateMoves(pos, cur) : generateMoves(pos, cur); #else - endMoves = Search::Limits.generate_all_legal_moves ? generateMoves(pos, cur) : generateMoves(pos, cur); + endMoves = beginBadQuiets = endBadQuiets = Search::Limits.generate_all_legal_moves ? generateMoves(pos, cur) : generateMoves(pos, cur); #endif // 注意 : ここ⇑、CAPTURE_INITで生成した指し手に歩の成りの指し手が含まれているなら、それを除外しなければならない。 @@ -615,7 +568,7 @@ Move MovePicker::next_move(bool skipQuiets) { #if defined(USE_SUPER_SORT) && defined(USE_AVX2) // SuperSortを有効にするとinsertion_sortと結果が異なるのでbenchコマンドの探索node数が変わって困ることがあるので注意。 - partial_super_sort (cur, endMoves, quiet_threshold(depth)); + partial_super_sort(cur, endMoves, quiet_threshold(depth)); #else partial_insertion_sort(cur, endMoves, quiet_threshold(depth)); #endif @@ -632,16 +585,21 @@ Move MovePicker::next_move(bool skipQuiets) { ++stage; [[fallthrough]]; - // 駒を捕獲しない指し手を返す。 - // (置換表の指し手とkillerの指し手は返したあとなのでこれらの指し手は除外する必要がある) - // ※ これ、指し手の数が多い場合、AVXを使って一気に削除しておいたほうが良いのでは.. - case QUIET: - if ( !skipQuiets - && select([&]() { return *cur != refutations[0].move - && *cur != refutations[1].move - && *cur != refutations[2].move; - })) - return *(cur - 1); + // 駒を捕獲しない指し手を返す。 + // (置換表の指し手とkillerの指し手は返したあとなのでこれらの指し手は除外する必要がある) + // ※ これ、指し手の数が多い場合、AVXを使って一気に削除しておいたほうが良いのでは.. + case GOOD_QUIET: + if (!skipQuiets + && select([&]() { return true; })) + { + if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) + return *(cur - 1); + + // Remaining quiets are bad + // 残っているquietの手は悪手です + + beginBadQuiets = cur - 1; + } // bad capturesの指し手を返すためにポインタを準備する。 // bad capturesの先頭を指すようにする。これは指し手生成バッファの先頭からの領域を再利用している。 @@ -651,11 +609,27 @@ Move MovePicker::next_move(bool skipQuiets) { ++stage; [[fallthrough]]; - // see()が負の指し手を返す。 + // see()が負の指し手を返す。 case BAD_CAPTURE: - return select([]() { return true; }); + if (select([]() { return true; })) + return *(cur - 1); + + // Prepare the pointers to loop over the bad quiets + // 悪いquietの手をループするためのポインタを準備します + + cur = beginBadQuiets; + endMoves = endBadQuiets; - // 王手回避手の生成 + ++stage; + [[fallthrough]]; + + case BAD_QUIET: + if (!skipQuiets) + return select([]() { return true; }); + + return MOVE_NONE; + + // 王手回避手の生成 case EVASION_INIT: cur = moves; @@ -667,53 +641,19 @@ Move MovePicker::next_move(bool skipQuiets) { ++stage; [[fallthrough]]; - // 王手回避の指し手を返す + // 王手回避の指し手を返す case EVASION: // そんなに数は多くないはずだから、オーダリングがベストのスコアのものを選択する - return select([](){ return true; }); + return select([]() { return true; }); - // PROBCUTの指し手を返す + // PROBCUTの指し手を返す case PROBCUT: return select([&]() { return pos.see_ge(*cur, threshold); }); // threadshold以上のSEE値で、ベストのものを一つずつ返す // 静止探索用の指し手を返す処理 case QCAPTURE: - // depthがDEPTH_QS_RECAPTURES(-5)以下(深い)なら、recaptureの升に移動する指し手(直前で取られた駒を取り返す指し手)のみを生成。 - if (select([&]() { return depth > DEPTH_QS_RECAPTURES - || to_sq(*cur) == recaptureSquare; })) - return *(cur - 1); - - // If we found no move and the depth is too low to try checks, then we have finished - // 指し手がなくて、depthがDEPTH_QS_NORMALより深いなら、これで終了 - // depth == DEPTH_QS_NORMAL + 1 == DEPTH_QS_CHECKS のときは特別に - // 王手になる指し手も生成する。 - if (depth <= DEPTH_QS_NORMAL) - return MOVE_NONE; - - ++stage; - [[fallthrough]]; - - // 王手となる指し手の生成 - case QCHECK_INIT: - // この前のフェーズでCAPTURES_PRO_PLUSで生成していたので、駒を取らない王手の指し手生成(QUIET_CHECKS) - 歩の成る指し手の除外 が必要。 - // (歩の成る指し手は王手であろうとすでに生成して試したあとである) - // QUIET_CHECKS_PRO_MINUSがあれば良いのだが、実装が難しいので、QUIET_CHECKSで生成して、このあとQCHECK_で歩の成る指し手を除外する。 - cur = moves; - - //endMoves = Search::Limits.generate_all_legal_moves ? generateMoves(pos, cur) : generateMoves(pos, cur); - // → qsearch()なので歩の成らずは生成しなくてもいいや.. - endMoves = generateMoves(pos, cur); - - ++stage; - [[fallthrough]]; - - // 王手になる指し手を一手ずつ返すフェーズ - case QCHECK: - return select([](){ return true; }); - //return select([&]() { return !pos.pawn_promotion(*cur); }); - - // 王手する指し手は、即詰みを狙うものであり、駒捨てとかがあるからオーダリングが難しく、効果に乏しいのでオーダリングしない。 + return select([]() { return true; }); default: UNREACHABLE; diff --git a/source/movepick.h b/source/movepick.h index 0afefd52f..ff4a058f3 100644 --- a/source/movepick.h +++ b/source/movepick.h @@ -162,6 +162,35 @@ struct ButterflyHistory }; +// LowPlyHistory is adressed by play and move's from and to squares, used +// to improve move ordering near the root + +// LowPlyHistoryはプレイおよび手の「from」と「to」のマスで管理され、 +// ルート付近での手順の順序を改善するために使用されます。 + +struct LowPlyHistory +{ + using T = int16_t; // StatsEntryの型 + static constexpr int D = 7183; // StatsEntryの範囲 + + // 必ず以下のアクセッサを通してアクセスすること。 + // ※ 引数の順番は、Stockfishの配列の添字の順番と合わせてある。 + + const StatsEntry& operator() (int ply, int from_to) const { + return stats[from_to][ply]; + } + + StatsEntry& operator() (int ply, int from_to) { + return stats[from_to][ply]; + } + void fill(T t) { stats.fill(t); } + + //using LowPlyHistory = Stats; + // ⇨ Stockfishのコードだと、末尾が2の冪にならないので並び順を変更する。 + Stats stats; +}; + + // CounterMoveHistory stores counter moves indexed by [piece][to] of the previous // move, see www.chessprogramming.org/Countermove_Heuristic // CounterMoveHistoryは、直前の指し手の[piece][to]によってindexされるcounter moves(応手)を格納する。 @@ -356,40 +385,36 @@ class MovePicker { // 生成順に次の1手を取得するのか、オーダリング上、ベストな指し手を取得するのかの定数 // (このクラスの内部で用いる。) - enum PickType { Next, Best }; + enum PickType { + Next, + Best + }; public: + // このクラスは指し手生成バッファが大きいので、コピーして使うような使い方は禁止。 MovePicker(const MovePicker&) = delete; MovePicker& operator=(const MovePicker&) = delete; - // 通常探索(main search)から呼び出されるとき用のコンストラクタ。 - // cm = counter move , killers_p = killerの指し手へのポインタ - MovePicker(const Position& pos_, Move ttMove_, Depth depth_, const ButterflyHistory* mh, + // 通常探索(main search)と静止探索から呼び出されるとき用のコンストラクタ。 + MovePicker(const Position& pos_, + Move ttMove_, + Depth depth_, + const ButterflyHistory* mh, + const LowPlyHistory*, const CapturePieceToHistory* cph, const PieceToHistory** ch, #if defined(ENABLE_PAWN_HISTORY) - const PawnHistory*, -#endif - Move cm, - const Move* killers_p); - - // 静止探索(qsearch)から呼び出される時用。 - // recapSq = 直前に動かした駒の行き先の升(取り返される升) - MovePicker(const Position& pos_, Move ttMove_, Depth depth_, const ButterflyHistory* mh , - const CapturePieceToHistory* cph , - const PieceToHistory** ch, -#if defined(ENABLE_PAWN_HISTORY) - const PawnHistory*, + const PawnHistory* ph, #endif - Square recapSq); + int ply_ + ); // 通常探索時にProbCutの処理から呼び出されるのコンストラクタ。 // SEEの値がth以上となるcaptureの指してだけを生成する。 // threshold_ = 直前に取られた駒の価値。これ以下の捕獲の指し手は生成しない。 // capture_or_pawn_promotion()に該当する指し手しか返さない。 - MovePicker(const Position& pos_, Move ttMove_, Value threshold_, - const CapturePieceToHistory* cph); + MovePicker(const Position&, Move ttMove_, int threshold_, const CapturePieceToHistory*); // 呼び出されるごとに新しいpseudo legalな指し手をひとつ返す。 // 指し手が尽きればMOVE_NONEが返る。 @@ -414,6 +439,7 @@ class MovePicker // コンストラクタで渡されたhistroyのポインタを保存しておく変数。 const ButterflyHistory* mainHistory; + const LowPlyHistory* lowPlyHistory; const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; #if defined(ENABLE_PAWN_HISTORY) @@ -423,27 +449,25 @@ class MovePicker // 置換表の指し手(コンストラクタで渡される) Move ttMove; - // refutations[0] : killer[0] - // refutations[1] : killer[1] - // refutations[2] : counter move(コンストラクタで渡された、前の局面の指し手に対する応手) - // cur : 次に返す指し手 - // endMoves : 生成された指し手の末尾 - // endBadCapture : BadCaptureの終端(captureの指し手を試すフェイズでのendMovesから後方に向かって悪い捕獲の指し手を移動させていく時に用いる) - ExtMove refutations[3] , *cur, *endMoves, *endBadCaptures; + // cur : 次に返す指し手 + // endMoves : 生成された指し手の末尾 + // endBadCapture : BadCaptureの終端(captureの指し手を試すフェイズでのendMovesから後方に向かって悪い捕獲の指し手を移動させていく時に用いる) + // beginBadQuiets : あとで + // endBadQuiets : あとで + ExtMove *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; // 指し手生成の段階 int stage; - - // RECAPUTREの指し手で移動させる先の升 - Square recaptureSquare; - // ProbCut用の指し手生成に用いる、直前の指し手で捕獲された駒の価値 Value threshold; // コンストラクタで渡された探索深さ Depth depth; + // コンストラクタで渡されたrootからの手数 + int ply; + // 指し手生成バッファ // 最大合法手の数 = 593 , これを要素数が32の倍数になるようにpaddingすると608。 // 32byteの倍数になるようにcurを使いたいので+3(ttmoveとkillerの分)して、611。 diff --git a/source/search.h b/source/search.h index 5b14c62e6..a008f01f9 100644 --- a/source/search.h +++ b/source/search.h @@ -43,20 +43,18 @@ namespace Search { // 各検索スレッドは、現在の深さ(ply)に基づいてインデックスされた、独自のStackオブジェクトの配列を持っています。 struct Stack { - Move* pv; // PVへのポインター。RootMovesのvector pvを指している。 + Move* pv; // PVへのポインター。RootMovesのvector pvを指している。 PieceToHistory* continuationHistory;// historyのうち、counter moveに関するhistoryへのポインタ。実体はThreadが持っている。 - int ply; // rootからの手数。rootならば0。 - Move currentMove; // そのスレッドの探索においてこの局面で現在選択されている指し手 - Move excludedMove; // singular extension判定のときに置換表の指し手をそのnodeで除外して探索したいのでその除外する指し手 - Move killers[2]; // killer move - Value staticEval; // 評価関数を呼び出して得た値。NULL MOVEのときに親nodeでの評価値が欲しいので保存しておく。 - int statScore; // 一度計算したhistoryの合計値をcacheしておくのに用いる。 - int moveCount; // このnodeでdo_move()した生成した何手目の指し手か。(1ならおそらく置換表の指し手だろう) - - bool inCheck; // この局面で王手がかかっていたかのフラグ - bool ttPv; // 置換表にPV nodeで調べた値が格納されていたか(これは価値が高い) - bool ttHit; // 置換表にhitしたかのフラグ - int cutoffCnt; // cut off(betaを超えたので枝刈りとしてreturn)した回数。 + int ply; // rootからの手数。rootならば0。 + Move currentMove; // そのスレッドの探索においてこの局面で現在選択されている指し手 + Move excludedMove; // singular extension判定のときに置換表の指し手をそのnodeで除外して探索したいのでその除外する指し手 + Value staticEval; // 評価関数を呼び出して得た値。NULL MOVEのときに親nodeでの評価値が欲しいので保存しておく。 + int statScore; // 一度計算したhistoryの合計値をcacheしておくのに用いる。 + int moveCount; // このnodeでdo_move()した生成した何手目の指し手か。(1ならおそらく置換表の指し手だろう) + bool inCheck; // この局面で王手がかかっていたかのフラグ + bool ttPv; // 置換表にPV nodeで調べた値が格納されていたか(これは価値が高い) + bool ttHit; // 置換表にhitしたかのフラグ + int cutoffCnt; // cut off(betaを超えたので枝刈りとしてreturn)した回数。 }; #endif diff --git a/source/thread.h b/source/thread.h index 53a8597bf..1d0f0d895 100644 --- a/source/thread.h +++ b/source/thread.h @@ -188,6 +188,7 @@ class Thread // 近代的なMovePickerではオーダリングのために、スレッドごとにhistoryとcounter movesなどのtableを持たないといけない。 CounterMoveHistory counterMoves; ButterflyHistory mainHistory; + LowPlyHistory lowPlyHistory; CapturePieceToHistory captureHistory; // コア数が多いか、長い持ち時間においては、ContinuationHistoryもスレッドごとに確保したほうが良いらしい。 diff --git a/source/types.h b/source/types.h index f9ee3f5f0..cd0306175 100644 --- a/source/types.h +++ b/source/types.h @@ -386,14 +386,7 @@ enum : int { // In qsearch, however, TT entries only store the current QS movegen stage (which should thus compare // lower than any regular search depth). // 静止探索で王手がかかっているときにこれより少ない残り探索深さでの探索した結果が置換表にあってもそれは信用しない - DEPTH_QS_CHECKS = 0, - - // 静止探索で王手がかかっていないとき。 - DEPTH_QS_NORMAL = -1, - - // 静止探索でこれより深い(残り探索深さが少ない)ところではRECAPTURESしか生成しない。 - DEPTH_QS_RECAPTURES = -5, - + DEPTH_QS = 0, // For TT entries where no searching at all was done (whether regular or qsearch) we use // _UNSEARCHED, which should thus compare lower than any QS or regular depth. _ENTRY_OFFSET is used