Michel wrote:GNU Chess uses the single repetion rule only for repetions occurring within the search. For repetitions involving the games history it uses the threefold repetition rule. So it analyzes this position correctly.
This is interesting. I was just reading that thread, and realizing that DiscoCheck would have the same problem as SF -- if only it had a KPK bitbase.
And the good news is that it can be implemented without any measurable cost in the general case.
This is a dangerous assumption. This patch makes the search space a bit larger when playing real games. No way to tell it won't hurt elo until you test with games. It may very well be a regression.
off-topic: Looking at this particular loop, it seems you are making the life quite hard for the gcc optimizer
This is a dangerous assumption. This patch makes the search space a bit larger when playing real games. No way to tell it won't hurt elo until you test with games. It may very well be a regression.
off-topic: Looking at this particular loop, it seems you are making the life quite hard for the gcc optimizer
You make a valid point. My speed measure was done with an initially empty search stack. In real games, this means often looking back at the stack all the way down to the initial position. I am running a non regression test to be on the safe side.
Regargind the GCC optimization, I'm not sure what you mean. Here is the code:
JVMerlino wrote:
Unless I'm mistaken, I think you might have missed the initial point of the thread, with all of the other stuff that has been discussed. The point is that in the initial position the move Kf6 was forced and that SF THEN scored the position as draw, because the only way to win is to go back to the initial position, which most engines score as a draw because they have seen one repetition.
And I just confirmed this same behavior in Crafty 23.5. Setting the position, using "analyze" and then forcing Kf6 produces a -0.01 score up through depth 60, which it reaches in 1.42 seconds. Changing Crafty so that, if in analysis mode, it requires two reps to declare draw, will fix the problem.
jm
Ok, I think I have to retract the above because if you make that change then the behavior of the engine between analysis and "normal search" modes can be drastically different. Fine 70 is an obvious example. Since I use analysis mode for various test positions, making this change to the code would defeat the purpose of analysis mode.
So the other solution presented in this thread by Michel Van den Bergh is indeed correct.
Sorry for the confusion,
jm
BTW, didn't you mean "change crafty so that analysis requires THREE reps for a draw"?? It already does the 2-reps == draw everywhere in the search, analysis mode or not.
I think that it already evaluates first repetition as a draw.
The word repetition means that the position happened at least twice.
, this means often looking back at the stack all the way down to the initial position.
More precisely to the last irreversible move. You will have to do that often anyway even with the two fold repetition rule (in case there is no repetition).
For positions in the game history you could in principle xor 1 with the hash key on the stack to indicate a two fold repetition. This avoids having to go all the way down to the base of the stack. I suspect this optimization is not worth it however.
for (int i = 4, rep = 1; i <= st().rule50; i += 2) {
rep += sp[-i].key == st().key;
if (rep >= 2 + (sp-i < sp0))
return true;
}
where sp is the current stack pointer and sp0 the stack pointer value at the root position.
How would you rewrite that to make life easier for the optimizer ? And why ?
Maybe Richard meant that gcc won't find much more to optimize in that particular loop ....
The code above may run into trouble when starting search from a position given by FEN with "rule50" set to a value >= 4. As long as only reversible moves were made on the current path, "sp[-i].key" may be an illegal access since the stack size is actually smaller than "rule50". So it would be safer to calculate the loop termination condition from the minimum of "rule50" and the total length of the game from its starting position.
Uri Blass wrote:Nothing to do with evaluation of pawn endgames.
It is obvious that it is about evaluating first repetition and many engines evaluate first repetition as a draw because they are designed to play and not to analyze correctly and the programmers did not want to add code that is probably not productive for playing strength.
It is a disadvantage of the program but
I do not think that it is a bug because a bug is a behaviour that is different than the intention of the programmer.
Program authors have struggled with this for years - i.e. what to do with respect to repetition. There is pretty much a consensus to evaluate the first repetition as a draw as it does contribute to the strength. If you do not do it that way and you NEED a draw you may take several more ply to see it or you may allow the opponent to get a draw when you could win.
The 3-fold repetition rule is arbitrary anyway. Why 3? Why not 2 or 4? In principle if you are the one to repeat you are offering a draw - although you can change your mind or use it as a tactic with the 3-fold rule.
Capital punishment would be more effective as a preventive measure if it were administered prior to the crime.
for (int i = 4, rep = 1; i <= st().rule50; i += 2) {
rep += sp[-i].key == st().key;
if (rep >= 2 + (sp-i < sp0))
return true;
}
where sp is the current stack pointer and sp0 the stack pointer value at the root position.
How would you rewrite that to make life easier for the optimizer ? And why ?
Maybe Richard meant that gcc won't find much more to optimize in that particular loop ....
The code above may run into trouble when starting search from a position given by FEN with "rule50" set to a value >= 4. As long as only reversible moves were made on the current path, "sp[-i].key" may be an illegal access since the stack size is actually smaller than "rule50". So it would be safer to calculate the loop termination condition from the minimum of "rule50" and the total length of the game from its starting position.
Sven
Thanks! You just spotted a nasty bug. Actually DiscoCheck has had this bug all along
Theory and practice sometimes clash. And when that happens, theory loses. Every single time.
for (int i = 4, rep = 1; i <= st().rule50; i += 2) {
rep += sp[-i].key == st().key;
if (rep >= 2 + (sp-i < sp0))
return true;
}
where sp is the current stack pointer and sp0 the stack pointer value at the root position.
How would you rewrite that to make life easier for the optimizer ? And why ?
1. In tight loops don't do more calculations than absolutely necessary. Here the "rep >= 2 + (sp-i < sp0)" condition is evaluated even when the keys don't match. I'm not sure the optimizer is smart enough to optimize this. I would rather rearrange the code:
for (int i = 4, rep = 1; i <= st().rule50; i += 2) {
if (sp[-i].key == st().key) {
if (++rep >= 2 + (sp-i < sp0))
return true;
}
}
2. Problematic pointer arithmetic in 64bit environment: Compiler must insert an extra int => ptrdiff_t conversion at each loop iteration. Declaring i as ptrdiff_t might produce better 64bit code.