Rewrite unstoppable pawns evaluation
authorMarco Costalba <mcostalba@gmail.com>
Tue, 10 Sep 2013 09:41:10 +0000 (11:41 +0200)
committerMarco Costalba <mcostalba@gmail.com>
Mon, 16 Sep 2013 06:57:37 +0000 (08:57 +0200)
Instead of current code, give a bonus according to the frontmost
square among candidate + passed pawns.

This is a big simplification that removes a lot of accurate code
substituting it with a statistically based one using the common
'bonus' scheme, leaving to the search to sort out the details.

Results are equivalent but code is much less and, as an added bonus,
we now store candidates bitboard in pawns hash and allow this
info to be used in evaluation. This paves the way to possible
candidate pawns evaluations together with all the other pieces,
as we do for passed.

Patch passed short TC
LLR: 2.96 (-2.94,2.94) [-1.50,4.50]
Total: 16927 W: 3462 L: 3308 D: 10157

Then failed (quite quickly) at long TC
LLR: -2.95 (-2.94,2.94) [0.00,6.00]
Total: 8451 W: 1386 L: 1448 D: 5617

But when ran with a conclusive 40K fixed games at 60 secs it proved
almost equivalent to original one.

ELO: 1.08 +-2.0 (95%) LOS: 85.8%
Total: 40000 W: 6739 L: 6615 D: 26646

bench: 3884003

src/evaluate.cpp
src/pawns.cpp
src/pawns.h

