/* * A compromise between probe_hard and probe_soft. Check the cache, and return * if the position is found. If not, use a thread to load the position into * cache in the background while we return to the main search. The background * load uses a worker thread, and has minimal load implications because the * thread is blocked nearly 100% of the time. * Get DTM information instead of just WDL. */ bool probe_gtb_firm_dtm(const position_t* pos, int* score) { gtb_args_t args_storage; gtb_args_t* gtb_args = &args_storage; gtb_args->stm = stm_to_gtb(pos->side_to_move); gtb_args->ep = ep_to_gtb(pos->ep_square); gtb_args->castle = castle_to_gtb(pos->castle_rights); fill_gtb_arrays(pos, gtb_args->ws, gtb_args->bs, gtb_args->wp, gtb_args->bp); unsigned res, val; int success = tb_probe_soft(gtb_args->stm, gtb_args->ep, gtb_args->castle, gtb_args->ws, gtb_args->bs, gtb_args->wp, gtb_args->bp, &res, &val); if (success) { if (res == tb_DRAW) *score = DRAW_VALUE; else if (res == tb_BMATE) *score = mated_in(val); else if (res == tb_WMATE) *score = mate_in(val); else assert(false); if (pos->side_to_move == BLACK) *score *= -1; return true; } if (worker_task_ready) return false; memcpy(&worker_args, gtb_args, sizeof(gtb_args_t)); worker_task_ready = true; return false; }
/* * Probe all tbs, using the cache if available, but blocking and waiting for * disk if we miss in cache. * Get DTM information instead of just WDL. */ bool probe_gtb_hard_dtm(const position_t* pos, int* score) { int stm = stm_to_gtb(pos->side_to_move); int ep = ep_to_gtb(pos->ep_square); int castle = castle_to_gtb(pos->castle_rights); unsigned int ws[17], bs[17]; unsigned char wp[17], bp[17]; fill_gtb_arrays(pos, ws, bs, wp, bp); unsigned res, val; int success = tb_probe_hard(stm, ep, castle, ws, bs, wp, bp, &res, &val); if (success) { if (res == tb_DRAW) *score = DRAW_VALUE; else if (res == tb_BMATE) *score = mated_in(val); else if (res == tb_WMATE) *score = mate_in(val); else assert(false); if (pos->side_to_move == BLACK) *score *= -1; } return success; }
Value search(Position& pos, Value alpha, Value beta, Depth depth) { ASSERT_LV3(alpha < beta); // ----------------------- // nodeの種類 // ----------------------- // root nodeであるか const bool RootNode = NT == Root; // PV nodeであるか(root nodeはPV nodeに含まれる) const bool PvNode = NT == PV || NT == Root; // ----------------------- // 変数宣言 // ----------------------- // 現在のnodeのrootからの手数。これカウンターが必要。 // nanoだとこのカウンター持ってないので適当にごまかす。 const int ply_from_root = (pos.this_thread()->rootDepth - depth / ONE_PLY) + 1; // ----------------------- // 置換表のprobe // ----------------------- auto key = pos.state()->key(); bool ttHit; // 置換表がhitしたか TTEntry* tte = TT.probe(key, ttHit); // 置換表上のスコア // 置換表にhitしなければVALUE_NONE Value ttValue = ttHit ? value_from_tt(tte->value(), ply_from_root) : VALUE_NONE; auto thisThread = pos.this_thread(); // 置換表の指し手 // 置換表にhitしなければMOVE_NONE // RootNodeであるなら、指し手は現在注目している1手だけであるから、それが置換表にあったものとして指し手を進める。 Move ttMove = RootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] : ttHit ? tte->move() : MOVE_NONE; // 置換表の値による枝刈り if (!PvNode // PV nodeでは置換表の指し手では枝刈りしない(PV nodeはごくわずかしかないので..) && ttHit // 置換表の指し手がhitして && tte->depth() >= depth // 置換表に登録されている探索深さのほうが深くて && ttValue != VALUE_NONE // (VALUE_NONEだとすると他スレッドからTTEntryが読みだす直前に破壊された可能性がある) && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) : (tte->bound() & BOUND_UPPER)) // ttValueが下界(真の評価値はこれより大きい)もしくはジャストな値で、かつttValue >= beta超えならbeta cutされる // ttValueが上界(真の評価値はこれより小さい)だが、tte->depth()のほうがdepthより深いということは、 // 今回の探索よりたくさん探索した結果のはずなので、今回よりは枝刈りが甘いはずだから、その値を信頼して // このままこの値でreturnして良い。 ) { return ttValue; } // ----------------------- // 1手ずつ指し手を試す // ----------------------- pos.check_info_update(); MovePicker mp(pos,ttMove); Value value; Move move; StateInfo si; // この局面でdo_move()された合法手の数 int moveCount = 0; Move bestMove = MOVE_NONE; while (move = mp.nextMove()) { // root nodeでは、rootMoves()の集合に含まれていない指し手は探索をスキップする。 if (RootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, thisThread->rootMoves.end(), move)) continue; // legal()のチェック。root nodeだとlegal()だとわかっているのでこのチェックは不要。 if (!RootNode && !pos.legal(move)) continue; // ----------------------- // 1手進める // ----------------------- pos.do_move(move, si, pos.gives_check(move)); // do_moveした指し手の数のインクリメント ++moveCount; // ----------------------- // 再帰的にsearchを呼び出す // ----------------------- // PV nodeの1つ目の指し手で進めたnodeは、PV node。さもなくば、non PV nodeとして扱い、 // alphaの値を1でも超えるかどうかだけが問題なので簡単なチェックで済ませる。 // また、残り探索深さがなければ静止探索を呼び出して評価値を返す。 // (searchを再帰的に呼び出して、その先頭でチェックする呼び出しのオーバーヘッドが嫌なのでここで行なう) bool fullDepthSearch = (PV && moveCount == 1); if (!fullDepthSearch) { // nonPVならざっくり2手ぐらい深さを削っていいのでは..(本当はもっとちゃんとやるべき) Depth R = ONE_PLY * 2; value = depth - R < ONE_PLY ? -qsearch<NonPV>(pos, -beta, -alpha, depth - R) : -YaneuraOuNano::search<NonPV>(pos, -(alpha + 1), -alpha, depth - R); // 上の探索によりalphaを更新しそうだが、いい加減な探索なので信頼できない。まともな探索で検証しなおす fullDepthSearch = value > alpha; } if ( fullDepthSearch) value = depth - ONE_PLY < ONE_PLY ? -qsearch<PV>(pos, -beta, -alpha, depth - ONE_PLY) : -YaneuraOuNano::search<PV>(pos, -beta, -alpha, depth - ONE_PLY); // ----------------------- // 1手戻す // ----------------------- pos.undo_move(move); // 停止シグナルが来たら置換表を汚さずに終了。 if (Signals.stop) return VALUE_ZERO; // ----------------------- // root node用の特別な処理 // ----------------------- if (RootNode) { auto& rm = *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); if (moveCount == 1 || value > alpha) { // root nodeにおいてPVの指し手または、α値を更新した場合、スコアをセットしておく。 // (iterationの終わりでsortするのでそのときに指し手が入れ替わる。) rm.score = value; rm.pv.resize(1); // PVは変化するはずなのでいったんリセット // ここにPVを代入するコードを書く。(か、置換表からPVをかき集めてくるか) } else { // root nodeにおいてα値を更新しなかったのであれば、この指し手のスコアを-VALUE_INFINITEにしておく。 // こうしておかなければ、stable_sort()しているにもかかわらず、前回の反復深化のときの値との // 大小比較してしまい指し手の順番が入れ替わってしまうことによるオーダリング性能の低下がありうる。 rm.score = -VALUE_INFINITE; } } // ----------------------- // alpha値の更新処理 // ----------------------- if (value > alpha) { alpha = value; bestMove = move; // αがβを上回ったらbeta cut if (alpha >= beta) break; } } // end of while // ----------------------- // 生成された指し手がない? // ----------------------- // 合法手がない == 詰まされている ので、rootの局面からの手数で詰まされたという評価値を返す。 if (moveCount == 0) alpha = mated_in(ply_from_root); // ----------------------- // 置換表に保存する // ----------------------- tte->save(key, value_to_tt(alpha, ply_from_root), alpha >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, // betaを超えているということはbeta cutされるわけで残りの指し手を調べていないから真の値はまだ大きいと考えられる。 // すなわち、このとき値は下界と考えられるから、BOUND_LOWER。 // さもなくば、(PvNodeなら)枝刈りはしていないので、これが正確な値であるはずだから、BOUND_EXACTを返す。 // また、PvNodeでないなら、枝刈りをしているので、これは正確な値ではないから、BOUND_UPPERという扱いにする。 // ただし、指し手がない場合は、詰まされているスコアなので、これより短い/長い手順の詰みがあるかも知れないから、 // すなわち、スコアは変動するかも知れないので、BOUND_UPPERという扱いをする。 depth, bestMove, VALUE_NONE,TT.generation()); return alpha; }
Value qsearch(Position& pos, Value alpha, Value beta, Depth depth) { // 現在のnodeのrootからの手数。これカウンターが必要。 // nanoだとこのカウンター持ってないので適当にごまかす。 const int ply_from_root = (pos.this_thread()->rootDepth - depth / ONE_PLY) + 1; // この局面で王手がかかっているのか bool InCheck = pos.checkers(); Value value; if (InCheck) { // 王手がかかっているならすべての指し手を調べる。 alpha = -VALUE_INFINITE; } else { // 王手がかかっていないなら置換表の指し手を持ってくる // この局面で何も指さないときのスコア。recaptureすると損をする変化もあるのでこのスコアを基準に考える。 value = Eval::eval(pos); if (alpha < value) { alpha = value; if (alpha >= beta) return alpha; // beta cut } // 探索深さが-3以下ならこれ以上延長しない。 if (depth < -3 * ONE_PLY) return alpha; } // 取り合いの指し手だけ生成する pos.check_info_update(); MovePicker mp(pos,move_to(pos.state()->lastMove)); Move move; StateInfo si; while (move = mp.nextMove()) { if (!pos.legal(move)) continue; pos.do_move(move, si, pos.gives_check(move)); value = -YaneuraOuNano::qsearch<NT>(pos, -beta, -alpha, depth - ONE_PLY); pos.undo_move(move); if (Signals.stop) return VALUE_ZERO; if (value > alpha) // update alpha? { alpha = value; if (alpha >= beta) return alpha; // beta cut } } // 王手がかかっている状況ですべての指し手を調べたということだから、これは詰みである if (InCheck && alpha == -VALUE_INFINITE) return mated_in(ply_from_root); return alpha; }
Value name_NT_InCheck(qsearch)(Pos* pos, Stack* ss, Value alpha, BETA_ARG Depth depth) { assert(InCheck == !!pos_checkers()); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(depth <= DEPTH_ZERO); Move pv[MAX_PLY+1]; TTEntry *tte; Key posKey; Move ttMove, move, bestMove; Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; int ttHit, ttPv, givesCheck, evasionPrunable; Depth ttDepth; int moveCount; if (PvNode) { oldAlpha = alpha; // To flag BOUND_EXACT when eval above alpha and no available moves (ss+1)->pv = pv; ss->pv[0] = 0; } bestMove = 0; moveCount = 0; // Check for an instant draw or if the maximum ply has been reached if (is_draw(pos) || ss->ply >= MAX_PLY) return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); // Decide whether or not to include checks: this fixes also the type of // TT entry depth that we are going to use. Note that in qsearch we use // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = InCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; // Transposition table lookup posKey = pos_key(); tte = tt_probe(posKey, &ttHit); ttValue = ttHit ? value_from_tt(tte_value(tte), ss->ply) : VALUE_NONE; ttMove = ttHit ? tte_move(tte) : 0; ttPv = ttHit ? tte_is_pv(tte) : 0; if ( !PvNode && ttHit && tte_depth(tte) >= ttDepth && ttValue != VALUE_NONE // Only in case of TT access race && (ttValue >= beta ? (tte_bound(tte) & BOUND_LOWER) : (tte_bound(tte) & BOUND_UPPER))) return ttValue; // Evaluate the position statically if (InCheck) { ss->staticEval = VALUE_NONE; bestValue = futilityBase = -VALUE_INFINITE; } else { if (ttHit) { // Never assume anything on values stored in TT if ((ss->staticEval = bestValue = tte_eval(tte)) == VALUE_NONE) ss->staticEval = bestValue = evaluate(pos); // Can ttValue be used as a better position evaluation? if (ttValue != VALUE_NONE) if (tte_bound(tte) & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)) bestValue = ttValue; } else ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Tempo; // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { if (!ttHit) tte_save(tte, posKey, value_to_tt(bestValue, ss->ply), ttPv, BOUND_LOWER, DEPTH_NONE, 0, ss->staticEval, tt_generation()); return bestValue; } if (PvNode && bestValue > alpha) alpha = bestValue; futilityBase = bestValue + 128; } ss->history = &(*pos->counterMoveHistory)[0][0]; // Initialize move picker data for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // be generated. mp_init_q(pos, ttMove, depth, to_sq((ss-1)->currentMove)); // Loop through the moves until no moves remain or a beta cutoff occurs while ((move = next_move(pos, 0))) { assert(move_is_ok(move)); givesCheck = gives_check(pos, ss, move); moveCount++; // Futility pruning if ( !InCheck && !givesCheck && futilityBase > -VALUE_KNOWN_WIN && !advanced_pawn_push(pos, move)) { assert(type_of_m(move) != ENPASSANT); // Due to !advanced_pawn_push futilityValue = futilityBase + PieceValue[EG][piece_on(to_sq(move))]; if (futilityValue <= alpha) { bestValue = max(bestValue, futilityValue); continue; } if (futilityBase <= alpha && !see_test(pos, move, 1)) { bestValue = max(bestValue, futilityBase); continue; } } // Detect non-capture evasions that are candidates to be pruned evasionPrunable = InCheck && (depth != DEPTH_ZERO || moveCount > 2) && bestValue > VALUE_MATED_IN_MAX_PLY && !is_capture(pos, move); // Don't search moves with negative SEE values if ( (!InCheck || evasionPrunable) && !see_test(pos, move, 0)) continue; // Speculative prefetch as early as possible prefetch(tt_first_entry(key_after(pos, move))); // Check for legality just before making the move if (!is_legal(pos, move)) { moveCount--; continue; } ss->currentMove = move; ss->history = &(*pos->counterMoveHistory)[moved_piece(move)][to_sq(move)]; // Make and search the move do_move(pos, move, givesCheck); #if PvNode value = givesCheck ? -qsearch_PV_true(pos, ss+1, -beta, -alpha, depth - ONE_PLY) : -qsearch_PV_false(pos, ss+1, -beta, -alpha, depth - ONE_PLY); #else value = givesCheck ? -qsearch_NonPV_true(pos, ss+1, -beta, depth - ONE_PLY) : -qsearch_NonPV_false(pos, ss+1, -beta, depth - ONE_PLY); #endif undo_move(pos, move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); // Check for a new best move if (value > bestValue) { bestValue = value; if (value > alpha) { bestMove = move; if (PvNode) // Update pv even in fail-high case update_pv(ss->pv, move, (ss+1)->pv); if (PvNode && value < beta) // Update alpha here! alpha = value; else break; // Fail high } } } // All legal moves have been searched. A special case: If we're in check // and no legal moves were found, it is checkmate. if (InCheck && bestValue == -VALUE_INFINITE) return mated_in(ss->ply); // Plies to mate from the root tte_save(tte, posKey, value_to_tt(bestValue, ss->ply), ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, ttDepth, bestMove, ss->staticEval, tt_generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; }