Skip to content

Commit

Permalink
- Simplify continuation histories : official-stockfish/Stockfish@3d67567
Browse files Browse the repository at this point in the history


- Simplify statScore divisor into a constant : official-stockfish/Stockfish@c17d73c
- Simplify histories movepick formula : official-stockfish/Stockfish@ec1cda1
- Avoid changing bestvalue : official-stockfish/Stockfish@b0870cf
  • Loading branch information
yaneurao committed Jun 8, 2024
1 parent 8f6c7ab commit b546b8a
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 72 deletions.
91 changes: 39 additions & 52 deletions source/engine/yaneuraou-engine/yaneuraou-search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1078,8 +1078,7 @@ void Thread::search()
// four searchAgain steps (see issue #2717).

// fail highするごとにdepthを下げていく処理
Depth adjustedDepth =
std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
rootDelta = beta - alpha;
bestValue = ::search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);

Expand Down Expand Up @@ -1707,8 +1706,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo

if ( !PvNode // PV nodeでは置換表の指し手では枝刈りしない(PV nodeはごくわずかしかないので..)
&& !excludedMove
&& tte->depth() > depth - (ttValue <= beta)
// 置換表に登録されている探索深さのほうが深くて
&& tte->depth() > depth // 置換表に登録されている探索深さのほうが深くて
&& ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit
// (VALUE_NONEだとすると他スレッドからTTEntryが読みだす直前に破壊された可能性がある)
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
Expand Down Expand Up @@ -2019,7 +2017,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo

if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture)
{
int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1449, 1449);
int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546);
// この右辺の↑係数、調整すべきだろうけども、4 Eloのところ調整しても…みたいな意味はある。
bonus = bonus > 0 ? 2 * bonus : bonus / 2;
thisThread->mainHistory(~us, from_to((ss - 1)->currentMove)) << bonus;
Expand Down Expand Up @@ -2068,7 +2066,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
// TODO : ここのパラメーター調整するか考える。
// → ~1 Eloだとなー。

if (eval < alpha - 474 - (270 - 174 * ((ss + 1)->cutoffCnt > 3)) * depth * depth)
if (eval < alpha - 474 - 324 * depth * depth)
{
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
if (value < alpha)
Expand Down Expand Up @@ -2107,7 +2105,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
// ※ 統計値(mainHistoryとかstatScoreとか)のしきい値に関しては、やねうら王ではStockfishから調整しないことにしているので、
// 上のif式に出てくる定数については調整しないことにする。

return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval;
return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval;

// 次のようにするより、単にevalを返したほうが良いらしい。
// return eval - futility_margin(depth);
Expand Down Expand Up @@ -2521,7 +2519,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
// Continuation history based pruning (~2 Elo)
// Continuation historyに基づいた枝刈り(historyの値が悪いものに関してはskip)

if (lmrDepth < PARAM_PRUNING_BY_HISTORY_DEPTH && history < -3645 * depth)
if (lmrDepth < PARAM_PRUNING_BY_HISTORY_DEPTH && history < -4195 * depth)
continue;

history += 2 * thisThread->mainHistory(us, from_to(move));
Expand Down Expand Up @@ -2578,11 +2576,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
// then that move is singular and should be extended. To verify this we do
// a reduced search on the position excluding the ttMove and if the result
// is lower than ttValue minus a margin, then we will extend the ttMove.
// Recursive singular search is avoided.
// Note: the depth margin and singularBeta margin are known for having non-linear
// Note: the depth margin and singularBeta margin are known for having non-linear
// so changing them requires tests at these types of time controls.
// Generally, higher singularBeta (i.e closer to ttValue) and lower extension
// margins scale well.

// (alpha-s,beta-s)の探索(sはマージン値)において1手以外がすべてfail lowして、
// 1手のみが(alpha,beta)においてfail highしたなら、指し手はsingularであり、延長されるべきである。
Expand All @@ -2606,7 +2601,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
if (!rootNode
&& move == ttMove
&& !excludedMove // 再帰的なsingular延長を除外する。
&& depth >= PARAM_SINGULAR_EXTENSION_DEPTH - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv())
&& depth >= PARAM_SINGULAR_EXTENSION_DEPTH - (thisThread->completedDepth > 24) + ss->ttPv
/* && ttValue != VALUE_NONE Already implicit in the next condition */
&& std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY // 詰み絡みのスコアはsingular extensionはしない。(Stockfish 10~)
&& (tte->bound() & BOUND_LOWER)
Expand Down Expand Up @@ -2692,7 +2687,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
// もしカットノードにいるが、ttMoveが現在のベータを超えて高いスコアを出すとは想定されていない場合(約1 Elo)

else if (cutNode)
extension = depth < 19 ? -2 : -1;
extension = -2;

// If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo)
// もしttMoveがreduced searchの値を下回って失敗すると仮定される場合(約1 Elo)
Expand Down Expand Up @@ -2724,10 +2719,11 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
// ⇨ Stockfishで削除されたが、王手延長自体は何らかあった方が良い可能性はあるので条件を調整してはどうか。
// Remove check extension : https://github.com/official-stockfish/Stockfish/commit/96837bc4396d205536cdaabfc17e4885a48b0588

// Recapture extensions (~1 Elo)
else if (PvNode && move == ttMove && to_sq(move) == prevSq
// Extension for capturing the previous moved piece (~0 Elo on STC, ~1 Elo on LTC)

else if (PvNode && to_sq(move) == prevSq
&& captureHistory(movedPiece, to_sq(move), type_of(pos.piece_on(to_sq(move))))
> 4000)
> 3988)
extension = 1;
}

