1
0
Fork 0
stockfish/src/pawns.cpp

282 lines
9.5 KiB
C++
Raw Normal View History

2008-08-31 23:59:13 -06:00
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Add NNUE evaluation This patch ports the efficiently updatable neural network (NNUE) evaluation to Stockfish. Both the NNUE and the classical evaluations are available, and can be used to assign a value to a position that is later used in alpha-beta (PVS) search to find the best move. The classical evaluation computes this value as a function of various chess concepts, handcrafted by experts, tested and tuned using fishtest. The NNUE evaluation computes this value with a neural network based on basic inputs. The network is optimized and trained on the evalutions of millions of positions at moderate search depth. The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward. It can be evaluated efficiently on CPUs, and exploits the fact that only parts of the neural network need to be updated after a typical chess move. [The nodchip repository](https://github.com/nodchip/Stockfish) provides additional tools to train and develop the NNUE networks. This patch is the result of contributions of various authors, from various communities, including: nodchip, ynasu87, yaneurao (initial port and NNUE authors), domschl, FireFather, rqs, xXH4CKST3RXx, tttak, zz4032, joergoster, mstembera, nguyenpham, erbsenzaehler, dorzechowski, and vondele. This new evaluation needed various changes to fishtest and the corresponding infrastructure, for which tomtor, ppigazzini, noobpwnftw, daylen, and vondele are gratefully acknowledged. The first networks have been provided by gekkehenker and sergiovieri, with the latter net (nn-97f742aaefcd.nnue) being the current default. The evaluation function can be selected at run time with the `Use NNUE` (true/false) UCI option, provided the `EvalFile` option points the the network file (depending on the GUI, with full path). The performance of the NNUE evaluation relative to the classical evaluation depends somewhat on the hardware, and is expected to improve quickly, but is currently on > 80 Elo on fishtest: 60000 @ 10+0.1 th 1 https://tests.stockfishchess.org/tests/view/5f28fe6ea5abc164f05e4c4c ELO: 92.77 +-2.1 (95%) LOS: 100.0% Total: 60000 W: 24193 L: 8543 D: 27264 Ptnml(0-2): 609, 3850, 9708, 10948, 4885 40000 @ 20+0.2 th 8 https://tests.stockfishchess.org/tests/view/5f290229a5abc164f05e4c58 ELO: 89.47 +-2.0 (95%) LOS: 100.0% Total: 40000 W: 12756 L: 2677 D: 24567 Ptnml(0-2): 74, 1583, 8550, 7776, 2017 At the same time, the impact on the classical evaluation remains minimal, causing no significant regression: sprt @ 10+0.1 th 1 https://tests.stockfishchess.org/tests/view/5f2906a2a5abc164f05e4c5b LLR: 2.94 (-2.94,2.94) {-6.00,-4.00} Total: 34936 W: 6502 L: 6825 D: 21609 Ptnml(0-2): 571, 4082, 8434, 3861, 520 sprt @ 60+0.6 th 1 https://tests.stockfishchess.org/tests/view/5f2906cfa5abc164f05e4c5d LLR: 2.93 (-2.94,2.94) {-6.00,-4.00} Total: 10088 W: 1232 L: 1265 D: 7591 Ptnml(0-2): 49, 914, 3170, 843, 68 The needed networks can be found at https://tests.stockfishchess.org/nns It is recommended to use the default one as indicated by the `EvalFile` UCI option. Guidelines for testing new nets can be found at https://github.com/glinscott/fishtest/wiki/Creating-my-first-test#nnue-net-tests Integration has been discussed in various issues: https://github.com/official-stockfish/Stockfish/issues/2823 https://github.com/official-stockfish/Stockfish/issues/2728 The integration branch will be closed after the merge: https://github.com/official-stockfish/Stockfish/pull/2825 https://github.com/official-stockfish/Stockfish/tree/nnue-player-wip closes https://github.com/official-stockfish/Stockfish/pull/2912 This will be an exciting time for computer chess, looking forward to seeing the evolution of this approach. Bench: 4746616
2020-08-05 09:11:15 -06:00
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
2008-08-31 23:59:13 -06:00
Stockfish is free software: you can redistribute it and/or modify
2008-08-31 23:59:13 -06:00
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
2008-08-31 23:59:13 -06:00
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
2008-08-31 23:59:13 -06:00
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
2008-08-31 23:59:13 -06:00
#include <cassert>
#include "bitboard.h"
2008-08-31 23:59:13 -06:00
#include "pawns.h"
#include "position.h"
#include "thread.h"
2008-08-31 23:59:13 -06:00
namespace {
#define V Value
#define S(mg, eg) make_score(mg, eg)
2008-08-31 23:59:13 -06:00
// Pawn penalties
constexpr Score Backward = S( 8, 27);
constexpr Score Doubled = S(11, 55);
constexpr Score Isolated = S( 5, 17);
constexpr Score WeakLever = S( 2, 54);
constexpr Score WeakUnopposed = S(15, 25);
// Bonus for blocked pawns at 5th or 6th rank
constexpr Score BlockedPawn[2] = { S(-13, -4), S(-4, 3) };
constexpr Score BlockedStorm[RANK_NB] = {
S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2)
};
// Connected pawn bonus
constexpr int Connected[RANK_NB] = { 0, 7, 8, 11, 24, 45, 85 };
// Strength of pawn shelter for our king by [distance from edge][rank].
// RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king.
constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = {
{ V( -6), V( 81), V( 93), V( 58), V( 39), V( 18), V( 25) },
{ V(-43), V( 61), V( 35), V(-49), V(-29), V(-11), V( -63) },
{ V(-10), V( 75), V( 23), V( -2), V( 32), V( 3), V( -45) },
{ V(-39), V(-13), V(-29), V(-52), V(-48), V(-67), V(-166) }
};
// Danger of enemy pawns moving toward our king by [distance from edge][rank].
// RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn
// is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn
// on edge, likely blocked by our king.
constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = {
{ V( 85), V(-289), V(-166), V(97), V(50), V( 45), V( 50) },
{ V( 46), V( -25), V( 122), V(45), V(37), V(-10), V( 20) },
{ V( -6), V( 51), V( 168), V(34), V(-2), V(-22), V(-14) },
{ V(-15), V( -11), V( 101), V( 4), V(11), V(-15), V(-29) }
};
#undef S
#undef V
/// evaluate() calculates a score for the static pawn structure of the given position.
/// We cannot use the location of pieces or king in this function, as the evaluation
/// of the pawn structure will be stored in a small cache for speed reasons, and will
/// be re-used even when the pieces have moved.
template<Color Us>
Score evaluate(const Position& pos, Pawns::Entry* e) {
constexpr Color Them = ~Us;
constexpr Direction Up = pawn_push(Us);
Bitboard neighbours, stoppers, support, phalanx, opposed;
Bitboard lever, leverPush, blocked;
Square s;
bool backward, passed, doubled;
Score score = SCORE_ZERO;
const Square* pl = pos.squares<PAWN>(Us);
Bitboard ourPawns = pos.pieces( Us, PAWN);
Bitboard theirPawns = pos.pieces(Them, PAWN);
Bitboard doubleAttackThem = pawn_double_attacks_bb<Them>(theirPawns);
e->passedPawns[Us] = 0;
Bonus for double attacks on unsupported pawns This is a functional change that rewards double attacks on an unsupported pawns. STC (non-functional difference) LLR: 2.96 (-2.94,2.94) [0.50,4.50] Total: 83276 W: 18981 L: 18398 D: 45897 http://tests.stockfishchess.org/tests/view/5d0970500ebc5925cf0a77d4 LTC (incomplete looping version) LLR: 0.50 (-2.94,2.94) [0.00,3.50] Total: 82999 W: 14244 L: 13978 D: 54777 http://tests.stockfishchess.org/tests/view/5d0a8d480ebc5925cf0a8d58 LTC (completed non-looping version). LLR: 2.96 (-2.94,2.94) [0.00,3.50] Total: 223381 W: 38323 L: 37512 D: 147546 http://tests.stockfishchess.org/tests/view/5d0e80510ebc5925cf0ad320 Closes https://github.com/official-stockfish/Stockfish/pull/2205 Bench 3633546 ---------------------------------- Comments by Alain SAVARD: interesting result ! I would have expected that search would resolve such positions correctly on the very next move. This is not a very common pattern, and when it happens, it will quickly disappear. So I'm quite surprised that it passed LTC. I would be even more surprised if this would resist a simplification. Anyway, let's try to imagine a few cases. a) If you have White d5 f5 against Black e6, and White to move last move by Black was probably a capture on e6 and White is about to recapture on e6 b) If you have White d5 f5 against e6, and Black to move last move by White was possibly a capture on d5 or f5 or the pawn on e6 was pinned or could not move for some reason. and white wants to blast open the position and just pushed d4-d5 or f4-f5 Some possible follow-ups a) Motif is so rare that the popcount() can be safely replaced with a bool() But this would not pass a SPRT[0,4], So try a simplification with bool() and also without the & ~theirAttacks b) If it works, we probably can simply have this in the loop if (lever) score += S(0, 20); c) remove all this and tweak something in search for pawn captures (priority, SEE, extension,..)
2019-06-27 01:45:53 -06:00
e->kingSquares[Us] = SQ_NONE;
e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
Bonus for double attacks on unsupported pawns This is a functional change that rewards double attacks on an unsupported pawns. STC (non-functional difference) LLR: 2.96 (-2.94,2.94) [0.50,4.50] Total: 83276 W: 18981 L: 18398 D: 45897 http://tests.stockfishchess.org/tests/view/5d0970500ebc5925cf0a77d4 LTC (incomplete looping version) LLR: 0.50 (-2.94,2.94) [0.00,3.50] Total: 82999 W: 14244 L: 13978 D: 54777 http://tests.stockfishchess.org/tests/view/5d0a8d480ebc5925cf0a8d58 LTC (completed non-looping version). LLR: 2.96 (-2.94,2.94) [0.00,3.50] Total: 223381 W: 38323 L: 37512 D: 147546 http://tests.stockfishchess.org/tests/view/5d0e80510ebc5925cf0ad320 Closes https://github.com/official-stockfish/Stockfish/pull/2205 Bench 3633546 ---------------------------------- Comments by Alain SAVARD: interesting result ! I would have expected that search would resolve such positions correctly on the very next move. This is not a very common pattern, and when it happens, it will quickly disappear. So I'm quite surprised that it passed LTC. I would be even more surprised if this would resist a simplification. Anyway, let's try to imagine a few cases. a) If you have White d5 f5 against Black e6, and White to move last move by Black was probably a capture on e6 and White is about to recapture on e6 b) If you have White d5 f5 against e6, and Black to move last move by White was possibly a capture on d5 or f5 or the pawn on e6 was pinned or could not move for some reason. and white wants to blast open the position and just pushed d4-d5 or f4-f5 Some possible follow-ups a) Motif is so rare that the popcount() can be safely replaced with a bool() But this would not pass a SPRT[0,4], So try a simplification with bool() and also without the & ~theirAttacks b) If it works, we probably can simply have this in the loop if (lever) score += S(0, 20); c) remove all this and tweak something in search for pawn captures (priority, SEE, extension,..)
2019-06-27 01:45:53 -06:00
// Loop through all pawns of the current color and score each pawn
while ((s = *pl++) != SQ_NONE)
{
assert(pos.piece_on(s) == make_piece(Us, PAWN));
Rank r = relative_rank(Us, s);
// Flag the pawn
opposed = theirPawns & forward_file_bb(Us, s);
blocked = theirPawns & (s + Up);
stoppers = theirPawns & passed_pawn_span(Us, s);
lever = theirPawns & pawn_attacks_bb(Us, s);
leverPush = theirPawns & pawn_attacks_bb(Us, s + Up);
doubled = ourPawns & (s - Up);
neighbours = ourPawns & adjacent_files_bb(s);
phalanx = neighbours & rank_bb(s);
support = neighbours & rank_bb(s - Up);
// A pawn is backward when it is behind all pawns of the same color on
// the adjacent files and cannot safely advance.
backward = !(neighbours & forward_ranks_bb(Them, s + Up))
&& (leverPush | blocked);
// Compute additional span if pawn is not backward nor blocked
if (!backward && !blocked)
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
// A pawn is passed if one of the three following conditions is true:
// (a) there is no stoppers except some levers
// (b) the only stoppers are the leverPush, but we outnumber them
// (c) there is only one front stopper which can be levered.
// (Refined in Evaluation::passed)
passed = !(stoppers ^ lever)
|| ( !(stoppers ^ leverPush)
&& popcount(phalanx) >= popcount(leverPush))
|| ( stoppers == blocked && r >= RANK_5
&& (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
passed &= !(forward_file_bb(Us, s) & ourPawns);
// Passed pawns will be properly scored later in evaluation when we have
// full attack info.
if (passed)
e->passedPawns[Us] |= s;
// Score this pawn
if (support | phalanx)
{
int v = Connected[r] * (2 + bool(phalanx) - bool(opposed))
+ 21 * popcount(support);
score += make_score(v, v * (r - 2) / 4);
}
else if (!neighbours)
{
if ( opposed
&& (ourPawns & forward_file_bb(Them, s))
&& !(theirPawns & adjacent_files_bb(s)))
score -= Doubled;
else
score -= Isolated
+ WeakUnopposed * !opposed;
}
else if (backward)
score -= Backward
+ WeakUnopposed * !opposed;
if (!support)
score -= Doubled * doubled
+ WeakLever * more_than_one(lever);
if (blocked && r > RANK_4)
score += BlockedPawn[r-4];
}
return score;
}
} // namespace
2008-08-31 23:59:13 -06:00
namespace Pawns {
2008-08-31 23:59:13 -06:00
/// Pawns::probe() looks up the current position's pawns configuration in
/// the pawns hash table. It returns a pointer to the Entry if the position
/// is found. Otherwise a new Entry is computed and stored there, so we don't
/// have to recompute all when the same pawns configuration occurs again.
2008-08-31 23:59:13 -06:00
Entry* probe(const Position& pos) {
Key key = pos.pawn_key();
Entry* e = pos.this_thread()->pawnsTable[key];
2008-08-31 23:59:13 -06:00
if (e->key == key)
return e;
2008-08-31 23:59:13 -06:00
e->key = key;
e->blockedCount = 0;
e->scores[WHITE] = evaluate<WHITE>(pos, e);
e->scores[BLACK] = evaluate<BLACK>(pos, e);
return e;
2008-08-31 23:59:13 -06:00
}
/// Entry::evaluate_shelter() calculates the shelter bonus and the storm
/// penalty for a king, looking at the king file and the two closest files.
template<Color Us>
Score Entry::evaluate_shelter(const Position& pos, Square ksq) const {
constexpr Color Them = ~Us;
Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them];
Bitboard theirPawns = b & pos.pieces(Them);
Score bonus = make_score(5, 5);
Use special rule for BlockedByKing Simplification: remove BlockedByKing from storm array and use a special rule. The BlockedByKing section in the storm array is substantially similar to the Unopposed section except for two extreme values V(-290), V(-274). Turns out removing BlockedByKing and using a special rule for these two values shows no Elo loss. All the other values in the BlockedByKing section are apparently irrelevant. BlockedByKing now falls under unopposed which (to me) is a bit more logical since there is no defending pawn on this file. Also, retuning the Unopposed section may be another improvement. GOOD) This is a simplification because the entire BlockedByKing section of the storm array goes away reducing a few lines of code (and less values to tune). This also brings clarity because the special rule is self documenting. BAD) It takes execution time to apply the special rule. This should be negli- gible because it is based on a template parameter and is boiled down to two bitwise AND's. STC: LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 33470 W: 6820 L: 6721 D: 19929 http://tests.stockfishchess.org/tests/view/5ae7b6e60ebc5926dba90e13 LTC: LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47627 W: 7045 L: 6963 D: 33619 http://tests.stockfishchess.org/tests/view/5ae859ff0ebc5926dba90e85 Closes https://github.com/official-stockfish/Stockfish/pull/1574 Bench: 5512000 ----------- How to continue after this patch? This patch may open the possibility to move the special rule to evaluate.cpp in the evaluate::king() function, where we could refine the rule using king danger information. For instance, with a king in H2 blocking an opponent pawn in H3, it may be critical to know that the opponent has no safe check in G2 before giving the bonus :-)
2018-05-01 15:50:23 -06:00
File center = std::clamp(file_of(ksq), FILE_B, FILE_G);
for (File f = File(center - 1); f <= File(center + 1); ++f)
{
b = ourPawns & file_bb(f);
int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
b = theirPawns & file_bb(f);
int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
int d = edge_distance(f);
bonus += make_score(ShelterStrength[d][ourRank], 0);
if (ourRank && (ourRank == theirRank - 1))
bonus -= BlockedStorm[theirRank];
else
bonus -= make_score(UnblockedStorm[d][theirRank], 0);
}
return bonus;
}
/// Entry::do_king_safety() calculates a bonus for king safety. It is called only
/// when king square changes, which is about 20% of total king_safety() calls.
template<Color Us>
Score Entry::do_king_safety(const Position& pos) {
Square ksq = pos.square<KING>(Us);
kingSquares[Us] = ksq;
castlingRights[Us] = pos.castling_rights(Us);
auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); };
Score shelter = evaluate_shelter<Us>(pos, ksq);
// If we can castle use the bonus after castling if it is bigger
if (pos.can_castle(Us & KING_SIDE))
shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_G1)), compare);
if (pos.can_castle(Us & QUEEN_SIDE))
shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_C1)), compare);
// In endgame we like to bring our king near our closest pawn
Bitboard pawns = pos.pieces(Us, PAWN);
int minPawnDist = 6;
if (pawns & attacks_bb<KING>(ksq))
minPawnDist = 1;
else while (pawns)
minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns)));
return shelter - make_score(0, 16 * minPawnDist);
}
// Explicit template instantiation
template Score Entry::do_king_safety<WHITE>(const Position& pos);
template Score Entry::do_king_safety<BLACK>(const Position& pos);
} // namespace Pawns