I'm still convinced this hurts performance. The idea of storing best and
worst bounds is critical in PVS/NegaScout... the point being that everything
will fail high or low. If you offset the window, even by 1 millipawn, then
the things that normall fail low will fail low, but the things that normally
would fail high won't. (assuming you raised alpha and beta by .001). If you
lower them the same thing happens.
You also have a problem when you have path P1 that has moves m1, m2, m3, m4, ...
and path P2 that has moves m3, m2, m1, m4, ... You break that case completely
because with normal hashing, the position before m4 would be found in the table
and not searched. In your case it would be found but would return a wrong
value if a true value were stored, or else would return a wrong bound if the
entry only has a bound stored. In essence, you can't do *anything* to the "path"
because the path is not stored, only positions are in the hash, and there's no way
to determine that one position was stored with m1 at the root, and the other had
m3 at the root...
OK... I understand your point now... no one knows about the offset at
the root, except for the code that calls Search to process each root
move. That I buy now...
However, I still have trouble with perturbing the alpha/beta window in
any way, and your fudging with random[n] above is doing just that. If
move 2 is exactly 1 point better than move 1, and your random value is
2 (say) you fail high in one case, low when you search the other way.
BTW, your line above is not right, in that you would not search the
second move with -infinity, rather you'd use alpha which was the value
returned from searching the first root move, and beta=alpha+1. If you
aren't using PVS, I can see where this has no effect on what you are
doing other than to randomize a little and harm transposition table
entries a little due to the bounds being "fuzzy". However, if you use
PVS or scout, with a null-window, shifting it is very dangerous...
>I'm still convinced this hurts performance. The idea of storing best and
>worst bounds is critical in PVS/NegaScout... the point being that everything
>will fail high or low. If you offset the window, even by 1 millipawn, then
>the things that normall fail low will fail low, but the things that normally
>would fail high won't. (assuming you raised alpha and beta by .001). If you
>lower them the same thing happens.
Yes, I realize that this might hurt performance a little. However I think
that the main benefit of the hash tables in the early middle game (which
is when we apply randomization) comes from stored best moves, since most
of the positions searched before will have been searched with lower depth
(in a previous iteration).
Also, it's difficult to analyze these things.
Suppose you have two moves, and the best move gets +0.001 added, and the
other gets -0.001. If the search returns value 1.000 for the first, the
root search will say it's 1.001, so the second move is searched with a window
of (1.002,1.003) (since 1.002-0.001=1.001). Now if the real value of
this second move is say 0.500, could it happen that searching it with a
window of (1.002,1.003) takes longer than with a window of (1.000,1.001)?
According to your reasoning it can, but that would be a little
counterintuitive, since it should be easier to decide that 0.500<=1.002
than 0.500<=1.000. (Well, hash tables are strange things so I wouldn't
be too surprised if this really could occur and besides, I haven't
spend that many time thinking about this.)
Anyway, in my tests I certainly don't experience any substantial losses
of performance. (And on FICS, I don't suffer from people repeating their
wins over and over again...)
>You also have a problem when you have path P1 that has moves m1, m2, m3, m4, ...
>and path P2 that has moves m3, m2, m1, m4, ... You break that case completely
>because with normal hashing, the position before m4 would be found in the table
>and not searched. In your case it would be found but would return a wrong
>value if a true value were stored, or else would return a wrong bound if the
>entry only has a bound stored. In essence, you can't do *anything* to the "path"
>because the path is not stored, only positions are in the hash, and there's no way
>to determine that one position was stored with m1 at the root, and the other had
>m3 at the root...
NO!
(This was the third point I asked you to convince yourself of in my
previous post.)
If m1 is root move 2, m3 is root move 3, we search m1 (simplistically)
as follows:
MakeMove(move[2]);
value = random[2] - Search(-infinity,random[2]-alpha,depth);
UnMakeMove();
(Where we got alpha from root move 1.)
Suppose this search finds an exact value for the position before m4.
Then this value is equal to the value returned by
Search(-infinity,infinity,depth-2)
where you start searching at the position before m4. So random[2]
does not influence this value.
Now you search m3:
MakeMove(move[3]);
value = random[3] - Search(-infinity,random[3]-alpha,depth);
UnMakeMove();
Now after m2,m1, the search is again in the position before m4.
The hash table has the exact value for this position, and this value
can also be used at this stage. Only after the search will we add
random[3] to it and compare it with alpha.
The same holds for bounds. If searching m1 gives that the position
before m4 has 1.000 as an upper bound, this means that
Search(-infinity,infinity,depth-2)<=1.000,
and this information can and will be used when searching m3.
(Since the random values appear in the search window they can influence
the 1.000, but the 'absolute truth' of the equality/inequality we get
is not affected.)
Note that I don't change anything in Search(), only something
in SearchRoot().
Now you might ask, if Search() doesn't get the random values, how can
we ever get any randomization? The answer is that we randomize at the
root, see my second SearchRoot() procedure of my previous post, where
the same randomization is effected in a very straightforward (but
expensive) way.
Ronald de Man
>OK... I understand your point now... no one knows about the offset at
>the root, except for the code that calls Search to process each root
>move. That I buy now...
I'm glad :-)
>However, I still have trouble with perturbing the alpha/beta window in
>any way, and your fudging with random[n] above is doing just that. If
>move 2 is exactly 1 point better than move 1, and your random value is
>2 (say) you fail high in one case, low when you search the other way.
>BTW, your line above is not right, in that you would not search the
>second move with -infinity, rather you'd use alpha which was the value
>returned from searching the first root move, and beta=alpha+1. If you
>aren't using PVS, I can see where this has no effect on what you are
>doing other than to randomize a little and harm transposition table
>entries a little due to the bounds being "fuzzy". However, if you use
>PVS or scout, with a null-window, shifting it is very dangerous...
I do use PVS and null-windows. The line I gave was meant as a simple
illustration of what i'm doing. So using a null-window it would read
value = random[2] - Search(random[2]-alpha-1,random[2]-alpha,depth);
(since originally it was -Search(-alpha-1,-alpha,depth)).
I agree that shifting alpha and beta might make some hash tables entries
less usefull, but I think this effect (at least in the middle game) is
much smaller than for example the number of nodes you have to search
more/less in a certain position because of the more/less fail-highs you
get with a certain set of random numbers (at least, that is what I
experience).
And of course, adding randomness will never produce better moves from
a minimax point of view, but, if the loss in performance is small
enough, will most likely improve play against humans (on the long term)
and is an easy solution against killerlines.
Ronald de Man