index ada15a1615a9c015ead4668ffb494460b46bc254..7844deb6ae27290333531b91ae220986096f4009 100644 (file)
@@ -32,7 +32,7 @@
 namespace {
 
   enum ExtendedPieceType { // Used for tracing
-    PST = 8, IMBALANCE, MOBILITY, THREAT, PASSED, UNSTOPPABLE, SPACE, TOTAL
+    PST = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL
   };
 
   namespace Tracing {
@@ -175,6 +175,7 @@ namespace {
   const Score MinorBehindPawn  = make_score(16,  0);
   const Score UndefendedMinor  = make_score(25, 10);
   const Score TrappedRook      = make_score(90,  0);
+  const Score Unstoppable      = make_score( 0, 20);
 
   // Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by
   // a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only
@@ -245,7 +246,7 @@ namespace {
   template<Color Us>
   int evaluate_space(const Position& pos, const EvalInfo& ei);
 
-  Score evaluate_unstoppable_pawns(const Position& pos, const EvalInfo& ei);
+  Score evaluate_unstoppable_pawns(const Position& pos, Color us, const EvalInfo& ei);
 
   Value interpolate(const Score& v, Phase ph, ScaleFactor sf);
   Score apply_weight(Score v, Score w);
@@ -360,9 +361,10 @@ Value do_evaluate(const Position& pos, Value& margin) {
   score +=  evaluate_passed_pawns<WHITE, Trace>(pos, ei)
           - evaluate_passed_pawns<BLACK, Trace>(pos, ei);
 
-  // If one side has only a king, check whether exists any unstoppable passed pawn
+  // If one side has only a king, score for potential unstoppable pawns
   if (!pos.non_pawn_material(WHITE) || !pos.non_pawn_material(BLACK))
-      score += evaluate_unstoppable_pawns(pos, ei);
+      score +=  evaluate_unstoppable_pawns(pos, WHITE, ei)
+              - evaluate_unstoppable_pawns(pos, BLACK, ei);
 
   // Evaluate space for both sides, only in middle-game.
   if (ei.mi->space_weight())
@@ -405,7 +407,6 @@ Value do_evaluate(const Position& pos, Value& margin) {
       Tracing::add(PST, pos.psq_score());
       Tracing::add(IMBALANCE, ei.mi->material_value());
       Tracing::add(PAWN, ei.pi->pawns_value());
-      Tracing::add(UNSTOPPABLE, evaluate_unstoppable_pawns(pos, ei));
       Score w = ei.mi->space_weight() * evaluate_space<WHITE>(pos, ei);
       Score b = ei.mi->space_weight() * evaluate_space<BLACK>(pos, ei);
       Tracing::add(SPACE, apply_weight(w, Weights[Space]), apply_weight(b, Weights[Space]));
@@ -894,160 +895,18 @@ Value do_evaluate(const Position& pos, Value& margin) {
   }
 
 
-  // evaluate_unstoppable_pawns() evaluates the unstoppable passed pawns for both sides, this is quite
-  // conservative and returns a winning score only when we are very sure that the pawn is winning.
+  // evaluate_unstoppable_pawns() scores the most advanced among the passed and
+  // candidate pawns. In case opponent has no pieces but pawns, this is somewhat
+  // related to the possibility pawns are unstoppable.
 
-  Score evaluate_unstoppable_pawns(const Position& pos, const EvalInfo& ei) {
+  Score evaluate_unstoppable_pawns(const Position& pos, Color us, const EvalInfo& ei) {
 
-    Bitboard b, b2, blockers, supporters, queeningPath, candidates;
-    Square s, blockSq, queeningSquare;
-    Color c, winnerSide, loserSide;
-    bool pathDefended, opposed;
-    int pliesToGo, movesToGo, oppMovesToGo, sacptg, blockersCount, minKingDist, kingptg, d;
-    int pliesToQueen[] = { 256, 256 };
+    Bitboard b = ei.pi->passed_pawns(us) | ei.pi->candidate_pawns(us);
 
-    // Step 1. Hunt for unstoppable passed pawns. If we find at least one,
-    // record how many plies are required for promotion.
-    for (c = WHITE; c <= BLACK; ++c)
-    {
-        // Skip if other side has non-pawn pieces
-        if (pos.non_pawn_material(~c))
-            continue;
-
-        b = ei.pi->passed_pawns(c);
-
-        while (b)
-        {
-            s = pop_lsb(&b);
-            queeningSquare = relative_square(c, file_of(s) | RANK_8);
-            queeningPath = forward_bb(c, s);
-
-            // Compute plies to queening and check direct advancement
-            movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(c, s) == RANK_2);
-            oppMovesToGo = square_distance(pos.king_square(~c), queeningSquare) - int(c != pos.side_to_move());
-            pathDefended = ((ei.attackedBy[c][ALL_PIECES] & queeningPath) == queeningPath);
-
-            if (movesToGo >= oppMovesToGo && !pathDefended)
-                continue;
-
-            // Opponent king cannot block because path is defended and position
-            // is not in check. So only friendly pieces can be blockers.
-            assert(!pos.checkers());
-            assert((queeningPath & pos.pieces()) == (queeningPath & pos.pieces(c)));
-
-            // Add moves needed to free the path from friendly pieces and retest condition
-            movesToGo += popcount<Max15>(queeningPath & pos.pieces(c));
-
-            if (movesToGo >= oppMovesToGo && !pathDefended)
-                continue;
-
-            pliesToGo = 2 * movesToGo - int(c == pos.side_to_move());
-            pliesToQueen[c] = std::min(pliesToQueen[c], pliesToGo);
-        }
-    }
-
-    // Step 2. If either side cannot promote at least three plies before the other side then situation
-    // becomes too complex and we give up. Otherwise we determine the possibly "winning side"
-    if (abs(pliesToQueen[WHITE] - pliesToQueen[BLACK]) < 3)
-        return SCORE_ZERO;
-
-    winnerSide = (pliesToQueen[WHITE] < pliesToQueen[BLACK] ? WHITE : BLACK);
-    loserSide = ~winnerSide;
-
-    // Step 3. Can the losing side possibly create a new passed pawn and thus prevent the loss?
-    b = candidates = pos.pieces(loserSide, PAWN);
-
-    while (b)
-    {
-        s = pop_lsb(&b);
-
-        // Compute plies from queening
-        queeningSquare = relative_square(loserSide, file_of(s) | RANK_8);
-        movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(loserSide, s) == RANK_2);
-        pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move());
-
-        // Check if (without even considering any obstacles) we're too far away or doubled
-        if (   pliesToQueen[winnerSide] + 3 <= pliesToGo
-            || (forward_bb(loserSide, s) & pos.pieces(loserSide, PAWN)))
-            candidates ^= s;
-    }
-
-    // If any candidate is already a passed pawn it _may_ promote in time. We give up.
-    if (candidates & ei.pi->passed_pawns(loserSide))
+    if (!b || pos.non_pawn_material(~us))
         return SCORE_ZERO;
 
-    // Step 4. Check new passed pawn creation through king capturing and pawn sacrifices
-    b = candidates;
-
-    while (b)
-    {
-        s = pop_lsb(&b);
-        sacptg = blockersCount = 0;
-        minKingDist = kingptg = 256;
-
-        // Compute plies from queening
-        queeningSquare = relative_square(loserSide, file_of(s) | RANK_8);
-        movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(loserSide, s) == RANK_2);
-        pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move());
-
-        // Generate list of blocking pawns and supporters
-        supporters = adjacent_files_bb(file_of(s)) & candidates;
-        opposed = forward_bb(loserSide, s) & pos.pieces(winnerSide, PAWN);
-        blockers = passed_pawn_mask(loserSide, s) & pos.pieces(winnerSide, PAWN);
-
-        assert(blockers);
-
-        // How many plies does it take to remove all the blocking pawns?
-        while (blockers)
-        {
-            blockSq = pop_lsb(&blockers);
-            movesToGo = 256;
-
-            // Check pawns that can give support to overcome obstacle, for instance
-            // black pawns: a4, b4 white: b2 then pawn in b4 is giving support.
-            if (!opposed)
-            {
-                b2 = supporters & in_front_bb(winnerSide, rank_of(blockSq + pawn_push(winnerSide)));
-
-                if (b2)
-                {
-                    d = square_distance(blockSq, backmost_sq(winnerSide, b2)) - 2;
-                    movesToGo = std::min(movesToGo, d);
-                }
-            }
-
-            // Check pawns that can be sacrificed against the blocking pawn
-            b2 = pawn_attack_span(winnerSide, blockSq) & candidates & ~SquareBB[s];
-
-            if (b2)
-            {
-                d = square_distance(blockSq, backmost_sq(winnerSide, b2)) - 2;
-                movesToGo = std::min(movesToGo, d);
-            }
-
-            // If obstacle can be destroyed with an immediate pawn exchange / sacrifice,
-            // it's not a real obstacle and we have nothing to add to pliesToGo.
-            if (movesToGo <= 0)
-                continue;
-
-            // Plies needed to sacrifice against all the blocking pawns
-            sacptg += movesToGo * 2;
-            blockersCount++;
-
-            // Plies needed for the king to capture all the blocking pawns
-            d = square_distance(pos.king_square(loserSide), blockSq);
-            minKingDist = std::min(minKingDist, d);
-            kingptg = (minKingDist + blockersCount) * 2;
-        }
-
-        // Check if pawn sacrifice or king capture plan _may_ save the day
-        if (pliesToQueen[winnerSide] + 3 > pliesToGo + std::min(kingptg, sacptg))
-            return SCORE_ZERO;
-    }
-
-    // Winning pawn is unstoppable and will promote as first, return big score
-    Score score = make_score(0, (Value) 1280 - 32 * pliesToQueen[winnerSide]);
-    return winnerSide == WHITE ? score : -score;
+    return Unstoppable * int(relative_rank(WHITE, frontmost_sq(us, b)));
   }
 
 
@@ -1132,7 +991,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
     Score bScore = scores[BLACK][idx];
 
     switch (idx) {
-    case PST: case IMBALANCE: case PAWN: case UNSTOPPABLE: case TOTAL:
+    case PST: case IMBALANCE: case PAWN: case TOTAL:
         stream << std::setw(20) << name << " |   ---   --- |   ---   --- | "
                << std::setw(6)  << to_cp(mg_value(wScore)) << " "
                << std::setw(6)  << to_cp(eg_value(wScore)) << " \n";
@@ -1176,7 +1035,6 @@ Value do_evaluate(const Position& pos, Value& margin) {
     row("King safety", KING);
     row("Threats", THREAT);
     row("Passed pawns", PASSED);
-    row("Unstoppable pawns", UNSTOPPABLE);
     row("Space", SPACE);
 
     stream <<             "---------------------+-------------+-------------+---------------\n";
index d65b0418f0145f448c3d37dff8ecb7262c86fdaf..c5a48e930c134e361f3733391e1ee79f6e7f510a 100644 (file)
@@ -99,7 +99,7 @@ namespace {
     Bitboard ourPawns = pos.pieces(Us, PAWN);
     Bitboard theirPawns = pos.pieces(Them, PAWN);
 
-    e->passedPawns[Us] = 0;
+    e->passedPawns[Us] = e->candidatePawns[Us] = 0;
     e->kingSquares[Us] = SQ_NONE;
     e->semiopenFiles[Us] = 0xFF;
     e->pawnAttacks[Us] = shift_bb<Right>(ourPawns) | shift_bb<Left>(ourPawns);
@@ -179,7 +179,12 @@ namespace {
             value += ChainMember[f];
 
         if (candidate)
+        {
             value += CandidatePassed[relative_rank(Us, s)];
+
+            if (!doubled)
+                e->candidatePawns[Us] |= s;
+        }
     }
 
     return value;
index 9f2af30e4f5028472e493e1ea42ee5fbd630691b..0098a963db224a478d74eed4164d24acb5ffebad 100644 (file)
@@ -37,6 +37,7 @@ struct Entry {
   Score pawns_value() const { return value; }
   Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
   Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
+  Bitboard candidate_pawns(Color c) const { return candidatePawns[c]; }
   int pawns_on_same_color_squares(Color c, Square s) const { return pawnsOnSquares[c][!!(DarkSquares & s)]; }
   int semiopen(Color c, File f) const { return semiopenFiles[c] & (1 << int(f)); }
   int semiopen_on_side(Color c, File f, bool left) const {
@@ -59,6 +60,7 @@ struct Entry {
 
   Key key;
   Bitboard passedPawns[COLOR_NB];
+  Bitboard candidatePawns[COLOR_NB];
   Bitboard pawnAttacks[COLOR_NB];
   Square kingSquares[COLOR_NB];
   int minKPdistance[COLOR_NB];