mirror of
https://github.com/sockspls/badfish
synced 2025-04-29 16:23:09 +00:00
Introduce static evaluation correction history
Idea from Caissa (https://github.com/Witek902/Caissa) chess engine. With given pawn structure collect data with how often search result and by how much it was better / worse than static evalution of position and use it to adjust static evaluation of positions with given pawn structure. Details: 1. excludes positions with fail highs and moves producing it being a capture; 2. update value is function of not only difference between best value and static evaluation but also is multiplied by linear function of depth; 3. maximum update value is maximum value of correction history divided by 2; 4. correction history itself is divided by 32 when applied so maximum value of static evaluation adjustment is 32 internal units. Passed STC: https://tests.stockfishchess.org/tests/view/658fc7b679aa8af82b955cac LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 128672 W: 32757 L: 32299 D: 63616 Ptnml(0-2): 441, 15241, 32543, 15641, 470 Passed LTC: https://tests.stockfishchess.org/tests/view/65903f6979aa8af82b9566f1 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 97422 W: 24626 L: 24178 D: 48618 Ptnml(0-2): 41, 10837, 26527, 11245, 61 closes https://github.com/official-stockfish/Stockfish/pull/4950 Bench: 1157852
This commit is contained in:
parent
4ff297a6df
commit
b4d995d0d9
5 changed files with 98 additions and 26 deletions
|
@ -179,7 +179,7 @@ void MovePicker::score() {
|
|||
|
||||
// histories
|
||||
m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)];
|
||||
m.value += 2 * (*pawnHistory)[pawn_structure(pos)][pc][to];
|
||||
m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to];
|
||||
m.value += 2 * (*continuationHistory[0])[pc][to];
|
||||
m.value += (*continuationHistory[1])[pc][to];
|
||||
m.value += (*continuationHistory[2])[pc][to] / 4;
|
||||
|
@ -216,7 +216,7 @@ void MovePicker::score() {
|
|||
else
|
||||
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
|
||||
+ (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
||||
+ (*pawnHistory)[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)];
|
||||
+ (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][to_sq(m)];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,11 +34,24 @@
|
|||
namespace Stockfish {
|
||||
|
||||
constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2
|
||||
constexpr int CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2
|
||||
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
|
||||
|
||||
static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0,
|
||||
"PAWN_HISTORY_SIZE has to be a power of 2");
|
||||
|
||||
inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); }
|
||||
static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0,
|
||||
"CORRECTION_HISTORY_SIZE has to be a power of 2");
|
||||
|
||||
enum PawnHistoryType {
|
||||
Normal,
|
||||
Correction
|
||||
};
|
||||
|
||||
template<PawnHistoryType T = Normal>
|
||||
inline int pawn_structure_index(const Position& pos) {
|
||||
return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1);
|
||||
}
|
||||
|
||||
// StatsEntry stores the stat table value. It is usually a number but could
|
||||
// be a move or even a nested history. We use a class instead of a naked value
|
||||
|
@ -122,6 +135,10 @@ using ContinuationHistory = Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>
|
|||
// PawnHistory is addressed by the pawn structure and a move's [piece][to]
|
||||
using PawnHistory = Stats<int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>;
|
||||
|
||||
// CorrectionHistory is addressed by color and pawn structure
|
||||
using CorrectionHistory =
|
||||
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>;
|
||||
|
||||
// MovePicker class is used to pick one pseudo-legal move at a time from the
|
||||
// current position. The most important method is next_move(), which returns a
|
||||
// new pseudo-legal move each time it is called, until there are no moves left,
|
||||
|
|
|
@ -93,6 +93,11 @@ constexpr int futility_move_count(bool improving, Depth depth) {
|
|||
return improving ? (3 + depth * depth) : (3 + depth * depth) / 2;
|
||||
}
|
||||
|
||||
// Guarantee evaluation does not hit the tablebase range
|
||||
constexpr Value to_static_eval(const Value v) {
|
||||
return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||
}
|
||||
|
||||
// History and stats update bonus, based on depth
|
||||
int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); }
|
||||
|
||||
|
@ -712,6 +717,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
|
|||
|
||||
CapturePieceToHistory& captureHistory = thisThread->captureHistory;
|
||||
|
||||
Value unadjustedStaticEval = VALUE_NONE;
|
||||
|
||||
// Step 6. Static evaluation of the position
|
||||
if (ss->inCheck)
|
||||
{
|
||||
|
@ -725,26 +732,40 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
|
|||
// Providing the hint that this node's accumulator will be used often
|
||||
// brings significant Elo gain (~13 Elo).
|
||||
Eval::NNUE::hint_common_parent_position(pos);
|
||||
eval = ss->staticEval;
|
||||
unadjustedStaticEval = eval = ss->staticEval;
|
||||
}
|
||||
else if (ss->ttHit)
|
||||
{
|
||||
// Never assume anything about values stored in TT
|
||||
ss->staticEval = eval = tte->eval();
|
||||
unadjustedStaticEval = ss->staticEval = eval = tte->eval();
|
||||
if (eval == VALUE_NONE)
|
||||
ss->staticEval = eval = evaluate(pos);
|
||||
unadjustedStaticEval = ss->staticEval = eval = evaluate(pos);
|
||||
else if (PvNode)
|
||||
Eval::NNUE::hint_common_parent_position(pos);
|
||||
|
||||
Value newEval =
|
||||
ss->staticEval
|
||||
+ thisThread->correctionHistory[us][pawn_structure_index<Correction>(pos)] / 32;
|
||||
|
||||
ss->staticEval = eval = to_static_eval(newEval);
|
||||
|
||||
// ttValue can be used as a better position evaluation (~7 Elo)
|
||||
if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)))
|
||||
eval = ttValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss->staticEval = eval = evaluate(pos);
|
||||
// Save static evaluation into the transposition table
|
||||
tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
|
||||
unadjustedStaticEval = ss->staticEval = eval = evaluate(pos);
|
||||
|
||||
Value newEval =
|
||||
ss->staticEval
|
||||
+ thisThread->correctionHistory[us][pawn_structure_index<Correction>(pos)] / 32;
|
||||
|
||||
ss->staticEval = eval = to_static_eval(newEval);
|
||||
|
||||
// Static evaluation is saved as it was before adjustment by correction history
|
||||
tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE,
|
||||
unadjustedStaticEval);
|
||||
}
|
||||
|
||||
// Use static evaluation difference to improve quiet move ordering (~9 Elo)
|
||||
|
@ -753,7 +774,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
|
|||
int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546);
|
||||
thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus;
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION)
|
||||
thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4;
|
||||
thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq]
|
||||
<< bonus / 4;
|
||||
}
|
||||
|
||||
// Set up the improving flag, which is true if current static evaluation is
|
||||
|
@ -888,7 +910,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo
|
|||
{
|
||||
// Save ProbCut data into transposition table
|
||||
tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3,
|
||||
move, ss->staticEval);
|
||||
move, unadjustedStaticEval);
|
||||
return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta)
|
||||
: value;
|
||||
}
|
||||
|
@ -999,10 +1021,10 @@ moves_loop: // When in check, search starts here
|
|||
}
|
||||
else
|
||||
{
|
||||
int history = (*contHist[0])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[1])[movedPiece][to_sq(move)]
|
||||
int history =
|
||||
(*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[3])[movedPiece][to_sq(move)]
|
||||
+ thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)];
|
||||
+ thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][to_sq(move)];
|
||||
|
||||
// Continuation history based pruning (~2 Elo)
|
||||
if (lmrDepth < 6 && history < -3752 * depth)
|
||||
|
@ -1364,12 +1386,23 @@ moves_loop: // When in check, search starts here
|
|||
ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3);
|
||||
|
||||
// Write gathered information in transposition table
|
||||
// Static evaluation is saved as it was before correction history
|
||||
if (!excludedMove && !(rootNode && thisThread->pvIdx))
|
||||
tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv,
|
||||
bestValue >= beta ? BOUND_LOWER
|
||||
: PvNode && bestMove ? BOUND_EXACT
|
||||
: BOUND_UPPER,
|
||||
depth, bestMove, ss->staticEval);
|
||||
depth, bestMove, unadjustedStaticEval);
|
||||
|
||||
// Adjust correction history
|
||||
if (!ss->inCheck && (!bestMove || !pos.capture(bestMove))
|
||||
&& !(bestValue >= beta && bestValue <= ss->staticEval)
|
||||
&& !(!bestMove && bestValue >= ss->staticEval))
|
||||
{
|
||||
auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8,
|
||||
-CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);
|
||||
thisThread->correctionHistory[us][pawn_structure_index<Correction>(pos)] << bonus;
|
||||
}
|
||||
|
||||
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
|
||||
|
||||
|
@ -1450,6 +1483,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
|
|||
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
|
||||
return ttValue;
|
||||
|
||||
Value unadjustedStaticEval = VALUE_NONE;
|
||||
|
||||
// Step 4. Static evaluation of the position
|
||||
if (ss->inCheck)
|
||||
bestValue = futilityBase = -VALUE_INFINITE;
|
||||
|
@ -1458,8 +1493,14 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
|
|||
if (ss->ttHit)
|
||||
{
|
||||
// Never assume anything about values stored in TT
|
||||
if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE)
|
||||
ss->staticEval = bestValue = evaluate(pos);
|
||||
if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE)
|
||||
unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos);
|
||||
|
||||
Value newEval =
|
||||
ss->staticEval
|
||||
+ thisThread->correctionHistory[us][pawn_structure_index<Correction>(pos)] / 32;
|
||||
|
||||
ss->staticEval = bestValue = to_static_eval(newEval);
|
||||
|
||||
// ttValue can be used as a better position evaluation (~13 Elo)
|
||||
if (ttValue != VALUE_NONE
|
||||
|
@ -1467,16 +1508,24 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
|
|||
bestValue = ttValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In case of null move search, use previous static eval with a different sign
|
||||
ss->staticEval = bestValue =
|
||||
unadjustedStaticEval = ss->staticEval = bestValue =
|
||||
(ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval;
|
||||
|
||||
Value newEval =
|
||||
ss->staticEval
|
||||
+ thisThread->correctionHistory[us][pawn_structure_index<Correction>(pos)] / 32;
|
||||
|
||||
ss->staticEval = bestValue = to_static_eval(newEval);
|
||||
}
|
||||
|
||||
// Stand pat. Return immediately if static value is at least beta
|
||||
if (bestValue >= beta)
|
||||
{
|
||||
if (!ss->ttHit)
|
||||
tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE,
|
||||
MOVE_NONE, ss->staticEval);
|
||||
MOVE_NONE, unadjustedStaticEval);
|
||||
|
||||
return bestValue;
|
||||
}
|
||||
|
@ -1620,8 +1669,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
|
|||
bestValue = (3 * bestValue + beta) / 4;
|
||||
|
||||
// Save gathered info in transposition table
|
||||
// Static evaluation is saved as it was before adjustment by correction history
|
||||
tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit,
|
||||
bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, ss->staticEval);
|
||||
bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove,
|
||||
unadjustedStaticEval);
|
||||
|
||||
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
|
||||
|
||||
|
@ -1720,15 +1771,17 @@ void update_all_stats(const Position& pos,
|
|||
|
||||
// Increase stats for the best move in case it was a quiet move
|
||||
update_quiet_stats(pos, ss, bestMove, bestMoveBonus);
|
||||
thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)]
|
||||
<< quietMoveBonus;
|
||||
|
||||
int pIndex = pawn_structure_index(pos);
|
||||
thisThread->pawnHistory[pIndex][moved_piece][to_sq(bestMove)] << quietMoveBonus;
|
||||
|
||||
// Decrease stats for all non-best quiet moves
|
||||
for (int i = 0; i < quietCount; ++i)
|
||||
{
|
||||
thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])]
|
||||
[to_sq(quietsSearched[i])]
|
||||
thisThread
|
||||
->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][to_sq(quietsSearched[i])]
|
||||
<< -quietMoveMalus;
|
||||
|
||||
thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus;
|
||||
update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]),
|
||||
to_sq(quietsSearched[i]), -quietMoveMalus);
|
||||
|
|
|
@ -70,6 +70,7 @@ void Thread::clear() {
|
|||
mainHistory.fill(0);
|
||||
captureHistory.fill(0);
|
||||
pawnHistory.fill(0);
|
||||
correctionHistory.fill(0);
|
||||
|
||||
for (bool inCheck : {false, true})
|
||||
for (StatsType c : {NoCaptures, Captures})
|
||||
|
|
|
@ -69,6 +69,7 @@ class Thread {
|
|||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
PawnHistory pawnHistory;
|
||||
CorrectionHistory correctionHistory;
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue