Skip to content

Commit

Permalink
- qsearch、step 1.~step 4.までコード修正した。
Browse files Browse the repository at this point in the history
  - prevSq、ここでもMOVE_NULL考慮するようにした。
- update_all_stats()のパラメーターPAWN_VALUEを外だしした。→ PARAM_UPDATE_ALL_STATS_EVAL_TH
- update_all_stats()で直前の指し手がMOVE_NULLの時、continuation_historiesを更新しないことにした。
- update_all_stats()のrefactoring
- check_time、1024回に1回のチェックを512回に1回に変更。
  • Loading branch information
yaneurao committed Oct 15, 2023
1 parent 8b8865b commit 1796880
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 51 deletions.
6 changes: 6 additions & 0 deletions source/engine/yaneuraou-engine/yaneuraou-param.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ PARAM_DEFINE PARAM_PRUNING_BY_HISTORY_DEPTH = 5;
PARAM_DEFINE PARAM_REDUCTION_BY_HISTORY = 4334;


// update_all_stats()で、静止探索時のquietMoveとみなすbestvalueとbetaの差(PAWN_VALUEより少し小さな値)
// 元の値 = 90 , step = 5
// [PARAM] min:10,max:200,step:90,interval:5,time_rate:1,fixed
PARAM_DEFINE PARAM_UPDATE_ALL_STATS_EVAL_TH = 90;


