return nnue;
};
- // If there is PSQ imbalance use classical eval, with small probability if it is small
+ // If there is PSQ imbalance we use the classical eval. We also introduce
+ // a small probability of using the classical eval when PSQ imbalance 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 pure = !pos.check_counting();
+ bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50 && !pure;
+ bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB) && !pure);
// Use classical evaluation for really low piece endgames.
- // The most critical case is a bishop + A/H file pawn vs naked king draw.
- bool strongClassical = pos.non_pawn_material() < 2 * RookValueMg && pos.count<PAWN>() < 2;
+ // One critical case is the draw for bishop + A/H file pawn vs naked king.
+ bool lowPieceEndgame = pos.non_pawn_material() == BishopValueMg
+ || (pos.non_pawn_material() < 2 * RookValueMg && pos.count<PAWN>() < 2);
- v = classical || strongClassical ? Evaluation<NO_TRACE>(pos).value() : adjusted_NNUE();
+ v = classical || lowPieceEndgame ? Evaluation<NO_TRACE>(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
+ // 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
+ && !lowPieceEndgame
&& ( abs(v) * 16 < NNUEThreshold2 * r50
|| ( pos.opposite_bishops()
&& abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50
}
}
- // Get a list of indices for recently changed features
+
+ // AppendChangedIndices() : get a list of indices for recently changed features
+
+ // IMPORTANT: The `pos` in this function is pretty much useless as it
+ // is not always the position the features are updated to. The feature
+ // transformer code right now can update multiple accumulators per move,
+ // but since Stockfish only keeps the full state of the current leaf
+ // search position it is not possible to always pass here the position for
+ // which the accumulator is being updated. Therefore the only thing that
+ // can be reliably extracted from `pos` is the king square for the king
+ // of the `perspective` color (note: not even the other king's square will
+ // match reality in all cases, this is also the reason why `dp` is passed
+ // as a parameter and not extracted from pos.state()). This is of particular
+ // problem for future nets with other feature sets, where updating the active
+ // feature might require more information from the intermediate positions. In
+ // this case the only easy solution is to remove the multiple updates from
+ // the feature transformer update code and only update the accumulator for
+ // the current leaf position (the position after the move).
+
template <Side AssociatedKing>
- void HalfKP<AssociatedKing>::AppendChangedIndices(
+ void HalfKPChess<AssociatedKing>::AppendChangedIndices(
const Position& pos, const DirtyPiece& dp, Color perspective,
IndexList* removed, IndexList* added) {
&& (pos.is_discovered_check_on_king(~us, move) || pos.see_ge(move)))
extension = 1;
- // Last captures extension
- else if ( PieceValue[EG][pos.captured_piece()] > PawnValueEg
- && pos.non_pawn_material() <= 2 * RookValueMg)
- extension = 1;
-
+ // Losing chess capture extension
+ else if ( pos.must_capture()
+ && pos.capture(move)
+ && (ss->inCheck || MoveList<CAPTURES>(pos).size() == 1))
+ extension = 1;
+
// Add extension to new depth
newDepth += extension;