Follow-up for the previous patch: we use an affine formula to mix stats
and evaluation in search. The idea is to give a bonus if the previous
move of the opponent was historically bad, and a malus if the previous
move of the opponent was historically good.
More precisely, if x is the stat score of the previous move by the opponent,
we implement the following formulas to tweak the evaluation at an internal
node of the tree for our pruning decisions at this node:
if x = 0, use v' = eval(P)
if x > 0, use v' = eval(P) - 5 - x/1024
if x < 0, use v' = eval(P) + 5 - x/1024
For reference, the previous master had this simpler rule:
if x > 0, use v' = eval(P) - 10
if x <= 0, use v' = eval(P)
STC:
LLR: 2.95 (-2.94,2.94) [0.00,5.00]
Total: 29322 W: 6359 L: 6088 D: 16875
http://tests.stockfishchess.org/tests/view/5b76a5980ebc5902bdba957f
LTC:
LLR: 2.96 (-2.94,2.94) [0.00,5.00]
Total: 30893 W: 5154 L: 4910 D: 20829
http://tests.stockfishchess.org/tests/view/5b76ca6d0ebc5902bdba9914
Closes https://github.com/official-stockfish/Stockfish/pull/1740
Bench: 4592766
Mix search stats with evaluation: if the opponent's move has a good historyStat,
then decrease the evaluation of the internal node a bit for the pruning decisions
during search.
STC;
LLR: 2.96 (-2.94,2.94) [0.00,5.00]
Total: 72083 W: 15683 L: 15203 D: 41197
http://tests.stockfishchess.org/tests/view/5b74c3ea0ebc5902bdba7d41
LTC:
LLR: 2.95 (-2.94,2.94) [0.00,5.00]
Total: 29104 W: 4867 L: 4630 D: 19607
http://tests.stockfishchess.org/tests/view/5b7565000ebc5902bdba851b
Closes https://github.com/official-stockfish/Stockfish/pull/1738
Bench: 4514101
-----------
How to continue from there?
• the use of the previous stat score can probably be simplified in lines 587 and 716
• we could try to use a continuous bonus based on the previous stat score, instead
of just a fixed offset of -10 when the opponent previous move was good.
----------
Comments by Stefan Geschwentner:
Interesting idea. Because only the eval in search is tweak this should only
influence the eval and static eval used at inner nodes, and not on the return
search value (which comes in the end from quiescence search), except through
saving in TT followed by a TT cutoff.
So essentialy this effects diverse pruning/reduction parts -- eval and static
eval are lowered for good opponent moves:
• tt cutoff (ttValue)
• improving (static eval)
• more razoring (eval)
• less futility pruning (eval)
• less null move pruning (eval + static eval) (but with little more depth)
• more probcut (static eval)
• more move futility pruning (static eval)
This patch introduces a non-linear bonus for pawns, along with some
(linear) corrections for the other pieces types.
The original values were obtained by a massive non-linear tuning of both
pawns and other pieces by GuardianRM, while Alain Savard and Chris Cain
later simplified the patch by observing that, apart from the pawn case, the
tuned corrections were in fact almost affine and could be incorporated in
our current code base via the piece values in types.h (offset) and the diagonal
of the quadratic matrix (slope). See discussion in PR#1725 :
https://github.com/official-stockfish/Stockfish/pull/1725
STC:
LLR: 2.97 (-2.94,2.94) [0.00,5.00]
Total: 42948 W: 9662 L: 9317 D: 23969
http://tests.stockfishchess.org/tests/view/5b6ff6e60ebc5902bdba1d87
LTC:
LLR: 2.97 (-2.94,2.94) [0.00,5.00]
Total: 19683 W: 3409 L: 3206 D: 13068
http://tests.stockfishchess.org/tests/view/5b702dbd0ebc5902bdba216b
How to continue from there?
- Maybe the non-linearity for the pawn value could be somewhat tempered
again and a simpler linear correction for pawns would work?
Closes https://github.com/official-stockfish/Stockfish/pull/1734
Bench: 4681496
We simplify the razoring logic by applying it to all nodes at depth 1 only.
An added advantage is that only one razor margin is needed now, and we treat
PV and Non-PV nodes in the same manner.
How to continue?
- There may be some conditions in which depth 2 razoring is beneficial.
- We can see whether the razor margin can be tuned, perhaps even with a
different value for PV nodes.
- Perhaps we can unify the treatment of PV and Non-PV nodes in other parts
of the search as well.
STC:
LLR: 2.96 (-2.94,2.94) [-3.00,1.00]
Total: 5474 W: 1281 L: 1127 D: 3066
http://tests.stockfishchess.org/tests/view/5b6de3b20ebc5902bdba0d1e
LTC:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 62670 W: 10749 L: 10697 D: 41224
http://tests.stockfishchess.org/tests/view/5b6dee340ebc5902bdba0eb0
In addition, we ran a fixed LTC test against a similar patch which also
passed SPRT [-3, 1]:
ELO: 0.23 +-2.1 (95%) LOS: 58.6%
Total: 36412 W: 6168 L: 6144 D: 24100
http://tests.stockfishchess.org/tests/view/5b6e83940ebc5902bdba1485
We are opting for this patch as the more logical and simple of the two,
and it appears to be no less strong. Thanks in particular to @DU-jdto
for input into this patch.
Bench: 4476945
Unify the "quiet" and "non-quiet" reduction rules for use at any kind of moves.
The idea behind it was that both rules reduce at similiar cases in master:
one directly for late previous moves and the other indirectly by using a
bad stat score which is used for most move sorting and so approximates the
late move condition.
For captures/promotions the old rule was triggered in 25% but the new
rule only for 3% of all cases (so now more reductions are done, whereas
for quiet moves reductions keep the same level).
STC:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 162327 W: 35976 L: 36134 D: 90217
http://tests.stockfishchess.org/tests/view/5b6a9a430ebc5902bdb9d5c1
LTC:
LLR: 2.96 (-2.94,2.94) [-3.00,1.00]
Total: 29570 W: 5083 L: 4976 D: 19511
http://tests.stockfishchess.org/tests/view/5b6bc5d00ebc5902bdb9e9d6
Bench: 4526980
After a session of tuning for King Psqt I got some new values, which was later
tweaked manually by me Fauzi, to result in an Elo-gain patch which seems to scale
pretty well:
STC: LLR: -2.96 (-2.94,2.94) [0.00,4.00]
Total: 100653 W: 22550 L: 22314 D: 55789 [Yellow patch]
LTC: LLR: 2.96 (-2.94,2.94) [0.00,4.00]
Total: 147079 W: 25584 L: 24947 D: 96548 [Green Patch]
Bench: 4669050
Introduce voting system for best move selction in multi-threads mode.
Joint work with Stefan Geschwentner, based on ideas introduced by
Michael Stembera.
Moves are upvoted by every thread using the margin to the minimum score
across threads and the completed depth.
First thread voting for the winner move is selected as best thread.
Passed STC, LTC. A further LTC test with only 4 threads failed with positive
score. A LTC with 31 threads was stopped with LLR 0.77 after 25k games to
avoid use of excessive resources (equivalent to 1.5M STC games).
Similar ideas were proposed by Michael Stembera 2 years ago #507, #508.
This implementation seems simpler and more understandable, the results
slightly more promising.
Further possible work:
1) Tweak of the formula using for assigning votes.
2) Use a different baseline for the score dependent part: maximum score
or winning probability could make more sense.
3) Assign votes in `Thread::Search` as iterations are completed and use
voting results to stop search.
4) Select best thread as the threads voting for best move with the highest
completed depth or, alternatively, vote on PV moves.
Link to SPRT tests
[stopped LTC, 31 threads 20+0.02](http://tests.stockfishchess.org/tests/view/5b61dc090ebc5902bdb95192)
LLR: 0.77 (-2.94,2.94) [0.00,5.00]
Total: 25602 W: 3977 L: 3850 D: 17775
Elo: 1.70 [-0.68,4.07] (95%)
[passed LTC, 8 threads 20+0.02](http://tests.stockfishchess.org/tests/view/5b5df5180ebc5902bdb9162d)
LLR: 2.96 (-2.94,2.94) [0.00,5.00]
Total: 44478 W: 7602 L: 7300 D: 29576
Elo: 1.92 [-0.29,3.94] (95%)
[failed LTC, 4 threads 20+0.02](http://tests.stockfishchess.org/tests/view/5b5f39ef0ebc5902bdb92792)
LLR: -2.94 (-2.94,2.94) [0.00,5.00]
Total: 29922 W: 5286 L: 5285 D: 19351
Elo: 0.48 [-1.98,3.10] (95%)
[passed STC, 4 threads 5+0.05](http://tests.stockfishchess.org/tests/view/5b5dbf0f0ebc5902bdb9131c)
LLR: 2.97 (-2.94,2.94) [0.00,5.00]
Total: 9108 W: 2033 L: 1858 D: 5217
Elo: 6.11 [1.26,10.89] (95%)
No functional change (in simple threat mode)
Use operator const T&() instead of operator T() to avoid possible
costly hidden copies of non-scalar nested types.
Currently StatsEntry has a single member T, so assuming
sizeof(StatsEntry) == sizeof(T) it happens to work, but it's
better to use the size of the proper entry type in std::fill.
Note that current code works because std::array items are ensured
to be allocated in contiguous memory and there is no padding among
nested arrays. The latter condition does not seem to be strictly
enforced by the standard, so be careful here.
Finally use address-of operator instead of get() to fully hide the
wrapper class StatsEntry at calling sites. For completness add
the arrow operator too and simplify the C++ code a bit more.
Same binary code as previous master under the Clang compiler.
No functional change.
As a note, current 2 LMR conditions on stat score
could be simplified in a single line:
r -= ((ss->statScore >= 0) - ((ss-1)->statScore >= 0)) * ONE_PLY;
We keep them splitted in 2 "if" statements because are easier
to (immediately) read.
No functional change.
This patch reverts the recent commit called "Tweak reductions formula, etc."
The decisions for the revert decision were as follows:
1) The original commit called "Tweak reductions formula: 0.88 * depth + 0.12"
showed bad scaling at in a Very Long Time Control (VLTC) test:
VLTC (180+1.8):
LLR: -1.59 (-2.94,2.94) [0.00,5.00]
Total: 14968 W: 2247 L: 2257 D: 10464
http://tests.stockfishchess.org/tests/view/5b559ffa0ebc5902bdb84f36
2) So there was a suspicion that the original fast passing LTC test which lead
us to accept the patch may have been a statistical accident, so we organized
a match against the previous master at LTC to get an Elo estimate for the
patch:
LTC match:
ELO: -1.83 +-2.1 (95%) LOS: 4.3%
Total: 36018 W: 6018 L: 6208 D: 23792
http://tests.stockfishchess.org/tests/view/5b55f8110ebc5902bdb8526f
3) Based on these results, we ran a simplification test with [-3..1] bounds
for the revert at LTC:
LTC:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 41501 W: 7107 L: 7020 D: 27374
http://tests.stockfishchess.org/tests/view/5b5738670ebc5902bdb86932
4) So we revert.
Bench: 4491691
Compiling the current master with MSVC gives the following error:
```
search.cpp(956): error C2660: 'operator *': function does not take 1 arguments
types.h(303): note: see declaration of 'operator *'
```
This was introduced in commit:
88de112b84
We use a suggestion by @vondele to fix the error, thanks!
No functional change.
The '- 1' subtrahend was introduced for guarding against null move
search at root, which would be nonsense. But this is actually already
guaranteed by the !PvNode condition. This followed from the discussion
in pull request 1609: https://github.com/official-stockfish/Stockfish/pull/1609
No functional change
Fix an error when compiling current master with MSVC due to the
ambiguity of which operator* overload was intended (reported by
Jarrod Torriero).
No functional change.
define Color us and use this instead of pos.side_to_move() and nmp_odd. The latter allows to clarify the nmp verification criterion.
Tested for no regression:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 76713 W: 15303 L: 15284 D: 46126
http://tests.stockfishchess.org/tests/view/5b046a0d0ebc5914abc12971
No functional change.
A position which has a move which draws by repetition, or which could have
been reached from an earlier position in the game tree, is considered to be
at least a draw for the side to move.
Cycle detection algorithm by Marcel van Kervink:
https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
----------------------------
How does the algorithm work in practice? The algorithm is an efficient
method to detect if the side to move has a drawing move, without doing any
move generation, thus possibly giving a cheap cutoffThe most interesting
conditions are both on line 1195:
```
if ( originalKey == (progressKey ^ stp->key)
|| progressKey == Zobrist::side)
```
This uses the position keys as a sort-of Bloom filter, to avoid the expensive
checks which follow. For "upcoming repetition" consider the opening Nf3 Nf6 Ng1.
The XOR of this position's key with the starting position gives their difference,
which can be used to look up black's repeating move (Ng8). But that look-up is
expensive, so line 1195 checks that the white pieces are on their original squares.
This is the subtlest part of the algorithm, but the basic idea in the above game
is there are 4 positions (starting position and the one after each move). An XOR
of the first pair (startpos and after Nf3) gives a key matching Nf3. An XOR of
the second pair (after Nf6 and after Ng1) gives a key matching the move Ng1. But
since the difference in each pair is the location of the white knight those keys
are "identical" (not quite because while there are 4 keys the the side to move
changed 3 times, so the keys differ by Zobrist::side). The loop containing line
1195 does this pair-wise XOR-ing.
Continuing the example, after line 1195 determines that the white pieces are
back where they started we still need to make sure the changes in the black
pieces represents a legal move. This is done by looking up the "moveKey" to
see if it corresponds to possible move, and that there are no pieces blocking
its way. There is the additional complication that, to match the behavior of
is_draw(), if the repetition is not inside the search tree then there must be
an additional repetition in the game history. Since a position can have more
than one upcoming repetition a simple count does not suffice. So there is a
search loop ending on line 1215.
On the other hand, the "no-progress' is the same thing but offset by 1 ply.
I like the concept but think it currently has minimal or negative benefit,
and I'd be happy to remove it if that would get the patch accepted. This
will not, however, save many lines of code.
-----------------------------
STC:
LLR: 2.95 (-2.94,2.94) [0.00,5.00]
Total: 36430 W: 7446 L: 7150 D: 21834
http://tests.stockfishchess.org/tests/view/5afc123f0ebc591fdf408dfc
LTC:
LLR: 2.96 (-2.94,2.94) [0.00,5.00]
Total: 12998 W: 2045 L: 1876 D: 9077
http://tests.stockfishchess.org/tests/view/5afc2c630ebc591fdf408e0c
How could we continue after the patch:
• The code in search() that checks for cycles has numerous possible variants.
Perhaps the check need could be done in qsearch() too.
• The biggest improvement would be to get "no progress" to be of actual benefit,
and it would be helpful understand why it (probably) isn't. Perhaps there is an
interaction with the transposition table or the (fantastically complex) tree
search. Perhaps this would be hard to fix, but there may be a simple oversight.
Closes https://github.com/official-stockfish/Stockfish/pull/1575
Bench: 4550412
At PvNodes allow bonus for prior counter move that caused a fail low
for depth 1 and 2. Note : I did a speculative LTC on yellow STC patch
since history stats tend to be highly TC sensitive
STC (Yellow):
LLR: -2.96 (-2.94,2.94) [0.00,5.00]
Total: 64295 W: 13042 L: 12873 D: 38380
http://tests.stockfishchess.org/tests/view/5af507c80ebc5968e6524153
LTC:
LLR: 2.96 (-2.94,2.94) [0.00,5.00]
Total: 22407 W: 3413 L: 3211 D: 15783
http://tests.stockfishchess.org/tests/view/5af85dd40ebc591fdf408b87
Also use local variable excludedMove in NMP (marotear)
Bench: 5294316
This patch simplifies the control flow in search(), removing an if
and a goto. A side effect of the patch is that Stockfish is now a
little bit more selective at low depths, because we allow razoring,
futility pruning and probcut pruning after a null move.
passed STC:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 32035 W: 6523 L: 6422 D: 19090
http://tests.stockfishchess.org/tests/view/5af142ca0ebc597fb3d39bb6
passed LTC:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 41431 W: 6187 L: 6097 D: 29147
http://tests.stockfishchess.org/tests/view/5af148770ebc597fb3d39bc1
Ideas for further work:
• Use the nodes credit opened by the patch (the increased selectivity)
to try somewhat higher razoring, futility or probcut margins at [0..4].
Bench: 4855031