PVS questions and self-re-search

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

PVS questions and self-re-search

Post by hgm »

So far all my engines always used vanilla alpha-beta; I tried switching to PVS once, but due to the poor move ordering, that was slower. I am writing a new engine now, paying attention to move orering this time, so I plan to use PVS. One question: PVS is usually described as something like

Code: Select all

Search(alpha, beta, depth)
{
  tempBeta = beta;
  for(ALL_MOVES) {
    MakeMove();
    LMR = 0;
    score = -Search(-tempBeta, -alpha, depth-1-LMR);
    if&#40;alpha < score && score < beta&#41;
      score = -Search&#40;-beta, -alpha, depth-1&#41;; // RE-SEARCH
    UnMake&#40;);
    if&#40;score>alpha&#41; &#123;
      alpha = score;
      if&#40;alpha >= beta&#41; goto cutoff&#58;
      tempBeta = alpha+1; // POSSIBILITY 2
    &#125;
    tempBeta = alpha+1; // POSSIBILITY 1
  &#125;
 cutoff&#58;
  ...
&#125;
(In this I already made provision for LMR, but it is still switched off by setting LMR=0 always, so ignore it for now.) Initally the window is 'opened' by using the real beta (which in most cases was already a null window, of course). For the remaining moves it is then collapsed to a null window, and when you fail high with respect to that, but not yet the original beta, you re-search the move with open window.

My question, however, is: should I close the window after the first move (possibility 1) or only after the first move that raised alpha (possibility 2). Most nodes with an open window are likely to occur from re-searches, where the parent just opened the window. That means that they were searched just before with a null window, to which they failed low. Because of that fail low, there will be no hash move. So it would not be uncommon that the first move we search is not the best reply, or would in fact even fail low. So my guess is that it would be better to use possibility 2: only collapse the window after alpha was raised. Am I right about this?

This classical way of representing PVS seems a bit inefficient in practice: when a re-search is triggered you re-enter a node immediately after returning from it, having to re-do move generation, sorting and everything. This could be easily prevented by having the node not return, but informing it that it should do the re-search with opened window of its own accord, without returnin. Pseudo-code for such an implementation would look like:

Code: Select all

Search&#40;alpha, beta, forceOpen, depth&#41;
&#123;
  originalAlpha = alpha;
  iidDepth = START_DEPTH;
  alpha = beta-1;
  do &#123;
    if&#40;forceOpen&#41; alpha = originalAlpha;
    open = forceOpen;
    startAlpha = alpha;
    for&#40;ALL_MOVES&#41; &#123;
      MakeMove&#40;);
      score = -Search&#40;-beta, -alpha, open, iidDepth-1&#41;;
      UnMake&#40;);
      if&#40;score > alpha&#41; &#123;
        alpha = score;
        open = 0; // POSSIBILITY 2
        if&#40;alpha >= beta&#41; goto cutoff;
      &#125;
    &#125;
    if&#40;originalAlpha < score && score < beta && alpha == startAlpha&#41; &#123;
      alpha = originalAlpha; // OPEN WINDOW
      continue;  // AND RE-SEARCH
    &#125;
   cutoff&#58;
    iid = NEXT_IID_DEPTH;
    alpha = beta-1; // NEXT ITERATION USES NULL WINDOW AGAIN (?)
  &#125; while&#40;iidDepth <= depth&#41;;
&#125;
(This is slighty more complex, because in this pseudo-code I also included the iterative deepening, which is likely be needed after a node becomes a PV node by opening the window.)

The parameter forceOpen now indicates if the search should be with open window, which was formerly implied by alpha an beta themselves, so that we can now pass the original alpha and beta even in case we want the search initially to be done with a closed window. Because of the IID we have to remember the original alpha, an reset to that before each new iteration.

The PVS is now implemented by normally setting the window {beta-1, beta}, (i.e. in nodes that are not forced open, which would have been called in regular PVS with null window already), which corresponds to {alpha, alpha+1} of the caller. When the whole iteration fails low to this null window, (meaning the move in the caller fails high), we then re-do the iteration at the same depth with open window. That is, if it was not immediately clear that we also fail low compared to the opened window, i.e. if bestScore > originalAlpha. That way we can simply use the original move list.

The condition score < beta would not really be needed when the cutoff label is behind the test, but is added for clarity. The test alpha == startAlpha indicates that we really had a fail low (no move increased alpha), and that we ended up with a score above originalAlpha then proves that we must have started with alpha != original alpha, i.e. with a null-window.

I am still wondering abot the interaction between IID and re-searching. I guess that depends on your IID strategy. If you do not normally do IID in non-PV nodes, you would initialy set START_DEPTH to depth, when forceOpen==0, so that iidDepth immediately starts at the maximum depth. But when you have to re-search with open window, without the benifit of a hash move, you might want to start this re-search at a lower iidDepth. And what would you do when in some earlier iteration, the score would fall out of the (opened) window? Would you close the window again to {beta-1, beta} for the next iteration, when the current one failed high (>=beta), to quickly verify cut-moves in subsequent iterations, untill the score of this cut-move candidate again falls in window, (and thus fails low w.r.t. the null window), and then re-search with open window at that depth?

And if your IID strategy was to use IID in non-PV nodes as well, woud yu first do all iteratons to maximum depth with null window, no matter how they failed, and then start again with IID in an open window when the score in the final iteration ended up in {alpha, beta}? (This would mimic the conventional seperate call for the re-search.) Or could you improve on it by immediately opening the window after a properly failing iteration?(And possibly close it again on a later iteration?) By self-re-searching you have more flexibility than with two separate calls, making it harder to decide what would be best. (In my engine HaQiKi D it proved advantageous to do one iteration of IID in non-PV nodes by starting at d=1 when the target depth was >=4, before jumping to the final depth.)

Yet another question: if you do LMR and PVS, you start searching with null window AND reduction. What best to do when this search fails high? Do one re-search with open window without reduction? First do a null-window search without reduction before deciding on an open-window search without reduction? And if the latter, where would you put the null window? (E.g. if the score in the reduced null-window search was >= beta, would you put a null window for the unreduced search at {alpha, alpha+1} or at {beta-1, beta}?) I would think that in all cases it would be best to take away the reduction: a move that fails high compared to alpha, even at reduced depth or because of search instability cannot be so without merit that it deserves to be reduced.
Mincho Georgiev
Posts: 454
Joined: Sat Apr 04, 2009 6:44 pm
Location: Bulgaria

Re: PVS questions and self-re-search

Post by Mincho Georgiev »

You may want to take a look:

http://www.talkchess.com/forum/viewtopi ... at&start=0


I totally agree, that the correct implementation of PVS is full width on 1st, NWS on the rest, regardless of some publications that examines the full width until alpha raise. I should say that I've been using both, with no success with the last.
About IID, I wonder, why would anyone want to do it in a NW, but that is just my opinion again after i tried it for a while.
User avatar
Greg Strong
Posts: 388
Joined: Sun Dec 21, 2008 6:57 pm
Location: Washington, DC

Re: PVS questions and self-re-search

Post by Greg Strong »

I do IID on NW nodes if the depth is >= 7 plies and the eval is within a pawn and a half of beta. It definitely helps a little. Although I do a nearly-full eval at every node, not a quick material-only eval, so that could make a difference. If you only have the material eval available, the IID on NW nodes might not work...
User avatar
Greg Strong
Posts: 388
Joined: Sun Dec 21, 2008 6:57 pm
Location: Washington, DC

Re: PVS questions and self-re-search

Post by Greg Strong »

For the last question, if a reduced move in a NW search fails high, I first re-search without the reduction, but still with a zero window. If it still fails high, then, when the score is returned, the parent node will redo the search with an open window. I'm pretty sure most programs with LMR work this way. I know Glaurung/Stockfish does.

I also use different search functions - "SearchPV" for open window searches, and "Search" for NW searches (taking only beta as a parameter, no alpha.) Not necessary, of course, but the division works well. It gave me a slight speed-up, and makes for cleaner looking functions (although certainly produces a fair amount of duplicated code.) Glaurung/Stockfish does this also.

Glad to hear you're writing a new engine! Joker is one of my favorite engines to use for testing. I was surprised to hear it didn't use PVS.
Daniel Shawul
Posts: 4185
Joined: Tue Mar 14, 2006 11:34 am
Location: Ethiopia

Re: PVS questions and self-re-search

Post by Daniel Shawul »

My question, however, is: should I close the window after the first move (possibility 1)
I believe it is better to close the window right after the first move is searched. When we use PVS we _assume_ good move ordering so i don't see any reason to wait. It also means that we will wait forever at ALL nodes where our alpha never gets updated ,in which case I expect possb 1 to perform better.
Most nodes with an open window are likely to occur from re-searches, where the parent just opened the window. That means that they were searched just before with a null window, to which they failed low. Because of that fail low, there will be no hash move. So it would not be uncommon that the first move we search is not the best reply, or would in fact even fail low. So my guess is that it would be better to use possibility 2: only collapse the window after alpha was raised. Am I right about this?
I am not sure if i follow you right. We do researches if we found a move that is better than alpha ? so we should have a good enough move there, no ?
Also since researches are done only at PV nodes (where we have open windows), they are very small in number. At CUT/ALL nodes I just update alpha and move on with the rest of the moves, because the window is already closed before we get to that node. By mistake i did that in the past, but i guess those researches were cheap due to hash table cutoffs.
Yet another question: if you do LMR and PVS, you start searching with null window AND reduction. What best to do when this search fails high? Do one re-search with open window without reduction? First do a null-window search without reduction before deciding on an open-window search without reduction?
I only do researches on LMR reduced move if the move fails high (score >= beta). So for most cases the two types of researches are exclusive in my case. After the PVS research is done and if it failed high and was reduced, then ofcourse i do LMR research too.
I am still wondering abot the interaction between IID and re-searching. I guess that depends on your IID strategy. If you do not normally do IID in non-PV nodes, you would initialy set START_DEPTH to depth, when forceOpen==0, so that iidDepth immediately starts at the maximum depth. But when you have to re-search with open window, without the benifit of a hash move, you might want to start this re-search at a lower iidDepth. And what would you do when in some earlier iteration, the score would fall out of the (opened) window? Would you close the window again to {beta-1, beta} for the next iteration, when the current one failed high (>=beta), to quickly verify cut-moves in subsequent iterations, untill the score of this cut-move candidate again falls in window, (and thus fails low w.r.t. the null window), and then re-search with open window at that depth?
For me IID is same as the rest but with depth - 2. Thus first move is searched with open window ,the rest with closed window. I tried to take advantage of the fact that the node where IID is done is same as where we are going to continue the search with full depth. I have an on_node_entry() function which does hash table cutoff test, repetition draws etc... which I skip after I do IID. My move generation (incremental one) was too complicated to skip it.