Expand Down Expand Up @@ -2785,13 +2781,12 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
if (ss->ttPv)
r -= 1 + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth);

// Decrease reduction if opponent's move count is high (~1 Elo)
// 相手の(1手前の)move countが大きければ、reductionを減らす。
// 相手の指し手をたくさん読んでいるのにこちらだけreductionするとバランスが悪いから。

if ((ss - 1)->moveCount > 7)
// Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC)
if (PvNode)
r--;

// These reduction adjustments have no proven non-linear scaling.

// Increase reduction for cut nodes (~4 Elo)
// cut nodeにおいてhistoryの値が悪い指し手に対してはreduction量を増やす。
// ※ PVnodeではIID時でもcutNode == trueでは呼ばないことにしたので、
Expand All @@ -2813,25 +2808,24 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
if ((ss + 1)->cutoffCnt > 3)
r++;

// Set reduction to 0 for first picked move (ttMove) (~2 Elo)
// Nullifies all previous reduction adjustments to ttMove and leaves only history to do them

// 最初に生成された手(ttMove)の減少値を0に設定する
// ttMoveに対するこれまでの全ての減少調整を無効にし、historyのみがそれを行うことにする

// For first picked move (ttMove) reduce reduction
// but never allow it to go below 0 (~3 Elo)
//
// 最初に生成された手(ttMove)のreductionを減らす。
// ただし、0以下になることは許さない。
//
else if (move == ttMove)
r = 0;
r = std::max(0, r - 2);

// 【計測資料 11.】statScoreの計算でcontHist[3]も調べるかどうか。
// contHist[5]も/2とかで入れたほうが良いのでは…。誤差か…?
ss->statScore = 2 * thisThread->mainHistory(us, from_to(move))
+ (*contHist[0])(movedPiece, to_sq(move))
+ (*contHist[1])(movedPiece, to_sq(move))
+ (*contHist[3])(movedPiece, to_sq(move))
- 3848;
- 5169;

// Decrease/increase reduction for moves with a good/bad history (~25 Elo)
r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23));
r -= ss->statScore / 11049;

// -----------------------
// Step 17. Late moves reduction / extension (LMR, ~117 Elo)
Expand All @@ -2841,22 +2835,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
// If the move fails high it will be re - searched at full depth.
// depthを減らして探索させて、その指し手がfail highしたら元のdepthで再度探索するという手法

// We use various heuristics for the sons of a node after the first son has
// been searched. In general, we would like to reduce them, but there are many
// cases where we extend a son if it has good chances to be "interesting".

// 最初の子ノードが探索された後、他の子ノードに対してさまざまなヒューリスティクスを使用します。
// 一般的には、これらを削減したいのですが、"興味深い" 可能性が高い場合には、
// 子ノードを拡張するケースが多いです。

// moveCountが大きいものなどは探索深さを減らしてざっくり調べる。
// alpha値を更新しそうなら(fail highが起きたら)、full depthで探索しなおす。

