Based on the example positions given above, I can accept that there are cases where it is possible to reduce the number of "SEE nodes" somehow. Now I have thought about the proper way of doing that.
Please excuse my long posting.
My first thought was: each additional intelligence being put into the SEE algorithm can make its implementation more complex by an amount that has potential for a significant slowdown, and it might even be possible that this effect outweighs any effort we save by adding intelligence, taking also into account that really saving some SEE nodes might turn out to be statistically rare since we need special circumstances to successfully apply a "cutoff".
But let's assume we really want to be smart and improve over the well-known SEE algorithm that provides us with a defined level of accuracy. Let's also assume that we want to keep as much accuracy of SEE as we need for our purpose.
In the following I stick with the recursive notation for simplicity, while knowing that there is always an equivalent iterative algorithm producing identical results and potentially being faster.
My next basic assumption is that the recursive see() function always returns a value representing the material gain relative to the current node, not to the "SEE root node". So see() always returns either 0 or the non-negative value expressed by:
Code: Select all
value_of_piece_just_captured - see_value_for_opponent_after_making_my_capture(...)
Now we "always" (but see my note about promotions further down!) know the following bounds:
- The maximum gain for the side to move relative to the current node is equal to value_of_piece_just_captured, since the opponent can decide to stand pat to avoid losing even more.
- The maximum gain for the opponent after I made my capture is the value of my moving piece, for a very similar reason.
Let's have two consecutive captures, the first captures the value C0 with a piece of value M0, and the second captures the value C1 (C1 == M0) with a piece of value M1.
Now I can express when it would definitely be safe to skip doing another recursive see() call:
The second capture will safely refute the first one if (C1 - M1 >= C0). The second see() call will return at least C1 - M1 (since this is definitely >= 0, and would even grow if the enemy's gain is less than M1), and due to the given condition this will make (C0 - (C1 - M1)) go negative or zero when backing up to the parent node, so there the opponent can safely decide to stand pat.
In other words: we can always skip calling another level of see() if the following condition is true:
Code: Select all
value_of_piece_just_captured - value_of_my_piece_just_moved >= value_of_my_piece_that_was_previously_captured
and simply return (value_of_piece_just_captured - value_of_my_piece_just_moved) instead.
Example 1: QxN is always refuted by RxQ since "Q > R + N" and therefore especially "Q - R >= N".
Example 2: BxP is always refuted by PxB since "B > P + P".
The implementation would require to pass one additional parameter: the value of the previously captured piece. The total material balance relative to the SEE root node would be irrelevant here.
Since this approach does not have any impact on the final SEE result at the root, i.e. does not lose accuracy, I state that it is safe to "always" apply this kind of cutoff. (Another question is, as mentioned above, whether this additional intelligence can be implemented effectively and passes the "cluster test".)
An exception to "always" could occur if a promoting pawn is involved in the capture sequence for which it is possible to postpone its promotion until all enemy attackers are gone, and then do the final capture move gloriously promoting to a queen which stays on board and increments the SEE result by value_of_queen - value_of_pawn. I guess it is not trivial to handle this case properly, since the possibility of xray attacks can make it impossible sometimes to decide whether the promoting capture shall be handled like all other pawn moves, thus ordered to the top of the attack list, or postponed until the end of the capture sequence (which is exactly when?). Knowing about this additional complexity, for now I vote for keeping that case out of my consideration and thinking further about its proper handling. Your proposals are very welcome.
Returning to the "SEE cutoff" topic, now I would like to know if and why any of the two other approaches that have been discussed within this thread is really better than the approach I have presented above:
a) HGM: use an alpha-beta window for SEE
b) Bob: "bail out [and return which value?] if, after making a capture, the current sum of material exchanged less the current capturing piece is still greater than zero"
Note my question added in brackets within b).
Currently I assume, until being disproven, that my approach is superior to both others since it does not affect SEE accuracy at all, and also since it is easy to derive it from the "original" SEE algorithm and therefore easy to prove its correctness (still excluding the promotion problem for now), while both others still appear partially unclear to me.
Your comments are welcome. Also, regarding the HGM approach I would appreciate to see some pseudocode that illustrates this alpha-beta based SEE algorithm.
Sven