draw by reps bug : oops I did it again

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

draw by reps bug : oops I did it again

Post by xr_a_y »

In a test game against Dropsgek, Weini fall again in a draw bug (https://lichess.org/o2AqjNAV#119). Still can't find it ... :evil:
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: draw by reps bug : oops I did it again

Post by xr_a_y »

Here's the full pgn

[pgn] 1. e4 {+0,00/10} e5 2. Nf3 {+0,26/11 0,5} Nf6 3. Nc3 {+0,00/9 0,5} Nc6 4. d4 {+0,19/10 0,3} exd4 {+0,05/12 4} 5. Nxd4 {+0,00/11 0,5} Bd6 {+0,05/11 0,4} 6. Nf5 {+0,97/9 0,3} O-O {-0,73/9 0,5} 7. Nxd6 {+1,78/10 0,3} cxd6 {-0,60/9 0,4} 8. Bd3 {+1,48/9 0,5} Ne5 {-0,75/10 0,4} 9. Be2 {+1,58/9 0,4} Re8 {-0,72/9 0,4} 10. Qxd6 {+1,74/9 0,3} Re6 {-0,72/10 0,4} 11. Qd1 {+1,71/9 0,3} Nc6 {-0,82/11 0,4} 12. f3 {+1,70/9 0,4} Qb6 {-1,05/9 0,4} 13. Nd5 {+1,86/10 0,4} Nxd5 {-0,91/8 0,4} 14. exd5 {+1,88/11 0,4} Rd6 {-0,79/10 0,4} 15. c4 {+2,03/8 0,4} Ne5 {-0,85/9 0,4} 16. Qd2 {+2,03/8 0,4} Rf6 {-1,14/8 0,4} 17. Qc3 {+0,00/12 0,6} Qc7 {-1,37/9 0,4} 18. Bg5 {+0,00/11 0,6} Rf5 {-1,20/8 0,4} 19. Be3 {+2,60/10 0,5} b6 {-1,34/10 0,5} 20. Rd1 {+2,71/8 0,3} Ba6 {-0,83/8 0,4} 21. b3 {+0,00/9 0,6} Re8 {-0,88/8 0,4} 22. f4 {+0,00/8 0,6} Ng6 {-0,41/9 0,4} 23. g3 {+0,00/9 0,6} Ne7 {-0,43/9 0,4} 24. Rc1 {+0,00/9 0,6} Rf6 {-0,41/8 0,4} 25. Kd1 {+1,79/9 0,6} Nf5 {-0,10/8 0,4} 26. Bf2 {+0,00/9 0,6} Nd6 {-0,20/6 0,4} 27. Bd4 {+0,00/11 0,6} Ne4 {-0,49/9 0,4} 28. Qb2 {+0,00/9 0,6} Rg6 {-0,42/8 0,4} 29. Re1 {+2,75/10 0,5} Nc5 {-0,49/7 0,4} 30. b4 {+3,63/10 0,4} Ne4 {-1,38/8 0,4} 31. b5 {+3,83/9 0,4} Bb7 {-2,26/10 0,5} 32. Bf3 {+3,91/11 0,4} Nd6 {-1,28/10 0,4} 33. Rxe8+ {+3,58/9 0,4} Nxe8 {-1,43/9 0,5} 34. c5 {+3,56/10 0,5} bxc5 {-1,50/9 0,5} 35. Bxc5 {+0,00/10 0,7} Qb8 {-1,53/9 0,5} 36. Qd4 {+0,00/10 0,7} a6 {-1,33/10 0,4} 37. b6 {+3,92/9 0,4} Rh6 {-1,45/8 0,5} 38. h4 {+3,77/10 0,4} Rg6 {-1,45/9 0,4} 39. g4 {+0,00/11 1,1} Rf6 {-1,89/9 0,5} 40. Rc4 {+4,78/10 0,8} a5 {-1,89/10 0,4} 41. g5 {+4,68/9 0,3} Rf5 {-1,81/10 0,5} 42. Ra4 {+4,58/9 0,4} Kh8 {-2,23/9 0,5} 43. Qd2 {+0,00/9 0,5} Nd6 {-2,63/8 0,5} 44. Qd3 {+5,19/10 0,3} g6 {-3,61/9 0,5} 45. Be3 {+0,00/10 0,5} Qc8 {-3,07/9 0,5} 46. Rxa5 {+0,00/9 0,5} Qc4 {-3,16/10 0,5} 47. Ke2 {+4,75/10 0,3} Rxf4 {-3,31/10 0,5} 48. Bxf4 {+5,09/12 0,4} Qxf4 {-3,03/10 0,5} 49. Qc3+ {+4,91/11 0,4} Kg8 {-3,05/9 0,5} 50. Qc7 {+4,92/11 0,3} Qe5+ {-2,52/9 0,5} 51. Kd3 {+4,92/10 0,4} Qf5+ {-2,64/9 0,5} 52. Ke2 {+0,00/10 0,5} Qe5+ {+0,10/14 0,5} 53. Kd3 {+4,47/9 0,3} Qf5+ {+0,10/16 0,5} 54. Kd4 {+4,63/10 0,4} Qxf3 {-1,01/11 3} 55. Qxd6 {+4,57/10 0,3} Qd1+ {-0,91/12 2,7} 56. Kc5 {+0,00/10 0,6} Qc2+ {-0,10/13 2,1} 57. Kb4 {+3,00/10 0,3} Qd2+ {-0,10/14 1,5} 58. Kb5 {+2,72/10 0,3} Qd3+ {+0,10/14 1,1} 59. Kb4 {+0,00/11 0,6} Qd2+ {-0,10/14 0,8} 60. Kb5 {+0,00/12 0,6} Bxd5 {+0,10/10 0,1} 61. Ra8+ {+10,30/10 0,4} Bxa8 {-9,84/9 0,6} 62. Qxd2 {+10,95/11 0,6} Bc6+ {-10,41/8 0,1} 63. Ka6 {+11,44/11 0,6} f6 {-10,68/8 0,1} 64. gxf6 {+0,00/11 0,6} Kf7 {-10,40/7 0,1} 65. Qd6 {+1000,04/9 0,1} g5 {-19,96/9 0,4} 66. h5 {+1000,03/5} Kg8 {-11,89/6} 67. Qe7 {+1000,02/4} Bb7+ {-78,31/3} 68. Kxb7 {+1000,01/2} d5 {-78,31/2} 69. Qg7# {+1000,00/1} {Xboard adjudication: Checkmate} 1-0 [/pgn]
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: draw by reps bug : oops I did it again

Post by xr_a_y »

0.1 is contempt. And I see alterning +0.1 -0.1 ... at least there is something bad here ...
User avatar
Ronald
Posts: 160
Joined: Tue Jan 23, 2018 10:18 am
Location: Rotterdam
Full name: Ronald Friederich

Re: draw by reps bug : oops I did it again

Post by Ronald »

oops I did it again 2 :D

Are you sure it's a bug in draw by repetition, and not due to not correctly handling stopped search of the current rootmove?

I took another short look at your sourcecode (0.0.23). In searcher::SearchRoot if the score of the current move is higher then alfa you always update bestMove.

Code: Select all

// =====================
// update alpha
 // =====================
    if ( score > alpha){
      alpha = score;
      bestMove = *it;
If you still do this in 0.0.24 then this will probably cause your problem. If the search of the current rootmove is stopped (stopFlag = true), the score of the move is totally unreliable and can not be used. If I understand correctly you return a high score from negamax in case of a stopFlag so the score of the current rootMove will always be better then alfa and thus will be returned as bestMove....
You can easily test if a stopped search is handled correctly (ie completely ignored) by setting _allowedThinkTime lower then the regular time, so that search for every move in the game is stopped prematurely. If you don't see more blunder moves then normal, then the bug must be somewhere else... :lol:
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: draw by reps bug : oops I did it again

Post by xr_a_y »

In fact there I use that

Code: Select all

HANDLE_TC_WINDOW
just before that will decide what to return from SearchRoot and there is a similar thing

Code: Select all

HANDLE_TC_SEARCH
inside the iterative deepening loop.
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: draw by reps bug : oops I did it again

Post by Sven »

xr_a_y wrote: Tue Oct 02, 2018 7:04 am In fact there I use that

Code: Select all

HANDLE_TC_WINDOW
just before that will decide what to return from SearchRoot and there is a similar thing

Code: Select all

HANDLE_TC_SEARCH
inside the iterative deepening loop.
HANDLE_TC_SEARCH seems to ignore the whole result of the last iteration on timeout. Is that what you intended?

Also your handling of "iteration 0" may lead to trouble in case of a timeout since HANDLE_TC_WINDOW returns the current move which may be "hazardous" as you write in your code comment there. You want to ensure that you play some move at least but then why not take the first move of the move list (after move ordering and possibly TT lookup) as your "default move" to return when time is up very early? Taking the current move instead is more like a random decision.
Sven Schüle (engine author: Jumbo, KnockOut, Surprise)
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: draw by reps bug : oops I did it again

Post by Sven »

Ronald wrote: Mon Oct 01, 2018 11:18 pm If the search of the current rootmove is stopped (stopFlag = true), the score of the move is totally unreliable and can not be used. If I understand correctly you return a high score from negamax in case of a stopFlag so the score of the current rootMove will always be better then alfa and thus will be returned as bestMove....
I think his code works differently here. The high score (+31000) returned from Negamax() in case of a timeout is negated to -31000 at the parent node so the whole subtree will be ignored. And furthermore the ID loop is left immediately if an iteration (represented by SearchRoot()) detects a timeout, without using the best move returned from there.
Sven Schüle (engine author: Jumbo, KnockOut, Surprise)
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: draw by reps bug : oops I did it again

Post by Sven »

Sven wrote: Wed Oct 03, 2018 2:12 pm
xr_a_y wrote: Tue Oct 02, 2018 7:04 am In fact there I use that

Code: Select all

HANDLE_TC_WINDOW
just before that will decide what to return from SearchRoot and there is a similar thing

Code: Select all

HANDLE_TC_SEARCH
inside the iterative deepening loop.
HANDLE_TC_SEARCH seems to ignore the whole result of the last iteration on timeout. Is that what you intended?

Also your handling of "iteration 0" may lead to trouble in case of a timeout since HANDLE_TC_WINDOW returns the current move which may be "hazardous" as you write in your code comment there. You want to ensure that you play some move at least but then why not take the first move of the move list (after move ordering and possibly TT lookup) as your "default move" to return when time is up very early? Taking the current move instead is more like a random decision.
Another (maybe minor) problem may occur in very fast games where visiting a few more nodes than needed after detecting timeout may lead to a few losses on time.

In Negamax() and NegaQuiesce() you return immediately if stopFlag is set. But within the move loop of both functions you do not check the stopFlag after returning from a subtree, which causes to enter (and immediately leave) all other sibling subtrees at all recursion levels from current level back to root. This is a small additional risk which can be avoided easily, and I would definitely do that at least within Negamax(), I think the additional runtime penalty is negligible compared to the risk of not doing so.
Sven Schüle (engine author: Jumbo, KnockOut, Surprise)
Daniel Anulliero
Posts: 759
Joined: Fri Jan 04, 2013 4:55 pm
Location: Nice

Re: draw by reps bug : oops I did it again

Post by Daniel Anulliero »

Remember me an error I had in Isa .
Sven , again , pointed that for me 😊
But fixing it , was worth 0 elo lol.
But I think it's interresting to fix it , to avoid some time losses at STC, and avoid choosing some bad moves
Dany
Isa download :
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: draw by reps bug : oops I did it again

Post by xr_a_y »

Sven wrote: Wed Oct 03, 2018 2:24 pm
Sven wrote: Wed Oct 03, 2018 2:12 pm
xr_a_y wrote: Tue Oct 02, 2018 7:04 am In fact there I use that

Code: Select all

HANDLE_TC_WINDOW
just before that will decide what to return from SearchRoot and there is a similar thing

Code: Select all

HANDLE_TC_SEARCH
inside the iterative deepening loop.
HANDLE_TC_SEARCH seems to ignore the whole result of the last iteration on timeout. Is that what you intended?

Also your handling of "iteration 0" may lead to trouble in case of a timeout since HANDLE_TC_WINDOW returns the current move which may be "hazardous" as you write in your code comment there. You want to ensure that you play some move at least but then why not take the first move of the move list (after move ordering and possibly TT lookup) as your "default move" to return when time is up very early? Taking the current move instead is more like a random decision.
Another (maybe minor) problem may occur in very fast games where visiting a few more nodes than needed after detecting timeout may lead to a few losses on time.

In Negamax() and NegaQuiesce() you return immediately if stopFlag is set. But within the move loop of both functions you do not check the stopFlag after returning from a subtree, which causes to enter (and immediately leave) all other sibling subtrees at all recursion levels from current level back to root. This is a small additional risk which can be avoided easily, and I would definitely do that at least within Negamax(), I think the additional runtime penalty is negligible compared to the risk of not doing so.
Thanks for the analysis. This was indeed two bugs that were corrected in 0.0.24 this summer. I should probably release it soon at least because helpful people like you want to read up-to-date code.

0.0.23 a some issue with very short TC mainly because I was too lazy to handle things correctly in SearchRoot() and Search().
As I tried out returning some move even if the full root move loop isn't finished (see another forum thread), I also have to fix a bug that lead me to check if current score is not +/-stopflagscore.

Following HGM advice I was not checking for 3reps but 1rep for a while and I see some engine are testing 3reps in PV and 1rep elsewhere. I am trying that right now ...