if ( depth >= 2
&& moveCount > 1 + rootNode
&& ( !ss->ttPv
|| !capture
|| (cutNode && (ss - 1)->moveCount > 1)))
if (depth >= 2 && moveCount > 1 + rootNode)
{
// In general we want to cap the LMR depth search at newDepth, but when
// reduction is negative, we allow this move a limited search extension
Expand Down Expand Up @@ -3059,7 +3041,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo

if (value >= beta)
{
ss->cutoffCnt += 1 + !ttMove - (extension >= 2);
ss->cutoffCnt += 1 + !ttMove;
ASSERT_LV3(value >= beta); // Fail high

// value >= beta なら fail high(beta cut)
Expand Down Expand Up @@ -3396,8 +3378,11 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth)

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

// Decide the replacement and cutoff priority of the qsearch TT entries
//qsearchのTTエントリの置き換えとカットオフの優先順位を決定する
// qsearchのTTエントリの置き換えとカットオフの優先順位を決定する

// Note that unlike regular search, which stores literal depth, in QS we only store the
// current movegen stage. If in check, we search all evasions and thus store
// DEPTH_QS_CHECKS. (Evasions may be quiet, and _CHECKS includes quiets.)

// 置換表に登録するdepthはあまりマイナスの値だとおかしいので、
// 王手がかかっているときは、DEPTH_QS_CHECKS(=0)、
Expand Down Expand Up @@ -3524,7 +3509,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth)
// 置換表に格納されていたスコアは、この局面で今回探索するものと同等か少しだけ劣るぐらいの
// 精度で探索されたものであるなら、それをbestValueの初期値として使う。

if ( ttValue != VALUE_NONE
// ただし、mate valueは変更しない方が良いので、abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY は、
// そのための条件。

if (std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY
&& (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)))
bestValue = ttValue;

Expand Down Expand Up @@ -4053,9 +4041,8 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus)
if (ss->inCheck && i > 2)
break;
if (is_ok((ss - i)->currentMove))
(*(ss - i)->continuationHistory)(pc, to) << bonus / (1 + 3 * (i == 3));
// ⇨ (1 + 3 * (i == 3)) は、 i==3は弱くしか影響しないので4で割ると言う意味。
//(*(ss - i)->continuationHistory)(pc, to) << bonus;
(*(ss - i)->continuationHistory)(pc, to) << bonus / (1 + (i == 3));
// ⇨  i==3は弱くしか影響しないので2で割ると言う意味。
}
}

Expand Down
14 changes: 10 additions & 4 deletions source/movepick.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,13 +343,13 @@ void MovePicker::score()
//Square from = from_sq(m);
Square to = to_sq(m);

m.value = 2 * (*mainHistory)(pos.side_to_move(), from_to(m));
m.value = (*mainHistory)(pos.side_to_move(), from_to(m));
#if defined(ENABLE_PAWN_HISTORY)
m.value += 2 * (*pawnHistory)(pawn_structure(pos), pc, to);
#endif
m.value += 2 * (*continuationHistory[0])(pc,to);
m.value += (*continuationHistory[1])(pc,to);
m.value += (*continuationHistory[2])(pc,to) / 4;
m.value += (*continuationHistory[2])(pc,to) / 3;
m.value += (*continuationHistory[3])(pc,to);
m.value += (*continuationHistory[5])(pc,to);

