X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fevaluate.cpp;h=2f1d5067d013ce2d0ffee3785aefe78910d689c1;hp=90d11a00fd886fe5ee78e185382be71fa70264de;hb=f7494961de13c3341a1ca2d05ea5f3d28fa35d31;hpb=190dd26b9f1bc6442acf7b2ae4750eb4ab8b90bd diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 90d11a00..2f1d5067 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,16 +33,18 @@ #include "misc.h" #include "pawns.h" #include "thread.h" +#include "timeman.h" #include "uci.h" #include "incbin/incbin.h" -// Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler). +// Macro to embed the default efficiently updatable neural network (NNUE) file +// data in the engine binary (using incbin.h, by Dale Weiler). // This macro invocation will declare the following three variables // const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data // const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end // const unsigned int gEmbeddedNNUESize; // the size of the embedded file -// Note that this does not work in Microsof Visual Studio. +// Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) INCBIN(EmbeddedNNUE, EvalFileDefaultName); #else @@ -53,16 +55,17 @@ using namespace std; -using namespace Eval::NNUE; + +namespace Stockfish { namespace Eval { bool useNNUE; - string eval_file_loaded = "None"; + string currentEvalFileName = "None"; - /// NNUE::init() tries to load a nnue network at startup time, or when the engine + /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" - /// The name of the nnue network is always retrieved from the EvalFile option. + /// The name of the NNUE network is always retrieved from the EvalFile option. /// We search the given network in three locations: internally (the default /// network may be embedded in the binary), in the active working directory and /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY @@ -75,6 +78,8 @@ namespace Eval { return; string eval_file = string(Options["EvalFile"]); + if (eval_file.empty()) + eval_file = EvalFileDefaultName; #if defined(DEFAULT_NNUE_DIRECTORY) #define stringify2(x) #x @@ -85,13 +90,13 @@ namespace Eval { #endif for (string directory : dirs) - if (eval_file_loaded != eval_file) + if (currentEvalFileName != eval_file) { if (directory != "") { ifstream stream(directory + eval_file, ios::binary); if (load_eval(eval_file, stream)) - eval_file_loaded = eval_file; + currentEvalFileName = eval_file; } if (directory == "" && eval_file == EvalFileDefaultName) @@ -106,7 +111,7 @@ namespace Eval { istream stream(&buffer); if (load_eval(eval_file, stream)) - eval_file_loaded = eval_file; + currentEvalFileName = eval_file; } } } @@ -115,16 +120,16 @@ namespace Eval { void NNUE::verify() { string eval_file = string(Options["EvalFile"]); + if (eval_file.empty()) + eval_file = EvalFileDefaultName; - if (useNNUE && eval_file_loaded != eval_file) + if (useNNUE && currentEvalFileName != eval_file) { - UCI::OptionsMap defaults; - UCI::init(defaults); string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + string(defaults["EvalFile"]); + string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; @@ -177,7 +182,7 @@ namespace Trace { else os << scores[t][WHITE] << " | " << scores[t][BLACK]; - os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n"; + os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n"; return os; } } @@ -187,11 +192,9 @@ using namespace Trace; namespace { // Threshold for lazy and space evaluation - constexpr Value LazyThreshold1 = Value(1565); - constexpr Value LazyThreshold2 = Value(1102); - constexpr Value SpaceThreshold = Value(11551); - constexpr Value NNUEThreshold1 = Value(682); - constexpr Value NNUEThreshold2 = Value(176); + constexpr Value LazyThreshold1 = Value(3130); + constexpr Value LazyThreshold2 = Value(2204); + constexpr Value SpaceThreshold = Value(11551); // KingAttackWeights[PieceType] contains king attack weights by piece type constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 }; @@ -225,7 +228,7 @@ namespace { // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on // squares of the same color as our bishop. constexpr Score BishopPawns[int(FILE_NB) / 2] = { - S(3, 8), S(3, 9), S(1, 8), S(3, 7) + S(3, 8), S(3, 9), S(2, 8), S(3, 8) }; // KingProtector[knight/bishop] contains penalty for each distance unit to own king @@ -233,16 +236,15 @@ namespace { // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. - constexpr Score Outpost[] = { S(56, 34), S(31, 23) }; + constexpr Score Outpost[] = { S(57, 38), S(31, 24) }; // PassedRank[Rank] contains a bonus according to the rank of a passed pawn constexpr Score PassedRank[RANK_NB] = { - S(0, 0), S(9, 28), S(15, 31), S(17, 39), S(64, 70), S(171, 177), S(277, 260) + S(0, 0), S(7, 27), S(16, 32), S(17, 40), S(64, 71), S(170, 174), S(278, 262) }; - // RookOnFile[semiopen/open] contains bonuses for each rook when there is - // no (friendly) pawn on the rook file. - constexpr Score RookOnFile[] = { S(19, 7), S(48, 27) }; + constexpr Score RookOnClosedFile = S(10, 5); + constexpr Score RookOnOpenFile[] = { S(19, 6), S(47, 26) }; // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to // which piece type attacks which one. Attacks on lesser pieces which are @@ -255,11 +257,12 @@ namespace { S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43) }; + constexpr Value CorneredBishop = Value(50); + // Assorted bonuses and penalties - constexpr Score BadOutpost = S( -7, 36); + constexpr Score UncontestedOutpost = S( 1, 10); constexpr Score BishopOnKingRing = S( 24, 0); constexpr Score BishopXRayPawns = S( 4, 5); - constexpr Score CorneredBishop = S( 50, 50); constexpr Score FlankAttacks = S( 8, 0); constexpr Score Hanging = S( 69, 36); constexpr Score KnightOnQueen = S( 16, 11); @@ -388,15 +391,16 @@ namespace { constexpr Direction Down = -pawn_push(Us); constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB : Rank5BB | Rank4BB | Rank3BB); - const Square* pl = pos.squares(Us); - + Bitboard b1 = pos.pieces(Us, Pt); Bitboard b, bb; Score score = SCORE_ZERO; attackedBy[Us][Pt] = 0; - for (Square s = *pl; s != SQ_NONE; s = *++pl) + while (b1) { + Square s = pop_lsb(b1); + // Find attacked squares, including x-ray attacks for bishops and rooks b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) @@ -423,13 +427,12 @@ namespace { score += BishopOnKingRing; int mob = popcount(b & mobilityArea[Us]); - mobility[Us] += MobilityBonus[Pt - 2][mob]; if (Pt == BISHOP || Pt == KNIGHT) { // Bonus if the piece is on an outpost square or can reach one - // Reduced bonus for knights (BadOutpost) if few relevant targets + // Bonus for knights (UncontestedOutpost) if few relevant targets bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) & ~pe->pawn_attacks_span(Them); Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); @@ -438,7 +441,7 @@ namespace { && bb & s & ~CenterFiles // on a side outpost && !(b & targets) // no relevant attacks && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) - score += BadOutpost; + score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide)); else if (bb & s) score += Outpost[Pt == BISHOP]; else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) @@ -451,7 +454,7 @@ namespace { // Penalty if the piece is far from the king score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); - if (Pt == BISHOP) + if constexpr (Pt == BISHOP) { // Penalty according to the number of our pawns on the same color square as the // bishop, bigger when the center files are blocked with pawns and smaller @@ -476,29 +479,40 @@ namespace { { Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); if (pos.piece_on(s + d) == make_piece(Us, PAWN)) - score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4 - : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2 - : CorneredBishop; + score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop) + : 3 * make_score(CorneredBishop, CorneredBishop); } } } - if (Pt == ROOK) + if constexpr (Pt == ROOK) { - // Bonus for rook on an open or semi-open file + // Bonuses for rook on a (semi-)open or closed file if (pos.is_on_semiopen_file(Us, s)) - score += RookOnFile[pos.is_on_semiopen_file(Them, s)]; - - // Penalty when trapped by the king, even more if the king cannot castle - else if (mob <= 3) { - File kf = file_of(pos.square(Us)); - if ((kf < FILE_E) == (file_of(s) < kf)) - score -= TrappedRook * (1 + !pos.castling_rights(Us)); + score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)]; + } + else + { + // If our pawn on this file is blocked, increase penalty + if ( pos.pieces(Us, PAWN) + & shift(pos.pieces()) + & file_bb(s)) + { + score -= RookOnClosedFile; + } + + // Penalty when trapped by the king, even more if the king cannot castle + if (mob <= 3) + { + File kf = file_of(pos.square(Us)); + if ((kf < FILE_E) == (file_of(s) < kf)) + score -= TrappedRook * (1 + !pos.castling_rights(Us)); + } } } - if (Pt == QUEEN) + if constexpr (Pt == QUEEN) { // Penalty if any relative pin or discovered attack against the queen Bitboard queenPinners; @@ -506,7 +520,7 @@ namespace { score -= WeakQueen; } } - if (T) + if constexpr (T) Trace::add(Pt, Us, score); return score; @@ -583,7 +597,7 @@ namespace { int kingFlankDefense = popcount(b3); kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo) - + 185 * popcount(kingRing[Us] & weak) // (~15 Elo) + + 183 * popcount(kingRing[Us] & weak) // (~15 Elo) + 148 * popcount(unsafeChecks) // (~4 Elo) + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo) + 69 * kingAttacksCount[Them] // (~0.5 Elo) @@ -606,7 +620,7 @@ namespace { // Penalty if king flank is under attack, potentially moving toward the king score -= FlankAttacks * kingFlankAttack; - if (T) + if constexpr (T) Trace::add(KING, Us, score); return score; @@ -645,11 +659,11 @@ namespace { { b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); while (b) - score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(&b)))]; + score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))]; b = weak & attackedBy[Us][ROOK]; while (b) - score += ThreatByRook[type_of(pos.piece_on(pop_lsb(&b)))]; + score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))]; if (weak & attackedBy[Us][KING]) score += ThreatByKing; @@ -707,7 +721,7 @@ namespace { score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); } - if (T) + if constexpr (T) Trace::add(THREAT, Us, score); return score; @@ -747,7 +761,7 @@ namespace { while (b) { - Square s = pop_lsb(&b); + Square s = pop_lsb(b); assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); @@ -777,14 +791,16 @@ namespace { bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); if (!(pos.pieces(Them) & bb)) - unsafeSquares &= attackedBy[Them][ALL_PIECES]; + unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); - // If there are no enemy attacks on passed pawn span, assign a big bonus. + // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus. + // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus. // Otherwise assign a smaller bonus if the path to queen is not attacked // and even smaller bonus if it is attacked but block square is not. - int k = !unsafeSquares ? 35 : - !(unsafeSquares & squaresToQueen) ? 20 : - !(unsafeSquares & blockSq) ? 9 : + int k = !unsafeSquares ? 36 : + !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 : + !(unsafeSquares & squaresToQueen) ? 17 : + !(unsafeSquares & blockSq) ? 7 : 0 ; // Assign a larger bonus if the block square is defended @@ -798,7 +814,7 @@ namespace { score += bonus - PassedFile * edge_distance(file_of(s)); } - if (T) + if constexpr (T) Trace::add(PASSED, Us, score); return score; @@ -833,11 +849,13 @@ namespace { behind |= shift(behind); behind |= shift(behind); + // Compute space score based on the number of safe squares and number of our pieces + // increased with number of total blocked pawns in position. int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); Score score = make_score(bonus * weight * weight / 16, 0); - if (T) + if constexpr (T) Trace::add(SPACE, Us, score); return score; @@ -852,7 +870,7 @@ namespace { Value Evaluation::winnable(Score score) const { int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) - - distance(pos.square(WHITE), pos.square(BLACK)); + + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) && (pos.pieces(PAWN) & KingSide); @@ -889,29 +907,41 @@ namespace { Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; int sf = me->scale_factor(pos, strongSide); - // If scale factor is not already specific, scale down via general heuristics + // If scale factor is not already specific, scale up/down via general heuristics if (sf == SCALE_FACTOR_NORMAL) { if (pos.opposite_bishops()) { + // For pure opposite colored bishops endgames use scale factor + // based on the number of passed pawns of the strong side. if ( pos.non_pawn_material(WHITE) == BishopValueMg && pos.non_pawn_material(BLACK) == BishopValueMg) sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); + // For every other opposite colored bishops endgames use scale factor + // based on the number of all pieces of the strong side. else sf = 22 + 3 * pos.count(strongSide); } + // For rook endgames with strong side not having overwhelming pawn number advantage + // and its pawns being on one flank and weak side protecting its pieces with a king + // use lower scale factor. else if ( pos.non_pawn_material(WHITE) == RookValueMg && pos.non_pawn_material(BLACK) == RookValueMg && pos.count(strongSide) - pos.count(~strongSide) <= 1 && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) sf = 36; + // For queen vs no queen endgames use scale factor + // based on number of minors of side that doesn't have queen. else if (pos.count() == 1) sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) : pos.count(WHITE) + pos.count(WHITE)); + // In every other case use scale factor based on + // the number of pawns of the strong side reduced if pawns are on a single flank. else sf = std::min(sf, 36 + 7 * pos.count(strongSide)) - 4 * !pawnsOnBothFlanks; + // Reduce scale factor in case of pawns being on a single flank sf -= 4 * !pawnsOnBothFlanks; } @@ -920,7 +950,7 @@ namespace { + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; v /= PHASE_MIDGAME; - if (T) + if constexpr (T) { Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); @@ -950,7 +980,7 @@ namespace { // Initialize score by reading the incrementally updated scores included in // the position object (material + piece square tables) and the material // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->contempt; + Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->trend; // Probe the pawn hash table pe = Pawns::probe(pos); @@ -958,7 +988,7 @@ namespace { // Early exit if score is high auto lazy_skip = [&](Value lazyThreshold) { - return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64; + return abs(mg_value(score) + eg_value(score)) > lazyThreshold + pos.non_pawn_material() / 32; }; if (lazy_skip(LazyThreshold1)) @@ -992,7 +1022,7 @@ make_v: Value v = winnable(score); // In case of tracing add all remaining individual evaluation terms - if (T) + if constexpr (T) { Trace::add(MATERIAL, pos.psq_score()); Trace::add(IMBALANCE, me->imbalance()); @@ -1004,12 +1034,44 @@ make_v: v = (v / 16) * 16; // Side to move point of view - v = (pos.side_to_move() == WHITE ? v : -v) + Tempo; + v = (pos.side_to_move() == WHITE ? v : -v); return v; } -} // namespace + + /// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE + + Value fix_FRC(const Position& pos) { + + constexpr Bitboard Corners = 1ULL << SQ_A1 | 1ULL << SQ_H1 | 1ULL << SQ_A8 | 1ULL << SQ_H8; + + if (!(pos.pieces(BISHOP) & Corners)) + return VALUE_ZERO; + + int correction = 0; + + if ( pos.piece_on(SQ_A1) == W_BISHOP + && pos.piece_on(SQ_B2) == W_PAWN) + correction -= CorneredBishop; + + if ( pos.piece_on(SQ_H1) == W_BISHOP + && pos.piece_on(SQ_G2) == W_PAWN) + correction -= CorneredBishop; + + if ( pos.piece_on(SQ_A8) == B_BISHOP + && pos.piece_on(SQ_B7) == B_PAWN) + correction += CorneredBishop; + + if ( pos.piece_on(SQ_H8) == B_BISHOP + && pos.piece_on(SQ_G7) == B_PAWN) + correction += CorneredBishop; + + return pos.side_to_move() == WHITE ? Value(5 * correction) + : -Value(5 * correction); + } + +} // namespace Eval /// evaluate() is the evaluator for the outer world. It returns a static @@ -1019,35 +1081,22 @@ Value Eval::evaluate(const Position& pos) { Value v; - if (!Eval::useNNUE) - v = Evaluation(pos).value(); + // Deciding between classical and NNUE eval: for high PSQ imbalance we use classical, + // but we switch to NNUE during long shuffling or with high material on the board. + + if ( !useNNUE + || abs(eg_value(pos.psq_score())) * 5 > (850 + pos.non_pawn_material() / 64) * (5 + pos.rule50_count())) + v = Evaluation(pos).value(); // classical else { - // Scale and shift NNUE for compatibility with search and classical evaluation - auto adjusted_NNUE = [&](){ - int mat = pos.non_pawn_material() + PawnValueMg * pos.count(); - return NNUE::evaluate(pos) * (720 + mat / 32) / 1024 + Tempo; - }; - - // If there is PSQ imbalance use classical eval, with small probability if it is small - Value psq = Value(abs(eg_value(pos.psq_score()))); - int r50 = 16 + pos.rule50_count(); - bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50; - bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB)); - - bool strongClassical = pos.non_pawn_material() < 2 * RookValueMg && pos.count() < 2; - - v = classical || strongClassical ? Evaluation(pos).value() : adjusted_NNUE(); - - // If the classical eval is small and imbalance large, use NNUE nevertheless. - // For the case of opposite colored bishops, switch to NNUE eval with - // small probability if the classical eval is less than the threshold. - if ( largePsq && !strongClassical - && ( abs(v) * 16 < NNUEThreshold2 * r50 - || ( pos.opposite_bishops() - && abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50 - && !(pos.this_thread()->nodes & 0xB)))) - v = adjusted_NNUE(); + int scale = 883 + + 32 * pos.count() + + 32 * pos.non_pawn_material() / 1024; + + v = NNUE::evaluate(pos, true) * scale / 1024; // NNUE + + if (pos.is_chess960()) + v += fix_FRC(pos); } // Damp down the evaluation linearly when shuffling @@ -1064,7 +1113,7 @@ Value Eval::evaluate(const Position& pos) { /// descriptions and values of each evaluation term. Useful for debugging. /// Trace scores are from white's point of view -std::string Eval::trace(const Position& pos) { +std::string Eval::trace(Position& pos) { if (pos.checkers()) return "Final evaluation: none (in check)"; @@ -1076,44 +1125,55 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); - pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt + pos.this_thread()->trend = SCORE_ZERO; // Reset any dynamic contempt v = Evaluation(pos).value(); ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) - << " Term | White | Black | Total \n" - << " | MG EG | MG EG | MG EG \n" - << " ------------+-------------+-------------+------------\n" - << " Material | " << Term(MATERIAL) - << " Imbalance | " << Term(IMBALANCE) - << " Pawns | " << Term(PAWN) - << " Knights | " << Term(KNIGHT) - << " Bishops | " << Term(BISHOP) - << " Rooks | " << Term(ROOK) - << " Queens | " << Term(QUEEN) - << " Mobility | " << Term(MOBILITY) - << " King safety | " << Term(KING) - << " Threats | " << Term(THREAT) - << " Passed | " << Term(PASSED) - << " Space | " << Term(SPACE) - << " Winnable | " << Term(WINNABLE) - << " ------------+-------------+-------------+------------\n" - << " Total | " << Term(TOTAL); + << " Contributing terms for the classical eval:\n" + << "+------------+-------------+-------------+-------------+\n" + << "| Term | White | Black | Total |\n" + << "| | MG EG | MG EG | MG EG |\n" + << "+------------+-------------+-------------+-------------+\n" + << "| Material | " << Term(MATERIAL) + << "| Imbalance | " << Term(IMBALANCE) + << "| Pawns | " << Term(PAWN) + << "| Knights | " << Term(KNIGHT) + << "| Bishops | " << Term(BISHOP) + << "| Rooks | " << Term(ROOK) + << "| Queens | " << Term(QUEEN) + << "| Mobility | " << Term(MOBILITY) + << "|King safety | " << Term(KING) + << "| Threats | " << Term(THREAT) + << "| Passed | " << Term(PASSED) + << "| Space | " << Term(SPACE) + << "| Winnable | " << Term(WINNABLE) + << "+------------+-------------+-------------+-------------+\n" + << "| Total | " << Term(TOTAL) + << "+------------+-------------+-------------+-------------+\n"; - v = pos.side_to_move() == WHITE ? v : -v; + if (Eval::useNNUE) + ss << '\n' << NNUE::trace(pos) << '\n'; - ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n"; + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n"; if (Eval::useNNUE) { - v = NNUE::evaluate(pos); + v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "\nNNUE evaluation: " << to_cp(v) << " (white side)\n"; + ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; } v = evaluate(pos); v = pos.side_to_move() == WHITE ? v : -v; - ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n"; + ss << "Final evaluation " << to_cp(v) << " (white side)"; + if (Eval::useNNUE) + ss << " [with scaled NNUE, hybrid, ...]"; + ss << "\n"; return ss.str(); } + +} // namespace Stockfish