//
// etc..
//
Expand Down
140 changes: 90 additions & 50 deletions source/engine/yaneuraou-engine/yaneuraou-search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ namespace {

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, Move* childPv);
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_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq,
Expand Down Expand Up @@ -1484,8 +1484,9 @@ namespace {
ss->depth = depth;

// 前の指し手で移動させた先の升目
// TODO : null moveのときにprevSq == 1 == SQ_12になるのどうなのか…。
Square prevSq = to_sq((ss - 1)->currentMove);
// → null moveのときにprevSq == 1 == SQ_12になるのどうなのか…。
// → StockfishでMOVE_NULLの時は、prevSq == SQ_NBとして扱うように変更になった。[2023/10/15]
Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NB;

// Initialize statScore to zero for the grandchildren of the current position.
// So statScore is shared between all grandchildren and only the first grandchild
Expand Down Expand Up @@ -2932,6 +2933,10 @@ namespace {
// 静止探索
// -----------------------

// qsearch() is the quiescence search function, which is called by the main search
// function with zero depth, or recursively with further decreasing depth per call.
// (~155 Elo)

// qsearch()は静止探索を行う関数で、search()でdepth(残り探索深さ)が0になったときに呼び出されるか、
// このqseach()自身から再帰的にもっと低いdepthで呼び出される。

Expand All @@ -2951,6 +2956,21 @@ namespace {
ASSERT_LV3(PvNode || alpha == beta - 1);
ASSERT_LV3(depth <= 0);

// Stockfishではここで千日手に突入できるかのチェックがあるようだが将棋でこれをやっても強くならないので導入しない。

// Stockfishのコードの原理としては、次の一手で千日手局面に持ち込めるなら、少なくともこの局面は引き分けであるから、
// betaが引き分けのスコアより低いならbeta cutできるというもの。

//// Check if we have an upcoming move that draws by repetition, or
//// if the opponent had an alternative move earlier to this position.
//if ( alpha < VALUE_DRAW
// && pos.has_game_cycle(ss->ply))
//{
// alpha = value_draw(pos.this_thread());
// if (alpha >= beta)
// return alpha;
//}

// PV求める用のbuffer
// (これnonPVでは不要なので、nonPVでは参照していないの削除される。)
Move pv[MAX_PLY + 1];
Expand Down Expand Up @@ -2987,10 +3007,15 @@ namespace {
// このnodeで何手目の指し手であるか
int moveCount;

// 現局面の手番側のColor
Color us = pos.side_to_move();

// -----------------------
// nodeの初期化
// -----------------------

// Step 1. Initialize node

if (PvNode)
{
(ss + 1)->pv = pv;
Expand All @@ -3006,7 +3031,7 @@ namespace {
// 最大手数へ到達したか?
// -----------------------

// Check for an immediate draw or maximum ply reached
// Step 2. Check for an immediate draw or maximum ply reached

if (ss->ply >= MAX_PLY)
return draw_value(REPETITION_DRAW, pos.side_to_move());
Expand All @@ -3024,18 +3049,20 @@ namespace {

ASSERT_LV3(0 <= ss->ply && ss->ply < MAX_PLY);

// -----------------------
// 置換表のprobe
// -----------------------

// 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.

// 置換表に登録するdepthはあまりマイナスの値だとおかしいので、
// 王手がかかっているときは、DEPTH_QS_CHECKS(=0)、王手がかかっていないときはDEPTH_QS_NO_CHECKS(-1)とみなす。
ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS
: DEPTH_QS_NO_CHECKS;
: DEPTH_QS_NO_CHECKS;

// -----------------------
// 置換表のprobe
// -----------------------

// Step 3. Transposition table lookup

// Transposition table lookup
// 置換表のlookup
Expand All @@ -3048,43 +3075,46 @@ namespace {

ASSERT_LV3(pos.legal_promote(ttMove));

// At non-PV nodes we check for an early TT cutoff

// pos.to_move()でlegalityのcheckに引っかかったパターンなので置換表にhitしなかったことにする。
if (tte->move().to_u16() && !ttMove)
ss->ttHit = false;

// nonPVでは置換表の指し手で枝刈りする
// PVでは置換表の指し手では枝刈りしない(前回evaluateした値は使える)
if ( !PvNode
&& ss->ttHit
&& tte->depth() >= ttDepth
&& ttValue != VALUE_NONE // Only in case of TT access race
&& ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit
// ↑置換表から取り出したときに他スレッドが値を潰している可能性があるのでこのチェックが必要
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
: (tte->bound() & BOUND_UPPER)))
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))

// ↑ここは、↓この意味。

//&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
// : (tte->bound() & BOUND_UPPER)))

// ttValueが下界(真の評価値はこれより大きい)もしくはジャストな値で、かつttValue >= beta超えならbeta cutされる
// ttValueが上界(真の評価値はこれより小さい)だが、tte->depth()のほうがdepthより深いということは、
// 今回の探索よりたくさん探索した結果のはずなので、今回よりは枝刈りが甘いはずだから、その値を信頼して
// このままこの値でreturnして良い。
{

return ttValue;
}

// -----------------------
// eval呼び出し
// -----------------------

// Evaluate the position statically
// Step 4. Static evaluation of the position

if (ss->inCheck)
{
ss->staticEval = VALUE_NONE;

// bestValueはalphaとは違う。
// 王手がかかっているときは-VALUE_INFINITEを初期値として、すべての指し手を生成してこれを上回るものを探すので
// alphaとは区別しなければならない。
bestValue = futilityBase = -VALUE_INFINITE;

} else {
else {

// -----------------------
// 一手詰め判定
Expand Down Expand Up @@ -3131,7 +3161,7 @@ namespace {
// 置換表の指し手でこのまま枝刈りできるケースがあるから難しい。
// 評価関数がKPPTより軽ければ、tte->eval()をなくしても良いぐらいなのだが…。

// ttValue can be used as a better position evaluation (~7 Elo)
// ttValue can be used as a better position evaluation (~13 Elo)

// 置換表に格納されていたスコアは、この局面で今回探索するものと同等か少しだけ劣るぐらいの
// 精度で探索されたものであるなら、それをbestValueの初期値として使う。
Expand All @@ -3153,9 +3183,9 @@ namespace {
if (!PARAM_QSEARCH_FORCE_EVAL)
{
// Stockfish相当のコード
ss->staticEval = bestValue =
(ss - 1)->currentMove != MOVE_NULL ? evaluate(pos)
: -(ss - 1)->staticEval;
ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
: -(ss-1)->staticEval;


// 1手前の局面(相手の手番)において 評価値が 500だとしたら、
// 自分の手番側から見ると -500 なので、 -(ss - 1)->staticEval はそういう意味。
Expand Down Expand Up @@ -3186,12 +3216,13 @@ namespace {

// 王手がかかっていなくてPvNodeでかつ、bestValueがalphaより大きいならそれをalphaの初期値に使う。
// 王手がかかっているなら全部の指し手を調べたほうがいい。
if (PvNode && bestValue > alpha)
if (bestValue > alpha)
alpha = bestValue;

// futilityの基準となる値をbestValueにmargin値を加算したものとして、
// これを下回るようであれば枝刈りする。
futilityBase = bestValue + PARAM_FUTILITY_MARGIN_QUIET /*118*/;
futilityBase = std::min(ss->staticEval, bestValue) + PARAM_FUTILITY_MARGIN_QUIET /*118 → 200*/;

}

// -----------------------
Expand All @@ -3208,9 +3239,8 @@ namespace {
// will be generated.

// 取り合いの指し手だけ生成する
// searchから呼び出された場合、直前の指し手がMOVE_NULLであることがありうるが、
// 静止探索の1つ目の深さではrecaptureを生成しないならこれは問題とならない。
Square prevSq = to_sq((ss - 1)->currentMove);
// searchから呼び出された場合、直前の指し手がMOVE_NULLであることがありうる。この場合、SQ_NBを設定する。
Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NB;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
&thisThread->captureHistory,
contHist,
Expand Down Expand Up @@ -3508,7 +3538,7 @@ namespace {
// PV lineをコピーする。
// pv に move(1手) + childPv(複数手,末尾MOVE_NONE)をコピーする。
// 番兵として末尾はMOVE_NONEにすることになっている。
void update_pv(Move* pv, Move move, Move* childPv) {
void update_pv(Move* pv, Move move, const Move* childPv) {

for (*pv++ = move; childPv && *childPv != MOVE_NONE; )
*pv++ = *childPv++;
Expand All @@ -3523,50 +3553,57 @@ namespace {

// update_all_stats()は、bestmoveが見つかったときにそのnodeの探索の終端で呼び出される。
// 統計情報一式を更新する。
// prevSq : 直前の指し手の駒の移動先。直前の指し手がMOVE_NONEの時はSQ_NB

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) {

int bonus1, bonus2;
Color us = pos.side_to_move();
Thread* thisThread = pos.this_thread();
CapturePieceToHistory& captureHistory = thisThread->captureHistory;
Piece moved_piece = pos.moved_piece_after(bestMove);
PieceType captured = type_of(pos.piece_on(to_sq(bestMove)));
PieceType captured;

bonus1 = stat_bonus(depth + 1);
bonus2 = bestValue > beta + PawnValue ? bonus1 // larger bonus
: stat_bonus(depth); // smaller bonus
int quietMoveBonus = stat_bonus(depth + 1);

// Stockfishではcapture_or_promotion()からcapture()に変更された。[2022/3/23]
// Stockfishでは、capture()からcapture_stage()に変更された。[2023/10/15
if (!pos.capture(bestMove))
{
// Increase stats for the best move in case it was a quiet move
update_quiet_stats(pos, ss, bestMove, bonus2);
// PARAM_UPDATE_ALL_STATS_EVAL_TH は、PawnValueより少し小さな値がベストっぽい。

int bestMoveBonus = bestValue > beta + PARAM_UPDATE_ALL_STATS_EVAL_TH ? quietMoveBonus // larger bonus
: 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);

// Decrease stats for all non-best quiet moves
for (int i = 0; i < quietCount; ++i)
{
thisThread->mainHistory[from_to(quietsSearched[i])][us] << -bonus2;
thisThread->mainHistory[from_to(quietsSearched[i])][us] << -bestMoveBonus;
// StockfishはmainHistory↑は、[Color][from_to]の順なので注意。

update_continuation_histories(ss, pos.moved_piece_after(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2);
update_continuation_histories(ss, pos.moved_piece_after(quietsSearched[i]), to_sq(quietsSearched[i]), -bestMoveBonus);
}
}
else
else {
// Increase stats for the best move in case it was a capture move

captureHistory[to_sq(bestMove)][moved_piece][captured] << bonus1;
// ※ StockfishはcaptureHistory↑は[pc][to][captured]の順なので注意。
captured = type_of(pos.piece_on(to_sq(bestMove)));
captureHistory[to_sq(bestMove)][moved_piece][captured] << quietMoveBonus;
// ※ StockfishはcaptureHistory↑は[pc][to][captured]の順なので注意。
}

// Extra penalty for a quiet early move that was not a TT move or
// main killer move in previous ply when it gets refuted.

// (ss-1)->ttHit : 一つ前のnodeで置換表にhitしたか

if (((ss - 1)->moveCount == 1 + (ss - 1)->ttHit || ((ss - 1)->currentMove == (ss - 1)->killers[0]))
// MOVE_NULLの場合、Stockfishでは65(移動後の升がSQ_NBであることを保証している。やねうら王もそう変更した。)
if ( prevSq != SQ_NB
&& ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0]))
&& !pos.captured_piece())
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -bonus1);
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveBonus);

// Decrease stats for all non-best capture moves
// 捕獲する指し手でベストではなかったものをすべて減点する。
Expand All @@ -3575,7 +3612,7 @@ namespace {
{
moved_piece = pos.moved_piece_after(capturesSearched[i]);
captured = type_of(pos.piece_on(to_sq(capturesSearched[i])));
captureHistory[to_sq(capturesSearched[i])][moved_piece][captured] << -bonus1;
captureHistory[to_sq(capturesSearched[i])][moved_piece][captured] << -quietMoveBonus;
// Stockfishは[pc][to][captured]の順なので注意。
}
}
Expand All @@ -3593,7 +3630,7 @@ namespace {
{
for (int i : {1, 2, 4, 6})
{
// Only update first 2 continuation histories if we are in check
// Only update the first 2 continuation histories if we are in check
if (ss->inCheck && i > 2)
break;
if (is_ok((ss - i)->currentMove))
Expand Down Expand Up @@ -3635,7 +3672,7 @@ namespace {
}
}

// When playing with strength handicap, choose best move among a set of RootMoves
// When playing with strength handicap, choose the best move among a set of RootMoves
// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.

// 手加減が有効であるなら、best moveを'level'に依存する統計ルールに基づくRootMovesの集合から選ぶ。
Expand Down Expand Up @@ -3698,9 +3735,12 @@ void MainThread::check_time()
// このデフォルト値、ある程度小さくしておかないと、通信遅延分のマージンを削ったときに
// ちょうど1秒を超えて計測2秒になり、損をしうるという議論があるようだ。
// cf. Check the clock every 1024 nodes : https://github.com/official-stockfish/Stockfish/commit/8db75dd9ec05410136898aa2f8c6dc720b755eb8
// Stockfish9から10になったときに4096→1024に引き下げられた。
// main threadでしか判定しないからチェックに要するコストは微小だと思われる。
callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024;

// When using nodes, ensure checking rate is not lower than 0.1% of nodes
// → NodesLimitを有効にした時、その指定されたノード数の0.1%程度の誤差であって欲しいのでそれくらいの頻度でチェックする。

callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512;

// 1秒ごとにdbg_print()を呼び出す処理。
// dbg_print()は、dbg_hit_on()呼び出しによる統計情報を表示する。
Expand Down Expand Up @@ -3732,7 +3772,7 @@ void MainThread::check_time()
if ((Limits.use_time_management() &&
(elapsed > Time.maximum() || (Time.search_end > 0 && elapsed > Time.search_end)))
|| (Limits.movetime && elapsed >= Limits.movetime)
|| (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)
|| (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))
)
{
if (Limits.wait_stop)
Expand Down
2 changes: 1 addition & 1 deletion source/extra/key128.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
// 置換表で用いるためにPositionクラスから得られるhash keyを64bit版の他に128,256bitに変更することが出来るのでそのための構造体。

// 64bit版
typedef uint64_t Key64;
using Key64 = uint64_t;

// 128bit版
struct alignas(16) Key128
Expand Down
5 changes: 5 additions & 0 deletions source/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ std::ostream& operator<<(std::ostream& os, RepetitionState rs)
namespace Search {
LimitsType Limits;

/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move
/// before exiting the search, for instance, in case we stop the search during a
/// fail high at root. We try hard to have a ponder move to return to the GUI,
/// otherwise in case of 'ponder on' we have nothing to think about.

// 探索を抜ける前にponderの指し手がないとき(rootでfail highしているだとか)にこの関数を呼び出す。
// ponderの指し手として何かを指定したほうが、その分、相手の手番において考えられて得なので。

Expand Down

0 comments on commit 1796880

Please sign in to comment.