Expand Down Expand Up @@ -459,6 +459,12 @@ Move MovePicker::select(Pred filter) {
// skipQuiets : これがtrueだとQUIETな指し手は返さない。
Move MovePicker::next_move(bool skipQuiets) {

#if defined(USE_SUPER_SORT) && defined(USE_AVX2)
auto quiet_threshold = [](Depth d) { return -PARAM_MOVEPICKER_SORT_ALPHA1 * d; };
#else
auto quiet_threshold = [](Depth d) { return -PARAM_MOVEPICKER_SORT_ALPHA2 * d; };
#endif

top:
switch (stage) {

Expand Down Expand Up @@ -609,9 +615,9 @@ Move MovePicker::next_move(bool skipQuiets) {

#if defined(USE_SUPER_SORT) && defined(USE_AVX2)
// SuperSortを有効にするとinsertion_sortと結果が異なるのでbenchコマンドの探索node数が変わって困ることがあるので注意。
partial_super_sort (cur, endMoves, - PARAM_MOVEPICKER_SORT_ALPHA1 /*3500*/ * depth);
partial_super_sort (cur, endMoves, quiet_threshold(depth));
#else
partial_insertion_sort(cur, endMoves, - PARAM_MOVEPICKER_SORT_ALPHA2 /*3330*/ * depth);
partial_insertion_sort(cur, endMoves, quiet_threshold(depth));
#endif

// → sort時間がもったいないのでdepthが浅いときはscoreの悪い指し手を無視するようにしているだけで
Expand Down
39 changes: 23 additions & 16 deletions source/tt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ void TTEntry::save_(TTEntry::KEY_TYPE key_for_ttentry, Value v, bool pv , Bound
// これは、このnodeで、TT::probeでhitして、その指し手は試したが、それよりいい手が見つかって、枝刈り等が発生しているような
// ケースが考えられる。ゆえに、今回の指し手のほうが、いまの置換表の指し手より価値があると考えられる。

// Preserve the old ttmove if we don't have a new one
if (m || key_for_ttentry != key)
move16 = uint16_t(m);

Expand All @@ -79,6 +80,25 @@ void TTEntry::save_(TTEntry::KEY_TYPE key_for_ttentry, Value v, bool pv , Bound
}
}

uint8_t TTEntry::relative_age(const uint8_t generation8) const {
// Due to our packed storage format for generation and its cyclic
// nature we add GENERATION_CYCLE (256 is the modulus, plus what
// is needed to keep the unrelated lowest n bits from affecting
// the result) to calculate the entry age correctly even after
// generation8 overflows into the next cycle.

// generationは256になるとオーバーフローして0になるのでそれをうまく処理できなければならない。
// a,bが8bitであるとき ( 256 + a - b ) & 0xff のようにすれば、オーバーフローを考慮した引き算が出来る。
// このテクニックを用いる。
// いま、
// a := generationは下位3bitは用いていないので0。
// b := genBound8は下位3bitにはBoundが入っているのでこれはゴミと考える。
// ( 256 + a - b + c) & 0xfc として c = 7としても結果に影響は及ぼさない、かつ、このゴミを無視した計算が出来る。

return (TranspositionTable::GENERATION_CYCLE + generation8 - genBound8)
& TranspositionTable::GENERATION_MASK;
}

// 置換表のサイズを確保しなおす。
void TranspositionTable::resize(size_t mbSize) {

Expand Down Expand Up @@ -211,22 +231,9 @@ TTEntry* TranspositionTable::probe(const Key key_for_index, const TTEntry::KEY_T
// https://yaneuraou.yaneu.com/2023/06/09/replacement-strategy-in-transposition-table/
//

// Due to our packed storage format for generation and its cyclic
// nature we add GENERATION_CYCLE (256 is the modulus, plus what
// is needed to keep the unrelated lowest n bits from affecting
// the result) to calculate the entry age correctly even after
// generation8 overflows into the next cycle.
if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK)
> tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK))
replace = &tte[i];

// generationは256になるとオーバーフローして0になるのでそれをうまく処理できなければならない。
// a,bが8bitであるとき ( 256 + a - b ) & 0xff のようにすれば、オーバーフローを考慮した引き算が出来る。
// このテクニックを用いる。
// いま、
// a := generationは下位3bitは用いていないので0。
// b := genBound8は下位3bitにはBoundが入っているのでこれはゴミと考える。
// ( 256 + a - b + c) & 0xfc として c = 7としても結果に影響は及ぼさない、かつ、このゴミを無視した計算が出来る。
if (replace->depth8 - replace->relative_age(generation8) * 2
> tte[i].depth8 - tte[i].relative_age(generation8) * 2)
replace = &tte[i];

return found = false, replace;
}
Expand Down
2 changes: 2 additions & 0 deletions source/tt.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ struct TTEntry {
void save(Key128& k, Value v, bool pv , Bound b, Depth d, Move m, Value ev);
void save(Key256& k, Value v, bool pv , Bound b, Depth d, Move m, Value ev);

uint8_t relative_age(const uint8_t generation8) const;

// -- やねうら王独自拡張

// やねうら王では、TTClusterSizeを変更できて、これが2の時は、TTEntryに格納するhash keyは64bit。(Stockfishのように)3の時は16bit。
Expand Down

0 comments on commit b546b8a

Please sign in to comment.