From adeded29fb6ce483bbbafaa0f67aa086cad968f9 Mon Sep 17 00:00:00 2001 From: Lucas Braesch Date: Tue, 3 Jun 2014 20:48:43 +0800 Subject: [PATCH] Symmetric King Safety: take 2 Another attempt at retiring current asymmetric king evaluation and use a much simpler symmetric one. As a good side effect we can avoid recalculating eval after a null move. Tested in no-regression mode and passed STC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 21580 W: 3752 L: 3632 D: 14196 LTC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 18253 W: 2593 L: 2469 D: 13191 And a LTC regression test against SF DD to verify we don't have regression against weaker engines due to some kind of 'contempt' effect: ELO: 54.69 +-2.1 (95%) LOS: 100.0% Total: 40000 W: 11072 L: 4827 D: 24101 bench: 8205159 --- src/evaluate.cpp | 40 ++++++++++++++++------------------------ src/search.cpp | 33 +++++++++++++++++---------------- src/search.h | 4 ++-- src/uci.cpp | 6 +----- 4 files changed, 36 insertions(+), 47 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 30b107ee..804e1dd7 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -90,9 +90,9 @@ namespace { } // Evaluation weights, indexed by evaluation term - enum { Mobility, PawnStructure, PassedPawns, Space, KingDangerUs, KingDangerThem }; + enum { Mobility, PawnStructure, PassedPawns, Space, KingSafety }; const struct Weight { int mg, eg; } Weights[] = { - {289, 344}, {233, 201}, {221, 273}, {46, 0}, {271, 0}, {307, 0} + {289, 344}, {233, 201}, {221, 273}, {46, 0}, {318, 0} }; typedef Value V; @@ -150,12 +150,11 @@ namespace { S(0, 0), S(0, 0), S(56, 70), S(56, 70), S(76, 99), S(86, 118) }; - // Hanging[side to move] contains a bonus for each enemy hanging piece - const Score Hanging[2] = { S(23, 20) , S(35, 45) }; + // Hanging contains a bonus for each enemy hanging piece + const Score Hanging = S(23, 20); #undef S - const Score Tempo = make_score(24, 11); const Score RookOnPawn = make_score(10, 28); const Score RookOpenFile = make_score(43, 21); const Score RookSemiopenFile = make_score(19, 10); @@ -194,9 +193,9 @@ namespace { const int BishopCheck = 2; const int KnightCheck = 3; - // KingDanger[Color][attackUnits] contains the actual king danger weighted - // scores, indexed by color and by a calculated integer number. - Score KingDanger[COLOR_NB][128]; + // KingDanger[attackUnits] contains the actual king danger weighted + // scores, indexed by a calculated integer number. + Score KingDanger[128]; // apply_weight() weighs score 'v' by weight 'w' trying to prevent overflow @@ -426,9 +425,7 @@ namespace { | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][ROOK]); if (b) - attackUnits += QueenContactCheck - * popcount(b) - * (Them == pos.side_to_move() ? 2 : 1); + attackUnits += QueenContactCheck * popcount(b); } // Analyse the enemy's safe rook contact checks. Firstly, find the @@ -446,9 +443,7 @@ namespace { | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][QUEEN]); if (b) - attackUnits += RookContactCheck - * popcount(b) - * (Them == pos.side_to_move() ? 2 : 1); + attackUnits += RookContactCheck * popcount(b); } // Analyse the enemy's safe distance checks for sliders and knights @@ -482,7 +477,7 @@ namespace { // Finally, extract the king danger score from the KingDanger[] // array and subtract the score from evaluation. - score -= KingDanger[Us == Search::RootColor][attackUnits]; + score -= KingDanger[attackUnits]; } if (Trace) @@ -521,8 +516,7 @@ namespace { b = weakEnemies & ~ei.attackedBy[Them][ALL_PIECES]; if (b) - score += more_than_one(b) ? Hanging[Us != pos.side_to_move()] * popcount(b) - : Hanging[Us == pos.side_to_move()]; + score += more_than_one(b) ? Hanging * popcount(b) : Hanging; } if (Trace) @@ -677,9 +671,9 @@ namespace { Thread* thisThread = pos.this_thread(); // Initialize score by reading the incrementally updated scores included - // in the position object (material + piece square tables) and adding a - // Tempo bonus. Score is computed from the point of view of white. - score = pos.psq_score() + (pos.side_to_move() == WHITE ? Tempo : -Tempo); + // in the position object (material + piece square tables). + // Score is computed from the point of view of white. + score = pos.psq_score(); // Probe the material hash table ei.mi = Material::probe(pos, thisThread->materialTable, thisThread->endgames); @@ -831,7 +825,7 @@ namespace { << " | MG EG | MG EG | MG EG \n" << "---------------------+-------------+-------------+-------------\n"; - format_row(ss, "Material, PST, Tempo", PST); + format_row(ss, "Material, PST", PST); format_row(ss, "Material imbalance", IMBALANCE); format_row(ss, "Pawns", PAWN); format_row(ss, "Knights", KNIGHT); @@ -885,9 +879,7 @@ namespace Eval { for (int t = 0, i = 1; i < 100; ++i) { t = int(std::min(Peak, std::min(0.4 * i * i, t + MaxSlope))); - - KingDanger[1][i] = apply_weight(make_score(t, 0), Weights[KingDangerUs]); - KingDanger[0][i] = apply_weight(make_score(t, 0), Weights[KingDangerThem]); + KingDanger[i] = apply_weight(make_score(t, 0), Weights[KingSafety]); } } diff --git a/src/search.cpp b/src/search.cpp index 9a6bd863..37a96266 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -42,7 +42,6 @@ namespace Search { LimitsType Limits; std::vector RootMoves; Position RootPos; - Color RootColor; Time::point SearchTime; StateStackPtr SetupStates; } @@ -77,6 +76,9 @@ namespace { return (Depth) Reductions[PvNode][i][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)]; } + // Tempo bonus. Must be handled by search to preserve eval symmetry. + const int Tempo = 17; + size_t MultiPV, PVIdx; TimeManager TimeMgr; double BestMoveChanges; @@ -180,12 +182,11 @@ uint64_t Search::perft(Position& pos, Depth depth) { void Search::think() { - RootColor = RootPos.side_to_move(); - TimeMgr.init(Limits, RootPos.game_ply(), RootColor); + TimeMgr.init(Limits, RootPos.game_ply(), RootPos.side_to_move()); int cf = Options["Contempt Factor"] * PawnValueEg / 100; // From centipawns - DrawValue[ RootColor] = VALUE_DRAW - Value(cf); - DrawValue[~RootColor] = VALUE_DRAW + Value(cf); + DrawValue[ RootPos.side_to_move()] = VALUE_DRAW - Value(cf); + DrawValue[~RootPos.side_to_move()] = VALUE_DRAW + Value(cf); if (RootMoves.empty()) { @@ -203,8 +204,8 @@ void Search::think() { log << "\nSearching: " << RootPos.fen() << "\ninfinite: " << Limits.infinite << " ponder: " << Limits.ponder - << " time: " << Limits.time[RootColor] - << " increment: " << Limits.inc[RootColor] + << " time: " << Limits.time[RootPos.side_to_move()] + << " increment: " << Limits.inc[RootPos.side_to_move()] << " moves to go: " << Limits.movestogo << "\n" << std::endl; } @@ -472,7 +473,7 @@ namespace { bestValue = -VALUE_INFINITE; ss->currentMove = ss->ttMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; ss->ply = (ss-1)->ply + 1; - (ss+1)->skipNullMove = false; (ss+1)->reduction = DEPTH_ZERO; + (ss+1)->skipNullMove = (ss+1)->nullChild = false; (ss+1)->reduction = DEPTH_ZERO; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; // Used to send selDepth info to GUI @@ -483,7 +484,7 @@ namespace { { // Step 2. Check for aborted search and immediate draw if (Signals.stop || pos.is_draw() || ss->ply > MAX_PLY) - return ss->ply > MAX_PLY && !inCheck ? evaluate(pos) : DrawValue[pos.side_to_move()]; + return ss->ply > MAX_PLY && !inCheck ? evaluate(pos) + Tempo : DrawValue[pos.side_to_move()]; // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply+1), but if alpha is already bigger because @@ -538,7 +539,7 @@ namespace { { // Never assume anything on values stored in TT if ((ss->staticEval = eval = tte->eval_value()) == VALUE_NONE) - eval = ss->staticEval = evaluate(pos); + eval = ss->staticEval = evaluate(pos) + Tempo; // Can ttValue be used as a better position evaluation? if (ttValue != VALUE_NONE) @@ -547,7 +548,7 @@ namespace { } else { - eval = ss->staticEval = evaluate(pos); + eval = ss->staticEval = ss->nullChild ? -(ss-1)->staticEval + 2 * Tempo : evaluate(pos) + Tempo; TT.store(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, ss->staticEval); } @@ -607,10 +608,10 @@ namespace { + int(eval - beta) / PawnValueMg * ONE_PLY; pos.do_null_move(st); - (ss+1)->skipNullMove = true; + (ss+1)->skipNullMove = (ss+1)->nullChild = true; nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -beta+1, DEPTH_ZERO) : - search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); - (ss+1)->skipNullMove = false; + (ss+1)->skipNullMove = (ss+1)->nullChild = false; pos.undo_null_move(); if (nullValue >= beta) @@ -1065,7 +1066,7 @@ moves_loop: // When in check and at SpNode search starts from here // Check for an instant draw or if the maximum ply has been reached if (pos.is_draw() || ss->ply > MAX_PLY) - return ss->ply > MAX_PLY && !InCheck ? evaluate(pos) : DrawValue[pos.side_to_move()]; + return ss->ply > MAX_PLY && !InCheck ? evaluate(pos) + Tempo : DrawValue[pos.side_to_move()]; // Decide whether or not to include checks: this fixes also the type of // TT entry depth that we are going to use. Note that in qsearch we use @@ -1102,7 +1103,7 @@ moves_loop: // When in check and at SpNode search starts from here { // Never assume anything on values stored in TT if ((ss->staticEval = bestValue = tte->eval_value()) == VALUE_NONE) - ss->staticEval = bestValue = evaluate(pos); + ss->staticEval = bestValue = evaluate(pos) + Tempo; // Can ttValue be used as a better position evaluation? if (ttValue != VALUE_NONE) @@ -1110,7 +1111,7 @@ moves_loop: // When in check and at SpNode search starts from here bestValue = ttValue; } else - ss->staticEval = bestValue = evaluate(pos); + ss->staticEval = bestValue = ss->nullChild ? -(ss-1)->staticEval + 2 * Tempo : evaluate(pos) + Tempo; // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) diff --git a/src/search.h b/src/search.h index a9f21fa2..6f2bcdb3 100644 --- a/src/search.h +++ b/src/search.h @@ -45,7 +45,8 @@ struct Stack { Move killers[2]; Depth reduction; Value staticEval; - int skipNullMove; + bool skipNullMove; + bool nullChild; }; @@ -101,7 +102,6 @@ extern volatile SignalsType Signals; extern LimitsType Limits; extern std::vector RootMoves; extern Position RootPos; -extern Color RootColor; extern Time::point SearchTime; extern StateStackPtr SetupStates; diff --git a/src/uci.cpp b/src/uci.cpp index 8d625bd9..26507064 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -197,11 +197,7 @@ void UCI::loop(int argc, char* argv[]) { << "\n" << Options << "\nuciok" << sync_endl; - else if (token == "eval") - { - Search::RootColor = pos.side_to_move(); // Ensure it is set - sync_cout << Eval::trace(pos) << sync_endl; - } + else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; else if (token == "ucinewgame") TT.clear(); else if (token == "go") go(pos, is); else if (token == "position") position(pos, is); -- 2.39.2