1
0
Fork 0

Merge branch 'official-stockfish:master' into range-reduction

pull/3717/head
Ofek Shochat 2021-09-24 09:07:00 +02:00 committed by GitHub
commit 9ac5185f5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 20 deletions

View File

@ -85,6 +85,33 @@ static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
static inline const bool IsLittleEndian = (Le.c[0] == 4);
// RunningAverage : a class to calculate a running average of a series of values.
// For efficiency, all computations are done with integers.
class RunningAverage {
public:
// Constructor
RunningAverage() {}
// Reset the running average to rational value p / q
void set(int64_t p, int64_t q)
{ average = p * PERIOD * RESOLUTION / q; }
// Update average with value v
void update(int64_t v)
{ average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; }
// Test if average is strictly greater than rational a / b
bool is_greater(int64_t a, int64_t b)
{ return b * average > a * PERIOD * RESOLUTION ; }
private :
static constexpr int64_t PERIOD = 4096;
static constexpr int64_t RESOLUTION = 1024;
int64_t average;
};
template <typename T>
class ValueListInserter {
public:

View File

@ -61,9 +61,6 @@ namespace {
// Different node types, used as a template parameter
enum NodeType { NonPV, PV, Root };
constexpr uint64_t TtHitAverageWindow = 4096;
constexpr uint64_t TtHitAverageResolution = 1024;
// Futility margin
Value futility_margin(Depth d, bool improving) {
return Value(214 * (d - improving));
@ -91,6 +88,30 @@ namespace {
return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1);
}
// Check if the current thread is in a search explosion
ExplosionState search_explosion(Thread* thisThread) {
uint64_t nodesNow = thisThread->nodes;
bool explosive = thisThread->doubleExtensionAverage[WHITE].is_greater(2, 100)
|| thisThread->doubleExtensionAverage[BLACK].is_greater(2, 100);
if (explosive)
thisThread->nodesLastExplosive = nodesNow;
else
thisThread->nodesLastNormal = nodesNow;
if ( explosive
&& thisThread->state == EXPLOSION_NONE
&& nodesNow - thisThread->nodesLastNormal > 6000000)
thisThread->state = MUST_CALM_DOWN;
if ( thisThread->state == MUST_CALM_DOWN
&& nodesNow - thisThread->nodesLastExplosive > 6000000)
thisThread->state = EXPLOSION_NONE;
return thisThread->state;
}
// Skill structure is used to implement strength limit
struct Skill {
explicit Skill(int l) : level(l) {}
@ -310,8 +331,14 @@ void Thread::search() {
multiPV = std::max(multiPV, (size_t)4);
multiPV = std::min(multiPV, rootMoves.size());
ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2;
ttHitAverage.set(50, 100); // initialize the running average at 50%
doubleExtensionAverage[WHITE].set(0, 100); // initialize the running average at 0%
doubleExtensionAverage[BLACK].set(0, 100); // initialize the running average at 0%
nodesLastExplosive = nodes;
nodesLastNormal = nodes;
state = EXPLOSION_NONE;
trend = SCORE_ZERO;
int searchAgainCounter = 0;
@ -518,6 +545,14 @@ namespace {
template <NodeType nodeType>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
Thread* thisThread = pos.this_thread();
// Step 0. Limit search explosion
if ( ss->ply > 10
&& search_explosion(thisThread) == MUST_CALM_DOWN
&& depth > (ss-1)->depth)
depth = (ss-1)->depth;
constexpr bool PvNode = nodeType != NonPV;
constexpr bool rootNode = nodeType == Root;
const Depth maxNextDepth = rootNode ? depth : depth + 1;
@ -554,12 +589,11 @@ namespace {
Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
bool givesCheck, improving, didLMR, priorCapture;
bool captureOrPromotion, doFullDepthSearch, moveCountPruning,
ttCapture, singularQuietLMR;
ttCapture, singularQuietLMR, noLMRExtension;
Piece movedPiece;
int moveCount, captureCount, quietCount;
// Step 1. Initialize node
Thread* thisThread = pos.this_thread();
ss->inCheck = pos.checkers();
priorCapture = pos.captured_piece();
Color us = pos.side_to_move();
@ -602,8 +636,12 @@ namespace {
(ss+1)->excludedMove = bestMove = MOVE_NONE;
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
ss->doubleExtensions = (ss-1)->doubleExtensions;
ss->depth = depth;
Square prevSq = to_sq((ss-1)->currentMove);
// Update the running average statistics for double extensions
thisThread->doubleExtensionAverage[us].update(ss->depth > (ss-1)->depth);
// Initialize statScore to zero for the grandchildren of the current position.
// So statScore is shared between all grandchildren and only the first grandchild
// starts with statScore = 0. Later grandchildren start with the last calculated
@ -632,9 +670,8 @@ namespace {
&& is_ok((ss-1)->currentMove))
thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5);
// thisThread->ttHitAverage can be used to approximate the running average of ttHit
thisThread->ttHitAverage = (TtHitAverageWindow - 1) * thisThread->ttHitAverage / TtHitAverageWindow
+ TtHitAverageResolution * ss->ttHit;
// running average of ttHit
thisThread->ttHitAverage.update(ss->ttHit);
// At non-PV nodes we check for an early TT cutoff
if ( !PvNode
@ -949,8 +986,7 @@ moves_loop: // When in check, search starts here
ss->ply);
value = bestValue;
singularQuietLMR = moveCountPruning = false;
bool doubleExtension = false;
singularQuietLMR = moveCountPruning = noLMRExtension = false;
// Indicate PvNodes that will probably fail low if the node was searched
// at a depth equal or greater than the current depth, and the result of this search was a fail low.
@ -1070,13 +1106,13 @@ moves_loop: // When in check, search starts here
extension = 1;
singularQuietLMR = !ttCapture;
// Avoid search explosion by limiting the number of double extensions to at most 3
// Avoid search explosion by limiting the number of double extensions
if ( !PvNode
&& value < singularBeta - 93
&& ss->doubleExtensions < 3)
&& value < singularBeta - 75
&& ss->doubleExtensions <= 6)
{
extension = 2;
doubleExtension = true;
noLMRExtension = true;
}
}
@ -1147,7 +1183,7 @@ moves_loop: // When in check, search starts here
r--;
// Decrease reduction if the ttHit running average is large (~0 Elo)
if (thisThread->ttHitAverage > 537 * TtHitAverageResolution * TtHitAverageWindow / 1024)
if (thisThread->ttHitAverage.is_greater(537, 1024))
r--;
// Decrease reduction if position is or has been on the PV
@ -1188,8 +1224,16 @@ moves_loop: // When in check, search starts here
// In general we want to cap the LMR depth search at newDepth. But if
// reductions are really negative and movecount is low, we allow this move
// to be searched deeper than the first move in specific cases.
Depth d = std::clamp(newDepth - r, 1, newDepth + (r < -1 && (moveCount <= 5 || (depth > 6 && PvNode)) && !doubleExtension));
// to be searched deeper than the first move in specific cases (note that
// this may lead to hidden double extensions if newDepth got it own extension
// before).
int deeper = r >= -1 ? 0
: noLMRExtension ? 0
: moveCount <= 5 ? 1
: (depth > 6 && PvNode) ? 1
: 0;
Depth d = std::clamp(newDepth - r, 1, newDepth + deeper);
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
if (ss->staticEval - value < 30 && depth > 7) {

View File

@ -47,6 +47,7 @@ struct Stack {
Move excludedMove;
Move killers[2];
Value staticEval;
Depth depth;
int statScore;
int moveCount;
bool inCheck;

View File

@ -60,10 +60,14 @@ public:
Pawns::Table pawnsTable;
Material::Table materialTable;
size_t pvIdx, pvLast;
uint64_t ttHitAverage;
RunningAverage ttHitAverage;
RunningAverage doubleExtensionAverage[COLOR_NB];
uint64_t nodesLastExplosive;
uint64_t nodesLastNormal;
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
int selDepth, nmpMinPly;
Color nmpColor;
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
ExplosionState state;
Position rootPos;
StateInfo rootState;

View File

@ -173,6 +173,11 @@ enum Bound {
BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
};
enum ExplosionState {
EXPLOSION_NONE,
MUST_CALM_DOWN
};
enum Value : int {
VALUE_ZERO = 0,
VALUE_DRAW = 0,