Static Evaluation Correction History

From Chessprogramming wiki
Jump to: navigation, search

Static Evaluation Correction History,
also known as Correction History or CorrHist for short. It records the difference between static evaluation and search score of a position to a table indexed by the corresponding board feature, then uses the difference to adjust future static evaluations in positions with the same feature. Correction history was first introduced in Caissa in Oct 2023 and quickly adopted by other engines. Correction history has been shown to exhibit non-linear scaling behavior with respect to increasing time control.

Correction Conditions

An update to correction history happens when the following conditions are satisfied:

  • Side-to-move is not in check
  • Best move either does not exist or is a capture
  • If score type is Lower bound, then score should not be above static evaluation
  • If score type is Upper bound, then score should not be below static evaluation

Implementations

Correction History Updates

Correction History is most commonly implemented as a exponential moving average. A version as seen in Alexandria is shown below:

void updateCorrHistScore(const Position *pos, SearchData *sd, const int depth, const int diff) {
    int &entry = sd->corrHist[pos->side][pos->pawnKey % CORRHIST_SIZE];
    const int scaledDiff = diff * CORRHIST_GRAIN;
    const int newWeight = std::min(depth * depth + 2 * depth + 1, 128);
    assert(newWeight <= CORRHIST_WEIGHT_SCALE);

    entry = (entry * (CORRHIST_WEIGHT_SCALE - newWeight) + scaledDiff * newWeight) / CORRHIST_WEIGHT_SCALE;
    entry = std::clamp(entry, -CORRHIST_MAX, CORRHIST_MAX);
}

Some engines such as Stockfish use the history gravity formula to scale correction history updates:

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->pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)] << bonus;
}

Applying Correction

In engines with exponential moving average, the correction value is applied by summing the unadjusted static evaluation and the correction history value normalized by correction grain.

int adjustEvalWithCorrHist(const Position *pos, const SearchData *sd, const int rawEval) {
    const int &entry = sd->corrHist[pos->side][pos->pawnKey % CORRHIST_SIZE];
    return std::clamp(rawEval + entry / CORRHIST_GRAIN, -MATE_FOUND + 1, MATE_FOUND - 1);
}

In engines with history gravity updates, the correction value is applied by summing the unadjusted static evaluation with a fraction of correction history value.

Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) {
    const auto cv =
      w.pawnCorrectionHistory[pos.side_to_move()][pawn_structure_index<Correction>(pos)];
    v += 66 * cv / 512;
    return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
}

External Links

https://github.com/Witek902/Caissa/commit/6dd05529dea17e7a675dc593e4e0ab99965a306e

https://github.com/official-stockfish/Stockfish/pull/4950